@vvfx/sdk 0.2.8 → 0.2.9

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.
Files changed (107) hide show
  1. package/dist/config.js +231 -0
  2. package/dist/exporter/config.js +18 -0
  3. package/dist/exporter/const.js +71 -0
  4. package/dist/exporter/export-media.js +878 -0
  5. package/dist/exporter/exporter.js +121 -0
  6. package/dist/exporter/index.js +1 -0
  7. package/dist/exporter/types.js +1 -0
  8. package/dist/exporter/utils.js +190 -0
  9. package/dist/exporter/wav-audio.js +50 -0
  10. package/dist/gesture-handler/gizmo/adsorption-gizmo.js +227 -0
  11. package/dist/gesture-handler/gizmo/control-gizmo.js +149 -0
  12. package/dist/gesture-handler/gizmo/gizmo.js +17 -0
  13. package/dist/gesture-handler/gizmo/icon-gizmo.js +189 -0
  14. package/dist/gesture-handler/gizmo/index.js +6 -0
  15. package/dist/gesture-handler/gizmo/item-create-gizmo.js +256 -0
  16. package/dist/gesture-handler/gizmo/loading-gizmo.js +320 -0
  17. package/dist/gesture-handler/gizmo/mask-gizmo.js +648 -0
  18. package/dist/gesture-handler/gizmo/picture-cut-gizmo.js +550 -0
  19. package/dist/gesture-handler/gizmo/picture-expand-gizmo.js +491 -0
  20. package/dist/gesture-handler/gizmo/preference-gizmo.js +148 -0
  21. package/dist/gesture-handler/gizmo/selector-gizmo.d.ts +1 -1
  22. package/dist/gesture-handler/gizmo/selector-gizmo.js +468 -0
  23. package/dist/gesture-handler/gizmo/sprite-text-edit-gizmo.js +247 -0
  24. package/dist/gesture-handler/gizmo/text-gizmo.js +1033 -0
  25. package/dist/gesture-handler/gizmo/transform-gizmo.js +1199 -0
  26. package/dist/gesture-handler/gizmo/type.js +18 -0
  27. package/dist/gesture-handler/index.js +3 -0
  28. package/dist/gesture-handler/src/gesture-handler.js +770 -0
  29. package/dist/gesture-handler/src/icons.js +24 -0
  30. package/dist/gesture-handler/utils.js +40 -0
  31. package/dist/html-overlay/anchor-navigation-runtime.js +27 -0
  32. package/dist/html-overlay/auto-height-runtime.js +132 -0
  33. package/dist/html-overlay/document-runtime.js +71 -0
  34. package/dist/html-overlay/document-transform.js +96 -0
  35. package/dist/html-overlay/dom-order.js +8 -0
  36. package/dist/html-overlay/host-message-runtime.js +24 -0
  37. package/dist/html-overlay/html-capture-runtime.js +282 -0
  38. package/dist/html-overlay/html-viewport-style.js +10 -0
  39. package/dist/html-overlay/index.js +1 -0
  40. package/dist/html-overlay/manager.js +1663 -0
  41. package/dist/html-overlay/mime-utils.js +21 -0
  42. package/dist/html-overlay/overlay-transform.js +57 -0
  43. package/dist/html-overlay/path-utils.js +35 -0
  44. package/dist/html-overlay/shell-runtime.js +12 -0
  45. package/dist/index.cjs +1 -1
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.js +13 -75311
  48. package/dist/index.mjs +75311 -0
  49. package/dist/{index.js.map → index.mjs.map} +1 -1
  50. package/dist/layer-order.js +8 -0
  51. package/dist/math/box2.js +396 -0
  52. package/dist/math/circle.js +131 -0
  53. package/dist/math/euler.js +35 -0
  54. package/dist/math/index.js +13 -0
  55. package/dist/math/line2.js +153 -0
  56. package/dist/math/line3.js +36 -0
  57. package/dist/math/matrix4.js +207 -0
  58. package/dist/math/plane.js +52 -0
  59. package/dist/math/quaternion.js +3 -0
  60. package/dist/math/ray-caster.js +25 -0
  61. package/dist/math/ray.js +47 -0
  62. package/dist/math/type.js +1 -0
  63. package/dist/math/utils.js +77 -0
  64. package/dist/math/vector2.js +70 -0
  65. package/dist/math/vector3.js +60 -0
  66. package/dist/screen-shot/index.js +1 -0
  67. package/dist/screen-shot/screen-shot.js +113 -0
  68. package/dist/sdk-item/base-item.js +160 -0
  69. package/dist/sdk-item/card-item.js +131 -0
  70. package/dist/sdk-item/effects-item.js +144 -0
  71. package/dist/sdk-item/frame-item.js +217 -0
  72. package/dist/sdk-item/generator-item.js +188 -0
  73. package/dist/sdk-item/group-item.js +116 -0
  74. package/dist/sdk-item/index.js +39 -0
  75. package/dist/sdk-item/sprite-item.js +147 -0
  76. package/dist/sdk-item/text-item.js +223 -0
  77. package/dist/sdk-item/types.d.ts +1 -1
  78. package/dist/sdk-item/types.js +15 -0
  79. package/dist/sdk-item/video-item.js +174 -0
  80. package/dist/sdk.js +1903 -0
  81. package/dist/service/UndoRedo.js +79 -0
  82. package/dist/shared/index.d.ts +1 -0
  83. package/dist/shared/index.js +2 -0
  84. package/dist/shared/player.d.ts +1 -1
  85. package/dist/shared/player.js +9 -0
  86. package/dist/shared/spec.d.ts +1 -0
  87. package/dist/shared/spec.js +1 -0
  88. package/dist/size-adapte/index.js +1 -0
  89. package/dist/size-adapte/size-adapt.js +395 -0
  90. package/dist/types.d.ts +5 -1
  91. package/dist/types.js +17 -0
  92. package/dist/utils/background-manager.js +189 -0
  93. package/dist/utils/common-utils.js +183 -0
  94. package/dist/utils/index.js +8 -0
  95. package/dist/utils/interaction-utils.js +205 -0
  96. package/dist/utils/json-data-utils.js +1529 -0
  97. package/dist/utils/layout-utils.js +1217 -0
  98. package/dist/utils/page-data-utils.js +5397 -0
  99. package/dist/utils/player-data-utils.js +48 -0
  100. package/dist/utils/types.js +8 -0
  101. package/dist/wireframe/common/box.js +18 -0
  102. package/dist/wireframe/common/dashed-line.js +21 -0
  103. package/dist/wireframe/common/line.js +17 -0
  104. package/dist/wireframe/common/pixi-ext.js +1 -0
  105. package/dist/wireframe/common/pixi.js +31 -0
  106. package/dist/wireframe/index.js +4 -0
  107. package/package.json +10 -3
