@mirage-engine/core 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,21 +7,36 @@ import {
7
7
  SceneNode,
8
8
  Visibility,
9
9
  USER_LAYER,
10
- SYSTEM_LAYER,
10
+ SELECT_LAYER,
11
11
  ALLOWED_FILTERS,
12
+ ATTR_DOM,
13
+ ATTR_FILTER,
14
+ ATTR_SELECT,
15
+ ATTR_TRAVEL,
16
+ ATTR_SHADER,
12
17
  } from "../types";
13
18
 
14
19
  import { BoxStyles, TextStyles, ShaderHooks } from "@mirage-engine/painter";
15
- import { FilterConfig } from "../types/config";
16
20
 
17
21
  // Helper function: getTextNodeRect, isValidTextNode, isLeafTextElement, extractTextStyles
18
22
 
19
- function extractTextLines(textNode: Text): { text: string; rect: { left: number; top: number; width: number; height: number } }[] {
23
+ function extractTextLines(textNode: Text): {
24
+ text: string;
25
+ rect: { left: number; top: number; width: number; height: number };
26
+ }[] {
20
27
  const text = textNode.textContent || "";
21
- const lines: { text: string; rect: { left: number; top: number; width: number; height: number } }[] = [];
22
-
28
+ const lines: {
29
+ text: string;
30
+ rect: { left: number; top: number; width: number; height: number };
31
+ }[] = [];
32
+
23
33
  let currentLineText = "";
24
- let currentLineRect: { left: number; top: number; right: number; bottom: number } | null = null;
34
+ let currentLineRect: {
35
+ left: number;
36
+ top: number;
37
+ right: number;
38
+ bottom: number;
39
+ } | null = null;
25
40
  let currentTop = -1;
26
41
 
27
42
  const processChunk = (chunkText: string, offset: number) => {
@@ -31,13 +46,16 @@ function extractTextLines(textNode: Text): { text: string; rect: { left: number;
31
46
  range.setStart(textNode, offset + i);
32
47
  range.setEnd(textNode, offset + i + 1);
33
48
  const rect = range.getBoundingClientRect();
34
-
49
+
35
50
  if (rect.width === 0 && rect.height === 0) {
36
51
  currentLineText += char;
37
52
  continue;
38
53
  }
39
54
 
40
- if (currentTop === -1 || Math.abs(rect.top - currentTop) > rect.height / 2) {
55
+ if (
56
+ currentTop === -1 ||
57
+ Math.abs(rect.top - currentTop) > rect.height / 2
58
+ ) {
41
59
  if (currentLineText && currentLineRect) {
42
60
  lines.push({
43
61
  text: currentLineText,
@@ -50,7 +68,12 @@ function extractTextLines(textNode: Text): { text: string; rect: { left: number;
50
68
  });
51
69
  }
52
70
  currentLineText = char;
53
- currentLineRect = { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom };
71
+ currentLineRect = {
72
+ left: rect.left,
73
+ top: rect.top,
74
+ right: rect.right,
75
+ bottom: rect.bottom,
76
+ };
54
77
  currentTop = rect.top;
55
78
  } else {
56
79
  currentLineText += char;
@@ -58,7 +81,10 @@ function extractTextLines(textNode: Text): { text: string; rect: { left: number;
58
81
  currentLineRect.left = Math.min(currentLineRect.left, rect.left);
59
82
  currentLineRect.top = Math.min(currentLineRect.top, rect.top);
60
83
  currentLineRect.right = Math.max(currentLineRect.right, rect.right);
61
- currentLineRect.bottom = Math.max(currentLineRect.bottom, rect.bottom);
84
+ currentLineRect.bottom = Math.max(
85
+ currentLineRect.bottom,
86
+ rect.bottom,
87
+ );
62
88
  }
63
89
  }
64
90
  }
@@ -79,15 +105,19 @@ function extractTextLines(textNode: Text): { text: string; rect: { left: number;
79
105
  processChunk(token, currentOffset);
80
106
  } else {
81
107
  // Token is on a single line. Process it as a single block!
82
- const rect = rects.length === 1 ? rects[0] : range.getBoundingClientRect();
83
-
108
+ const rect =
109
+ rects.length === 1 ? rects[0] : range.getBoundingClientRect();
110
+
84
111
  if (rect.width === 0 && rect.height === 0) {
85
112
  currentLineText += token;
86
113
  currentOffset += token.length;
87
114
  continue;
88
115
  }
89
116
 
90
- if (currentTop === -1 || Math.abs(rect.top - currentTop) > rect.height / 2) {
117
+ if (
118
+ currentTop === -1 ||
119
+ Math.abs(rect.top - currentTop) > rect.height / 2
120
+ ) {
91
121
  if (currentLineText && currentLineRect) {
92
122
  lines.push({
93
123
  text: currentLineText,
@@ -100,7 +130,12 @@ function extractTextLines(textNode: Text): { text: string; rect: { left: number;
100
130
  });
101
131
  }
102
132
  currentLineText = token;
103
- currentLineRect = { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom };
133
+ currentLineRect = {
134
+ left: rect.left,
135
+ top: rect.top,
136
+ right: rect.right,
137
+ bottom: rect.bottom,
138
+ };
104
139
  currentTop = rect.top;
105
140
  } else {
106
141
  currentLineText += token;
@@ -108,11 +143,14 @@ function extractTextLines(textNode: Text): { text: string; rect: { left: number;
108
143
  currentLineRect.left = Math.min(currentLineRect.left, rect.left);
109
144
  currentLineRect.top = Math.min(currentLineRect.top, rect.top);
110
145
  currentLineRect.right = Math.max(currentLineRect.right, rect.right);
111
- currentLineRect.bottom = Math.max(currentLineRect.bottom, rect.bottom);
146
+ currentLineRect.bottom = Math.max(
147
+ currentLineRect.bottom,
148
+ rect.bottom,
149
+ );
112
150
  }
113
151
  }
114
152
  }
115
-
153
+
116
154
  currentOffset += token.length;
117
155
  }
118
156
 
@@ -128,7 +166,12 @@ function extractTextLines(textNode: Text): { text: string; rect: { left: number;
128
166
  });
129
167
  }
130
168
 
131
- return lines.filter(line => line.text.trim().length > 0 && line.rect.width > 0 && line.rect.height > 0);
169
+ return lines.filter(
170
+ (line) =>
171
+ line.text.trim().length > 0 &&
172
+ line.rect.width > 0 &&
173
+ line.rect.height > 0,
174
+ );
132
175
  }
133
176
 
134
177
  function extractTextStyles(computed: CSSStyleDeclaration): TextStyles {
@@ -161,7 +204,11 @@ export function extractSceneGraph(
161
204
  DIRTY_CONTENT |
162
205
  DIRTY_STRUCTURE,
163
206
  inheritedFlow: Visibility,
164
- filterConfig?: FilterConfig,
207
+ captureLayer: number = 1,
208
+ inheritedZIndex: number = 0,
209
+ qualityFactor: number = 2,
210
+ inheritedNativeLayer?: number,
211
+ inheritedNativeStyles?: any,
165
212
  ): SceneNode | null {
166
213
  // Check text node
167
214
  if (sourceNode.nodeType === Node.TEXT_NODE) {
@@ -181,10 +228,10 @@ export function extractSceneGraph(
181
228
  if (!computed) return null;
182
229
 
183
230
  // Calculate overall bounding box of the lines
184
- const minX = Math.min(...textLines.map(l => l.rect.left));
185
- const minY = Math.min(...textLines.map(l => l.rect.top));
186
- const maxX = Math.max(...textLines.map(l => l.rect.left + l.rect.width));
187
- const maxY = Math.max(...textLines.map(l => l.rect.top + l.rect.height));
231
+ const minX = Math.min(...textLines.map((l) => l.rect.left));
232
+ const minY = Math.min(...textLines.map((l) => l.rect.top));
233
+ const maxX = Math.max(...textLines.map((l) => l.rect.left + l.rect.width));
234
+ const maxY = Math.max(...textLines.map((l) => l.rect.top + l.rect.height));
188
235
 
189
236
  // Create SceneNode for the text node
190
237
  return {
@@ -201,30 +248,62 @@ export function extractSceneGraph(
201
248
  backgroundColor: "transparent",
202
249
  backgroundImage: "",
203
250
  opacity:
204
- parent && parent.dataset.mirageDom === "hide"
251
+ parent && parent.dataset[ATTR_DOM.KEY] === ATTR_DOM.VALUES.HIDE
205
252
  ? 1
206
253
  : parseFloat(computed.opacity),
207
- zIndex: 0,
254
+ zIndex:
255
+ (isNaN(parseInt(computed.zIndex)) ? 0 : parseInt(computed.zIndex)) +
256
+ inheritedZIndex,
208
257
  borderRadius: "0px",
209
258
  borderColor: "transparent",
210
259
  borderWidth: "0px",
211
260
  isTraveler: false,
212
261
  },
213
262
  textContent: normalizedText,
214
- textLines: textLines.map(l => ({
263
+ textLines: textLines.map((l) => ({
215
264
  text: l.text.trim(),
216
265
  rect: {
217
266
  x: l.rect.left + window.scrollX,
218
267
  y: l.rect.top + window.scrollY,
219
268
  width: l.rect.width,
220
269
  height: l.rect.height,
221
- }
270
+ },
222
271
  })),
223
272
  textStyles: extractTextStyles(computed),
224
273
  dirtyMask: initialMask,
225
274
  visibility: inheritedFlow,
226
275
  isTraveler: false,
276
+ captureLayer,
227
277
  isFixed: computed.position === "fixed",
278
+ nativeLayer: inheritedNativeLayer,
279
+ nativeStyles: inheritedNativeStyles
280
+ ? {
281
+ backgroundColor: "transparent",
282
+ backgroundImage: "",
283
+ opacity:
284
+ parent && parent.dataset[ATTR_DOM.KEY] === ATTR_DOM.VALUES.HIDE
285
+ ? 1
286
+ : parseFloat(computed.opacity),
287
+ zIndex:
288
+ (isNaN(parseInt(computed.zIndex))
289
+ ? 0
290
+ : parseInt(computed.zIndex)) + inheritedZIndex,
291
+ borderRadius: "0px",
292
+ borderColor: "transparent",
293
+ borderWidth: "0px",
294
+ isTraveler: false,
295
+ ...extractTextStyles(computed),
296
+ ...inheritedNativeStyles,
297
+ }
298
+ : undefined,
299
+ nativeRect: inheritedNativeStyles
300
+ ? {
301
+ x: minX + window.scrollX,
302
+ y: minY + window.scrollY,
303
+ width: maxX - minX,
304
+ height: maxY - minY,
305
+ }
306
+ : undefined,
228
307
  children: [],
229
308
  };
230
309
  }
@@ -235,14 +314,14 @@ export function extractSceneGraph(
235
314
 
236
315
  const element = sourceNode as HTMLElement;
237
316
  // [[Filter]] data attribute based filtering
238
- const filterData = element.dataset.mirageFilter;
317
+ const filterData = element.dataset[ATTR_FILTER.KEY];
239
318
  let visibleFlow = inheritedFlow;
240
319
  let visibleFlag = inheritedFlow;
241
320
  if (filterData) {
242
321
  const filterSet = new Set(filterData.split(/\s+/));
243
322
  // error check
244
323
  for (const token of filterSet) {
245
- if (!ALLOWED_FILTERS.includes(token)) {
324
+ if (!ALLOWED_FILTERS.includes(token as any)) {
246
325
  throw new Error(
247
326
  `[MirageEngine] Invalid filter token: '${token}'. ` +
248
327
  `Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`,
@@ -250,58 +329,152 @@ export function extractSceneGraph(
250
329
  }
251
330
  }
252
331
 
253
- if (filterSet.has("end")) return null;
332
+ if (filterSet.has(ATTR_FILTER.VALUES.END)) return null;
254
333
 
255
334
  // error check
256
- if (filterSet.has("include-tree") && filterSet.has("exclude-tree")) {
335
+ if (
336
+ filterSet.has(ATTR_FILTER.VALUES.INCLUDE_TREE) &&
337
+ filterSet.has(ATTR_FILTER.VALUES.EXCLUDE_TREE)
338
+ ) {
257
339
  throw new Error(
258
340
  `[MirageEngine] Conflicting filters: 'include-tree' and 'exclude-tree' cannot be used together on the same element.`,
259
341
  );
260
342
  }
261
- if (filterSet.has("include-self") && filterSet.has("exclude-self")) {
343
+ if (
344
+ filterSet.has(ATTR_FILTER.VALUES.INCLUDE_SELF) &&
345
+ filterSet.has(ATTR_FILTER.VALUES.EXCLUDE_SELF)
346
+ ) {
262
347
  throw new Error(
263
348
  `[MirageEngine] Conflicting filters: 'include-self' and 'exclude-self' cannot be used together on the same element.`,
264
349
  );
265
350
  }
266
351
 
267
- if (filterSet.has("include-tree")) {
352
+ if (filterSet.has(ATTR_FILTER.VALUES.INCLUDE_TREE)) {
268
353
  visibleFlow = (visibleFlow | USER_LAYER) as Visibility;
269
- } else if (filterSet.has("exclude-tree")) {
354
+ } else if (filterSet.has(ATTR_FILTER.VALUES.EXCLUDE_TREE)) {
270
355
  visibleFlow = (visibleFlow & ~USER_LAYER) as Visibility;
271
356
  }
272
357
 
273
358
  visibleFlag = visibleFlow;
274
359
 
275
- if (filterSet.has("include-self")) {
360
+ if (filterSet.has(ATTR_FILTER.VALUES.INCLUDE_SELF)) {
276
361
  visibleFlag = (visibleFlag | USER_LAYER) as Visibility;
277
- } else if (filterSet.has("exclude-self")) {
362
+ } else if (filterSet.has(ATTR_FILTER.VALUES.EXCLUDE_SELF)) {
278
363
  visibleFlag = (visibleFlag & ~USER_LAYER) as Visibility;
279
364
  }
280
365
  }
281
366
 
282
- // [[filter]] class based filtering
283
- // [Filter] end
284
- // if (filterConfig && filterConfig.end && filterConfig.end.length > 0) {
285
- // const isEnd = filterConfig.end.some((cls) =>
286
- // element.classList.contains(cls),
287
- // );
288
- // if (isEnd) return null;
289
- // }
367
+ // [[Select]] data attribute based selecting
368
+ const selectData = element.dataset[ATTR_SELECT.KEY];
369
+ if (selectData) {
370
+ const selectSet = new Set(selectData.split(/\s+/));
371
+ // error check
372
+ for (const token of selectSet) {
373
+ if (!ALLOWED_FILTERS.includes(token as any)) {
374
+ throw new Error(
375
+ `[MirageEngine] Invalid select token: '${token}'. ` +
376
+ `Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`,
377
+ );
378
+ }
379
+ }
290
380
 
291
- visibleFlag = (visibleFlag | (inheritedFlow & SYSTEM_LAYER)) as Visibility;
381
+ if (selectSet.has(ATTR_SELECT.VALUES.END)) return null;
292
382
 
293
- const travelData = element.dataset.mirageTravel;
383
+ if (
384
+ selectSet.has(ATTR_SELECT.VALUES.INCLUDE_TREE) &&
385
+ selectSet.has(ATTR_SELECT.VALUES.EXCLUDE_TREE)
386
+ ) {
387
+ throw new Error(
388
+ `[MirageEngine] Conflicting selects: 'include-tree' and 'exclude-tree' cannot be used together on the same element.`,
389
+ );
390
+ }
391
+ if (
392
+ selectSet.has(ATTR_SELECT.VALUES.INCLUDE_SELF) &&
393
+ selectSet.has(ATTR_SELECT.VALUES.EXCLUDE_SELF)
394
+ ) {
395
+ throw new Error(
396
+ `[MirageEngine] Conflicting selects: 'include-self' and 'exclude-self' cannot be used together on the same element.`,
397
+ );
398
+ }
399
+
400
+ if (selectSet.has(ATTR_SELECT.VALUES.INCLUDE_TREE)) {
401
+ visibleFlow = (visibleFlow | SELECT_LAYER) as Visibility;
402
+ } else if (selectSet.has(ATTR_SELECT.VALUES.EXCLUDE_TREE)) {
403
+ visibleFlow = (visibleFlow & ~SELECT_LAYER) as Visibility;
404
+ }
405
+
406
+ visibleFlag = visibleFlow;
407
+
408
+ if (selectSet.has(ATTR_SELECT.VALUES.INCLUDE_SELF)) {
409
+ visibleFlag = (visibleFlag | SELECT_LAYER) as Visibility;
410
+ } else if (selectSet.has(ATTR_SELECT.VALUES.EXCLUDE_SELF)) {
411
+ visibleFlag = (visibleFlag & ~SELECT_LAYER) as Visibility;
412
+ }
413
+ }
414
+
415
+ const travelData = element.dataset[ATTR_TRAVEL.KEY];
294
416
  let isTraveler = false;
417
+ let nativeParsedStyles: any = inheritedNativeStyles
418
+ ? { ...inheritedNativeStyles }
419
+ : {};
420
+ let nativeLayer: number | undefined = inheritedNativeLayer;
295
421
  if (travelData) {
296
- const travelSet = new Set(travelData.split(/\s+/));
297
- if (travelSet.has("traveler")) {
298
- visibleFlag = (visibleFlag & ~SYSTEM_LAYER) as Visibility;
299
- visibleFlow = (visibleFlow & ~SYSTEM_LAYER) as Visibility;
422
+ let explicitLayer = 1;
423
+
424
+ // '{' 부터 '}' 까지의 JSON 문자열 추출
425
+ const jsonStart = travelData.indexOf("{");
426
+ const jsonEnd = travelData.lastIndexOf("}");
427
+
428
+ let tokensPart = travelData;
429
+ if (jsonStart !== -1 && jsonEnd !== -1 && jsonEnd > jsonStart) {
430
+ tokensPart = travelData.substring(0, jsonStart).trim();
431
+ const jsonStr = travelData.substring(jsonStart, jsonEnd + 1);
432
+ try {
433
+ // 싱글 쿼트를 더블 쿼트로 변경하거나 키에 따옴표가 없는 객체를 지원하기 위해
434
+ // Function을 통한 안전한 객체 평가(또는 JSON.parse)를 사용
435
+ nativeParsedStyles = new Function("return " + jsonStr)();
436
+ } catch (e) {
437
+ console.warn(
438
+ `[MirageEngine] Failed to parse travel styles JSON: ${jsonStr}`,
439
+ );
440
+ }
441
+ }
442
+
443
+ const tokens = tokensPart.split(/\s+/);
444
+
445
+ let hasTravelToken = false;
446
+
447
+ // traveler 인 경우
448
+ if (tokens.includes(ATTR_TRAVEL.VALUES.TRAVELER)) {
300
449
  isTraveler = true;
450
+ hasTravelToken = true;
451
+ const numToken = tokens.find((t) => !isNaN(parseInt(t, 10)));
452
+ if (numToken) {
453
+ explicitLayer = parseInt(numToken, 10);
454
+ }
455
+ }
456
+ // native 인 경우
457
+ else if (tokens.includes(ATTR_TRAVEL.VALUES.NATIVE)) {
458
+ isTraveler = false;
459
+ const numToken = tokens.find((t) => !isNaN(parseInt(t, 10)));
460
+ if (numToken) {
461
+ nativeLayer = parseInt(numToken, 10);
462
+ }
463
+ }
464
+
465
+ // traveler만 명시된 레이어에 맞게 captureLayer 컨텍스트를 업데이트해야 함 (계층적 여행)
466
+ if (hasTravelToken) {
467
+ const targetCaptureLayer = explicitLayer + 1;
468
+ if (targetCaptureLayer < captureLayer) {
469
+ throw new Error(
470
+ `[MirageEngine] Traveler layer (${explicitLayer}) cannot be smaller than inherited capture layer (${captureLayer - 1}).`,
471
+ );
472
+ }
473
+ captureLayer = Math.min(targetCaptureLayer, ATTR_TRAVEL.MAX_LAYERS + 1);
301
474
  }
302
475
  }
303
476
 
304
- const shaderData = element.dataset.mirageShader;
477
+ const shaderData = element.dataset[ATTR_SHADER.KEY];
305
478
  let shaderHooks: ShaderHooks | undefined;
306
479
  if (shaderData) {
307
480
  shaderHooks = JSON.parse(shaderData) as ShaderHooks;
@@ -324,12 +497,70 @@ export function extractSceneGraph(
324
497
  element.setAttribute("data-mid", id);
325
498
  }
326
499
 
327
- const zIndex = parseInt(computed.zIndex);
500
+ const localZIndex = parseInt(computed.zIndex);
501
+ const effectiveZIndex =
502
+ (isNaN(localZIndex) ? 0 : localZIndex) + inheritedZIndex;
328
503
  // console.log(`${element.id}: ${computed.background}`);
329
504
  // console.log(computed.backgroundImage);
330
505
  let imageSrc: string | undefined;
331
506
  if (element.tagName === "IMG") {
332
507
  imageSrc = (element as HTMLImageElement).src;
508
+ } else if (element.tagName.toLowerCase() === "svg") {
509
+ const clone = element.cloneNode(true) as SVGSVGElement;
510
+
511
+ const overrideColor = nativeParsedStyles?.color;
512
+ const overrideFill = nativeParsedStyles?.fill;
513
+ const overrideStroke = nativeParsedStyles?.stroke;
514
+ const overrideOpacity = nativeParsedStyles?.opacity;
515
+
516
+ const inlineSVGStyles = (orig: Element, cloned: Element) => {
517
+ const computed = window.getComputedStyle(orig);
518
+ const clonedHtml = cloned as HTMLElement;
519
+
520
+ const isCurrentColorFill = computed.fill === computed.color;
521
+ const isCurrentColorStroke = computed.stroke === computed.color;
522
+
523
+ const fill = overrideFill || (isCurrentColorFill ? overrideColor : undefined) || computed.fill;
524
+ if (fill && fill !== "none") clonedHtml.style.fill = fill;
525
+
526
+ const stroke = overrideStroke || (isCurrentColorStroke ? overrideColor : undefined) || computed.stroke;
527
+ if (stroke && stroke !== "none") clonedHtml.style.stroke = stroke;
528
+
529
+ if (computed.strokeWidth && computed.strokeWidth !== "0px")
530
+ clonedHtml.style.strokeWidth = computed.strokeWidth;
531
+
532
+ const color = overrideColor || computed.color;
533
+ if (color) clonedHtml.style.color = color;
534
+
535
+ const opacity = overrideOpacity || computed.opacity;
536
+ if (opacity && opacity !== "1") clonedHtml.style.opacity = opacity;
537
+
538
+ for (let i = 0; i < orig.children.length; i++) {
539
+ inlineSVGStyles(orig.children[i], cloned.children[i]);
540
+ }
541
+ };
542
+
543
+ inlineSVGStyles(element, clone);
544
+
545
+ const svgRect = element.getBoundingClientRect();
546
+ const scale = window.devicePixelRatio * qualityFactor; // High-DPI 대응을 위한 해상도 스케일업
547
+
548
+ if (!clone.hasAttribute("viewBox")) {
549
+ clone.setAttribute("viewBox", `0 0 ${svgRect.width} ${svgRect.height}`);
550
+ }
551
+
552
+ clone.setAttribute("width", (svgRect.width * scale).toString());
553
+ clone.setAttribute("height", (svgRect.height * scale).toString());
554
+
555
+ let svgString = new XMLSerializer().serializeToString(clone);
556
+ if (!svgString.includes("xmlns=")) {
557
+ svgString = svgString.replace(
558
+ "<svg",
559
+ '<svg xmlns="http://www.w3.org/2000/svg"',
560
+ );
561
+ }
562
+
563
+ imageSrc = `data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`;
333
564
  } else if (computed.backgroundImage && computed.backgroundImage !== "none") {
334
565
  const match = computed.backgroundImage.match(/url\(['"]?(.*?)['"]?\)/);
335
566
  if (match) {
@@ -337,36 +568,49 @@ export function extractSceneGraph(
337
568
  }
338
569
  }
339
570
 
340
- const styles: BoxStyles = {
571
+ // Base node uses pure DOM styles, unpolluted by travelerStyles
572
+ const baseStyles: BoxStyles = {
341
573
  backgroundColor: computed.backgroundColor,
342
574
  backgroundImage: computed.backgroundImage,
343
575
  opacity:
344
- element.dataset.mirageDom === "hide" ? 1 : parseFloat(computed.opacity),
345
- zIndex: isNaN(zIndex) ? 0 : zIndex,
576
+ element.dataset[ATTR_DOM.KEY] === ATTR_DOM.VALUES.HIDE
577
+ ? 1
578
+ : parseFloat(computed.opacity),
579
+ zIndex: effectiveZIndex,
346
580
  borderRadius: computed.borderRadius,
347
581
  borderColor: computed.borderColor,
348
582
  borderWidth: computed.borderWidth,
349
583
  imageSrc,
350
- isTraveler,
584
+ isTraveler: isTraveler,
351
585
  };
352
586
 
587
+ const styles: BoxStyles = baseStyles;
588
+
353
589
  let textContent: string | undefined;
354
590
  let textStyles: TextStyles | undefined;
355
591
  const children: SceneNode[] = [];
356
592
 
357
- Array.from(element.childNodes).forEach((child) => {
358
- const visibleFlowToPass =
359
- child.nodeType === Node.TEXT_NODE ? visibleFlag : visibleFlow;
360
- const childNode = extractSceneGraph(
361
- child,
362
- initialMask,
363
- visibleFlowToPass,
364
- filterConfig,
365
- );
366
- if (childNode) {
367
- children.push(childNode);
368
- }
369
- });
593
+ if (element.tagName.toLowerCase() !== "svg") {
594
+ Array.from(element.childNodes).forEach((child) => {
595
+ const visibleFlowToPass =
596
+ child.nodeType === Node.TEXT_NODE ? visibleFlag : visibleFlow;
597
+ const childNode = extractSceneGraph(
598
+ child,
599
+ initialMask,
600
+ visibleFlowToPass,
601
+ captureLayer,
602
+ effectiveZIndex,
603
+ qualityFactor,
604
+ nativeLayer,
605
+ child.nodeType === Node.TEXT_NODE && Object.keys(nativeParsedStyles).length > 0
606
+ ? nativeParsedStyles
607
+ : undefined,
608
+ );
609
+ if (childNode) {
610
+ children.push(childNode);
611
+ }
612
+ });
613
+ }
370
614
 
371
615
  return {
372
616
  id,
@@ -384,6 +628,51 @@ export function extractSceneGraph(
384
628
  dirtyMask: initialMask,
385
629
  visibility: visibleFlag,
386
630
  isTraveler: isTraveler,
631
+ captureLayer,
632
+ nativeLayer,
633
+ nativeStyles:
634
+ nativeLayer !== undefined
635
+ ? {
636
+ ...baseStyles,
637
+ backgroundColor:
638
+ nativeParsedStyles.backgroundColor ?? baseStyles.backgroundColor,
639
+ backgroundImage:
640
+ nativeParsedStyles.backgroundImage ?? baseStyles.backgroundImage,
641
+ opacity: nativeParsedStyles.opacity ?? baseStyles.opacity,
642
+ zIndex:
643
+ nativeParsedStyles.zIndex !== undefined
644
+ ? nativeParsedStyles.zIndex + effectiveZIndex
645
+ : baseStyles.zIndex,
646
+ borderRadius:
647
+ nativeParsedStyles.borderRadius ?? baseStyles.borderRadius,
648
+ borderColor:
649
+ nativeParsedStyles.borderColor ?? baseStyles.borderColor,
650
+ borderWidth:
651
+ nativeParsedStyles.borderWidth ?? baseStyles.borderWidth,
652
+ isTraveler: baseStyles.isTraveler,
653
+ }
654
+ : undefined,
655
+ nativeRect:
656
+ nativeLayer !== undefined
657
+ ? {
658
+ x:
659
+ nativeParsedStyles.x !== undefined
660
+ ? parseFloat(nativeParsedStyles.x)
661
+ : rect.left + window.scrollX,
662
+ y:
663
+ nativeParsedStyles.y !== undefined
664
+ ? parseFloat(nativeParsedStyles.y)
665
+ : rect.top + window.scrollY,
666
+ width:
667
+ nativeParsedStyles.width !== undefined
668
+ ? parseFloat(nativeParsedStyles.width)
669
+ : rect.width,
670
+ height:
671
+ nativeParsedStyles.height !== undefined
672
+ ? parseFloat(nativeParsedStyles.height)
673
+ : rect.height,
674
+ }
675
+ : undefined,
387
676
  isFixed: computed.position === "fixed",
388
677
  children,
389
678
  shaderHooks,