@brixter/brix-builder 0.0.1

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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/dist/core.d.ts +103 -0
  4. package/dist/core.d.ts.map +1 -0
  5. package/dist/core.js +758 -0
  6. package/dist/core.js.map +1 -0
  7. package/dist/editor/BuilderApp.svelte +1299 -0
  8. package/dist/editor/BuilderFieldEditor.svelte +274 -0
  9. package/dist/editor/BuilderInspector.svelte +123 -0
  10. package/dist/editor/BuilderPreviewFrame.svelte +661 -0
  11. package/dist/editor/ComponentPreviewThumbnail.svelte +197 -0
  12. package/dist/editor/PageFlowSidebar.svelte +198 -0
  13. package/dist/editor/PreviewBlockInserter.svelte +35 -0
  14. package/dist/editor/PreviewIconEditor.svelte +213 -0
  15. package/dist/editor/PreviewImageEditor.svelte +221 -0
  16. package/dist/editor/PreviewTextEditor.svelte +246 -0
  17. package/dist/editor/RichTextEditor.svelte +234 -0
  18. package/dist/editor/contracts.d.ts +57 -0
  19. package/dist/editor/contracts.d.ts.map +1 -0
  20. package/dist/editor/contracts.js +2 -0
  21. package/dist/editor/contracts.js.map +1 -0
  22. package/dist/editor/index.d.ts +3 -0
  23. package/dist/editor/index.d.ts.map +1 -0
  24. package/dist/editor/index.js +2 -0
  25. package/dist/editor/index.js.map +1 -0
  26. package/dist/editor/shortcuts.d.ts +28 -0
  27. package/dist/editor/shortcuts.d.ts.map +1 -0
  28. package/dist/editor/shortcuts.js +28 -0
  29. package/dist/editor/shortcuts.js.map +1 -0
  30. package/dist/editor-controller.d.ts +50 -0
  31. package/dist/editor-controller.d.ts.map +1 -0
  32. package/dist/editor-controller.js +157 -0
  33. package/dist/editor-controller.js.map +1 -0
  34. package/dist/index.d.ts +7 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +6 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/preview/field-edit-debug.d.ts +5 -0
  39. package/dist/preview/field-edit-debug.d.ts.map +1 -0
  40. package/dist/preview/field-edit-debug.js +36 -0
  41. package/dist/preview/field-edit-debug.js.map +1 -0
  42. package/dist/preview/interactive-content.d.ts +8 -0
  43. package/dist/preview/interactive-content.d.ts.map +1 -0
  44. package/dist/preview/interactive-content.js +62 -0
  45. package/dist/preview/interactive-content.js.map +1 -0
  46. package/dist/preview-dom.d.ts +67 -0
  47. package/dist/preview-dom.d.ts.map +1 -0
  48. package/dist/preview-dom.js +191 -0
  49. package/dist/preview-dom.js.map +1 -0
  50. package/dist/svelte/SveltePreviewRenderer.svelte +490 -0
  51. package/dist/svelte/adapter.d.ts +7 -0
  52. package/dist/svelte/adapter.d.ts.map +1 -0
  53. package/dist/svelte/adapter.js +66 -0
  54. package/dist/svelte/adapter.js.map +1 -0
  55. package/dist/svelte/index.d.ts +3 -0
  56. package/dist/svelte/index.d.ts.map +1 -0
  57. package/dist/svelte/index.js +3 -0
  58. package/dist/svelte/index.js.map +1 -0
  59. package/dist/svelte/markup-schema.d.ts +5 -0
  60. package/dist/svelte/markup-schema.d.ts.map +1 -0
  61. package/dist/svelte/markup-schema.js +177 -0
  62. package/dist/svelte/markup-schema.js.map +1 -0
  63. package/package.json +56 -0