@@ -0,0 +1,1663 @@
1
+ import { snapdom } from '@zumer/snapdom';
2
+ import { isCardItem, isFrameItem } from '../sdk-item';
3
+ import { requestCardHTMLAutoHeight } from './auto-height-runtime';
4
+ import { renderDOMContentInIframe, renderDocumentContent } from './document-runtime';
5
+ import { createDocumentFileUrlFactory, normalizeDocumentFiles, transformHTMLDocument } from './document-transform';
6
+ import { syncElementStackOrder } from './dom-order';
7
+ import { withCardHTMLViewportStyle } from './html-viewport-style';
8
+ import { createHTMLCaptureReplica, createHTMLCaptureReplicaFromDocument } from './html-capture-runtime';
9
+ import { getCardOverlayBoxStyle, isPointInCardOverlayBox } from './overlay-transform';
10
+ import { normalizePath } from './path-utils';
11
+ import { mountHTMLShell } from './shell-runtime';
12
+ const SHELL_INTERACTIVE_SELECTOR = '[data-vvfx-html-shell-interactive="true"]';
13
+ const EDITING_INTERACTIVE_SELECTOR = '[data-vvfx-html-editing-interactive="true"]';
14
+ const CONTENT_INTERACTIVE_SELECTOR = [
15
+ '[data-vvfx-html-interaction-zone="self"]',
16
+ '[data-vvfx-html-interaction-zone="subtree"]',
17
+ 'a[href]',
18
+ 'button:not([disabled])',
19
+ 'input:not([disabled])',
20
+ 'textarea:not([disabled])',
21
+ 'select:not([disabled])',
22
+ 'summary',
23
+ 'video[controls]',
24
+ 'audio[controls]',
25
+ '[contenteditable]:not([contenteditable="false"])',
26
+ '[role="button"]',
27
+ '[role="link"]',
28
+ '[role="textbox"]',
29
+ '[tabindex]:not([tabindex="-1"])',
30
+ ].join(', ');
31
+ const CONTENT_INTERACTION_NONE_SELECTOR = [
32
+ '[data-vvfx-html-interaction-zone="none"]',
33
+ ].join(', ');
34
+ const EDITING_VIEWPORT_PADDING = 48;
35
+ const EDITING_VIEWPORT_BOX_SCALE = 1.1;
36
+ const AUTO_HEIGHT_EPSILON = 0.5;
37
+ const CARD_RASTERIZE_READY_TIMEOUT = 3000;
38
+ const CARD_HTML_EVENT_MESSAGE_SOURCE = 'vvfx-card-html-event';
39
+ const requestHTMLOverlayFrame = (callback) => {
40
+ if (typeof requestAnimationFrame === 'function') {
41
+ try {
42
+ return requestAnimationFrame(callback);
43
+ }
44
+ catch {
45
+ // Some non-browser test runtimes install a RAF polyfill that depends on window.
46
+ }
47
+ }
48
+ return setTimeout(() => {
49
+ callback(Date.now());
50
+ }, 16);
51
+ };
52
+ const cancelHTMLOverlayFrame = (frame) => {
53
+ if (typeof cancelAnimationFrame === 'function') {
54
+ try {
55
+ cancelAnimationFrame(frame);
56
+ return;
57
+ }
58
+ catch {
59
+ // Fall back to clearTimeout for frames created by requestHTMLOverlayFrame.
60
+ }
61
+ }
62
+ clearTimeout(frame);
63
+ };
64
+ export class HTMLOverlayManager {
65
+ options;
66
+ eventCleanups = [];
67
+ htmlRootConfigCleanups = new WeakMap();
68
+ pendingContentInteractionEvent;
69
+ contentInteractionFrame;
70
+ state = {
71
+ frameElements: new Map(),
72
+ elements: new Map(),
73
+ selectionElements: new Map(),
74
+ contentScaleElements: new Map(),
75
+ contentElements: new Map(),
76
+ contentMountElements: new Map(),
77
+ contentInteractionZones: new Map(),
78
+ contentInteractionZoneCleanups: new Map(),
79
+ contentInteractionResizeCleanups: new Map(),
80
+ contents: new Map(),
81
+ cleanups: new Map(),
82
+ autoHeightModes: new Map(),
83
+ autoHeightCleanups: new Map(),
84
+ autoHeightFrames: new Map(),
85
+ autoHeightStreamPhases: new Map(),
86
+ };
87
+ constructor(options) {
88
+ this.options = options;
89
+ }
90
+ attach() {
91
+ const { container } = this.options;
92
+ const layer = document.createElement('div');
93
+ if (getComputedStyle(container).position === 'static') {
94
+ container.style.position = 'relative';
95
+ }
96
+ layer.className = 'vvfx-card-html-overlay-layer';
97
+ layer.style.position = 'absolute';
98
+ layer.style.inset = '0';
99
+ layer.style.pointerEvents = 'none';
100
+ container.appendChild(layer);
101
+ this.state.layer = layer;
102
+ this.initDOMEvents();
103
+ this.initEvents();
104
+ this.scheduleRender();
105
+ }
106
+ get layerElement() {
107
+ return this.state.layer;
108
+ }
109
+ dispose() {
110
+ if (this.state.updateFrame !== undefined) {
111
+ cancelHTMLOverlayFrame(this.state.updateFrame);
112
+ this.state.updateFrame = undefined;
113
+ }
114
+ if (this.contentInteractionFrame !== undefined) {
115
+ cancelHTMLOverlayFrame(this.contentInteractionFrame);
116
+ this.contentInteractionFrame = undefined;
117
+ }
118
+ this.pendingContentInteractionEvent = undefined;
119
+ this.state.cleanups.forEach(cleanup => {
120
+ cleanup();
121
+ });
122
+ this.state.frameElements.clear();
123
+ this.state.elements.clear();
124
+ this.state.selectionElements.clear();
125
+ this.state.contentScaleElements.clear();
126
+ this.state.contentElements.clear();
127
+ this.state.contentMountElements.clear();
128
+ this.state.contentInteractionZones.clear();
129
+ this.state.contentInteractionZoneCleanups.forEach(cleanup => {
130
+ cleanup();
131
+ });
132
+ this.state.contentInteractionZoneCleanups.clear();
133
+ this.state.contentInteractionResizeCleanups.forEach(cleanup => {
134
+ cleanup();
135
+ });
136
+ this.state.contentInteractionResizeCleanups.clear();
137
+ this.state.contents.clear();
138
+ this.state.cleanups.clear();
139
+ this.state.autoHeightModes.clear();
140
+ this.state.autoHeightCleanups.forEach(cleanup => {
141
+ cleanup();
142
+ });
143
+ this.state.autoHeightCleanups.clear();
144
+ this.state.autoHeightFrames.forEach(frame => {
145
+ cancelHTMLOverlayFrame(frame);
146
+ });
147
+ this.state.autoHeightFrames.clear();
148
+ this.state.autoHeightStreamPhases.clear();
149
+ this.eventCleanups.forEach(cleanup => {
150
+ cleanup();
151
+ });
152
+ this.eventCleanups = [];
153
+ this.state.layer?.remove();
154
+ this.state.layer = undefined;
155
+ this.state.selectionLayer = undefined;
156
+ this.options.container.removeEventListener('dblclick', this.handleContainerDoubleClick, true);
157
+ this.options.container.removeEventListener('mousedown', this.handleContainerMouseDown, true);
158
+ this.options.container.removeEventListener('mousemove', this.handleContainerMouseMove, true);
159
+ this.options.container.removeEventListener('mouseleave', this.handleContainerMouseLeave, true);
160
+ this.options.container.removeEventListener('contextmenu', this.handleContainerContextMenu, true);
161
+ }
162
+ scheduleRender() {
163
+ if (this.state.updateFrame !== undefined) {
164
+ cancelHTMLOverlayFrame(this.state.updateFrame);
165
+ }
166
+ this.state.updateFrame = requestHTMLOverlayFrame(() => {
167
+ this.state.updateFrame = undefined;
168
+ this.render();
169
+ });
170
+ }
171
+ invalidateCardHTML(id) {
172
+ this.cleanupOverlayContent(id);
173
+ this.invalidateContentInteractionZones(id);
174
+ }
175
+ getHtmlCardWindow(id) {
176
+ const contentOverlay = this.state.contentElements.get(id);
177
+ const iframe = contentOverlay?.querySelector('iframe');
178
+ return iframe?.contentWindow;
179
+ }
180
+ patchCardHTML(id, patch) {
181
+ const html = this.state.contents.get(id);
182
+ if (!html) {
183
+ return false;
184
+ }
185
+ const patchHandler = html.content.patch;
186
+ if (!patchHandler) {
187
+ return false;
188
+ }
189
+ const context = this.createPatchContext(id, html);
190
+ return patchHandler(patch, context) !== false;
191
+ }
192
+ dispatchHtmlCardEvent(id, type, detail) {
193
+ const targetWindow = this.getHtmlCardWindow(id);
194
+ try {
195
+ if (!targetWindow || typeof targetWindow.postMessage !== 'function') {
196
+ return false;
197
+ }
198
+ targetWindow.postMessage({
199
+ detail,
200
+ payload: detail,
201
+ source: CARD_HTML_EVENT_MESSAGE_SOURCE,
202
+ type,
203
+ }, '*');
204
+ return true;
205
+ }
206
+ catch {
207
+ return false;
208
+ }
209
+ }
210
+ async rasterizeCard(id, options) {
211
+ if (this.state.updateFrame !== undefined) {
212
+ cancelHTMLOverlayFrame(this.state.updateFrame);
213
+ this.state.updateFrame = undefined;
214
+ }
215
+ this.render();
216
+ const contentOverlay = this.state.contentElements.get(id);
217
+ if (!contentOverlay || options.width <= 0 || options.height <= 0) {
218
+ return undefined;
219
+ }
220
+ try {
221
+ await withTimeout(waitForCardCaptureReady(contentOverlay), CARD_RASTERIZE_READY_TIMEOUT);
222
+ const capture = await createCardCaptureTarget(contentOverlay, this.state.contents.get(id), {
223
+ width: options.width,
224
+ height: options.height,
225
+ });
226
+ try {
227
+ const image = await snapdom.toPng(capture.target, {
228
+ embedFonts: true,
229
+ backgroundColor: 'transparent',
230
+ dpr: options.pixelRatio ?? window.devicePixelRatio ?? 2,
231
+ ...options.snapdomOptions,
232
+ });
233
+ return image.src;
234
+ }
235
+ finally {
236
+ capture.cleanup();
237
+ }
238
+ }
239
+ catch (error) {
240
+ console.warn(`Failed to rasterize card ${id}.`, error);
241
+ return undefined;
242
+ }
243
+ }
244
+ initEvents() {
245
+ const events = [
246
+ 'pageDataChange',
247
+ 'selectedItemChange',
248
+ 'preSelectedItemChange',
249
+ 'itemPropertyChange',
250
+ 'viewportTransform',
251
+ 'viewRebuildFinish',
252
+ ];
253
+ this.eventCleanups = events.map((event) => {
254
+ return this.options.emitter.on(event, () => {
255
+ if (event === 'viewportTransform') {
256
+ this.handleViewportTransform();
257
+ }
258
+ this.invalidateContentInteractionZones();
259
+ this.scheduleRender();
260
+ });
261
+ });
262
+ }
263
+ initDOMEvents() {
264
+ this.options.container.addEventListener('dblclick', this.handleContainerDoubleClick, true);
265
+ this.options.container.addEventListener('mousedown', this.handleContainerMouseDown, true);
266
+ this.options.container.addEventListener('mousemove', this.handleContainerMouseMove, true);
267
+ this.options.container.addEventListener('mouseleave', this.handleContainerMouseLeave, true);
268
+ this.options.container.addEventListener('contextmenu', this.handleContainerContextMenu, true);
269
+ }
270
+ handleContainerDoubleClick = (event) => {
271
+ const editingId = this.state.editing?.id;
272
+ if (editingId && this.isEventInsideOverlay(editingId, event)) {
273
+ return;
274
+ }
275
+ const targetId = this.getHTMLCardIdByEvent(event);
276
+ if (!targetId) {
277
+ if (editingId) {
278
+ this.exitEditing();
279
+ }
280
+ return;
281
+ }
282
+ if (!this.isEditableCard(targetId)) {
283
+ return;
284
+ }
285
+ event.preventDefault();
286
+ event.stopPropagation();
287
+ this.enterEditing(targetId);
288
+ };
289
+ handleContainerMouseDown = (event) => {
290
+ if (event.button === 0) {
291
+ this.selectHTMLInteractionCard(event);
292
+ }
293
+ const editingId = this.state.editing?.id;
294
+ if (!editingId || this.isEventInsideOverlay(editingId, event)) {
295
+ return;
296
+ }
297
+ event.preventDefault();
298
+ event.stopPropagation();
299
+ this.exitEditing();
300
+ };
301
+ selectHTMLInteractionCard(event) {
302
+ const decision = this.resolveHTMLInteractionTarget(event);
303
+ if (decision.kind !== 'html') {
304
+ return;
305
+ }
306
+ this.options.setSelectedItemIds([decision.interaction.id]);
307
+ }
308
+ handleContainerMouseMove = (event) => {
309
+ this.scheduleActiveContentInteractionSync(event);
310
+ const hoverShellInteractiveId = this.getHoverShellInteractiveIdByEvent(event);
311
+ if (this.state.hoverShellInteractiveId === hoverShellInteractiveId) {
312
+ return;
313
+ }
314
+ const previousId = this.state.hoverShellInteractiveId;
315
+ this.state.hoverShellInteractiveId = hoverShellInteractiveId;
316
+ if (previousId) {
317
+ this.syncContentInteraction(previousId);
318
+ }
319
+ if (hoverShellInteractiveId) {
320
+ this.syncContentInteraction(hoverShellInteractiveId);
321
+ }
322
+ };
323
+ handleContainerMouseLeave = (event) => {
324
+ if (this.isMouseLeaveWithinContainer(event)) {
325
+ return;
326
+ }
327
+ if (this.contentInteractionFrame !== undefined) {
328
+ cancelHTMLOverlayFrame(this.contentInteractionFrame);
329
+ this.contentInteractionFrame = undefined;
330
+ }
331
+ this.pendingContentInteractionEvent = undefined;
332
+ this.setActiveContentInteraction(undefined);
333
+ const previousId = this.state.hoverShellInteractiveId;
334
+ if (!previousId) {
335
+ return;
336
+ }
337
+ this.state.hoverShellInteractiveId = undefined;
338
+ this.syncContentInteraction(previousId);
339
+ };
340
+ isMouseLeaveWithinContainer(event) {
341
+ const relatedTarget = event.relatedTarget;
342
+ if (!relatedTarget) {
343
+ return false;
344
+ }
345
+ try {
346
+ return this.options.container.contains(relatedTarget);
347
+ }
348
+ catch {
349
+ return false;
350
+ }
351
+ }
352
+ handleContainerContextMenu = (event) => {
353
+ const activeInteraction = this.state.activeContentInteraction;
354
+ if (!activeInteraction || !this.isEventInsideOverlay(activeInteraction.id, event) || this.isHTMLContextMenuEvent(event)) {
355
+ return;
356
+ }
357
+ const canvas = this.options.getCanvasEventTarget?.();
358
+ if (!canvas) {
359
+ return;
360
+ }
361
+ event.preventDefault();
362
+ event.stopPropagation();
363
+ this.setActiveContentInteraction(undefined);
364
+ canvas.dispatchEvent(new MouseEvent('contextmenu', {
365
+ bubbles: true,
366
+ cancelable: true,
367
+ clientX: event.clientX,
368
+ clientY: event.clientY,
369
+ screenX: event.screenX,
370
+ screenY: event.screenY,
371
+ button: event.button,
372
+ buttons: event.buttons,
373
+ ctrlKey: event.ctrlKey,
374
+ shiftKey: event.shiftKey,
375
+ altKey: event.altKey,
376
+ metaKey: event.metaKey,
377
+ }));
378
+ };
379
+ isHTMLContextMenuEvent(event) {
380
+ if (typeof Element !== 'function' || !(event.target instanceof Element)) {
381
+ return false;
382
+ }
383
+ return !!event.target.closest('[data-interaction-contextmenu="self"], [data-vvfx-html-contextmenu="self"]');
384
+ }
385
+ handleViewportTransform() {
386
+ const editing = this.state.editing;
387
+ if (!editing?.restoreViewport || editing.ignoreViewportZoomChange) {
388
+ return;
389
+ }
390
+ const viewport = this.options.getViewport();
391
+ if (viewport.zoom !== editing.restoreViewport.zoom ||
392
+ viewport.translation[0] !== editing.restoreViewport.translation[0] ||
393
+ viewport.translation[1] !== editing.restoreViewport.translation[1]) {
394
+ editing.restoreViewport = undefined;
395
+ }
396
+ }
397
+ render() {
398
+ const { layer, elements } = this.state;
399
+ if (!layer) {
400
+ return;
401
+ }
402
+ const items = this.options.getItems();
403
+ const frameByChildId = this.renderFrameOverlays(items, layer);
404
+ const selectionLayer = this.getOrCreateSelectionLayer(layer);
405
+ const visibleCardIds = new Set();
406
+ let editingVisible = false;
407
+ items.forEach((item) => {
408
+ if (!isCardItem(item) || !item.visible) {
409
+ return;
410
+ }
411
+ const html = this.options.resolveCardHTML(item);
412
+ if (html === undefined) {
413
+ return;
414
+ }
415
+ const itemBox = this.options.getViewBoxById(item.id);
416
+ if (!itemBox || itemBox.isEmpty()) {
417
+ return;
418
+ }
419
+ visibleCardIds.add(item.id);
420
+ editingVisible ||= this.state.editing?.id === item.id;
421
+ const frameId = frameByChildId.get(item.id);
422
+ const frameOverlay = frameId ? this.state.frameElements.get(frameId) : undefined;
423
+ const parentOverlay = frameOverlay ?? layer;
424
+ const frameBox = frameId ? this.options.getViewBoxById(frameId) : undefined;
425
+ const overlay = this.getOrCreateOverlay(item.id, parentOverlay, elements);
426
+ const contentOverlay = this.getOrCreateContentOverlay(item.id, overlay);
427
+ const selectionOverlay = this.getOrCreateSelectionOverlay(item.id, selectionLayer);
428
+ const offsetX = frameBox && !frameBox.isEmpty() ? frameBox.min.x : 0;
429
+ const offsetY = frameBox && !frameBox.isEmpty() ? frameBox.min.y : 0;
430
+ const boxStyle = getCardOverlayBoxStyle(itemBox, item.property.width, item.property.height, offsetX, offsetY, this.isAutoScaleEnabled(item));
431
+ this.applyHTMLRootConfig(overlay, html);
432
+ overlay.style.left = boxStyle.left;
433
+ overlay.style.top = boxStyle.top;
434
+ overlay.style.width = boxStyle.width;
435
+ overlay.style.height = boxStyle.height;
436
+ overlay.style.transform = boxStyle.transform;
437
+ overlay.style.transformOrigin = '0 0';
438
+ const selectionBoxStyle = getCardOverlayBoxStyle(itemBox, item.property.width, item.property.height, 0, 0, this.isAutoScaleEnabled(item));
439
+ selectionOverlay.style.left = selectionBoxStyle.left;
440
+ selectionOverlay.style.top = selectionBoxStyle.top;
441
+ selectionOverlay.style.width = selectionBoxStyle.width;
442
+ selectionOverlay.style.height = selectionBoxStyle.height;
443
+ selectionOverlay.style.transform = selectionBoxStyle.transform;
444
+ selectionOverlay.style.transformOrigin = '0 0';
445
+ this.applyHTMLRootConfig(selectionOverlay, html);
446
+ this.makeSelectionOverlayTransparent(selectionOverlay);
447
+ this.syncOverlaySelection(item.id, overlay, selectionOverlay);
448
+ this.syncContentScale(item.id, boxStyle.contentWidth, boxStyle.contentHeight);
449
+ if (!this.state.contents.has(item.id)) {
450
+ this.renderOverlayHTML(item.id, contentOverlay, html);
451
+ }
452
+ this.syncAutoHeight(item.id, this.isAutoHeightEnabled(item));
453
+ this.syncContentInteraction(item.id);
454
+ });
455
+ elements.forEach((_overlay, id) => {
456
+ if (visibleCardIds.has(id)) {
457
+ return;
458
+ }
459
+ this.removeOverlay(id);
460
+ });
461
+ if (this.state.editing && !editingVisible) {
462
+ this.exitEditing();
463
+ }
464
+ this.syncOverlayOrder(items, frameByChildId, visibleCardIds);
465
+ }
466
+ syncOverlayOrder(items, frameByChildId, visibleCardIds) {
467
+ const { layer } = this.state;
468
+ if (!layer) {
469
+ return;
470
+ }
471
+ const rootElements = [];
472
+ for (const item of items) {
473
+ if (isFrameItem(item) && item.visible) {
474
+ const frameOverlay = this.state.frameElements.get(item.id);
475
+ if (frameOverlay) {
476
+ rootElements.push(frameOverlay);
477
+ }
478
+ continue;
479
+ }
480
+ if (!isCardItem(item) || !visibleCardIds.has(item.id) || frameByChildId.has(item.id)) {
481
+ continue;
482
+ }
483
+ const overlay = this.state.elements.get(item.id);
484
+ if (overlay) {
485
+ rootElements.push(overlay);
486
+ }
487
+ }
488
+ syncElementStackOrder(layer, rootElements);
489
+ if (this.state.selectionLayer) {
490
+ this.state.selectionLayer.style.zIndex = `${rootElements.length + 1}`;
491
+ }
492
+ this.state.frameElements.forEach((frameOverlay, frameId) => {
493
+ const frameElements = [];
494
+ for (const item of items) {
495
+ if (!isCardItem(item) || !visibleCardIds.has(item.id) || frameByChildId.get(item.id) !== frameId) {
496
+ continue;
497
+ }
498
+ const overlay = this.state.elements.get(item.id);
499
+ if (overlay) {
500
+ frameElements.push(overlay);
501
+ }
502
+ }
503
+ syncElementStackOrder(frameOverlay, frameElements);
504
+ });
505
+ }
506
+ getOrCreateSelectionLayer(layer) {
507
+ if (this.state.selectionLayer) {
508
+ if (this.state.selectionLayer.parentElement !== layer) {
509
+ layer.appendChild(this.state.selectionLayer);
510
+ }
511
+ return this.state.selectionLayer;
512
+ }
513
+ const ownerDocument = layer.ownerDocument ?? (typeof document === 'undefined' ? undefined : document);
514
+ if (!ownerDocument) {
515
+ return layer;
516
+ }
517
+ const selectionLayer = ownerDocument.createElement('div');
518
+ selectionLayer.className = 'vvfx-card-html-overlay-selection-layer';
519
+ selectionLayer.style.position = 'absolute';
520
+ selectionLayer.style.inset = '0';
521
+ selectionLayer.style.pointerEvents = 'none';
522
+ selectionLayer.style.zIndex = '1';
523
+ layer.appendChild(selectionLayer);
524
+ this.state.selectionLayer = selectionLayer;
525
+ return selectionLayer;
526
+ }
527
+ renderFrameOverlays(items, layer) {
528
+ const visibleFrameIds = new Set();
529
+ const frameByChildId = new Map();
530
+ items.forEach((item) => {
531
+ if (!isFrameItem(item) || !item.visible) {
532
+ return;
533
+ }
534
+ const frameBox = this.options.getViewBoxById(item.id);
535
+ if (!frameBox || frameBox.isEmpty()) {
536
+ return;
537
+ }
538
+ visibleFrameIds.add(item.id);
539
+ item.children.forEach(childId => {
540
+ frameByChildId.set(childId, item.id);
541
+ });
542
+ const frameOverlay = this.getOrCreateFrameOverlay(item.id, layer);
543
+ frameOverlay.style.left = `${frameBox.min.x}px`;
544
+ frameOverlay.style.top = `${frameBox.min.y}px`;
545
+ frameOverlay.style.width = `${frameBox.max.x - frameBox.min.x}px`;
546
+ frameOverlay.style.height = `${frameBox.max.y - frameBox.min.y}px`;
547
+ });
548
+ this.state.frameElements.forEach((frameOverlay, id) => {
549
+ if (visibleFrameIds.has(id)) {
550
+ return;
551
+ }
552
+ frameOverlay.remove();
553
+ this.state.frameElements.delete(id);
554
+ });
555
+ return frameByChildId;
556
+ }
557
+ getOrCreateFrameOverlay(id, layer) {
558
+ let frameOverlay = this.state.frameElements.get(id);
559
+ if (frameOverlay) {
560
+ return frameOverlay;
561
+ }
562
+ frameOverlay = document.createElement('div');
563
+ frameOverlay.className = 'vvfx-frame-html-overlay';
564
+ frameOverlay.dataset.itemId = id;
565
+ frameOverlay.style.position = 'absolute';
566
+ frameOverlay.style.overflow = 'hidden';
567
+ frameOverlay.style.pointerEvents = 'none';
568
+ layer.appendChild(frameOverlay);
569
+ this.state.frameElements.set(id, frameOverlay);
570
+ return frameOverlay;
571
+ }
572
+ getOrCreateOverlay(id, parent, elements) {
573
+ let overlay = elements.get(id);
574
+ if (overlay) {
575
+ if (overlay.parentElement !== parent) {
576
+ parent.appendChild(overlay);
577
+ }
578
+ return overlay;
579
+ }
580
+ overlay = document.createElement('div');
581
+ overlay.className = 'vvfx-card-html-overlay';
582
+ overlay.dataset.itemId = id;
583
+ overlay.style.position = 'absolute';
584
+ overlay.style.overflow = 'hidden';
585
+ overlay.style.pointerEvents = 'none';
586
+ parent.appendChild(overlay);
587
+ elements.set(id, overlay);
588
+ return overlay;
589
+ }
590
+ getOrCreateSelectionOverlay(id, parent) {
591
+ let selectionOverlay = this.state.selectionElements.get(id);
592
+ if (selectionOverlay) {
593
+ if (selectionOverlay.parentElement !== parent) {
594
+ parent.appendChild(selectionOverlay);
595
+ }
596
+ return selectionOverlay;
597
+ }
598
+ selectionOverlay = document.createElement('div');
599
+ selectionOverlay.className = 'vvfx-card-html-overlay-selection';
600
+ selectionOverlay.dataset.itemId = id;
601
+ selectionOverlay.style.position = 'absolute';
602
+ selectionOverlay.style.pointerEvents = 'none';
603
+ selectionOverlay.style.zIndex = '1';
604
+ parent.appendChild(selectionOverlay);
605
+ this.state.selectionElements.set(id, selectionOverlay);
606
+ return selectionOverlay;
607
+ }
608
+ syncOverlaySelection(id, overlay, selectionOverlay) {
609
+ const isSelected = this.options.getSelectedItemIds().includes(id);
610
+ const isPreSelected = !isSelected && this.options.getPreSelectedItemId() === id;
611
+ overlay.dataset.selected = isSelected ? 'true' : 'false';
612
+ overlay.dataset.preSelected = isPreSelected ? 'true' : 'false';
613
+ selectionOverlay.dataset.selected = isSelected ? 'true' : 'false';
614
+ selectionOverlay.dataset.preSelected = isPreSelected ? 'true' : 'false';
615
+ selectionOverlay.style.boxShadow = isSelected
616
+ ? this.getSelectionBoxShadow()
617
+ : isPreSelected
618
+ ? this.getPreSelectionBoxShadow()
619
+ : '';
620
+ }
621
+ getSelectionBoxShadow() {
622
+ const { color, alpha, width } = this.options.getSelectionEdgeStyle();
623
+ return `inset 0 0 0 ${formatCssNumber(width)}px ${formatCssColor(color, alpha)}`;
624
+ }
625
+ getPreSelectionBoxShadow() {
626
+ const { color, alpha, width } = this.options.getPreSelectionEdgeStyle();
627
+ return `inset 0 0 0 ${formatCssNumber(width)}px ${formatCssColor(color, alpha)}`;
628
+ }
629
+ getHTMLCardIdByEvent(event) {
630
+ const layer = this.state.layer;
631
+ if (!layer) {
632
+ return undefined;
633
+ }
634
+ const point = this.getLayerPoint(event);
635
+ const items = this.options.getItems();
636
+ for (let i = items.length - 1; i >= 0; i--) {
637
+ const item = items[i];
638
+ if (!isCardItem(item) || !item.visible || this.options.resolveCardHTML(item) === undefined) {
639
+ continue;
640
+ }
641
+ const box = this.options.getViewBoxById(item.id);
642
+ if (!box.isEmpty() && isPointInCardOverlayBox(box, point)) {
643
+ return item.id;
644
+ }
645
+ }
646
+ return undefined;
647
+ }
648
+ getHoverShellInteractiveIdByEvent(event) {
649
+ const layer = this.state.layer;
650
+ if (!layer) {
651
+ return undefined;
652
+ }
653
+ const point = this.getLayerPoint(event);
654
+ const items = this.options.getItems();
655
+ for (let i = items.length - 1; i >= 0; i--) {
656
+ const item = items[i];
657
+ if (!isCardItem(item) || !item.visible || this.options.resolveCardHTML(item) === undefined) {
658
+ continue;
659
+ }
660
+ const box = this.options.getViewBoxById(item.id);
661
+ if (box.isEmpty() || !isPointInCardOverlayBox(box, point)) {
662
+ continue;
663
+ }
664
+ const contentOverlay = this.state.contentElements.get(item.id);
665
+ const interactiveElements = contentOverlay
666
+ ? Array.from(contentOverlay.querySelectorAll(SHELL_INTERACTIVE_SELECTOR))
667
+ : [];
668
+ const isHoveringShellInteractive = interactiveElements.some(element => isMouseEventInsideRect(event, element.getBoundingClientRect()));
669
+ return isHoveringShellInteractive ? item.id : undefined;
670
+ }
671
+ return undefined;
672
+ }
673
+ syncActiveContentInteraction(event) {
674
+ const decision = this.resolveHTMLInteractionTarget(event);
675
+ if (decision.kind === 'canvas' && this.shouldKeepActiveContentInteractionForFocusedElement()) {
676
+ return;
677
+ }
678
+ this.setActiveContentInteraction(decision.kind === 'html' ? decision.interaction : undefined);
679
+ }
680
+ scheduleActiveContentInteractionSync(event) {
681
+ if (typeof requestAnimationFrame !== 'function') {
682
+ this.syncActiveContentInteraction(event);
683
+ return;
684
+ }
685
+ this.pendingContentInteractionEvent = event;
686
+ if (this.contentInteractionFrame !== undefined) {
687
+ return;
688
+ }
689
+ try {
690
+ this.contentInteractionFrame = requestAnimationFrame(() => {
691
+ this.contentInteractionFrame = undefined;
692
+ const pendingEvent = this.pendingContentInteractionEvent;
693
+ this.pendingContentInteractionEvent = undefined;
694
+ if (pendingEvent) {
695
+ this.syncActiveContentInteraction(pendingEvent);
696
+ }
697
+ });
698
+ }
699
+ catch {
700
+ this.contentInteractionFrame = undefined;
701
+ this.pendingContentInteractionEvent = undefined;
702
+ this.syncActiveContentInteraction(event);
703
+ }
704
+ }
705
+ setActiveContentInteraction(zone) {
706
+ const previous = this.state.activeContentInteraction;
707
+ if (previous?.id === zone?.id && previous?.element === zone?.element && previous?.zone === zone?.zone) {
708
+ return;
709
+ }
710
+ this.state.activeContentInteraction = zone;
711
+ const changedIds = new Set();
712
+ if (previous) {
713
+ changedIds.add(previous.id);
714
+ }
715
+ if (zone) {
716
+ changedIds.add(zone.id);
717
+ }
718
+ changedIds.forEach(id => {
719
+ this.syncContentInteraction(id);
720
+ });
721
+ }
722
+ shouldKeepActiveContentInteractionForFocusedElement() {
723
+ const activeInteraction = this.state.activeContentInteraction;
724
+ if (!activeInteraction || typeof document === 'undefined') {
725
+ return false;
726
+ }
727
+ const activeElement = document.activeElement;
728
+ if (!activeElement || activeElement === document.body) {
729
+ return false;
730
+ }
731
+ const interactionElement = activeInteraction.element;
732
+ return interactionElement === activeElement || (typeof interactionElement.contains === 'function' &&
733
+ interactionElement.contains(activeElement));
734
+ }
735
+ resolveHTMLInteractionTarget(event) {
736
+ const id = this.getHTMLCardIdByEvent(event);
737
+ if (!id || this.isEditableCard(id)) {
738
+ return { kind: 'canvas', id };
739
+ }
740
+ const contentOverlay = this.state.contentElements.get(id);
741
+ if (!contentOverlay) {
742
+ return { kind: 'canvas', id };
743
+ }
744
+ const activeSubtree = this.getActiveSubtreeInteractionAtEvent(id, event);
745
+ if (activeSubtree) {
746
+ return {
747
+ kind: 'html',
748
+ interaction: activeSubtree,
749
+ };
750
+ }
751
+ const matchedZones = this.getContentInteractionZones(id, contentOverlay).filter(zone => isMouseEventInsideRect(event, zone.rect));
752
+ const zone = this.resolveContentInteractionZone(matchedZones);
753
+ if (zone) {
754
+ return {
755
+ kind: 'html',
756
+ interaction: {
757
+ id,
758
+ element: zone.element,
759
+ zone: zone.zone,
760
+ },
761
+ };
762
+ }
763
+ return { kind: 'canvas', id };
764
+ }
765
+ getActiveSubtreeInteractionAtEvent(id, event) {
766
+ const activeInteraction = this.state.activeContentInteraction;
767
+ if (activeInteraction?.id !== id || activeInteraction.zone !== 'subtree') {
768
+ return undefined;
769
+ }
770
+ const rect = activeInteraction.element.getBoundingClientRect();
771
+ return isMouseEventInsideRect(event, rect) ? activeInteraction : undefined;
772
+ }
773
+ resolveContentInteractionZone(zones) {
774
+ if (zones.length <= 1) {
775
+ return zones[0];
776
+ }
777
+ const subtreeRoot = zones.find(candidate => candidate.zone === 'subtree' &&
778
+ typeof candidate.element.contains === 'function' &&
779
+ zones.some(zone => zone.element !== candidate.element && candidate.element.contains(zone.element)));
780
+ return subtreeRoot ?? zones[0];
781
+ }
782
+ getContentInteractionZones(id, contentOverlay) {
783
+ const cachedZones = this.state.contentInteractionZones.get(id);
784
+ if (cachedZones) {
785
+ return cachedZones;
786
+ }
787
+ const zones = Array.from(contentOverlay.querySelectorAll(this.getContentInteractiveSelector(id)))
788
+ .filter(element => !this.isContentInteractionDisabled(element))
789
+ .map(element => ({
790
+ element,
791
+ rect: element.getBoundingClientRect(),
792
+ zone: this.getContentInteractionZoneMode(element),
793
+ }));
794
+ this.state.contentInteractionZones.set(id, zones);
795
+ this.observeContentInteractionZoneResizes(id, zones);
796
+ return zones;
797
+ }
798
+ getContentInteractiveSelector(id) {
799
+ const item = this.options.getItems().find(item => item.id === id);
800
+ if (!item) {
801
+ return CONTENT_INTERACTIVE_SELECTOR;
802
+ }
803
+ const selectors = this.options.resolveCardTypeConfig(item)?.htmlInteractionSelectors?.filter(Boolean) ?? [];
804
+ if (selectors.length === 0) {
805
+ return CONTENT_INTERACTIVE_SELECTOR;
806
+ }
807
+ return [CONTENT_INTERACTIVE_SELECTOR, ...selectors].join(', ');
808
+ }
809
+ isContentInteractionDisabled(element) {
810
+ if (typeof element.closest !== 'function') {
811
+ return false;
812
+ }
813
+ return !!element.closest(CONTENT_INTERACTION_NONE_SELECTOR);
814
+ }
815
+ getContentInteractionZoneMode(element) {
816
+ const zone = element.dataset.vvfxHtmlInteractionZone;
817
+ return zone === 'subtree' ? 'subtree' : 'self';
818
+ }
819
+ invalidateContentInteractionZones(id) {
820
+ if (id) {
821
+ this.state.contentInteractionZones.delete(id);
822
+ this.state.contentInteractionResizeCleanups.get(id)?.();
823
+ this.state.contentInteractionResizeCleanups.delete(id);
824
+ return;
825
+ }
826
+ this.state.contentInteractionZones.clear();
827
+ this.state.contentInteractionResizeCleanups.forEach(cleanup => {
828
+ cleanup();
829
+ });
830
+ this.state.contentInteractionResizeCleanups.clear();
831
+ }
832
+ observeContentInteractionZoneResizes(id, zones) {
833
+ this.state.contentInteractionResizeCleanups.get(id)?.();
834
+ this.state.contentInteractionResizeCleanups.delete(id);
835
+ if (zones.length === 0 || typeof ResizeObserver !== 'function') {
836
+ return;
837
+ }
838
+ const observer = new ResizeObserver(() => {
839
+ this.invalidateContentInteractionZones(id);
840
+ });
841
+ zones.forEach(zone => {
842
+ observer.observe(zone.element);
843
+ });
844
+ this.state.contentInteractionResizeCleanups.set(id, () => {
845
+ observer.disconnect();
846
+ });
847
+ }
848
+ getLayerPoint(event) {
849
+ const rect = this.options.container.getBoundingClientRect();
850
+ return {
851
+ x: event.clientX - rect.left,
852
+ y: event.clientY - rect.top,
853
+ };
854
+ }
855
+ getOrCreateContentOverlay(id, overlay) {
856
+ let contentOverlay = this.state.contentElements.get(id);
857
+ let scaleOverlay = this.state.contentScaleElements.get(id);
858
+ if (contentOverlay) {
859
+ if (!scaleOverlay) {
860
+ scaleOverlay = this.createContentScaleOverlay(id);
861
+ scaleOverlay.appendChild(contentOverlay);
862
+ this.state.contentScaleElements.set(id, scaleOverlay);
863
+ }
864
+ if (scaleOverlay.parentElement !== overlay) {
865
+ overlay.appendChild(scaleOverlay);
866
+ }
867
+ if (contentOverlay.parentElement !== scaleOverlay) {
868
+ scaleOverlay.appendChild(contentOverlay);
869
+ }
870
+ return contentOverlay;
871
+ }
872
+ scaleOverlay = this.createContentScaleOverlay(id);
873
+ contentOverlay = document.createElement('div');
874
+ contentOverlay.className = 'vvfx-card-html-overlay-content';
875
+ contentOverlay.dataset.itemId = id;
876
+ contentOverlay.style.position = 'absolute';
877
+ contentOverlay.style.left = '0';
878
+ contentOverlay.style.top = '0';
879
+ contentOverlay.style.overflow = 'hidden';
880
+ contentOverlay.style.pointerEvents = 'none';
881
+ scaleOverlay.appendChild(contentOverlay);
882
+ overlay.appendChild(scaleOverlay);
883
+ this.state.contentScaleElements.set(id, scaleOverlay);
884
+ this.state.contentElements.set(id, contentOverlay);
885
+ return contentOverlay;
886
+ }
887
+ createContentScaleOverlay(id) {
888
+ const scaleOverlay = document.createElement('div');
889
+ scaleOverlay.className = 'vvfx-card-html-overlay-content-scale';
890
+ scaleOverlay.dataset.itemId = id;
891
+ scaleOverlay.style.position = 'absolute';
892
+ scaleOverlay.style.left = '0';
893
+ scaleOverlay.style.top = '0';
894
+ scaleOverlay.style.transformOrigin = '0 0';
895
+ scaleOverlay.style.pointerEvents = 'none';
896
+ return scaleOverlay;
897
+ }
898
+ syncContentScale(id, width, height) {
899
+ const scaleOverlay = this.state.contentScaleElements.get(id);
900
+ const contentOverlay = this.state.contentElements.get(id);
901
+ if (!scaleOverlay || !contentOverlay || width <= 0 || height <= 0) {
902
+ return;
903
+ }
904
+ const nextWidth = `${width}px`;
905
+ const nextHeight = `${height}px`;
906
+ const sizeChanged = scaleOverlay.style.width !== nextWidth || scaleOverlay.style.height !== nextHeight;
907
+ scaleOverlay.style.width = nextWidth;
908
+ scaleOverlay.style.height = nextHeight;
909
+ scaleOverlay.style.transform = 'none';
910
+ contentOverlay.style.width = nextWidth;
911
+ contentOverlay.style.height = nextHeight;
912
+ if (sizeChanged && this.state.autoHeightModes.get(id) === 'message') {
913
+ this.requestAutoHeightFromIframes(id, contentOverlay);
914
+ }
915
+ if (sizeChanged) {
916
+ this.invalidateContentInteractionZones(id);
917
+ }
918
+ }
919
+ requestAutoHeightFromIframes(id, contentOverlay) {
920
+ contentOverlay.querySelectorAll('iframe').forEach(iframe => {
921
+ requestCardHTMLAutoHeight(iframe, id);
922
+ });
923
+ }
924
+ syncContentInteraction(id) {
925
+ const isEditing = this.state.editing?.id === id;
926
+ const activeInteraction = this.state.activeContentInteraction?.id === id
927
+ ? this.state.activeContentInteraction
928
+ : undefined;
929
+ const overlay = this.state.elements.get(id);
930
+ const scaleOverlay = this.state.contentScaleElements.get(id);
931
+ const contentOverlay = this.state.contentElements.get(id);
932
+ const contentMount = this.state.contentMountElements.get(id);
933
+ const isInteractive = isEditing || !!activeInteraction;
934
+ if (overlay) {
935
+ overlay.dataset.editing = isEditing ? 'true' : 'false';
936
+ overlay.style.pointerEvents = 'none';
937
+ }
938
+ if (scaleOverlay) {
939
+ scaleOverlay.dataset.editing = isEditing ? 'true' : 'false';
940
+ scaleOverlay.style.pointerEvents = 'none';
941
+ }
942
+ if (contentOverlay) {
943
+ contentOverlay.dataset.editing = isEditing ? 'true' : 'false';
944
+ contentOverlay.style.pointerEvents = isInteractive ? 'auto' : 'none';
945
+ contentOverlay.querySelectorAll('iframe').forEach(iframe => {
946
+ const isActiveIframe = isEditing || activeInteraction?.element === iframe;
947
+ iframe.style.pointerEvents = isActiveIframe ? 'auto' : 'none';
948
+ iframe.tabIndex = isActiveIframe ? 0 : -1;
949
+ });
950
+ this.syncShellInteraction(id, contentOverlay, isInteractive);
951
+ this.syncContentInteractionZones(id, contentOverlay, activeInteraction, isEditing, isInteractive);
952
+ }
953
+ if (contentMount) {
954
+ contentMount.dataset.editing = isEditing ? 'true' : 'false';
955
+ contentMount.style.pointerEvents = isInteractive ? 'auto' : 'none';
956
+ contentMount.inert = !isInteractive;
957
+ if (isInteractive) {
958
+ contentMount.removeAttribute('aria-hidden');
959
+ }
960
+ else {
961
+ contentMount.setAttribute('aria-hidden', 'true');
962
+ }
963
+ }
964
+ }
965
+ syncContentInteractionZones(id, contentOverlay, activeInteraction, isEditing, isInteractive) {
966
+ const activeElement = activeInteraction?.element;
967
+ contentOverlay.querySelectorAll(this.getContentInteractiveSelector(id)).forEach(element => {
968
+ if (activeInteraction?.zone === 'subtree' &&
969
+ activeElement &&
970
+ element !== activeElement &&
971
+ typeof activeElement.contains === 'function' &&
972
+ activeElement.contains(element)) {
973
+ this.clearContentInteractionState(element);
974
+ return;
975
+ }
976
+ if (!isInteractive) {
977
+ this.clearContentInteractionState(element);
978
+ return;
979
+ }
980
+ if (this.isContentInteractionDisabled(element)) {
981
+ element.dataset.vvfxHtmlInteractionActive = 'false';
982
+ element.style.pointerEvents = 'none';
983
+ return;
984
+ }
985
+ const isElementInteractive = isEditing || activeElement === element || (activeInteraction?.zone === 'subtree' &&
986
+ typeof activeElement?.contains === 'function' &&
987
+ activeElement.contains(element));
988
+ element.dataset.vvfxHtmlInteractionActive = isElementInteractive ? 'true' : 'false';
989
+ element.style.pointerEvents = isElementInteractive ? 'auto' : 'none';
990
+ });
991
+ }
992
+ clearContentInteractionState(element) {
993
+ delete element.dataset.vvfxHtmlInteractionActive;
994
+ element.style.pointerEvents = '';
995
+ }
996
+ syncShellInteraction(id, contentOverlay, isInteractive) {
997
+ const isHoveringShellInteractive = this.state.hoverShellInteractiveId === id;
998
+ const isShellInteractiveActive = isInteractive || isHoveringShellInteractive;
999
+ contentOverlay.querySelectorAll(SHELL_INTERACTIVE_SELECTOR).forEach(element => {
1000
+ element.dataset.vvfxHtmlShellInteractiveHover = isHoveringShellInteractive ? 'true' : 'false';
1001
+ element.dataset.vvfxHtmlShellInteractiveActive = isShellInteractiveActive ? 'true' : 'false';
1002
+ element.style.pointerEvents = isShellInteractiveActive ? 'auto' : 'none';
1003
+ });
1004
+ contentOverlay.querySelectorAll(EDITING_INTERACTIVE_SELECTOR).forEach(element => {
1005
+ element.style.pointerEvents = isInteractive ? 'auto' : 'none';
1006
+ });
1007
+ }
1008
+ enterEditing(id) {
1009
+ if (this.state.editing?.id === id) {
1010
+ return;
1011
+ }
1012
+ if (!this.isEditableCard(id)) {
1013
+ return;
1014
+ }
1015
+ const box = this.options.getViewBoxById(id);
1016
+ if (box.isEmpty()) {
1017
+ return;
1018
+ }
1019
+ this.exitEditing({ restoreZoom: false });
1020
+ const currentViewport = this.options.getViewport();
1021
+ const currentZoom = currentViewport.zoom;
1022
+ const fitZoom = this.getEditingFitZoom(box, currentZoom);
1023
+ const shouldFitViewport = fitZoom === undefined || fitZoom >= currentZoom;
1024
+ this.state.editing = {
1025
+ id,
1026
+ restoreViewport: shouldFitViewport
1027
+ ? {
1028
+ zoom: currentViewport.zoom,
1029
+ translation: [...currentViewport.translation],
1030
+ }
1031
+ : undefined,
1032
+ ignoreViewportZoomChange: true,
1033
+ };
1034
+ this.syncContentInteraction(id);
1035
+ if (shouldFitViewport) {
1036
+ this.options.viewportFit(box);
1037
+ }
1038
+ const editing = this.state.editing;
1039
+ if (editing?.id === id) {
1040
+ editing.ignoreViewportZoomChange = false;
1041
+ }
1042
+ this.scheduleRender();
1043
+ }
1044
+ exitEditing(options) {
1045
+ const editing = this.state.editing;
1046
+ if (!editing) {
1047
+ return;
1048
+ }
1049
+ this.state.editing = undefined;
1050
+ this.syncContentInteraction(editing.id);
1051
+ if (options?.restoreZoom === false || editing.restoreViewport === undefined) {
1052
+ this.scheduleRender();
1053
+ return;
1054
+ }
1055
+ this.options.setViewport(editing.restoreViewport);
1056
+ this.scheduleRender();
1057
+ }
1058
+ isEditableCard(id) {
1059
+ const item = this.options.getItems().find(item => item.id === id);
1060
+ return isCardItem(item) && this.options.resolveCardTypeConfig(item)?.editable !== false;
1061
+ }
1062
+ getEditingFitZoom(box, currentZoom) {
1063
+ const viewportElement = this.options.container.parentElement;
1064
+ if (!viewportElement) {
1065
+ return undefined;
1066
+ }
1067
+ const width = viewportElement.offsetWidth - EDITING_VIEWPORT_PADDING * 2;
1068
+ const height = viewportElement.offsetHeight - EDITING_VIEWPORT_PADDING * 2;
1069
+ if (width <= 0 || height <= 0) {
1070
+ return undefined;
1071
+ }
1072
+ const fitBox = box.clone().scale(EDITING_VIEWPORT_BOX_SCALE);
1073
+ const size = fitBox.getSize();
1074
+ if (size.x <= 0 || size.y <= 0) {
1075
+ return undefined;
1076
+ }
1077
+ const fitScale = size.x / width * height > size.y ? 1 / (size.x / width) : 1 / (size.y / height);
1078
+ return fitScale * currentZoom;
1079
+ }
1080
+ isEventInsideOverlay(id, event) {
1081
+ const overlay = this.state.elements.get(id);
1082
+ return !!overlay && event.target instanceof Node && overlay.contains(event.target);
1083
+ }
1084
+ renderOverlayHTML(id, overlay, html) {
1085
+ this.cleanupOverlayContent(id);
1086
+ this.invalidateContentInteractionZones(id);
1087
+ overlay.innerHTML = '';
1088
+ const shell = mountHTMLShell(overlay, html.content, html.shell);
1089
+ const autoHeightMeasureElement = shell.contentContainer.isConnected ? shell.contentContainer : overlay;
1090
+ this.state.contentMountElements.set(id, autoHeightMeasureElement);
1091
+ const item = this.options.getItems().find(item => item.id === id);
1092
+ const contentCleanup = this.renderOverlayContent(shell.contentContainer, html.content, {
1093
+ id,
1094
+ autoHeight: this.isAutoHeightEnabled(item),
1095
+ });
1096
+ this.observeContentInteractionZones(id, shell.contentContainer);
1097
+ this.state.cleanups.set(id, () => {
1098
+ contentCleanup?.();
1099
+ shell.cleanup?.();
1100
+ });
1101
+ this.state.contents.set(id, html);
1102
+ }
1103
+ createPatchContext(id, html) {
1104
+ return {
1105
+ id,
1106
+ html,
1107
+ content: html.content,
1108
+ container: this.state.contentMountElements.get(id),
1109
+ contentWindow: this.getHtmlCardWindow(id),
1110
+ refresh: () => {
1111
+ this.invalidateCardHTML(id);
1112
+ this.scheduleRender();
1113
+ },
1114
+ };
1115
+ }
1116
+ observeContentInteractionZones(id, contentContainer) {
1117
+ this.state.contentInteractionZoneCleanups.get(id)?.();
1118
+ this.state.contentInteractionZoneCleanups.delete(id);
1119
+ if (typeof MutationObserver !== 'function') {
1120
+ return;
1121
+ }
1122
+ const observer = new MutationObserver(() => {
1123
+ this.invalidateContentInteractionZones(id);
1124
+ });
1125
+ observer.observe(contentContainer, {
1126
+ attributes: true,
1127
+ childList: true,
1128
+ subtree: true,
1129
+ });
1130
+ this.state.contentInteractionZoneCleanups.set(id, () => {
1131
+ observer.disconnect();
1132
+ });
1133
+ }
1134
+ applyHTMLRootConfig(root, html) {
1135
+ this.htmlRootConfigCleanups.get(root)?.();
1136
+ this.htmlRootConfigCleanups.delete(root);
1137
+ const config = html.root;
1138
+ if (!config) {
1139
+ return;
1140
+ }
1141
+ const classNames = config.className?.split(/\s+/).filter(Boolean) ?? [];
1142
+ const styleProperties = [];
1143
+ classNames.forEach(className => {
1144
+ root.classList.add(className);
1145
+ });
1146
+ Object.entries(config.style ?? {}).forEach(([property, value]) => {
1147
+ if (value === undefined) {
1148
+ return;
1149
+ }
1150
+ styleProperties.push(property);
1151
+ if (property.startsWith('--') || property.includes('-')) {
1152
+ root.style.setProperty(property, String(value));
1153
+ return;
1154
+ }
1155
+ ;
1156
+ root.style[property] = String(value);
1157
+ });
1158
+ this.htmlRootConfigCleanups.set(root, () => {
1159
+ classNames.forEach(className => {
1160
+ root.classList.remove(className);
1161
+ });
1162
+ styleProperties.forEach(property => {
1163
+ if (property.startsWith('--') || property.includes('-')) {
1164
+ root.style.removeProperty(property);
1165
+ return;
1166
+ }
1167
+ ;
1168
+ root.style[property] = '';
1169
+ });
1170
+ });
1171
+ }
1172
+ makeSelectionOverlayTransparent(selectionOverlay) {
1173
+ selectionOverlay.style.background = 'transparent';
1174
+ selectionOverlay.style.backgroundColor = 'transparent';
1175
+ }
1176
+ renderOverlayContent(overlay, content, options) {
1177
+ switch (content.kind) {
1178
+ case 'inline':
1179
+ if (options?.autoHeight) {
1180
+ this.state.autoHeightModes.set(options.id, 'message');
1181
+ }
1182
+ return this.chainCleanups(() => content.cleanup?.(overlay), renderDocumentContent(overlay, {
1183
+ kind: 'document',
1184
+ entry: 'index.html',
1185
+ files: {
1186
+ 'index.html': content.html,
1187
+ },
1188
+ allowAnchorNavigation: content.allowAnchorNavigation,
1189
+ sandbox: content.sandbox,
1190
+ }, {
1191
+ autoHeightId: options?.autoHeight ? options.id : undefined,
1192
+ onAutoHeight: options?.autoHeight ? height => {
1193
+ this.applyAutoHeight(options.id, height);
1194
+ } : undefined,
1195
+ onStreamRuntimePhase: options?.autoHeight ? phase => {
1196
+ this.setAutoHeightStreamPhase(options.id, phase);
1197
+ } : undefined,
1198
+ }));
1199
+ case 'dom': {
1200
+ if (options?.autoHeight) {
1201
+ this.state.autoHeightModes.set(options.id, 'measure');
1202
+ }
1203
+ if (content.target === 'iframe') {
1204
+ return renderDOMContentInIframe(overlay, content.render, content.cleanup);
1205
+ }
1206
+ content.render(overlay);
1207
+ return content.cleanup ? () => content.cleanup?.(overlay) : undefined;
1208
+ }
1209
+ case 'document':
1210
+ if (options?.autoHeight) {
1211
+ this.state.autoHeightModes.set(options.id, 'message');
1212
+ }
1213
+ return this.chainCleanups(() => content.cleanup?.(overlay), renderDocumentContent(overlay, content, {
1214
+ autoHeightId: options?.autoHeight ? options.id : undefined,
1215
+ onAutoHeight: options?.autoHeight ? height => {
1216
+ this.applyAutoHeight(options.id, height);
1217
+ } : undefined,
1218
+ onStreamRuntimePhase: options?.autoHeight ? phase => {
1219
+ this.setAutoHeightStreamPhase(options.id, phase);
1220
+ } : undefined,
1221
+ }));
1222
+ }
1223
+ }
1224
+ chainCleanups(...cleanups) {
1225
+ const validCleanups = cleanups.filter((cleanup) => typeof cleanup === 'function');
1226
+ if (validCleanups.length === 0) {
1227
+ return undefined;
1228
+ }
1229
+ return () => {
1230
+ validCleanups.forEach(cleanup => {
1231
+ cleanup();
1232
+ });
1233
+ };
1234
+ }
1235
+ cleanupOverlayContent(id) {
1236
+ const cleanup = this.state.cleanups.get(id);
1237
+ if (cleanup) {
1238
+ cleanup();
1239
+ this.state.cleanups.delete(id);
1240
+ }
1241
+ this.state.contents.delete(id);
1242
+ this.state.contentMountElements.delete(id);
1243
+ this.state.autoHeightModes.delete(id);
1244
+ this.state.autoHeightStreamPhases.delete(id);
1245
+ this.invalidateContentInteractionZones(id);
1246
+ this.state.contentInteractionZoneCleanups.get(id)?.();
1247
+ this.state.contentInteractionZoneCleanups.delete(id);
1248
+ this.cleanupAutoHeight(id);
1249
+ }
1250
+ removeOverlay(id) {
1251
+ this.cleanupOverlayContent(id);
1252
+ this.state.elements.get(id)?.remove();
1253
+ this.state.selectionElements.get(id)?.remove();
1254
+ this.state.elements.delete(id);
1255
+ this.state.selectionElements.delete(id);
1256
+ this.state.contentScaleElements.delete(id);
1257
+ this.state.contentElements.delete(id);
1258
+ }
1259
+ syncAutoHeight(id, enabled) {
1260
+ if (!enabled) {
1261
+ this.cleanupAutoHeight(id);
1262
+ return;
1263
+ }
1264
+ this.ensureAutoHeightMode(id);
1265
+ if (!this.state.autoHeightCleanups.has(id)) {
1266
+ this.state.autoHeightCleanups.set(id, this.createAutoHeightObserver(id));
1267
+ }
1268
+ this.scheduleAutoHeightMeasure(id);
1269
+ }
1270
+ ensureAutoHeightMode(id) {
1271
+ if (this.state.autoHeightModes.has(id)) {
1272
+ return;
1273
+ }
1274
+ const content = this.state.contents.get(id)?.content;
1275
+ if (!content) {
1276
+ return;
1277
+ }
1278
+ this.state.autoHeightModes.set(id, content.kind === 'inline' || content.kind === 'document' ? 'message' : 'measure');
1279
+ }
1280
+ createAutoHeightObserver(id) {
1281
+ const cleanupFns = [];
1282
+ const observeElement = (element) => {
1283
+ if (!element) {
1284
+ return;
1285
+ }
1286
+ const resizeObserver = new ResizeObserver(() => {
1287
+ this.scheduleAutoHeightMeasure(id);
1288
+ });
1289
+ resizeObserver.observe(element);
1290
+ cleanupFns.push(() => {
1291
+ resizeObserver.disconnect();
1292
+ });
1293
+ const mutationObserver = new MutationObserver(() => {
1294
+ this.scheduleAutoHeightMeasure(id);
1295
+ });
1296
+ mutationObserver.observe(element, {
1297
+ attributes: true,
1298
+ childList: true,
1299
+ subtree: true,
1300
+ characterData: true,
1301
+ });
1302
+ cleanupFns.push(() => {
1303
+ mutationObserver.disconnect();
1304
+ });
1305
+ };
1306
+ const mount = this.state.contentMountElements.get(id);
1307
+ observeElement(mount);
1308
+ mount?.querySelectorAll('iframe').forEach(iframe => {
1309
+ const syncIframeDocument = () => {
1310
+ let doc = null;
1311
+ try {
1312
+ doc = iframe.contentDocument;
1313
+ }
1314
+ catch {
1315
+ doc = null;
1316
+ }
1317
+ if (!doc) {
1318
+ return;
1319
+ }
1320
+ observeElement(doc.documentElement);
1321
+ observeElement(doc.body);
1322
+ this.scheduleAutoHeightMeasure(id);
1323
+ };
1324
+ iframe.addEventListener('load', syncIframeDocument);
1325
+ cleanupFns.push(() => {
1326
+ iframe.removeEventListener('load', syncIframeDocument);
1327
+ });
1328
+ syncIframeDocument();
1329
+ });
1330
+ return () => {
1331
+ cleanupFns.forEach(cleanup => {
1332
+ cleanup();
1333
+ });
1334
+ };
1335
+ }
1336
+ scheduleAutoHeightMeasure(id) {
1337
+ if (this.state.autoHeightFrames.has(id)) {
1338
+ return;
1339
+ }
1340
+ const frame = requestHTMLOverlayFrame(() => {
1341
+ this.state.autoHeightFrames.delete(id);
1342
+ this.measureAutoHeight(id);
1343
+ });
1344
+ this.state.autoHeightFrames.set(id, frame);
1345
+ }
1346
+ measureAutoHeight(id) {
1347
+ const item = this.options.getItems().find(candidate => candidate.id === id);
1348
+ if (!this.isAutoHeightEnabled(item)) {
1349
+ this.cleanupAutoHeight(id);
1350
+ return;
1351
+ }
1352
+ const mount = this.state.contentMountElements.get(id);
1353
+ if (!mount) {
1354
+ return;
1355
+ }
1356
+ const height = this.getNaturalContentHeight(mount);
1357
+ this.applyAutoHeight(id, height);
1358
+ }
1359
+ getNaturalContentHeight(element) {
1360
+ const iframeHeights = Array.from(element.querySelectorAll('iframe'))
1361
+ .map(iframe => this.getIframeNaturalContentHeight(iframe))
1362
+ .filter((height) => typeof height === 'number' && Number.isFinite(height) && height > 0);
1363
+ const childHeight = this.getChildrenNaturalHeight(element, {
1364
+ includeIframes: iframeHeights.length === 0,
1365
+ });
1366
+ const naturalHeight = Math.max(childHeight, ...iframeHeights);
1367
+ return naturalHeight > 0 ? naturalHeight : element.scrollHeight;
1368
+ }
1369
+ getIframeNaturalContentHeight(iframe) {
1370
+ const doc = iframe.contentDocument;
1371
+ if (!doc) {
1372
+ return undefined;
1373
+ }
1374
+ const body = doc.body;
1375
+ const documentElement = doc.documentElement;
1376
+ if (!documentElement) {
1377
+ return undefined;
1378
+ }
1379
+ const childHeight = body ? this.getChildrenNaturalHeight(body) : 0;
1380
+ if (childHeight > 0) {
1381
+ return childHeight;
1382
+ }
1383
+ return Math.max(documentElement.scrollHeight, body?.scrollHeight ?? 0);
1384
+ }
1385
+ getChildrenNaturalHeight(element, options) {
1386
+ const includeIframes = options?.includeIframes ?? true;
1387
+ return Array.from(element.childNodes).reduce((height, child) => {
1388
+ if (child.nodeType === Node.ELEMENT_NODE) {
1389
+ const childElement = child;
1390
+ if (!includeIframes && childElement.tagName === 'IFRAME') {
1391
+ return height;
1392
+ }
1393
+ if (globalThis.getComputedStyle(childElement).position === 'fixed') {
1394
+ return height;
1395
+ }
1396
+ return Math.max(height, childElement.offsetTop + childElement.offsetHeight);
1397
+ }
1398
+ if (child.nodeType === Node.TEXT_NODE && child.textContent?.trim()) {
1399
+ const range = element.ownerDocument.createRange();
1400
+ range.selectNodeContents(child);
1401
+ const rects = Array.from(range.getClientRects());
1402
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
1403
+ range.detach();
1404
+ return rects.reduce((textHeight, rect) => Math.max(textHeight, rect.bottom - element.getBoundingClientRect().top), height);
1405
+ }
1406
+ return height;
1407
+ }, 0);
1408
+ }
1409
+ cleanupAutoHeight(id) {
1410
+ const cleanup = this.state.autoHeightCleanups.get(id);
1411
+ if (cleanup) {
1412
+ cleanup();
1413
+ this.state.autoHeightCleanups.delete(id);
1414
+ }
1415
+ const frame = this.state.autoHeightFrames.get(id);
1416
+ if (frame !== undefined) {
1417
+ cancelHTMLOverlayFrame(frame);
1418
+ this.state.autoHeightFrames.delete(id);
1419
+ }
1420
+ }
1421
+ applyAutoHeight(id, height) {
1422
+ const item = this.options.getItems().find(candidate => candidate.id === id);
1423
+ if (!isCardItem(item) || !this.isAutoHeightEnabled(item)) {
1424
+ return;
1425
+ }
1426
+ if (!Number.isFinite(height) || height <= 0) {
1427
+ return;
1428
+ }
1429
+ const nextHeight = Math.ceil(this.getAutoHeightItemHeight(item, height));
1430
+ if (this.isAutoHeightStreamActive(id) && nextHeight < item.property.height) {
1431
+ return;
1432
+ }
1433
+ if (Math.abs(nextHeight - item.property.height) <= AUTO_HEIGHT_EPSILON) {
1434
+ return;
1435
+ }
1436
+ this.options.setCardItemHeight(id, nextHeight, {
1437
+ anchor: this.getAutoHeightAnchor(item),
1438
+ previousHeight: item.property.height,
1439
+ });
1440
+ }
1441
+ setAutoHeightStreamPhase(id, phase) {
1442
+ if (phase === 'parsing' || phase === 'draining' || phase === 'finalizing') {
1443
+ this.state.autoHeightStreamPhases.set(id, phase);
1444
+ return;
1445
+ }
1446
+ this.state.autoHeightStreamPhases.delete(id);
1447
+ }
1448
+ isAutoHeightStreamActive(id) {
1449
+ return this.state.autoHeightStreamPhases.has(id);
1450
+ }
1451
+ getAutoHeightAnchor(item) {
1452
+ if (!isCardItem(item)) {
1453
+ return 'top';
1454
+ }
1455
+ return this.options.resolveCardTypeConfig(item)?.autoHeightAnchor ?? 'top';
1456
+ }
1457
+ getAutoHeightItemHeight(item, contentHeight) {
1458
+ if (!isCardItem(item) || this.isAutoScaleEnabled(item)) {
1459
+ return contentHeight;
1460
+ }
1461
+ const scaleOverlay = this.state.contentScaleElements.get(item.id);
1462
+ const displayedHeight = scaleOverlay ? Number.parseFloat(scaleOverlay.style.height) : 0;
1463
+ if (!Number.isFinite(displayedHeight) || displayedHeight <= 0 || item.property.height <= 0) {
1464
+ return contentHeight;
1465
+ }
1466
+ return contentHeight / displayedHeight * item.property.height;
1467
+ }
1468
+ isAutoHeightEnabled(item) {
1469
+ return isCardItem(item) && this.options.resolveCardTypeConfig(item)?.autoHeight === true;
1470
+ }
1471
+ isAutoScaleEnabled(item) {
1472
+ if (!isCardItem(item)) {
1473
+ return true;
1474
+ }
1475
+ return this.options.resolveCardTypeConfig(item)?.autoScale !== false;
1476
+ }
1477
+ }
1478
+ async function waitForCardCaptureReady(root) {
1479
+ await nextAnimationFrame();
1480
+ await waitForDocumentFonts(root.ownerDocument);
1481
+ await Promise.all([
1482
+ waitForImages(root),
1483
+ waitForIframes(root),
1484
+ ]);
1485
+ await nextAnimationFrame();
1486
+ }
1487
+ function formatCssColor(color, alpha) {
1488
+ const normalizedColor = Math.max(0, Math.min(0xFFFFFF, Math.round(color)));
1489
+ const red = normalizedColor >> 16 & 0xFF;
1490
+ const green = normalizedColor >> 8 & 0xFF;
1491
+ const blue = normalizedColor & 0xFF;
1492
+ return `rgba(${red}, ${green}, ${blue}, ${formatCssNumber(alpha)})`;
1493
+ }
1494
+ function formatCssNumber(value) {
1495
+ const normalized = Number.isFinite(value) ? value : 0;
1496
+ return Number(normalized.toFixed(6)).toString();
1497
+ }
1498
+ function nextAnimationFrame() {
1499
+ return new Promise(resolve => {
1500
+ requestHTMLOverlayFrame(() => {
1501
+ resolve();
1502
+ });
1503
+ });
1504
+ }
1505
+ async function waitForDocumentFonts(doc) {
1506
+ await doc.fonts?.ready;
1507
+ }
1508
+ async function waitForImages(root) {
1509
+ const images = Array.from(root.querySelectorAll('img')).filter(image => !image.complete);
1510
+ await Promise.all(images.map(image => new Promise(resolve => {
1511
+ image.addEventListener('load', () => {
1512
+ resolve();
1513
+ }, { once: true });
1514
+ image.addEventListener('error', () => {
1515
+ resolve();
1516
+ }, { once: true });
1517
+ })));
1518
+ }
1519
+ async function waitForIframes(root) {
1520
+ const iframes = Array.from(root.querySelectorAll('iframe'));
1521
+ await Promise.all(iframes.map(async (iframe) => {
1522
+ await waitForIframeLoad(iframe);
1523
+ const doc = iframe.contentDocument;
1524
+ if (!doc) {
1525
+ return;
1526
+ }
1527
+ await waitForDocumentFonts(doc);
1528
+ await waitForImages(doc);
1529
+ }));
1530
+ }
1531
+ function waitForIframeLoad(iframe) {
1532
+ try {
1533
+ if (iframe.contentDocument?.readyState === 'complete') {
1534
+ return undefined;
1535
+ }
1536
+ }
1537
+ catch {
1538
+ return undefined;
1539
+ }
1540
+ return new Promise(resolve => {
1541
+ iframe.addEventListener('load', () => {
1542
+ resolve();
1543
+ }, { once: true });
1544
+ iframe.addEventListener('error', () => {
1545
+ resolve();
1546
+ }, { once: true });
1547
+ });
1548
+ }
1549
+ async function createCardCaptureTarget(root, html, logicalSize) {
1550
+ const liveDocument = root.querySelector('iframe')?.contentDocument;
1551
+ if (liveDocument) {
1552
+ const liveReplica = await createHTMLCaptureReplicaFromDocument(root.ownerDocument, liveDocument, logicalSize);
1553
+ if (liveReplica) {
1554
+ return {
1555
+ target: liveReplica.target,
1556
+ cleanup: liveReplica.cleanup,
1557
+ };
1558
+ }
1559
+ }
1560
+ const previewHTML = createCardPreviewHTML(html?.content);
1561
+ if (previewHTML) {
1562
+ const preview = await createHTMLCaptureReplica(root.ownerDocument, previewHTML, logicalSize);
1563
+ if (preview) {
1564
+ return {
1565
+ target: preview.target,
1566
+ cleanup: preview.cleanup,
1567
+ };
1568
+ }
1569
+ }
1570
+ const flattenedTarget = createFlattenedExistingIframeTarget(root);
1571
+ return {
1572
+ target: flattenedTarget,
1573
+ cleanup: () => {
1574
+ if (flattenedTarget !== root) {
1575
+ flattenedTarget.remove();
1576
+ }
1577
+ },
1578
+ };
1579
+ }
1580
+ function createFlattenedExistingIframeTarget(root) {
1581
+ const iframes = Array.from(root.querySelectorAll('iframe'));
1582
+ if (iframes.length === 0) {
1583
+ return root;
1584
+ }
1585
+ const clone = root.cloneNode(true);
1586
+ clone.style.position = 'fixed';
1587
+ clone.style.left = '-100000px';
1588
+ clone.style.top = '0';
1589
+ clone.style.pointerEvents = 'none';
1590
+ clone.style.opacity = '1';
1591
+ clone.style.zIndex = '-1';
1592
+ const clonedIframes = Array.from(clone.querySelectorAll('iframe'));
1593
+ iframes.forEach((iframe, index) => {
1594
+ const clonedIframe = clonedIframes[index];
1595
+ const iframeContent = createFlattenedIframeContent(iframe);
1596
+ if (!clonedIframe || !iframeContent) {
1597
+ return;
1598
+ }
1599
+ clonedIframe.replaceWith(iframeContent);
1600
+ });
1601
+ root.ownerDocument.body.appendChild(clone);
1602
+ return clone;
1603
+ }
1604
+ function createCardPreviewHTML(content) {
1605
+ if (!content) {
1606
+ return undefined;
1607
+ }
1608
+ if (content.kind === 'inline') {
1609
+ return withCardHTMLViewportStyle(content.html);
1610
+ }
1611
+ if (content.kind === 'document') {
1612
+ const files = normalizeDocumentFiles(content.files);
1613
+ const entry = normalizePath(content.entry);
1614
+ const createFileUrl = createDocumentFileUrlFactory(files);
1615
+ const entryHtml = files.get(entry) ?? '';
1616
+ return withCardHTMLViewportStyle(transformHTMLDocument(entry, entryHtml, createFileUrl));
1617
+ }
1618
+ return undefined;
1619
+ }
1620
+ function isMouseEventInsideRect(event, rect) {
1621
+ return event.clientX >= rect.left &&
1622
+ event.clientX <= rect.right &&
1623
+ event.clientY >= rect.top &&
1624
+ event.clientY <= rect.bottom;
1625
+ }
1626
+ function createFlattenedIframeContent(iframe) {
1627
+ const doc = iframe.contentDocument;
1628
+ if (!doc) {
1629
+ return undefined;
1630
+ }
1631
+ const replacement = document.createElement('div');
1632
+ const computedStyle = getComputedStyle(iframe);
1633
+ Object.assign(replacement.style, {
1634
+ width: computedStyle.width,
1635
+ height: computedStyle.height,
1636
+ display: computedStyle.display === 'none' ? 'block' : computedStyle.display,
1637
+ overflow: 'hidden',
1638
+ border: computedStyle.border,
1639
+ boxSizing: computedStyle.boxSizing,
1640
+ background: computedStyle.background,
1641
+ });
1642
+ Array.from(doc.head.children).forEach(child => {
1643
+ replacement.appendChild(child.cloneNode(true));
1644
+ });
1645
+ Array.from(doc.body.children).forEach(child => {
1646
+ replacement.appendChild(child.cloneNode(true));
1647
+ });
1648
+ return replacement;
1649
+ }
1650
+ function withTimeout(promise, timeout) {
1651
+ return new Promise(resolve => {
1652
+ const timer = window.setTimeout(() => {
1653
+ resolve(undefined);
1654
+ }, timeout);
1655
+ promise.then(value => {
1656
+ window.clearTimeout(timer);
1657
+ resolve(value);
1658
+ }, () => {
1659
+ window.clearTimeout(timer);
1660
+ resolve(undefined);
1661
+ });
1662
+ });
1663
+ }