2727 * @module clone
2828 */
2929
30- import { inlineAllStyles } from './styles.js' ;
3130
3231/**
3332 * Freeze the responsive selection of an <img> that has srcset/sizes.
@@ -57,14 +56,11 @@ function freezeImgSrcset(original, cloned) {
5756 * Creates a deep clone of a DOM node, including styles, shadow DOM, and special handling for excluded/placeholder/canvas nodes.
5857 *
5958 * @param {Node } node - Node to clone
60- * @param {boolean } compress - Whether to compress style keys
61- * @param {Object } [options={}] - Capture options including exclude and filter
62- * @param {Node } [originalRoot] - Original root element being captured
6359 * @returns {Node|null } Cloned node with styles and shadow DOM content, or null for empty text nodes or filtered elements
6460 */
6561
6662
67- export function deepClone ( node , styleMap = new Map ( ) , styleCache = new WeakMap ( ) , nodeMap = new Map ( ) , compress , options = { } , originalRoot ) {
63+ export function deepCloneBasic ( node ) {
6864 if ( ! node ) throw new Error ( 'Invalid node' ) ;
6965
7066 // Local set to avoid duplicates in slot processing
@@ -81,81 +77,28 @@ export function deepClone(node, styleMap = new Map(), styleCache = new WeakMap()
8177 return node . cloneNode ( true ) ;
8278 }
8379
84- // 3. Exclude by attribute
85- if ( node . getAttribute ( "data-capture" ) === "exclude" ) {
86- const spacer = document . createElement ( "div" ) ;
87- const rect = node . getBoundingClientRect ( ) ;
88- spacer . style . cssText = `display:inline-block;width:${ rect . width } px;height:${ rect . height } px;visibility:hidden;` ;
89- return spacer ;
90- }
91-
92- // 4. Exclude by selectors
93- if ( options . exclude && Array . isArray ( options . exclude ) ) {
94- for ( const selector of options . exclude ) {
95- try {
96- if ( node . matches ?. ( selector ) ) {
97- const spacer = document . createElement ( "div" ) ;
98- const rect = node . getBoundingClientRect ( ) ;
99- spacer . style . cssText = `display:inline-block;width:${ rect . width } px;height:${ rect . height } px;visibility:hidden;` ;
100- return spacer ;
101- }
102- } catch ( err ) {
103- console . warn ( `Invalid selector in exclude option: ${ selector } ` , err ) ;
104- }
105- }
106- }
107-
108- // 5. Custom filter function
109- if ( typeof options . filter === "function" ) {
110- try {
111- if ( ! options . filter ( node , originalRoot || node ) ) {
112- const spacer = document . createElement ( "div" ) ;
113- const rect = node . getBoundingClientRect ( ) ;
114- spacer . style . cssText = `display:inline-block;width:${ rect . width } px;height:${ rect . height } px;visibility:hidden;` ;
115- return spacer ;
116- }
117- } catch ( err ) {
118- console . warn ( "Error in filter function:" , err ) ;
119- }
120- }
121-
12280 // 6. Special case: iframe → fallback pattern
12381 if ( node . tagName === "IFRAME" ) {
12482 const fallback = document . createElement ( "div" ) ;
12583 fallback . style . cssText = `width:${ node . offsetWidth } px;height:${ node . offsetHeight } px;background-image:repeating-linear-gradient(45deg,#ddd,#ddd 5px,#f9f9f9 5px,#f9f9f9 10px);display:flex;align-items:center;justify-content:center;font-size:12px;color:#555;border:1px solid #aaa;` ;
12684 return fallback ;
12785 }
12886
129- // 7. Placeholder nodes
130- if ( node . getAttribute ( "data-capture" ) === "placeholder" ) {
131- const clone2 = node . cloneNode ( false ) ;
132- nodeMap . set ( clone2 , node ) ;
133- if ( ! options . skipInlineStyles ) inlineAllStyles ( node , clone2 , styleMap , styleCache , compress ) ;
134- const placeholder = document . createElement ( "div" ) ;
135- placeholder . textContent = node . getAttribute ( "data-placeholder-text" ) || "" ;
136- placeholder . style . cssText = `color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;` ;
137- clone2 . appendChild ( placeholder ) ;
138- return clone2 ;
139- }
140-
14187 // 8. Canvas → convert to image
14288 if ( node . tagName === "CANVAS" ) {
14389 const dataURL = node . toDataURL ( ) ;
14490 const img = document . createElement ( "img" ) ;
14591 img . src = dataURL ;
14692 img . width = node . width ;
14793 img . height = node . height ;
148- nodeMap . set ( img , node ) ;
149- if ( ! options . skipInlineStyles ) inlineAllStyles ( node , img , styleMap , styleCache , compress ) ;
15094 return img ;
15195 }
15296
15397 // 9. Base clone (without children)
15498 let clone ;
15599 try {
156100 clone = node . cloneNode ( false ) ;
157- nodeMap . set ( clone , node ) ;
158-
101+
159102 if ( node . tagName === 'IMG' ) {
160103 freezeImgSrcset ( node , clone ) ;
161104 }
@@ -191,37 +134,19 @@ export function deepClone(node, styleMap = new Map(), styleCache = new WeakMap()
191134 pendingSelectValue = node . value ;
192135 }
193136
194- // 11. Inline styles
195- if ( ! options . skipInlineStyles ) inlineAllStyles ( node , clone , styleMap , styleCache , compress ) ;
196-
197137 // 12. ShadowRoot logic
198138 if ( node . shadowRoot ) {
199139 const hasSlot = Array . from ( node . shadowRoot . querySelectorAll ( "slot" ) ) . length > 0 ;
200140
201141 if ( hasSlot ) {
202- // ShadowRoot with slots: only store styles
203- for ( const child of node . shadowRoot . childNodes ) {
204- if ( child . nodeType === Node . ELEMENT_NODE && child . tagName === "STYLE" ) {
205- const cssText = child . textContent || "" ;
206- if ( cssText . trim ( ) && compress ) {
207- //if (!cache.preStyle) cache.preStyle = new WeakMap();
208- styleCache . set ( child , cssText ) ;
209- }
210- }
211- }
212142 } else {
213143 // ShadowRoot without slots: clone full content
214144 const shadowFrag = document . createDocumentFragment ( ) ;
215145 for ( const child of node . shadowRoot . childNodes ) {
216146 if ( child . nodeType === Node . ELEMENT_NODE && child . tagName === "STYLE" ) {
217- const cssText = child . textContent || "" ;
218- if ( cssText . trim ( ) && compress ) {
219- // if (!cache.preStyle) cache.preStyle = new WeakMap();
220- styleCache . set ( child , cssText ) ;
221- }
222147 continue ;
223148 }
224- const clonedChild = deepClone ( child , styleMap , styleCache , nodeMap , compress , options , originalRoot || node ) ;
149+ const clonedChild = deepCloneBasic ( child ) ;
225150 if ( clonedChild ) shadowFrag . appendChild ( clonedChild ) ;
226151 }
227152 clone . appendChild ( shadowFrag ) ;
@@ -235,7 +160,7 @@ export function deepClone(node, styleMap = new Map(), styleCache = new WeakMap()
235160 const fragment = document . createDocumentFragment ( ) ;
236161
237162 for ( const child of nodesToClone ) {
238- const clonedChild = deepClone ( child , styleMap , styleCache , nodeMap , compress , options , originalRoot || node ) ;
163+ const clonedChild = deepCloneBasic ( child ) ;
239164 if ( clonedChild ) fragment . appendChild ( clonedChild ) ;
240165 }
241166 return fragment ;
@@ -245,7 +170,7 @@ export function deepClone(node, styleMap = new Map(), styleCache = new WeakMap()
245170 for ( const child of node . childNodes ) {
246171 if ( clonedAssignedNodes . has ( child ) ) continue ;
247172
248- const clonedChild = deepClone ( child , styleMap , styleCache , nodeMap , compress , options , originalRoot || node ) ;
173+ const clonedChild = deepCloneBasic ( child ) ;
249174 if ( clonedChild ) clone . appendChild ( clonedChild ) ;
250175 }
251176
@@ -261,5 +186,24 @@ export function deepClone(node, styleMap = new Map(), styleCache = new WeakMap()
261186 }
262187 }
263188
189+ // Fix scrolling (taken from prepareClone).
190+ const scrollX = node . scrollLeft ;
191+ const scrollY = node . scrollTop ;
192+ const hasScroll = scrollX || scrollY ;
193+ if ( hasScroll && clone instanceof HTMLElement ) {
194+ clone . style . overflow = "hidden" ;
195+ clone . style . scrollbarWidth = "none" ;
196+ clone . style . msOverflowStyle = "none" ;
197+ const inner = document . createElement ( "div" ) ;
198+ inner . style . transform = `translate(${ - scrollX } px, ${ - scrollY } px)` ;
199+ inner . style . willChange = "transform" ;
200+ inner . style . display = "inline-block" ;
201+ inner . style . width = "100%" ;
202+ while ( clone . firstChild ) {
203+ inner . appendChild ( clone . firstChild ) ;
204+ }
205+ clone . appendChild ( inner ) ;
206+ }
207+
264208 return clone ;
265209}
0 commit comments