@akinon/pz-theme 2.0.35-rc.0 → 2.0.35
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.
- package/CHANGELOG.md +12 -28
- package/package.json +4 -3
- package/src/__tests__/theme-contract.test.ts +130 -0
- package/src/blocks/__tests__/text-block.test.tsx +8 -1
- package/src/blocks/__tests__/video-block.test.tsx +14 -7
- package/src/blocks/counter-block.tsx +44 -56
- package/src/blocks/group-block.tsx +23 -22
- package/src/blocks/hotspot-block.tsx +171 -80
- package/src/blocks/input-block.tsx +15 -17
- package/src/blocks/link-block.tsx +18 -21
- package/src/blocks/slider-block.tsx +15 -12
- package/src/components/with-designer-features.tsx +2 -0
- package/src/editor-bridge.ts +160 -0
- package/src/hooks/use-designer-features.tsx +30 -37
- package/src/placeholder-registry.ts +5 -7
- package/src/sections/__tests__/countdown-campaign-banner-section.test.tsx +8 -4
- package/src/sections/__tests__/coupon-banner-section.test.tsx +4 -2
- package/src/sections/before-after-section.tsx +189 -134
- package/src/sections/contact-form-section.tsx +185 -60
- package/src/sections/countdown-campaign-banner-section.tsx +73 -27
- package/src/sections/coupon-banner-section.tsx +152 -48
- package/src/sections/featured-product-spotlight-section.tsx +67 -66
- package/src/sections/find-in-store-section.tsx +37 -25
- package/src/sections/hover-showcase-section.tsx +221 -89
- package/src/sections/image-hotspot-section.tsx +208 -94
- package/src/sections/installment-options-section.tsx +268 -259
- package/src/sections/notification-banner-section.tsx +111 -25
- package/src/sections/order-tracking-lookup-section.tsx +272 -247
- package/src/sections/posts-slider-section.tsx +17 -16
- package/src/sections/pre-order-launch-banner-section.tsx +31 -50
- package/src/sections/section-wrapper.tsx +9 -10
- package/src/sections/shipping-threshold-progress-section.tsx +72 -29
- package/src/sections/stats-counter-section.tsx +68 -30
- package/src/sections/tabs-section.tsx +124 -147
- package/src/theme-placeholder-client.tsx +11 -14
- package/src/theme-placeholder-wrapper.tsx +42 -51
- package/src/theme-section.tsx +27 -34
- package/src/utils/__tests__/transform-styles.test.ts +97 -0
- package/src/utils/index.ts +9 -0
|
@@ -10,6 +10,7 @@ import { twMerge } from 'tailwind-merge';
|
|
|
10
10
|
import clsx from 'clsx';
|
|
11
11
|
import { useThemeSettingsContext } from '../theme-settings-context';
|
|
12
12
|
|
|
13
|
+
import { postToEditor } from '../editor-bridge';
|
|
13
14
|
interface TabsSectionProps {
|
|
14
15
|
section: Section;
|
|
15
16
|
currentBreakpoint?: string;
|
|
@@ -30,16 +31,21 @@ const TabsSection: React.FC<TabsSectionProps> = ({
|
|
|
30
31
|
|
|
31
32
|
// Separate tab blocks from non-tab blocks (like headers)
|
|
32
33
|
const nonTabBlocks = section.blocks
|
|
33
|
-
.filter(
|
|
34
|
+
.filter(
|
|
35
|
+
(block) => block.type !== 'tab' && (isDesigner ? true : !block.hidden)
|
|
36
|
+
)
|
|
34
37
|
.sort((a, b) => (a.order || 0) - (b.order || 0));
|
|
35
38
|
|
|
36
39
|
const tabBlocks = section.blocks
|
|
37
|
-
.filter(
|
|
40
|
+
.filter(
|
|
41
|
+
(block) => block.type === 'tab' && (isDesigner ? true : !block.hidden)
|
|
42
|
+
)
|
|
38
43
|
.sort((a, b) => (a.order || 0) - (b.order || 0));
|
|
39
44
|
|
|
40
45
|
const getResponsiveValue = useCallback(
|
|
41
46
|
<T,>(value: unknown, fallback: T): T => {
|
|
42
|
-
if (value === undefined || value === null || value === '')
|
|
47
|
+
if (value === undefined || value === null || value === '')
|
|
48
|
+
return fallback;
|
|
43
49
|
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
44
50
|
const responsiveValue = value as Record<string, T | undefined> & {
|
|
45
51
|
desktop?: T;
|
|
@@ -93,12 +99,7 @@ const TabsSection: React.FC<TabsSectionProps> = ({
|
|
|
93
99
|
if (activeTabIndex > tabBlocks.length - 1) {
|
|
94
100
|
setActiveTabIndex(tabBlocks.length - 1);
|
|
95
101
|
}
|
|
96
|
-
}, [
|
|
97
|
-
activeTabIndex,
|
|
98
|
-
findTabIndexBySelectedBlock,
|
|
99
|
-
selectedBlockId,
|
|
100
|
-
tabBlocks
|
|
101
|
-
]);
|
|
102
|
+
}, [activeTabIndex, findTabIndexBySelectedBlock, selectedBlockId, tabBlocks]);
|
|
102
103
|
|
|
103
104
|
const activeTabColor = resolveThemeCssVariables(
|
|
104
105
|
getResponsiveValue<string>(
|
|
@@ -218,93 +219,75 @@ const TabsSection: React.FC<TabsSectionProps> = ({
|
|
|
218
219
|
selectedBlockId={selectedBlockId}
|
|
219
220
|
onMoveUp={() => {
|
|
220
221
|
if (window.parent) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
},
|
|
230
|
-
'*'
|
|
231
|
-
);
|
|
222
|
+
postToEditor({
|
|
223
|
+
type: 'MOVE_BLOCK_UP',
|
|
224
|
+
data: {
|
|
225
|
+
placeholderId,
|
|
226
|
+
sectionId: section.id,
|
|
227
|
+
blockId: block.id
|
|
228
|
+
}
|
|
229
|
+
});
|
|
232
230
|
}
|
|
233
231
|
}}
|
|
234
232
|
onMoveDown={() => {
|
|
235
233
|
if (window.parent) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
},
|
|
245
|
-
'*'
|
|
246
|
-
);
|
|
234
|
+
postToEditor({
|
|
235
|
+
type: 'MOVE_BLOCK_DOWN',
|
|
236
|
+
data: {
|
|
237
|
+
placeholderId,
|
|
238
|
+
sectionId: section.id,
|
|
239
|
+
blockId: block.id
|
|
240
|
+
}
|
|
241
|
+
});
|
|
247
242
|
}
|
|
248
243
|
}}
|
|
249
244
|
onDuplicate={() => {
|
|
250
245
|
if (window.parent) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
},
|
|
260
|
-
'*'
|
|
261
|
-
);
|
|
246
|
+
postToEditor({
|
|
247
|
+
type: 'DUPLICATE_BLOCK',
|
|
248
|
+
data: {
|
|
249
|
+
placeholderId,
|
|
250
|
+
sectionId: section.id,
|
|
251
|
+
blockId: block.id
|
|
252
|
+
}
|
|
253
|
+
});
|
|
262
254
|
}
|
|
263
255
|
}}
|
|
264
256
|
onToggleVisibility={() => {
|
|
265
257
|
if (window.parent) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
},
|
|
275
|
-
'*'
|
|
276
|
-
);
|
|
258
|
+
postToEditor({
|
|
259
|
+
type: 'TOGGLE_BLOCK_VISIBILITY',
|
|
260
|
+
data: {
|
|
261
|
+
placeholderId,
|
|
262
|
+
sectionId: section.id,
|
|
263
|
+
blockId: block.id
|
|
264
|
+
}
|
|
265
|
+
});
|
|
277
266
|
}
|
|
278
267
|
}}
|
|
279
268
|
onDelete={() => {
|
|
280
269
|
if (window.parent) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
},
|
|
290
|
-
'*'
|
|
291
|
-
);
|
|
270
|
+
postToEditor({
|
|
271
|
+
type: 'DELETE_BLOCK',
|
|
272
|
+
data: {
|
|
273
|
+
placeholderId,
|
|
274
|
+
sectionId: section.id,
|
|
275
|
+
blockId: block.id
|
|
276
|
+
}
|
|
277
|
+
});
|
|
292
278
|
}
|
|
293
279
|
}}
|
|
294
280
|
onRename={(newLabel) => {
|
|
295
281
|
if (window.parent) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
},
|
|
306
|
-
'*'
|
|
307
|
-
);
|
|
282
|
+
postToEditor({
|
|
283
|
+
type: 'RENAME_BLOCK',
|
|
284
|
+
data: {
|
|
285
|
+
placeholderId,
|
|
286
|
+
sectionId: section.id,
|
|
287
|
+
blockId: block.id,
|
|
288
|
+
label: newLabel
|
|
289
|
+
}
|
|
290
|
+
});
|
|
308
291
|
}
|
|
309
292
|
}}
|
|
310
293
|
/>
|
|
@@ -328,7 +311,11 @@ const TabsSection: React.FC<TabsSectionProps> = ({
|
|
|
328
311
|
|
|
329
312
|
const isActive = index === activeTabIndex;
|
|
330
313
|
const headerStyles = resolveThemeStyleObject(
|
|
331
|
-
getCSSStyles(
|
|
314
|
+
getCSSStyles(
|
|
315
|
+
headerBlock.styles || {},
|
|
316
|
+
themeSettings,
|
|
317
|
+
currentBreakpoint
|
|
318
|
+
),
|
|
332
319
|
themeSettings
|
|
333
320
|
);
|
|
334
321
|
const cleanHeaderStyles = {
|
|
@@ -370,93 +357,75 @@ const TabsSection: React.FC<TabsSectionProps> = ({
|
|
|
370
357
|
selectedBlockId={selectedBlockId}
|
|
371
358
|
onMoveUp={() => {
|
|
372
359
|
if (window.parent) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
},
|
|
382
|
-
'*'
|
|
383
|
-
);
|
|
360
|
+
postToEditor({
|
|
361
|
+
type: 'MOVE_BLOCK_UP',
|
|
362
|
+
data: {
|
|
363
|
+
placeholderId,
|
|
364
|
+
sectionId: section.id,
|
|
365
|
+
blockId: tabBlock.id
|
|
366
|
+
}
|
|
367
|
+
});
|
|
384
368
|
}
|
|
385
369
|
}}
|
|
386
370
|
onMoveDown={() => {
|
|
387
371
|
if (window.parent) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
},
|
|
397
|
-
'*'
|
|
398
|
-
);
|
|
372
|
+
postToEditor({
|
|
373
|
+
type: 'MOVE_BLOCK_DOWN',
|
|
374
|
+
data: {
|
|
375
|
+
placeholderId,
|
|
376
|
+
sectionId: section.id,
|
|
377
|
+
blockId: tabBlock.id
|
|
378
|
+
}
|
|
379
|
+
});
|
|
399
380
|
}
|
|
400
381
|
}}
|
|
401
382
|
onDuplicate={() => {
|
|
402
383
|
if (window.parent) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
},
|
|
412
|
-
'*'
|
|
413
|
-
);
|
|
384
|
+
postToEditor({
|
|
385
|
+
type: 'DUPLICATE_BLOCK',
|
|
386
|
+
data: {
|
|
387
|
+
placeholderId,
|
|
388
|
+
sectionId: section.id,
|
|
389
|
+
blockId: tabBlock.id
|
|
390
|
+
}
|
|
391
|
+
});
|
|
414
392
|
}
|
|
415
393
|
}}
|
|
416
394
|
onToggleVisibility={() => {
|
|
417
395
|
if (window.parent) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
},
|
|
427
|
-
'*'
|
|
428
|
-
);
|
|
396
|
+
postToEditor({
|
|
397
|
+
type: 'TOGGLE_BLOCK_VISIBILITY',
|
|
398
|
+
data: {
|
|
399
|
+
placeholderId,
|
|
400
|
+
sectionId: section.id,
|
|
401
|
+
blockId: tabBlock.id
|
|
402
|
+
}
|
|
403
|
+
});
|
|
429
404
|
}
|
|
430
405
|
}}
|
|
431
406
|
onDelete={() => {
|
|
432
407
|
if (window.parent) {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
},
|
|
442
|
-
'*'
|
|
443
|
-
);
|
|
408
|
+
postToEditor({
|
|
409
|
+
type: 'DELETE_BLOCK',
|
|
410
|
+
data: {
|
|
411
|
+
placeholderId,
|
|
412
|
+
sectionId: section.id,
|
|
413
|
+
blockId: tabBlock.id
|
|
414
|
+
}
|
|
415
|
+
});
|
|
444
416
|
}
|
|
445
417
|
}}
|
|
446
418
|
onRename={(newLabel) => {
|
|
447
419
|
if (window.parent) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
},
|
|
458
|
-
'*'
|
|
459
|
-
);
|
|
420
|
+
postToEditor({
|
|
421
|
+
type: 'RENAME_BLOCK',
|
|
422
|
+
data: {
|
|
423
|
+
placeholderId,
|
|
424
|
+
sectionId: section.id,
|
|
425
|
+
blockId: tabBlock.id,
|
|
426
|
+
label: newLabel
|
|
427
|
+
}
|
|
428
|
+
});
|
|
460
429
|
}
|
|
461
430
|
}}
|
|
462
431
|
/>
|
|
@@ -485,7 +454,9 @@ const TabsSection: React.FC<TabsSectionProps> = ({
|
|
|
485
454
|
onClick={() => setActiveTabIndex(index)}
|
|
486
455
|
>
|
|
487
456
|
{headerBlock.blocks
|
|
488
|
-
?.filter((childBlock) =>
|
|
457
|
+
?.filter((childBlock) =>
|
|
458
|
+
isDesigner ? true : !childBlock.hidden
|
|
459
|
+
)
|
|
489
460
|
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
|
490
461
|
.map((childBlock, childIndex) => {
|
|
491
462
|
// If tab is active, we want to override the child's color with the active tab color
|
|
@@ -537,7 +508,11 @@ const TabsSection: React.FC<TabsSectionProps> = ({
|
|
|
537
508
|
if (!contentBlock) return null;
|
|
538
509
|
|
|
539
510
|
const contentStyles = resolveThemeStyleObject(
|
|
540
|
-
getCSSStyles(
|
|
511
|
+
getCSSStyles(
|
|
512
|
+
contentBlock.styles || {},
|
|
513
|
+
themeSettings,
|
|
514
|
+
currentBreakpoint
|
|
515
|
+
),
|
|
541
516
|
themeSettings
|
|
542
517
|
);
|
|
543
518
|
|
|
@@ -547,7 +522,9 @@ const TabsSection: React.FC<TabsSectionProps> = ({
|
|
|
547
522
|
style={contentStyles}
|
|
548
523
|
>
|
|
549
524
|
{contentBlock.blocks
|
|
550
|
-
?.filter((childBlock) =>
|
|
525
|
+
?.filter((childBlock) =>
|
|
526
|
+
isDesigner ? true : !childBlock.hidden
|
|
527
|
+
)
|
|
551
528
|
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
|
552
529
|
.map((childBlock, childIndex) => (
|
|
553
530
|
<ThemeBlock
|
|
@@ -11,6 +11,8 @@ import { useVisibilityContext } from './hooks/use-visibility-context';
|
|
|
11
11
|
import { applyVisibilityRulesToSections } from './utils/visibility-rules';
|
|
12
12
|
import { isPublishWindowVisible } from './utils/publish-window';
|
|
13
13
|
|
|
14
|
+
import { isTrustedEditorEvent, postToEditor } from './editor-bridge';
|
|
15
|
+
import { PreviewToEditorMessageType } from '@akinon/theme-contract';
|
|
14
16
|
// Global flags that must survive component remounts (e.g. in-iframe route
|
|
15
17
|
// changes): IFRAME_READY is sent once per document, and the designer flag
|
|
16
18
|
// arrives once from the editor in response — remounted components must not
|
|
@@ -64,12 +66,9 @@ export default function ThemePlaceholderClient({
|
|
|
64
66
|
if (isInIframe && window.parent) {
|
|
65
67
|
// Only send IFRAME_READY once per page session to prevent re-initialization
|
|
66
68
|
if (!window.__iframeReadySent) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
},
|
|
71
|
-
'*'
|
|
72
|
-
);
|
|
69
|
+
postToEditor({
|
|
70
|
+
type: 'IFRAME_READY'
|
|
71
|
+
});
|
|
73
72
|
window.__iframeReadySent = true;
|
|
74
73
|
}
|
|
75
74
|
|
|
@@ -77,6 +76,7 @@ export default function ThemePlaceholderClient({
|
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
const handleMessage = (event: MessageEvent) => {
|
|
79
|
+
if (!isTrustedEditorEvent(event)) return;
|
|
80
80
|
switch (event.data?.type) {
|
|
81
81
|
case 'SET_THEME_EDITOR_COOKIE':
|
|
82
82
|
if (event.data.data) {
|
|
@@ -144,7 +144,7 @@ export default function ThemePlaceholderClient({
|
|
|
144
144
|
}, [slug]);
|
|
145
145
|
|
|
146
146
|
const sendAction = (
|
|
147
|
-
action:
|
|
147
|
+
action: PreviewToEditorMessageType,
|
|
148
148
|
data: {
|
|
149
149
|
placeholderId?: string;
|
|
150
150
|
sectionId?: string;
|
|
@@ -153,13 +153,10 @@ export default function ThemePlaceholderClient({
|
|
|
153
153
|
}
|
|
154
154
|
) => {
|
|
155
155
|
if (window.parent) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
},
|
|
161
|
-
'*'
|
|
162
|
-
);
|
|
156
|
+
postToEditor({
|
|
157
|
+
type: action,
|
|
158
|
+
data
|
|
159
|
+
});
|
|
163
160
|
}
|
|
164
161
|
};
|
|
165
162
|
|
|
@@ -16,6 +16,8 @@ import { createPageContextDiscovery } from './utils/page-context-discovery';
|
|
|
16
16
|
import { applyVisibilityRulesToSections } from './utils/visibility-rules';
|
|
17
17
|
import { isPublishWindowVisible } from './utils/publish-window';
|
|
18
18
|
import { buildClientRequestUrl } from '@akinon/next/utils';
|
|
19
|
+
import { PreviewToEditorMessageType } from '@akinon/theme-contract';
|
|
20
|
+
import { parseEditorMessage, postToEditor } from './editor-bridge';
|
|
19
21
|
|
|
20
22
|
// Helper function to inject theme colors as CSS custom properties
|
|
21
23
|
const injectThemeColors = (settings: Record<string, unknown>) => {
|
|
@@ -166,22 +168,15 @@ const postProductPickerResponse = (
|
|
|
166
168
|
result?: unknown,
|
|
167
169
|
error?: string
|
|
168
170
|
) => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
ok,
|
|
179
|
-
result,
|
|
180
|
-
error
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
'*'
|
|
184
|
-
);
|
|
171
|
+
postToEditor({
|
|
172
|
+
type: 'PRODUCT_PICKER_RESPONSE',
|
|
173
|
+
data: {
|
|
174
|
+
requestId,
|
|
175
|
+
ok,
|
|
176
|
+
result,
|
|
177
|
+
error
|
|
178
|
+
}
|
|
179
|
+
});
|
|
185
180
|
};
|
|
186
181
|
|
|
187
182
|
const attachProductPickerPreviewListener = () => {
|
|
@@ -195,8 +190,14 @@ const attachProductPickerPreviewListener = () => {
|
|
|
195
190
|
window.__productPickerPreviewListenerAttached = true;
|
|
196
191
|
|
|
197
192
|
window.addEventListener('message', async (event) => {
|
|
198
|
-
|
|
199
|
-
|
|
193
|
+
// This listener performs storefront fetches on request — never service
|
|
194
|
+
// a frame from an untrusted embedder.
|
|
195
|
+
const message = parseEditorMessage(event);
|
|
196
|
+
if (!message) return;
|
|
197
|
+
|
|
198
|
+
const type = message.type;
|
|
199
|
+
const requestId = (message.data as { requestId?: unknown } | undefined)
|
|
200
|
+
?.requestId;
|
|
200
201
|
|
|
201
202
|
if (
|
|
202
203
|
![
|
|
@@ -374,15 +375,12 @@ export default function ThemePlaceholderWrapper({
|
|
|
374
375
|
if (typeof window === 'undefined') return;
|
|
375
376
|
if (window.self === window.top || !window.parent) return;
|
|
376
377
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
},
|
|
384
|
-
'*'
|
|
385
|
-
);
|
|
378
|
+
postToEditor({
|
|
379
|
+
type: 'PAGE_CONTEXT_DISCOVERY',
|
|
380
|
+
data: {
|
|
381
|
+
discovery: createPageContextDiscovery(resolvedPageContext)
|
|
382
|
+
}
|
|
383
|
+
});
|
|
386
384
|
}, [resolvedPageContext]);
|
|
387
385
|
|
|
388
386
|
// Load fonts from initial sections on mount (for production site, not iframe)
|
|
@@ -494,12 +492,7 @@ export default function ThemePlaceholderWrapper({
|
|
|
494
492
|
|
|
495
493
|
// Only send IFRAME_READY once per page session to prevent re-initialization
|
|
496
494
|
if (!window.__iframeReadySent) {
|
|
497
|
-
|
|
498
|
-
{
|
|
499
|
-
type: 'IFRAME_READY'
|
|
500
|
-
},
|
|
501
|
-
'*'
|
|
502
|
-
);
|
|
495
|
+
postToEditor({ type: 'IFRAME_READY' });
|
|
503
496
|
window.__iframeReadySent = true;
|
|
504
497
|
}
|
|
505
498
|
|
|
@@ -507,9 +500,15 @@ export default function ThemePlaceholderWrapper({
|
|
|
507
500
|
}
|
|
508
501
|
|
|
509
502
|
const handleMessage = (event: MessageEvent) => {
|
|
510
|
-
|
|
503
|
+
const message = parseEditorMessage(event);
|
|
504
|
+
if (!message) return;
|
|
505
|
+
|
|
506
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
507
|
+
const messageData: any = message.data;
|
|
508
|
+
|
|
509
|
+
switch (message.type) {
|
|
511
510
|
case 'SET_THEME_EDITOR_COOKIE':
|
|
512
|
-
if (
|
|
511
|
+
if (messageData) {
|
|
513
512
|
window.__themeEditorDesigner = true;
|
|
514
513
|
setIsDesigner(true);
|
|
515
514
|
}
|
|
@@ -517,7 +516,7 @@ export default function ThemePlaceholderWrapper({
|
|
|
517
516
|
|
|
518
517
|
case 'LOAD_THEME':
|
|
519
518
|
case 'UPDATE_THEME': {
|
|
520
|
-
const theme =
|
|
519
|
+
const theme = messageData?.theme;
|
|
521
520
|
|
|
522
521
|
// Inject theme colors as CSS custom properties
|
|
523
522
|
if (theme?.settings) {
|
|
@@ -621,7 +620,7 @@ export default function ThemePlaceholderWrapper({
|
|
|
621
620
|
}
|
|
622
621
|
|
|
623
622
|
case 'SELECT_SECTION': {
|
|
624
|
-
const { sectionId } =
|
|
623
|
+
const { sectionId } = messageData || {};
|
|
625
624
|
if (sectionId) {
|
|
626
625
|
setSelectedSectionId(sectionId);
|
|
627
626
|
setSelectedBlockId(null);
|
|
@@ -630,7 +629,7 @@ export default function ThemePlaceholderWrapper({
|
|
|
630
629
|
}
|
|
631
630
|
|
|
632
631
|
case 'SELECT_BLOCK': {
|
|
633
|
-
const { blockId } =
|
|
632
|
+
const { blockId } = messageData || {};
|
|
634
633
|
if (blockId) {
|
|
635
634
|
setSelectedBlockId(blockId);
|
|
636
635
|
setSelectedSectionId(null);
|
|
@@ -639,7 +638,7 @@ export default function ThemePlaceholderWrapper({
|
|
|
639
638
|
}
|
|
640
639
|
|
|
641
640
|
case 'CHANGE_BREAKPOINT': {
|
|
642
|
-
const { breakpoint } =
|
|
641
|
+
const { breakpoint } = messageData || {};
|
|
643
642
|
if (breakpoint) {
|
|
644
643
|
setCurrentBreakpoint(breakpoint);
|
|
645
644
|
}
|
|
@@ -653,7 +652,7 @@ export default function ThemePlaceholderWrapper({
|
|
|
653
652
|
}
|
|
654
653
|
|
|
655
654
|
case 'LOAD_FONT': {
|
|
656
|
-
const { fontFamily, fontWeight } =
|
|
655
|
+
const { fontFamily, fontWeight } = messageData || {};
|
|
657
656
|
if (fontFamily) {
|
|
658
657
|
loadGoogleFont(fontFamily, fontWeight);
|
|
659
658
|
}
|
|
@@ -661,7 +660,7 @@ export default function ThemePlaceholderWrapper({
|
|
|
661
660
|
}
|
|
662
661
|
|
|
663
662
|
case 'LOAD_CUSTOM_FONT': {
|
|
664
|
-
const { name, sources } =
|
|
663
|
+
const { name, sources } = messageData || {};
|
|
665
664
|
if (!name || !Array.isArray(sources) || sources.length === 0) break;
|
|
666
665
|
|
|
667
666
|
const styleId = `custom-font-${name
|
|
@@ -693,7 +692,7 @@ export default function ThemePlaceholderWrapper({
|
|
|
693
692
|
}, [slug]);
|
|
694
693
|
|
|
695
694
|
const sendAction = (
|
|
696
|
-
action:
|
|
695
|
+
action: PreviewToEditorMessageType,
|
|
697
696
|
data: {
|
|
698
697
|
placeholderId?: string;
|
|
699
698
|
sectionId?: string;
|
|
@@ -701,15 +700,7 @@ export default function ThemePlaceholderWrapper({
|
|
|
701
700
|
label?: string;
|
|
702
701
|
}
|
|
703
702
|
) => {
|
|
704
|
-
|
|
705
|
-
window.parent.postMessage(
|
|
706
|
-
{
|
|
707
|
-
type: action,
|
|
708
|
-
data
|
|
709
|
-
},
|
|
710
|
-
'*'
|
|
711
|
-
);
|
|
712
|
-
}
|
|
703
|
+
postToEditor({ type: action, data });
|
|
713
704
|
};
|
|
714
705
|
|
|
715
706
|
const dynamicCSS = useMemo(
|