@@ -0,0 +1,661 @@
1
+ <script lang="ts">
2
+ import { mount, unmount } from 'svelte';
3
+ import type { BrikDefinition } from '../svelte/adapter.js';
4
+ import SveltePreviewRenderer from '../svelte/SveltePreviewRenderer.svelte';
5
+ import type { BuilderAppPreviewProps } from './contracts.js';
6
+
7
+ let {
8
+ definitions,
9
+ blocks,
10
+ propsErrors,
11
+ previewOverlays,
12
+ previewCollectionOverlays,
13
+ activeBlockId,
14
+ activeFieldEdit,
15
+ previewContainer,
16
+ onPreviewClick,
17
+ onPreviewKeydown,
18
+ onSelectBlock,
19
+ onCloseFieldEdit,
20
+ onUpdateRichText,
21
+ onUpdateText,
22
+ onQueueFileEdit,
23
+ onAddBlockBefore,
24
+ onAddBlockAfter,
25
+ onAddItem,
26
+ onRemoveItem,
27
+ onMoveItem,
28
+ onOpenReorderModal,
29
+ onOpenInserterModal,
30
+ onDeselectBlock,
31
+ onKeydown,
32
+ previewMode = false,
33
+ viewportSize = 'desktop'
34
+ }: BuilderAppPreviewProps & {
35
+ definitions: BrikDefinition[];
36
+ onKeydown?: (event: KeyboardEvent) => void;
37
+ } = $props();
38
+
39
+ const rendererProps = $state({} as BuilderAppPreviewProps & { definitions: BrikDefinition[] });
40
+
41
+ $effect(() => {
42
+ syncRendererProps();
43
+ });
44
+
45
+ function syncRendererProps(): void {
46
+ rendererProps.definitions = definitions;
47
+ rendererProps.blocks = blocks;
48
+ rendererProps.propsErrors = propsErrors;
49
+ rendererProps.previewOverlays = previewOverlays;
50
+ rendererProps.previewCollectionOverlays = previewCollectionOverlays;
51
+ rendererProps.activeBlockId = activeBlockId;
52
+ rendererProps.activeFieldEdit = activeFieldEdit;
53
+ rendererProps.previewContainer = previewContainer;
54
+ rendererProps.onPreviewClick = onPreviewClick;
55
+ rendererProps.onPreviewKeydown = onPreviewKeydown;
56
+ rendererProps.onSelectBlock = onSelectBlock;
57
+ rendererProps.onCloseFieldEdit = onCloseFieldEdit;
58
+ rendererProps.onUpdateRichText = onUpdateRichText;
59
+ rendererProps.onUpdateText = onUpdateText;
60
+ rendererProps.onQueueFileEdit = onQueueFileEdit;
61
+ rendererProps.onAddBlockBefore = onAddBlockBefore;
62
+ rendererProps.onAddBlockAfter = onAddBlockAfter;
63
+ rendererProps.onAddItem = onAddItem;
64
+ rendererProps.onRemoveItem = onRemoveItem;
65
+ rendererProps.onMoveItem = onMoveItem;
66
+ rendererProps.onOpenReorderModal = onOpenReorderModal;
67
+ rendererProps.onOpenInserterModal = onOpenInserterModal;
68
+ rendererProps.onDeselectBlock = onDeselectBlock;
69
+ rendererProps.previewMode = previewMode;
70
+ rendererProps.viewportSize = viewportSize;
71
+ }
72
+
73
+ function previewFrame(node: HTMLIFrameElement): { destroy: () => void } {
74
+ let renderer: Record<string, unknown> | null = null;
75
+ let cleanupFrameDocument: (() => void) | null = null;
76
+ let destroyed = false;
77
+
78
+ void initialize();
79
+
80
+ async function initialize(): Promise<void> {
81
+ const frameDocument = node.contentDocument;
82
+ if (!frameDocument) {
83
+ return;
84
+ }
85
+
86
+ frameDocument.open();
87
+ frameDocument.write(`<!doctype html>
88
+ <html>
89
+ <head>
90
+ <meta charset="utf-8">
91
+ <meta name="viewport" content="width=device-width, initial-scale=1">
92
+ <base href="${escapeHtml(document.baseURI)}">
93
+ </head>
94
+ <body>
95
+ <div id="builder-preview-root"></div>
96
+ </body>
97
+ </html>`);
98
+ frameDocument.close();
99
+
100
+ await Promise.resolve();
101
+ if (destroyed) {
102
+ return;
103
+ }
104
+
105
+ const target = frameDocument.getElementById('builder-preview-root');
106
+ if (!target) {
107
+ return;
108
+ }
109
+
110
+ syncRendererProps();
111
+ cleanupFrameDocument = setupFrameDocument(frameDocument);
112
+ renderer = mount(SveltePreviewRenderer, {
113
+ target,
114
+ props: rendererProps
115
+ }) as Record<string, unknown>;
116
+ }
117
+
118
+ return {
119
+ destroy() {
120
+ destroyed = true;
121
+ cleanupFrameDocument?.();
122
+ cleanupFrameDocument = null;
123
+ if (renderer) {
124
+ void unmount(renderer);
125
+ renderer = null;
126
+ }
127
+ }
128
+ };
129
+ }
130
+
131
+ function setupFrameDocument(frameDocument: Document): () => void {
132
+ const styleElement = frameDocument.createElement('style');
133
+ styleElement.textContent = `
134
+ html,
135
+ body,
136
+ #builder-preview-root {
137
+ min-height: 100%;
138
+ }
139
+
140
+ html,
141
+ body {
142
+ margin: 0;
143
+ }
144
+
145
+ body {
146
+ background: #ffffff;
147
+ color: #1e1c18;
148
+ overflow-x: hidden;
149
+ overflow-y: auto;
150
+ }
151
+
152
+ body.dark {
153
+ background: #12100d;
154
+ color: #f3f4f6;
155
+ }
156
+
157
+ #builder-preview-root {
158
+ width: 100%;
159
+ }
160
+
161
+ * {
162
+ scrollbar-color: #cbd5e1 transparent;
163
+ scrollbar-width: thin;
164
+ }
165
+
166
+ *::-webkit-scrollbar {
167
+ width: 10px;
168
+ height: 10px;
169
+ }
170
+
171
+ *::-webkit-scrollbar-track {
172
+ background: transparent;
173
+ }
174
+
175
+ *::-webkit-scrollbar-thumb {
176
+ min-height: 40px;
177
+ border: 3px solid transparent;
178
+ border-radius: 999px;
179
+ background: #cbd5e1;
180
+ background-clip: padding-box;
181
+ }
182
+
183
+ *::-webkit-scrollbar-thumb:hover {
184
+ background: #fde047;
185
+ background-clip: padding-box;
186
+ }
187
+
188
+ *::-webkit-scrollbar-corner {
189
+ background: transparent;
190
+ }
191
+
192
+ .dark * {
193
+ scrollbar-color: #475569 transparent;
194
+ }
195
+
196
+ .dark *::-webkit-scrollbar-thumb {
197
+ background: #475569;
198
+ background-clip: padding-box;
199
+ }
200
+
201
+ .dark *::-webkit-scrollbar-thumb:hover {
202
+ background: #facc15;
203
+ background-clip: padding-box;
204
+ }
205
+
206
+ [data-builder-field-enhanced='pending'],
207
+ [data-builder-field-enhanced='true'],
208
+ .builder-preview-field-editor,
209
+ .builder-richtext-inline-editor,
210
+ .builder-preview-text-editor {
211
+ cursor: text;
212
+ }
213
+
214
+ .builder-preview-field-editor,
215
+ .builder-richtext-mount {
216
+ display: contents;
217
+ }
218
+
219
+ .builder-preview-text-editor {
220
+ position: relative;
221
+ white-space: inherit;
222
+ }
223
+
224
+ .builder-preview-text-editor.is-editor-empty::before {
225
+ content: attr(data-placeholder);
226
+ color: #9ca3af;
227
+ opacity: 0.6;
228
+ pointer-events: none;
229
+ position: absolute;
230
+ left: 0;
231
+ right: 0;
232
+ top: 0;
233
+ text-align: inherit;
234
+ }
235
+
236
+ .builder-preview-text-editor--inline.is-editor-empty::before {
237
+ position: static;
238
+ display: inline;
239
+ white-space: nowrap;
240
+ }
241
+
242
+ [data-builder-field-enhanced='true']:is(a, button, [role='button'], [role='link'])
243
+ .builder-preview-text-editor--inline.is-editor-empty::before {
244
+ white-space: nowrap;
245
+ }
246
+
247
+ .ProseMirror.is-editor-empty::before {
248
+ content: attr(data-placeholder);
249
+ color: #9ca3af;
250
+ opacity: 0.6;
251
+ pointer-events: none;
252
+ position: absolute;
253
+ left: 0;
254
+ right: 0;
255
+ top: 0;
256
+ text-align: inherit;
257
+ }
258
+
259
+ [data-builder-field-enhanced='true']:is(a, button, [role='button'], [role='link']) {
260
+ display: inline-flex !important;
261
+ align-items: center;
262
+ justify-content: center;
263
+ color: var(--builder-preview-field-text-color) !important;
264
+ -webkit-text-fill-color: var(--builder-preview-field-text-color);
265
+ }
266
+
267
+ .builder-richtext-inline-editor--host-inline.is-editor-empty::before {
268
+ position: absolute;
269
+ left: 0;
270
+ right: 0;
271
+ top: 50%;
272
+ transform: translateY(-50%);
273
+ display: block;
274
+ white-space: nowrap;
275
+ text-align: inherit;
276
+ }
277
+
278
+ .builder-richtext-inline-editor--host-inline.ProseMirror-focused.is-editor-empty::before {
279
+ content: none;
280
+ }
281
+
282
+ .builder-preview-text-editor::placeholder {
283
+ color: #9ca3af;
284
+ opacity: 0.6;
285
+ }
286
+
287
+ .ProseMirror {
288
+ position: relative;
289
+ white-space: inherit;
290
+ }
291
+
292
+ [data-builder-field]:empty::before,
293
+ [data-builder-field] > p:only-child:empty::before,
294
+ [data-builder-field] > p:only-child:has(> br:only-child)::before {
295
+ content: attr(data-builder-placeholder);
296
+ color: #9ca3af;
297
+ opacity: 1;
298
+ pointer-events: none;
299
+ }
300
+
301
+ [data-builder-field]:is(a, button, [role='button'], [role='link']):empty::before {
302
+ display: inline;
303
+ }
304
+
305
+ [data-builder-field] > p:only-child:has(> br:only-child) > br {
306
+ display: none;
307
+ }
308
+
309
+ img[data-builder-field][data-builder-placeholder-active] {
310
+ background-color: #f4f4f5;
311
+ object-fit: cover;
312
+ }
313
+
314
+ body.dark img[data-builder-field][data-builder-placeholder-active] {
315
+ background-color: #27272a;
316
+ }
317
+
318
+ [data-builder-placeholder-active]:not(img):not([data-builder-field-enhanced='true']):not(
319
+ :is(a, button, [role='button'], [role='link'])
320
+ ) {
321
+ opacity: 0.6;
322
+ }
323
+
324
+ [data-builder-placeholder-active]:not(img):not([data-builder-field-enhanced='true']):is(
325
+ a,
326
+ button,
327
+ [role='button'],
328
+ [role='link']
329
+ ) {
330
+ opacity: 1;
331
+ color: #9ca3af !important;
332
+ }
333
+
334
+ [data-builder-placeholder-active]:not(img):not([data-builder-field-enhanced='true']):not(
335
+ :is(a, button, [role='button'], [role='link'])
336
+ ),
337
+ [data-builder-placeholder-active]:not(img):not([data-builder-field-enhanced='true']):not(
338
+ :is(a, button, [role='button'], [role='link'])
339
+ ) *:not(.builder-preview-field-editor):not(.builder-preview-text-editor):not(.ProseMirror):not(.builder-richtext-inline-editor):not(.builder-richtext-mount) {
340
+ color: #9ca3af !important;
341
+ }
342
+
343
+ [data-builder-field-enhanced='true'] .builder-preview-text-editor,
344
+ [data-builder-field-enhanced='true'] .ProseMirror,
345
+ [data-builder-field-enhanced='true'] .builder-richtext-inline-editor,
346
+ [data-builder-field-enhanced='true'] .builder-richtext-inline-editor p {
347
+ color: var(--builder-preview-field-text-color, currentColor) !important;
348
+ -webkit-text-fill-color: var(--builder-preview-field-text-color, currentColor);
349
+ caret-color: var(--builder-preview-field-text-color, currentColor);
350
+ }
351
+
352
+ body[data-builder-preview-page-overflow='true']::before {
353
+ position: fixed;
354
+ top: 12px;
355
+ right: 12px;
356
+ z-index: 2147483647;
357
+ border: 1px solid #f59e0b;
358
+ background: #fffbeb;
359
+ color: #92400e;
360
+ content: 'Page overflow';
361
+ font: 600 12px/1.2 system-ui, sans-serif;
362
+ padding: 6px 8px;
363
+ pointer-events: none;
364
+ }
365
+
366
+ [data-builder-preview-overflow-item='true'] {
367
+ outline: 2px solid #f59e0b !important;
368
+ outline-offset: -2px;
369
+ }
370
+
371
+ body[data-builder-preview-canvas] [data-builder-preview-content] :is(
372
+ a,
373
+ button,
374
+ input[type='submit'],
375
+ input[type='button'],
376
+ input[type='reset'],
377
+ input[type='checkbox'],
378
+ input[type='radio'],
379
+ label[for],
380
+ select,
381
+ textarea,
382
+ summary,
383
+ [role='button'],
384
+ [role='link']
385
+ ),
386
+ body[data-builder-preview-canvas] [data-builder-preview-content] :is(
387
+ a,
388
+ button,
389
+ input[type='submit'],
390
+ input[type='button'],
391
+ input[type='reset'],
392
+ input[type='checkbox'],
393
+ input[type='radio'],
394
+ label[for],
395
+ select,
396
+ textarea,
397
+ summary,
398
+ [role='button'],
399
+ [role='link']
400
+ ) * {
401
+ pointer-events: none !important;
402
+ }
403
+
404
+ body[data-builder-preview-canvas] [data-builder-preview-content] [data-builder-field-enhanced='pending'],
405
+ body[data-builder-preview-canvas] [data-builder-preview-content] [data-builder-field-enhanced='true'] .builder-preview-field-editor,
406
+ body[data-builder-preview-canvas] [data-builder-preview-content] [data-builder-field-enhanced='true'] .builder-preview-text-editor,
407
+ body[data-builder-preview-canvas] [data-builder-preview-content] [data-builder-field-enhanced='true'] .ProseMirror,
408
+ body[data-builder-preview-canvas] [data-builder-preview-content] [data-builder-field-enhanced='true'] .builder-richtext-inline-editor,
409
+ body[data-builder-preview-canvas] [data-builder-preview-content] [data-builder-field-enhanced='true'] .builder-richtext-mount {
410
+ pointer-events: auto !important;
411
+ }
412
+
413
+ [data-builder-field][data-builder-kind='icon']:empty::before {
414
+ content: '+';
415
+ font-family: inherit;
416
+ font-weight: bold;
417
+ display: inline-flex;
418
+ align-items: center;
419
+ justify-content: center;
420
+ width: 1.25rem;
421
+ height: 1.25rem;
422
+ border: 1px dashed currentColor;
423
+ border-radius: 4px;
424
+ opacity: 0.6;
425
+ }
426
+
427
+ body[data-builder-preview-canvas] [data-builder-field][data-builder-kind='icon']:hover,
428
+ body[data-builder-preview-canvas] [data-builder-field][data-builder-kind='icon'][data-builder-field-enhanced='true'] {
429
+ outline: 2px dashed #fde047 !important;
430
+ outline-offset: 2px;
431
+ border-radius: 4px;
432
+ }
433
+ .dark body[data-builder-preview-canvas] [data-builder-field][data-builder-kind='icon']:hover,
434
+ .dark body[data-builder-preview-canvas] [data-builder-field][data-builder-kind='icon'][data-builder-field-enhanced='true'] {
435
+ outline-color: #facc15 !important;
436
+ }
437
+ `;
438
+ frameDocument.head.append(styleElement);
439
+
440
+ let cleanupHeadSync: (() => void) | null = syncHeadAssets(frameDocument);
441
+ const removeKeydownListener = syncFrameKeydown(frameDocument);
442
+ const removeThemeSync = syncThemeClass(frameDocument);
443
+ const removeOverflowDiagnostic = syncPreviewOverflow(frameDocument);
444
+
445
+ return () => {
446
+ cleanupHeadSync?.();
447
+ cleanupHeadSync = null;
448
+ removeKeydownListener();
449
+ removeThemeSync();
450
+ removeOverflowDiagnostic();
451
+ };
452
+ }
453
+
454
+ function syncHeadAssets(frameDocument: Document): () => void {
455
+ let syncQueued = false;
456
+
457
+ function sync(): void {
458
+ syncQueued = false;
459
+ for (const node of Array.from(
460
+ frameDocument.head.querySelectorAll('[data-builder-preview-head-asset="true"]')
461
+ )) {
462
+ node.remove();
463
+ }
464
+
465
+ for (const asset of document.head.querySelectorAll(
466
+ 'link[rel="stylesheet"], link[rel="preconnect"], style'
467
+ )) {
468
+ const clone = asset.cloneNode(true) as HTMLElement;
469
+ clone.dataset.builderPreviewHeadAsset = 'true';
470
+ if (asset instanceof HTMLLinkElement && clone instanceof HTMLLinkElement && asset.href) {
471
+ clone.href = asset.href;
472
+ }
473
+ frameDocument.head.append(clone);
474
+ }
475
+ }
476
+
477
+ function queueSync(): void {
478
+ if (syncQueued) {
479
+ return;
480
+ }
481
+ syncQueued = true;
482
+ requestAnimationFrame(sync);
483
+ }
484
+
485
+ const observer = new MutationObserver(queueSync);
486
+ observer.observe(document.head, {
487
+ attributes: true,
488
+ characterData: true,
489
+ childList: true,
490
+ subtree: true
491
+ });
492
+ sync();
493
+
494
+ return () => {
495
+ observer.disconnect();
496
+ };
497
+ }
498
+
499
+ function syncFrameKeydown(frameDocument: Document): () => void {
500
+ const handler = (event: KeyboardEvent) => {
501
+ onKeydown?.(event);
502
+ };
503
+ frameDocument.addEventListener('keydown', handler);
504
+
505
+ return () => {
506
+ frameDocument.removeEventListener('keydown', handler);
507
+ };
508
+ }
509
+
510
+ function syncThemeClass(frameDocument: Document): () => void {
511
+ function applyThemeClass(): void {
512
+ const isDark =
513
+ document.documentElement.classList.contains('dark') ||
514
+ document.body.classList.contains('dark');
515
+ frameDocument.documentElement.classList.toggle('dark', isDark);
516
+ frameDocument.body.classList.toggle('dark', isDark);
517
+ }
518
+
519
+ const observer = new MutationObserver(applyThemeClass);
520
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
521
+ observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });
522
+ applyThemeClass();
523
+
524
+ return () => {
525
+ observer.disconnect();
526
+ };
527
+ }
528
+
529
+ function syncPreviewOverflow(frameDocument: Document): () => void {
530
+ let syncQueued = false;
531
+ let animationFrame = 0;
532
+ const frameWindow = frameDocument.defaultView ?? window;
533
+ const FrameResizeObserver = frameWindow.ResizeObserver ?? ResizeObserver;
534
+
535
+ function queueSync(): void {
536
+ if (syncQueued) {
537
+ return;
538
+ }
539
+
540
+ syncQueued = true;
541
+ animationFrame = frameWindow.requestAnimationFrame(sync);
542
+ }
543
+
544
+ function sync(): void {
545
+ syncQueued = false;
546
+ clearOverflowMarkers(frameDocument);
547
+
548
+ const viewportWidth = frameDocument.documentElement.clientWidth;
549
+ const offenders = findPreviewOverflowElements(frameDocument, viewportWidth);
550
+ frameDocument.body.toggleAttribute(
551
+ 'data-builder-preview-page-overflow',
552
+ offenders.length > 0
553
+ );
554
+
555
+ for (const offender of offenders.slice(0, 20)) {
556
+ offender.setAttribute('data-builder-preview-overflow-item', 'true');
557
+ }
558
+ }
559
+
560
+ const mutationObserver = new MutationObserver((records) => {
561
+ if (records.every(isOverflowDiagnosticMutation)) {
562
+ return;
563
+ }
564
+
565
+ queueSync();
566
+ });
567
+ mutationObserver.observe(frameDocument.body, {
568
+ attributes: true,
569
+ characterData: true,
570
+ childList: true,
571
+ subtree: true
572
+ });
573
+
574
+ const resizeObserver = new FrameResizeObserver(queueSync);
575
+ resizeObserver.observe(frameDocument.documentElement);
576
+ queueSync();
577
+
578
+ return () => {
579
+ if (animationFrame) {
580
+ frameWindow.cancelAnimationFrame(animationFrame);
581
+ }
582
+ mutationObserver.disconnect();
583
+ resizeObserver.disconnect();
584
+ clearOverflowMarkers(frameDocument);
585
+ frameDocument.body.removeAttribute('data-builder-preview-page-overflow');
586
+ };
587
+ }
588
+
589
+ function isOverflowDiagnosticMutation(record: MutationRecord): boolean {
590
+ return (
591
+ record.type === 'attributes' &&
592
+ (record.attributeName === 'data-builder-preview-overflow-item' ||
593
+ record.attributeName === 'data-builder-preview-page-overflow')
594
+ );
595
+ }
596
+
597
+ function findPreviewOverflowElements(
598
+ frameDocument: Document,
599
+ viewportWidth: number
600
+ ): HTMLElement[] {
601
+ const offenders = new Set<HTMLElement>();
602
+ const contentRoots = Array.from(
603
+ frameDocument.querySelectorAll<HTMLElement>('[data-builder-preview-content]')
604
+ );
605
+
606
+ for (const contentRoot of contentRoots) {
607
+ if (contentRoot.scrollWidth > contentRoot.clientWidth + 1) {
608
+ offenders.add(contentRoot);
609
+ }
610
+
611
+ for (const element of Array.from(contentRoot.querySelectorAll<HTMLElement>('*'))) {
612
+ if (element.getClientRects().length === 0) {
613
+ continue;
614
+ }
615
+
616
+ const rect = element.getBoundingClientRect();
617
+ if (rect.left < -1 || rect.right > viewportWidth + 1) {
618
+ offenders.add(element);
619
+ }
620
+ }
621
+ }
622
+
623
+ return Array.from(offenders);
624
+ }
625
+
626
+ function clearOverflowMarkers(frameDocument: Document): void {
627
+ for (const element of Array.from(
628
+ frameDocument.querySelectorAll('[data-builder-preview-overflow-item]')
629
+ )) {
630
+ element.removeAttribute('data-builder-preview-overflow-item');
631
+ }
632
+ }
633
+
634
+ function escapeHtml(value: string): string {
635
+ return value
636
+ .replaceAll('&', '&amp;')
637
+ .replaceAll('"', '&quot;')
638
+ .replaceAll('<', '&lt;')
639
+ .replaceAll('>', '&gt;');
640
+ }
641
+ </script>
642
+
643
+ <div
644
+ class="relative flex h-full w-full items-center justify-center p-0 transition-colors duration-300"
645
+ class:bg-gray-100={viewportSize !== 'desktop'}
646
+ class:dark:bg-gray-950={viewportSize !== 'desktop'}
647
+ class:p-4={viewportSize !== 'desktop'}
648
+ >
649
+ <iframe
650
+ use:previewFrame
651
+ title="Preview"
652
+ class="block h-full border-0 bg-white dark:bg-[#12100d] transition-all duration-300 ease-in-out"
653
+ class:w-full={viewportSize === 'desktop'}
654
+ class:shadow-2xl={viewportSize !== 'desktop'}
655
+ class:border={viewportSize !== 'desktop'}
656
+ class:border-gray-200={viewportSize !== 'desktop'}
657
+ class:dark:border-gray-800={viewportSize !== 'desktop'}
658
+ class:rounded-md={viewportSize !== 'desktop'}
659
+ style={viewportSize === 'tablet' ? 'width: 768px;' : viewportSize === 'mobile' ? 'width: 375px;' : ''}
660
+ ></iframe>
661
+ </div>