payaza-storefront-layouts 1.1.76 → 1.1.78

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 (27) hide show
  1. package/dist/editor/payaza-form/EditorCanvasPreview.d.ts +38 -0
  2. package/dist/editor/payaza-form/EditorCanvasPreview.d.ts.map +1 -0
  3. package/dist/editor/payaza-form/EditorCanvasPreview.js +55 -0
  4. package/dist/editor/payaza-form/IframePreviewMessageBridge.d.ts +3 -4
  5. package/dist/editor/payaza-form/IframePreviewMessageBridge.d.ts.map +1 -1
  6. package/dist/editor/payaza-form/IframePreviewMessageBridge.js +11 -81
  7. package/dist/editor/payaza-form/PayazaFormEditor.d.ts.map +1 -1
  8. package/dist/editor/payaza-form/PayazaFormEditor.js +109 -68
  9. package/dist/editor/payaza-form/preview-message-handler.d.ts +18 -0
  10. package/dist/editor/payaza-form/preview-message-handler.d.ts.map +1 -0
  11. package/dist/editor/payaza-form/preview-message-handler.js +84 -0
  12. package/dist/editor/shared/InlineCanvasPreview.d.ts +34 -0
  13. package/dist/editor/shared/InlineCanvasPreview.d.ts.map +1 -0
  14. package/dist/editor/shared/InlineCanvasPreview.js +145 -0
  15. package/dist/editor/shared/index.d.ts +1 -0
  16. package/dist/editor/shared/index.d.ts.map +1 -1
  17. package/dist/editor/shared/index.js +1 -0
  18. package/dist/lib/utils/mobile-editor.d.ts +2 -0
  19. package/dist/lib/utils/mobile-editor.d.ts.map +1 -1
  20. package/dist/lib/utils/mobile-editor.js +4 -0
  21. package/dist/preview/canvas/PreviewCanvasEnhancer.d.ts +3 -1
  22. package/dist/preview/canvas/PreviewCanvasEnhancer.d.ts.map +1 -1
  23. package/dist/preview/canvas/PreviewCanvasEnhancer.js +27 -19
  24. package/dist/preview/canvas/PreviewCanvasStyles.d.ts +4 -0
  25. package/dist/preview/canvas/PreviewCanvasStyles.d.ts.map +1 -0
  26. package/dist/preview/canvas/PreviewCanvasStyles.js +51 -0
  27. package/package.json +1 -1
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import type { PreviewZoom } from '../shared/preview-viewport';
3
+ import { StoreConfig } from '../../lib/store-types';
4
+ import type { InteractionMode } from '../document/types';
5
+ import type { PayazaInlineFieldCommitPayload } from './IframePreviewMessageBridge';
6
+ import type { PreviewElementFocusPayload } from '../../preview/canvas/preview-messages';
7
+ export interface EditorCanvasPreviewProps {
8
+ layoutId: string;
9
+ config: StoreConfig;
10
+ activePageId: string;
11
+ viewMode: 'desktop' | 'tablet' | 'mobile';
12
+ selectedSection?: string | null;
13
+ focusedField?: {
14
+ sectionKey: string;
15
+ fieldPath: string[];
16
+ elementKind: string;
17
+ } | null;
18
+ interactionMode?: InteractionMode;
19
+ slideIndexSync?: {
20
+ sectionKey: string;
21
+ arrayField: string;
22
+ index: number;
23
+ } | null;
24
+ flushRequestId?: number;
25
+ previewZoom?: PreviewZoom;
26
+ editorContext: {
27
+ layoutId: string;
28
+ activePageId: string;
29
+ };
30
+ onSectionSelected: (sectionKey: string) => void;
31
+ onFieldCommit: (payload: PayazaInlineFieldCommitPayload) => void;
32
+ onSectionReorder: (sectionKey: string, direction: 'up' | 'down') => void;
33
+ onFocusSidebarSection: (sectionKey: string) => void;
34
+ onPreviewElementFocus?: (payload: Omit<PreviewElementFocusPayload, 'type'>) => void;
35
+ onRouteChange?: (route: string) => void;
36
+ }
37
+ export declare function EditorCanvasPreview({ layoutId, config, activePageId, viewMode, selectedSection, focusedField, interactionMode, slideIndexSync, flushRequestId, previewZoom, editorContext, onSectionSelected, onFieldCommit, onSectionReorder, onFocusSidebarSection, onPreviewElementFocus, onRouteChange, }: EditorCanvasPreviewProps): React.JSX.Element;
38
+ //# sourceMappingURL=EditorCanvasPreview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorCanvasPreview.d.ts","sourceRoot":"","sources":["../../../src/editor/payaza-form/EditorCanvasPreview.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAsB,MAAM,OAAO,CAAC;AAG3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAIzD,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,8BAA8B,CAAC;AACnF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAExF,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC1C,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,YAAY,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACvF,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,cAAc,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,aAAa,EAAE;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,aAAa,EAAE,CAAC,OAAO,EAAE,8BAA8B,KAAK,IAAI,CAAC;IACjE,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC;IACzE,qBAAqB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,0BAA0B,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IACpF,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,eAAe,EACf,cAAc,EACd,cAAc,EACd,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,aAAa,GACd,EAAE,wBAAwB,qBA6D1B"}
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useCallback } from 'react';
4
+ import { IframePreview } from '../shared/IframePreview.js';
5
+ import { InlineCanvasPreview } from '../shared/InlineCanvasPreview.js';
6
+ import { shouldUseInlineEditorPreview } from '../../lib/utils/mobile-editor.js';
7
+ import { useAssetSelection } from './AssetSelectionContext.js';
8
+ import { processPreviewMessage } from './preview-message-handler.js';
9
+ export function EditorCanvasPreview({ layoutId, config, activePageId, viewMode, selectedSection, focusedField, interactionMode, slideIndexSync, flushRequestId, previewZoom, editorContext, onSectionSelected, onFieldCommit, onSectionReorder, onFocusSidebarSection, onPreviewElementFocus, onRouteChange, }) {
10
+ const useInline = shouldUseInlineEditorPreview();
11
+ const { onOpenAssetSelect } = useAssetSelection();
12
+ const handlePreviewEvent = useCallback((payload) => {
13
+ processPreviewMessage(payload, {
14
+ editorContext,
15
+ onSectionSelected,
16
+ onFieldCommit,
17
+ onSectionReorder,
18
+ onFocusSidebarSection,
19
+ onPreviewElementFocus,
20
+ onRouteChange,
21
+ onOpenAssetSelect,
22
+ });
23
+ }, [
24
+ editorContext,
25
+ onFieldCommit,
26
+ onFocusSidebarSection,
27
+ onOpenAssetSelect,
28
+ onPreviewElementFocus,
29
+ onRouteChange,
30
+ onSectionReorder,
31
+ onSectionSelected,
32
+ ]);
33
+ const handleInlineReady = useCallback(() => {
34
+ if (typeof window !== 'undefined') {
35
+ window.postMessage({ type: 'PREVIEW_READY' }, '*');
36
+ }
37
+ }, []);
38
+ const previewViewMode = useInline ? 'mobile' : viewMode;
39
+ const commonProps = {
40
+ layoutId,
41
+ config,
42
+ activePageId,
43
+ viewMode: previewViewMode,
44
+ selectedSection,
45
+ focusedField,
46
+ interactionMode,
47
+ slideIndexSync,
48
+ flushRequestId,
49
+ previewZoom,
50
+ };
51
+ if (useInline) {
52
+ return (_jsx(InlineCanvasPreview, { ...commonProps, onPreviewEvent: handlePreviewEvent, onReady: handleInlineReady }));
53
+ }
54
+ return _jsx(IframePreview, { ...commonProps });
55
+ }
@@ -1,10 +1,9 @@
1
- import { type PreviewElementFocusPayload, type PreviewFieldCommitPayload } from '../../preview/canvas/preview-messages';
2
1
  export interface PayazaInlineFieldCommitPayload {
3
2
  sectionKey: string;
4
3
  fieldPath: string[];
5
4
  valueText: string;
6
- richText?: PreviewFieldCommitPayload['richText'];
7
- elementKind?: PreviewFieldCommitPayload['elementKind'];
5
+ richText?: import('../../preview/canvas/preview-messages').PreviewFieldCommitPayload['richText'];
6
+ elementKind?: import('../../preview/canvas/preview-messages').PreviewFieldCommitPayload['elementKind'];
8
7
  }
9
8
  export interface IframePreviewMessageBridgeProps {
10
9
  /** Active editor layout id + overlay page — commits must match. */
@@ -16,7 +15,7 @@ export interface IframePreviewMessageBridgeProps {
16
15
  onFieldCommit: (payload: PayazaInlineFieldCommitPayload) => void;
17
16
  onSectionReorder: (sectionKey: string, direction: 'up' | 'down') => void;
18
17
  onFocusSidebarSection: (sectionKey: string) => void;
19
- onPreviewElementFocus?: (payload: Omit<PreviewElementFocusPayload, 'type'>) => void;
18
+ onPreviewElementFocus?: (payload: Omit<import('../../preview/canvas/preview-messages').PreviewElementFocusPayload, 'type'>) => void;
20
19
  /** Called when the preview navigates to a new route so the editor can sync its activePageId. */
21
20
  onRouteChange?: (route: string) => void;
22
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"IframePreviewMessageBridge.d.ts","sourceRoot":"","sources":["../../../src/editor/payaza-form/IframePreviewMessageBridge.tsx"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,0BAA0B,EAC/B,KAAK,yBAAyB,EAC/B,MAAM,uCAAuC,CAAC;AAI/C,MAAM,WAAW,8BAA8B;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,yBAAyB,CAAC,UAAU,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,yBAAyB,CAAC,aAAa,CAAC,CAAC;CACxD;AAED,MAAM,WAAW,+BAA+B;IAC9C,mEAAmE;IACnE,aAAa,EAAE;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,aAAa,EAAE,CAAC,OAAO,EAAE,8BAA8B,KAAK,IAAI,CAAC;IACjE,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC;IACzE,qBAAqB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,0BAA0B,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IACpF,gGAAgG;IAChG,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,EACzC,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,aAAa,GACd,EAAE,+BAA+B,OAqIjC;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAElE"}
1
+ {"version":3,"file":"IframePreviewMessageBridge.d.ts","sourceRoot":"","sources":["../../../src/editor/payaza-form/IframePreviewMessageBridge.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,8BAA8B;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,uCAAuC,EAAE,yBAAyB,CAAC,UAAU,CAAC,CAAC;IACjG,WAAW,CAAC,EAAE,OAAO,uCAAuC,EAAE,yBAAyB,CAAC,aAAa,CAAC,CAAC;CACxG;AAED,MAAM,WAAW,+BAA+B;IAC9C,mEAAmE;IACnE,aAAa,EAAE;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,aAAa,EAAE,CAAC,OAAO,EAAE,8BAA8B,KAAK,IAAI,CAAC;IACjE,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC;IACzE,qBAAqB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,uCAAuC,EAAE,0BAA0B,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IACpI,gGAAgG;IAChG,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,EACzC,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,aAAa,GACd,EAAE,+BAA+B,OAqCjC;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAElE"}
@@ -1,9 +1,8 @@
1
1
  'use client';
2
2
  import { useEffect, useCallback } from 'react';
3
3
  import { useAssetSelection } from './AssetSelectionContext.js';
4
- import { PREVIEW_MSG, } from '../../preview/canvas/preview-messages.js';
4
+ import { processPreviewMessage } from './preview-message-handler.js';
5
5
  import { normalizeEditorLayoutId } from '../document/layout-id.js';
6
- import { previewCommitsMatchEditorContext } from '../../preview/canvas/preview-isolation.js';
7
6
  /**
8
7
  * Handles postMessage from the layout preview iframe (field commit, image replace, reorder).
9
8
  * Must render under AssetSelectionProvider for image replacement.
@@ -14,85 +13,16 @@ export function IframePreviewMessageBridge({ editorContext, onSectionSelected, o
14
13
  const d = event.data;
15
14
  if (!d || typeof d !== 'object')
16
15
  return;
17
- if (d.type === 'SECTION_SELECTED') {
18
- const sectionKey = d.section || d.sectionKey;
19
- if (typeof sectionKey === 'string' && sectionKey) {
20
- onSectionSelected(sectionKey);
21
- }
22
- return;
23
- }
24
- if (d.type === PREVIEW_MSG.ELEMENT_FOCUS) {
25
- const payload = d;
26
- const ok = previewCommitsMatchEditorContext({ layoutId: payload.layoutId, pageId: payload.pageId }, { layoutId: editorContext.layoutId, pageId: editorContext.activePageId });
27
- if (!ok)
28
- return;
29
- onPreviewElementFocus?.({
30
- layoutId: payload.layoutId,
31
- pageId: payload.pageId,
32
- sectionKey: payload.sectionKey,
33
- fieldPath: payload.fieldPath,
34
- elementKind: payload.elementKind,
35
- });
36
- return;
37
- }
38
- if (d.type === PREVIEW_MSG.FIELD_COMMIT) {
39
- const payload = d;
40
- const ok = previewCommitsMatchEditorContext({ layoutId: payload.layoutId, pageId: payload.pageId }, { layoutId: editorContext.layoutId, pageId: editorContext.activePageId });
41
- if (!ok)
42
- return;
43
- onFieldCommit({
44
- sectionKey: payload.sectionKey,
45
- fieldPath: payload.fieldPath,
46
- valueText: String(payload.value ?? ''),
47
- richText: payload.richText,
48
- elementKind: payload.elementKind,
49
- });
50
- return;
51
- }
52
- if (d.type === PREVIEW_MSG.SECTION_REORDER) {
53
- const { sectionKey, direction, layoutId, pageId } = d;
54
- const ok = layoutId &&
55
- pageId &&
56
- previewCommitsMatchEditorContext({ layoutId, pageId }, { layoutId: editorContext.layoutId, pageId: editorContext.activePageId });
57
- if (!ok)
58
- return;
59
- if (sectionKey && (direction === 'up' || direction === 'down')) {
60
- onSectionReorder(sectionKey, direction);
61
- }
62
- return;
63
- }
64
- if (d.type === PREVIEW_MSG.IMAGE_ACTIVATE) {
65
- const { sectionKey, fieldPath, layoutId, pageId } = d;
66
- const ok = layoutId &&
67
- pageId &&
68
- previewCommitsMatchEditorContext({ layoutId, pageId }, { layoutId: editorContext.layoutId, pageId: editorContext.activePageId });
69
- if (!ok)
70
- return;
71
- if (sectionKey && Array.isArray(fieldPath)) {
72
- onOpenAssetSelect((url) => {
73
- onFieldCommit({
74
- sectionKey,
75
- fieldPath,
76
- valueText: url,
77
- elementKind: 'image',
78
- });
79
- });
80
- }
81
- return;
82
- }
83
- if (d.type === 'PREVIEW_FOCUS_SIDEBAR') {
84
- const sk = d.sectionKey;
85
- if (typeof sk === 'string' && sk) {
86
- onFocusSidebarSection(sk);
87
- }
88
- return;
89
- }
90
- if (d.type === 'ROUTE_CHANGED') {
91
- const route = d.route;
92
- if (typeof route === 'string' && route) {
93
- onRouteChange?.(route);
94
- }
95
- }
16
+ processPreviewMessage(d, {
17
+ editorContext,
18
+ onSectionSelected,
19
+ onFieldCommit,
20
+ onSectionReorder,
21
+ onFocusSidebarSection,
22
+ onPreviewElementFocus,
23
+ onRouteChange,
24
+ onOpenAssetSelect,
25
+ });
96
26
  }, [
97
27
  editorContext.activePageId,
98
28
  editorContext.layoutId,
@@ -1 +1 @@
1
- {"version":3,"file":"PayazaFormEditor.d.ts","sourceRoot":"","sources":["../../../src/editor/payaza-form/PayazaFormEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6E,MAAM,OAAO,CAAC;AAClG,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAmB/C,OAAO,EAA4B,eAAe,EAAgB,MAAM,mBAAmB,CAAC;AAgC5F,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB;IAChE;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACjD;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CA4uD5D,CAAC"}
1
+ {"version":3,"file":"PayazaFormEditor.d.ts","sourceRoot":"","sources":["../../../src/editor/payaza-form/PayazaFormEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6E,MAAM,OAAO,CAAC;AAClG,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAkB/C,OAAO,EAA4B,eAAe,EAAgB,MAAM,mBAAmB,CAAC;AA2D5F,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB;IAChE;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACjD;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAoxD5D,CAAC"}
@@ -3,7 +3,6 @@ import { useState, useMemo, useEffect, useLayoutEffect, useRef, useCallback } fr
3
3
  import { previewRouteToPageId } from '../../preview/canvas/preview-route.js';
4
4
  import { EditorHeader } from '../shared/EditorHeader.js';
5
5
  import { EditorNavigator } from '../shared/EditorNavigator.js';
6
- import { IframePreview } from '../shared/IframePreview.js';
7
6
  import { ResizableSidebar } from '../shared/ResizableSidebar.js';
8
7
  import { moveSectionKeyInPageOrder, normalizeEditorLayoutId, resolveSectionKeyOrderForPage, storeConfigToEnvelope, } from '../document/index.js';
9
8
  import { payazaSectionStyleKey, getPayazaSectionStyleEntries } from '../../lib/payaza-section-styles.js';
@@ -19,6 +18,7 @@ import { AIGenerateModal } from '../shared/AIGenerateModal.js';
19
18
  import { useAlertModal } from '../../components/ui/alert-modal.js';
20
19
  import { AssetSelectionProvider } from './AssetSelectionContext.js';
21
20
  import { IframePreviewMessageBridge } from './IframePreviewMessageBridge.js';
21
+ import { EditorCanvasPreview } from './EditorCanvasPreview.js';
22
22
  import { BlockStylesInspector } from './components/BlockStylesInspector.js';
23
23
  import { CanvasGranularInspector } from './components/CanvasGranularInspector.js';
24
24
  import { SectionSchemaInspector } from './components/SectionSchemaInspector.js';
@@ -27,7 +27,34 @@ import { resolveCanvasInspectorLeaf } from '../alignment-schemas/resolve-canvas-
27
27
  import { granularPathForArrayItem, granularPathForBlockFirstAttr, outlineTreeForSectionPayload, } from '../blocks/nav-outline-utils.js';
28
28
  import { ActiveArrayFocusProvider, } from './ActiveArrayFocusContext.js';
29
29
  import { EditorLayoutProvider } from './EditorLayoutContext.js';
30
- import { isMobileEditorViewport } from '../../lib/utils/mobile-editor.js';
30
+ import { isMobileEditorViewport, shouldUseInlineEditorPreview } from '../../lib/utils/mobile-editor.js';
31
+ /** Lightweight fingerprint for undo history on mobile — avoids JSON.stringify on large configs. */
32
+ function lightweightConfigFingerprint(config) {
33
+ try {
34
+ const layoutConfig = config.layoutConfig;
35
+ const pages = layoutConfig?.pages;
36
+ if (Array.isArray(pages)) {
37
+ return pages
38
+ .map((page) => {
39
+ const p = page;
40
+ const sections = p.sections;
41
+ if (!Array.isArray(sections))
42
+ return '';
43
+ return sections
44
+ .map((s) => {
45
+ const sec = s;
46
+ return String(sec.key ?? sec.id ?? '');
47
+ })
48
+ .join(',');
49
+ })
50
+ .join('|');
51
+ }
52
+ return String(config.layout ?? '');
53
+ }
54
+ catch {
55
+ return '';
56
+ }
57
+ }
31
58
  export const PayazaFormEditor = ({ layoutId, initialData, inventory, onChange, onSave, onPublish, onBack, onBackToSettings, onOpenAssets, onBackToTheme, title, className = '', customWidgets, storeName, storeDescription, assets, publishButtonText, onEditorDocumentChange, initialDataIsPublished, onEditorHydrated, preloadedBaseRequirement, catalogOverlay, }) => {
32
59
  const [activePageId, setActivePageId] = useState('home');
33
60
  const [data, setData] = useState(initialData);
@@ -40,7 +67,8 @@ export const PayazaFormEditor = ({ layoutId, initialData, inventory, onChange, o
40
67
  const [history, setHistory] = useState([initialData]);
41
68
  const [historyIndex, setHistoryIndex] = useState(0);
42
69
  const isInternalChange = useRef(false);
43
- const MAX_HISTORY = 50;
70
+ const useInlinePreview = shouldUseInlineEditorPreview();
71
+ const MAX_HISTORY = useInlinePreview ? 8 : 50;
44
72
  // Track last published data to disable publish button if no changes.
45
73
  // When initialDataIsPublished is explicitly false (a draft was loaded), start with null
46
74
  // so hasChangesSincePublish is true immediately — the draft IS an unpublished change.
@@ -175,7 +203,11 @@ export const PayazaFormEditor = ({ layoutId, initialData, inventory, onChange, o
175
203
  setHistory(prev => {
176
204
  const newHistory = prev.slice(0, historyIndex + 1);
177
205
  // Only add if data is different from the last entry
178
- if (JSON.stringify(newHistory[newHistory.length - 1]) !== JSON.stringify(data)) {
206
+ const lastEntry = newHistory[newHistory.length - 1];
207
+ const isDuplicate = useInlinePreview
208
+ ? lightweightConfigFingerprint(lastEntry) === lightweightConfigFingerprint(data)
209
+ : JSON.stringify(lastEntry) === JSON.stringify(data);
210
+ if (!isDuplicate) {
179
211
  const updatedHistory = [...newHistory, data];
180
212
  if (updatedHistory.length > MAX_HISTORY) {
181
213
  return updatedHistory.slice(1);
@@ -189,7 +221,7 @@ export const PayazaFormEditor = ({ layoutId, initialData, inventory, onChange, o
189
221
  setHistoryIndex(prev => prev + 1);
190
222
  }
191
223
  isInternalChange.current = false;
192
- }, [data]);
224
+ }, [data, historyIndex, MAX_HISTORY, useInlinePreview]);
193
225
  // Fix the history index after history update
194
226
  useEffect(() => {
195
227
  if (history.length > 0 && historyIndex >= history.length) {
@@ -947,6 +979,76 @@ export const PayazaFormEditor = ({ layoutId, initialData, inventory, onChange, o
947
979
  // but it helps prevent state updates if the user closes the modal or navigates away.
948
980
  // In a real scenario, we might want to use AbortController.
949
981
  };
982
+ const previewEditorContext = useMemo(() => ({
983
+ layoutId: normalizedLayoutType ?? String(layoutId),
984
+ activePageId,
985
+ }), [normalizedLayoutType, layoutId, activePageId]);
986
+ const handlePreviewRouteChange = useCallback((route) => {
987
+ const editorLayoutId = normalizedLayoutType ?? String(layoutId);
988
+ const nextPageId = previewRouteToPageId(route, editorLayoutId);
989
+ if (nextPageId &&
990
+ nextPageId !== activePageId &&
991
+ isLayoutPageEditable(editorLayoutId, nextPageId)) {
992
+ setActivePageId(nextPageId);
993
+ setSelectedSection(null);
994
+ setCanvasElementFocus(null);
995
+ setActiveArrayFocus(null);
996
+ }
997
+ }, [normalizedLayoutType, layoutId, activePageId]);
998
+ const handlePreviewSectionSelected = useCallback((sectionKey) => {
999
+ setSelectedSection(sectionKey);
1000
+ requestAnimationFrame(() => {
1001
+ document
1002
+ .querySelector(`[data-section-key="${sectionKey}"]`)
1003
+ ?.scrollIntoView({ behavior: 'smooth', block: 'start' });
1004
+ });
1005
+ }, []);
1006
+ const handlePreviewFieldCommit = useCallback((payload) => {
1007
+ let value = payload.richText ?? payload.valueText;
1008
+ if (payload.elementKind === 'rating') {
1009
+ const parsed = Number(payload.valueText);
1010
+ value = Number.isFinite(parsed)
1011
+ ? Math.min(5, Math.max(1, Math.round(parsed)))
1012
+ : 5;
1013
+ }
1014
+ handleFieldChange(payload.sectionKey, payload.fieldPath, value);
1015
+ },
1016
+ // handleFieldChange is a stable-enough plain function defined above in this component.
1017
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1018
+ []);
1019
+ const handlePreviewElementFocus = useCallback((p) => {
1020
+ setCanvasElementFocus(p);
1021
+ setSelectedSection(p.sectionKey);
1022
+ setBlockSubTab('settings');
1023
+ if (p.fieldPath[0] === 'items' && /^\d+$/.test(p.fieldPath[1] ?? '')) {
1024
+ setActiveArrayFocus({
1025
+ sectionKey: p.sectionKey,
1026
+ fieldKey: 'items',
1027
+ index: parseInt(p.fieldPath[1], 10),
1028
+ });
1029
+ }
1030
+ else if (p.fieldPath[0] === 'sliders' && /^\d+$/.test(p.fieldPath[1] ?? '')) {
1031
+ setActiveArrayFocus({
1032
+ sectionKey: p.sectionKey,
1033
+ fieldKey: 'sliders',
1034
+ index: parseInt(p.fieldPath[1], 10),
1035
+ });
1036
+ }
1037
+ requestAnimationFrame(() => {
1038
+ document
1039
+ .querySelector(`[data-section-key="${p.sectionKey}"]`)
1040
+ ?.scrollIntoView({ behavior: 'smooth', block: 'start' });
1041
+ });
1042
+ }, []);
1043
+ const handlePreviewFocusSidebarSection = useCallback((sectionKey) => {
1044
+ setSelectedSection(sectionKey);
1045
+ setBlockSubTab('settings');
1046
+ requestAnimationFrame(() => {
1047
+ document
1048
+ .querySelector(`[data-section-key="${sectionKey}"]`)
1049
+ ?.scrollIntoView({ behavior: 'smooth', block: 'start' });
1050
+ });
1051
+ }, []);
950
1052
  if (loading) {
951
1053
  return (_jsxs("div", { className: "flex h-full flex-col overflow-hidden bg-[#F7F5F0] font-sans", children: [_jsxs("div", { className: "flex min-h-[72px] shrink-0 items-center justify-between border-b border-[#e6e9ee] bg-white/95 px-4 py-2 md:px-5", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsx("div", { className: "h-10 w-10 animate-pulse rounded-xl bg-slate-100" }), _jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "h-3 w-28 animate-pulse rounded bg-slate-100" }), _jsx("div", { className: "h-2 w-20 animate-pulse rounded bg-slate-100" })] })] }), _jsxs("div", { className: "flex gap-3", children: [_jsx("div", { className: "h-10 w-24 animate-pulse rounded-xl bg-slate-100" }), _jsx("div", { className: "h-10 w-28 animate-pulse rounded-xl bg-slate-100" })] })] }), _jsxs("div", { className: "flex min-h-0 flex-1", children: [_jsx("div", { className: "flex flex-1 items-center justify-center bg-[#e2e4e7] p-8", children: _jsx("div", { className: "h-full min-h-[200px] w-full max-w-4xl animate-pulse rounded-lg bg-white/60 shadow-inner ring-1 ring-[#e8e8e8]" }) }), _jsxs("div", { className: "w-[380px] space-y-8 border-l border-[#dcdcde] bg-white p-6", children: [_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "w-32 h-4 bg-slate-50 animate-pulse rounded" }), _jsx("div", { className: "w-full h-12 bg-slate-50 animate-pulse rounded-xl" })] }), _jsx("div", { className: "space-y-6", children: [1, 2, 3, 4].map(i => (_jsx("div", { className: "w-full h-20 bg-slate-50 animate-pulse rounded-2xl" }, i))) })] })] })] }));
952
1054
  }
@@ -963,68 +1065,7 @@ export const PayazaFormEditor = ({ layoutId, initialData, inventory, onChange, o
963
1065
  handleFieldChange(canvasElementFocus.sectionKey, [...canvasElementFocus.fieldPath, 'attributes', 'payazaStyle'], next);
964
1066
  }
965
1067
  } })) : (_jsxs(_Fragment, { children: [granularInspectorActive && (_jsx("div", { className: "border-b border-[#e8e9ec] bg-[#fafafa] px-4 py-2.5", children: _jsxs("p", { className: "text-[11px] text-[#787c82]", children: ["Styles apply to the ", _jsx("strong", { className: "font-medium text-[#1b0029]", children: selectedSectionLabel ?? 'section' }), " containing this element."] }) })), _jsx(BlockStylesInspector, { supports: selectedSectionStyleSupports, value: selectedBlockStyleEntries, onChange: (next) => selectedSection && handleBlockStylesChange(selectedSection, next) })] }))) : canvasElementFocus?.elementKind === 'group' ? (_jsxs("div", { className: "px-4 py-6", children: [_jsxs("div", { className: "mb-4 flex items-center gap-2", children: [_jsx(Layers, { className: "h-4 w-4 shrink-0 text-[#440066]", strokeWidth: 2, "aria-hidden": true }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "text-[13px] font-semibold text-[#1b0029]", children: "Group" }), _jsx("p", { className: "truncate text-[11px] text-[#787c82]", children: canvasElementFocus.fieldPath.join(' › ') })] })] }), _jsxs("p", { className: "text-[12px] text-[#646970]", children: ["Switch to the ", _jsx("strong", { className: "font-medium text-[#1b0029]", children: "Styles" }), " tab to edit group layout, spacing, and colors."] })] })) : granularInspectorActive && canvasElementFocus && layoutJson && pageSchema ? (_jsxs(_Fragment, { children: [granularBreadcrumbLabel ? (_jsxs("div", { className: "border-b border-[#e8e9ec] bg-[#fafafa] px-4 py-2.5", children: [_jsx("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-[#1b0029]/40", children: "Editing" }), _jsx("p", { className: "mt-0.5 truncate text-[13px] font-semibold text-[#1b0029]", children: granularBreadcrumbLabel })] })) : null, _jsx(CanvasGranularInspector, { layoutJson: layoutJson, activePageId: activePageId, sectionKey: canvasElementFocus.sectionKey, fieldPath: canvasElementFocus.fieldPath, elementKind: canvasElementFocus.elementKind, pageSchemaRecord: pageSchema.schema, sectionData: pageSchema.data[canvasElementFocus.sectionKey], onChange: (sk, fp, val) => handleFieldChange(sk, fp, val) })] })) : selectedSection && pageSchema?.schema[selectedSection] ? (_jsx(SectionSchemaInspector, { sectionKey: selectedSection, sectionSchema: pageSchema.schema[selectedSection], sectionData: pageSchema.data[selectedSection], onFieldChange: (sk, fp, val) => handleFieldChange(sk, fp, val) })) : (_jsx("div", { className: "flex flex-col items-center justify-center px-5 py-12 text-center", children: _jsx("p", { className: "text-[13px] text-[#646970]", children: "Select a section in the List View or click the canvas to begin editing." }) })) })] }));
966
- return (_jsx(EditorLayoutProvider, { value: { layoutId: normalizedLayoutType ?? String(layoutId) }, children: _jsxs(AssetSelectionProvider, { onOpenAssets: onOpenAssets, assets: assets, children: [_jsx(IframePreviewMessageBridge, { editorContext: {
967
- layoutId: normalizedLayoutType ?? String(layoutId),
968
- activePageId,
969
- }, onRouteChange: (route) => {
970
- const editorLayoutId = normalizedLayoutType ?? String(layoutId);
971
- const nextPageId = previewRouteToPageId(route, editorLayoutId);
972
- if (nextPageId &&
973
- nextPageId !== activePageId &&
974
- isLayoutPageEditable(editorLayoutId, nextPageId)) {
975
- setActivePageId(nextPageId);
976
- setSelectedSection(null);
977
- setCanvasElementFocus(null);
978
- setActiveArrayFocus(null);
979
- }
980
- }, onSectionSelected: (sectionKey) => {
981
- setSelectedSection(sectionKey);
982
- requestAnimationFrame(() => {
983
- document
984
- .querySelector(`[data-section-key="${sectionKey}"]`)
985
- ?.scrollIntoView({ behavior: 'smooth', block: 'start' });
986
- });
987
- }, onFieldCommit: (payload) => {
988
- let value = payload.richText ?? payload.valueText;
989
- if (payload.elementKind === 'rating') {
990
- const parsed = Number(payload.valueText);
991
- value = Number.isFinite(parsed)
992
- ? Math.min(5, Math.max(1, Math.round(parsed)))
993
- : 5;
994
- }
995
- handleFieldChange(payload.sectionKey, payload.fieldPath, value);
996
- }, onSectionReorder: handleReorderSection, onPreviewElementFocus: (p) => {
997
- setCanvasElementFocus(p);
998
- setSelectedSection(p.sectionKey);
999
- setBlockSubTab('settings');
1000
- if (p.fieldPath[0] === 'items' && /^\d+$/.test(p.fieldPath[1] ?? '')) {
1001
- setActiveArrayFocus({
1002
- sectionKey: p.sectionKey,
1003
- fieldKey: 'items',
1004
- index: parseInt(p.fieldPath[1], 10),
1005
- });
1006
- }
1007
- else if (p.fieldPath[0] === 'sliders' && /^\d+$/.test(p.fieldPath[1] ?? '')) {
1008
- setActiveArrayFocus({
1009
- sectionKey: p.sectionKey,
1010
- fieldKey: 'sliders',
1011
- index: parseInt(p.fieldPath[1], 10),
1012
- });
1013
- }
1014
- requestAnimationFrame(() => {
1015
- document
1016
- .querySelector(`[data-section-key="${p.sectionKey}"]`)
1017
- ?.scrollIntoView({ behavior: 'smooth', block: 'start' });
1018
- });
1019
- }, onFocusSidebarSection: (sectionKey) => {
1020
- setSelectedSection(sectionKey);
1021
- setBlockSubTab('settings');
1022
- requestAnimationFrame(() => {
1023
- document
1024
- .querySelector(`[data-section-key="${sectionKey}"]`)
1025
- ?.scrollIntoView({ behavior: 'smooth', block: 'start' });
1026
- });
1027
- } }), _jsx(ActiveArrayFocusProvider, { sectionKey: selectedSection, focus: activeArrayFocus, onFocusChange: handleActiveArrayFocusChange, onBeforeIndexChange: flushPreviewEdits, children: _jsxs("div", { className: `flex h-full min-h-0 flex-col bg-[#F7F5F0] ${className} font-sans antialiased text-[#1b0029]`, children: [_jsx(EditorHeader, { layoutName: layoutJson.layoutName || layoutId, storeName: storeName, storeLogo: initialData?.branding?.logo, activePage: activePageId, pages: pages, onPageSwitch: setActivePageId, showPageSwitcher: false, showPageContextCard: false, currentPageLabel: currentPageLabel, onSave: handleSave, onPublish: onPublish && hasChangesSincePublish ? handlePublish : undefined, onBack: onBack, onBackToSettings: onBackToSettings, onOpenAssets: onOpenAssets, onBackToTheme: onBackToTheme, onUndo: handleUndo, onRedo: handleRedo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1, viewMode: viewMode, onViewModeChange: setViewMode, previewZoom: previewZoom, onPreviewZoomChange: setPreviewZoom, editorType: "payaza-form", isSaving: isSaving, isPublishing: isPublishing, onGenerateAI: () => setIsAIModalOpen(true), isGeneratingAI: isGeneratingAI, publishButtonText: publishButtonText, centerTemplateLabel: centerTemplateLabel, outlinePanelOpen: isMobileView ? mobilePanelView === 'outline' : outlinePanelOpen, onToggleOutlinePanel: handleToggleOutlinePanel, interactionMode: interactionMode, onInteractionModeChange: setInteractionMode, isMobileChrome: isMobileView }), _jsx(AIGenerateModal, { isOpen: isAIModalOpen, onClose: () => setIsAIModalOpen(false), onGenerate: handleAIGenerate, initialContext: {
1068
+ return (_jsx(EditorLayoutProvider, { value: { layoutId: normalizedLayoutType ?? String(layoutId) }, children: _jsxs(AssetSelectionProvider, { onOpenAssets: onOpenAssets, assets: assets, children: [!useInlinePreview ? (_jsx(IframePreviewMessageBridge, { editorContext: previewEditorContext, onRouteChange: handlePreviewRouteChange, onSectionSelected: handlePreviewSectionSelected, onFieldCommit: handlePreviewFieldCommit, onSectionReorder: handleReorderSection, onPreviewElementFocus: handlePreviewElementFocus, onFocusSidebarSection: handlePreviewFocusSidebarSection })) : null, _jsx(ActiveArrayFocusProvider, { sectionKey: selectedSection, focus: activeArrayFocus, onFocusChange: handleActiveArrayFocusChange, onBeforeIndexChange: flushPreviewEdits, children: _jsxs("div", { className: `flex h-full min-h-0 flex-col bg-[#F7F5F0] ${className} font-sans antialiased text-[#1b0029]`, children: [_jsx(EditorHeader, { layoutName: layoutJson.layoutName || layoutId, storeName: storeName, storeLogo: initialData?.branding?.logo, activePage: activePageId, pages: pages, onPageSwitch: setActivePageId, showPageSwitcher: false, showPageContextCard: false, currentPageLabel: currentPageLabel, onSave: handleSave, onPublish: onPublish && hasChangesSincePublish ? handlePublish : undefined, onBack: onBack, onBackToSettings: onBackToSettings, onOpenAssets: onOpenAssets, onBackToTheme: onBackToTheme, onUndo: handleUndo, onRedo: handleRedo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1, viewMode: viewMode, onViewModeChange: setViewMode, previewZoom: previewZoom, onPreviewZoomChange: setPreviewZoom, editorType: "payaza-form", isSaving: isSaving, isPublishing: isPublishing, onGenerateAI: () => setIsAIModalOpen(true), isGeneratingAI: isGeneratingAI, publishButtonText: publishButtonText, centerTemplateLabel: centerTemplateLabel, outlinePanelOpen: isMobileView ? mobilePanelView === 'outline' : outlinePanelOpen, onToggleOutlinePanel: handleToggleOutlinePanel, interactionMode: interactionMode, onInteractionModeChange: setInteractionMode, isMobileChrome: isMobileView }), _jsx(AIGenerateModal, { isOpen: isAIModalOpen, onClose: () => setIsAIModalOpen(false), onGenerate: handleAIGenerate, initialContext: {
1028
1069
  storeName: storeName || data?.name || '',
1029
1070
  storeDescription: storeDescription || data?.description || '',
1030
1071
  assets: assets || [],
@@ -1070,7 +1111,7 @@ export const PayazaFormEditor = ({ layoutId, initialData, inventory, onChange, o
1070
1111
  ? 'bg-white text-[#440066] shadow-sm'
1071
1112
  : 'text-[#5b6470]'}`, children: [_jsx(Pencil, { className: "h-3 w-3", strokeWidth: 2 }), "Edit"] }), _jsxs("button", { type: "button", onClick: () => setInteractionMode('preview'), "aria-pressed": interactionMode === 'preview', "aria-label": "Preview mode", className: `flex h-7 items-center gap-1 rounded-md px-2.5 text-[11px] font-medium transition-colors ${interactionMode === 'preview'
1072
1113
  ? 'bg-white text-[#440066] shadow-sm'
1073
- : 'text-[#5b6470]'}`, children: [_jsx(Eye, { className: "h-3 w-3", strokeWidth: 2 }), "Preview"] })] }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Smartphone, { className: "h-3.5 w-3.5 text-[#440066]", strokeWidth: 2, "aria-hidden": true }), _jsx("span", { className: "rounded-full bg-[#f3f0f7] px-2.5 py-0.5 text-[10px] font-semibold text-[#440066]", children: "Mobile" })] })] })), _jsxs("div", { className: "relative min-h-0 flex-1 overflow-hidden px-1", children: [_jsx(IframePreview, { layoutId: layoutId, config: data, activePageId: activePageId, viewMode: isMobileView ? 'desktop' : viewMode, previewZoom: previewZoom, selectedSection: selectedSection, focusedField: canvasElementFocus, interactionMode: interactionMode, slideIndexSync: slideIndexSync, flushRequestId: flushRequestId }), selectedSectionLabel && interactionMode === 'edit' && (_jsx("div", { className: "pointer-events-none absolute bottom-4 left-1/2 z-30 max-w-[min(100%-2rem,360px)] -translate-x-1/2", children: _jsxs("div", { className: "pointer-events-auto flex items-center gap-2 rounded-sm border border-[#dcdcde] bg-white px-2.5 py-1.5 shadow-sm", children: [_jsx(Box, { className: "h-3.5 w-3.5 shrink-0 text-[#1b0029]/45", strokeWidth: 2, "aria-hidden": true }), _jsx("span", { className: "truncate text-[12px] text-[#1b0029]", children: selectedSectionLabel })] }) }))] }), !isMobileView && (_jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-x-1 gap-y-1 border-t border-[#dcdcde] bg-[#eef0f3] px-3 py-2.5 text-[12px]", style: { contain: 'layout style' }, "aria-label": "Editing context", children: [_jsx("span", { className: "rounded-full border border-[#dcdcde] bg-white px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-[#646970]", children: "Editing" }), _jsx("span", { className: "truncate font-medium text-[#1b0029]", children: currentPageLabel }), selectedSectionLabel && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-[#1b0029]/25", "aria-hidden": true, children: "/" }), _jsx("span", { className: "max-w-[min(60vw,280px)] truncate text-[#1b0029]/72", children: selectedSectionLabel })] })), activeSlideBreadcrumbLabel && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-[#1b0029]/25", "aria-hidden": true, children: "/" }), _jsx("span", { className: "max-w-[min(40vw,160px)] truncate font-medium text-[#440066]", children: activeSlideBreadcrumbLabel })] })), canvasElementFocus?.fieldPath?.length ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-[#1b0029]/25", "aria-hidden": true, children: "/" }), _jsx("span", { className: "max-w-[min(72vw,360px)] truncate font-medium text-[#440066]", title: granularBreadcrumbLabel ?? canvasElementFocus.fieldPath.join(' › '), children: granularBreadcrumbLabel ?? canvasElementFocus.fieldPath.join(' › ') })] })) : null, canvasElementFocus?.elementKind && canvasElementFocus.elementKind !== 'section' ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-[#1b0029]/25", "aria-hidden": true, children: "/" }), _jsx("span", { className: "truncate text-[11px] text-[#1b0029]/45", children: canvasElementFocus.elementKind === 'richHtml'
1114
+ : 'text-[#5b6470]'}`, children: [_jsx(Eye, { className: "h-3 w-3", strokeWidth: 2 }), "Preview"] })] }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Smartphone, { className: "h-3.5 w-3.5 text-[#440066]", strokeWidth: 2, "aria-hidden": true }), _jsx("span", { className: "rounded-full bg-[#f3f0f7] px-2.5 py-0.5 text-[10px] font-semibold text-[#440066]", children: "Mobile" })] })] })), _jsxs("div", { className: "relative min-h-0 flex-1 overflow-hidden px-1", children: [_jsx(EditorCanvasPreview, { layoutId: layoutId, config: debouncedData, activePageId: activePageId, viewMode: viewMode, previewZoom: previewZoom, selectedSection: selectedSection, focusedField: canvasElementFocus, interactionMode: interactionMode, slideIndexSync: slideIndexSync, flushRequestId: flushRequestId, editorContext: previewEditorContext, onRouteChange: handlePreviewRouteChange, onSectionSelected: handlePreviewSectionSelected, onFieldCommit: handlePreviewFieldCommit, onSectionReorder: handleReorderSection, onPreviewElementFocus: handlePreviewElementFocus, onFocusSidebarSection: handlePreviewFocusSidebarSection }), selectedSectionLabel && interactionMode === 'edit' && (_jsx("div", { className: "pointer-events-none absolute bottom-4 left-1/2 z-30 max-w-[min(100%-2rem,360px)] -translate-x-1/2", children: _jsxs("div", { className: "pointer-events-auto flex items-center gap-2 rounded-sm border border-[#dcdcde] bg-white px-2.5 py-1.5 shadow-sm", children: [_jsx(Box, { className: "h-3.5 w-3.5 shrink-0 text-[#1b0029]/45", strokeWidth: 2, "aria-hidden": true }), _jsx("span", { className: "truncate text-[12px] text-[#1b0029]", children: selectedSectionLabel })] }) }))] }), !isMobileView && (_jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-x-1 gap-y-1 border-t border-[#dcdcde] bg-[#eef0f3] px-3 py-2.5 text-[12px]", style: { contain: 'layout style' }, "aria-label": "Editing context", children: [_jsx("span", { className: "rounded-full border border-[#dcdcde] bg-white px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-[#646970]", children: "Editing" }), _jsx("span", { className: "truncate font-medium text-[#1b0029]", children: currentPageLabel }), selectedSectionLabel && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-[#1b0029]/25", "aria-hidden": true, children: "/" }), _jsx("span", { className: "max-w-[min(60vw,280px)] truncate text-[#1b0029]/72", children: selectedSectionLabel })] })), activeSlideBreadcrumbLabel && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-[#1b0029]/25", "aria-hidden": true, children: "/" }), _jsx("span", { className: "max-w-[min(40vw,160px)] truncate font-medium text-[#440066]", children: activeSlideBreadcrumbLabel })] })), canvasElementFocus?.fieldPath?.length ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-[#1b0029]/25", "aria-hidden": true, children: "/" }), _jsx("span", { className: "max-w-[min(72vw,360px)] truncate font-medium text-[#440066]", title: granularBreadcrumbLabel ?? canvasElementFocus.fieldPath.join(' › '), children: granularBreadcrumbLabel ?? canvasElementFocus.fieldPath.join(' › ') })] })) : null, canvasElementFocus?.elementKind && canvasElementFocus.elementKind !== 'section' ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-[#1b0029]/25", "aria-hidden": true, children: "/" }), _jsx("span", { className: "truncate text-[11px] text-[#1b0029]/45", children: canvasElementFocus.elementKind === 'richHtml'
1074
1115
  ? 'Rich text'
1075
1116
  : canvasElementFocus.elementKind === 'image'
1076
1117
  ? 'Image'
@@ -0,0 +1,18 @@
1
+ import { type PreviewElementFocusPayload } from '../../preview/canvas/preview-messages';
2
+ import type { PayazaInlineFieldCommitPayload } from './IframePreviewMessageBridge';
3
+ export interface PreviewMessageHandlerContext {
4
+ editorContext: {
5
+ layoutId: string;
6
+ activePageId: string;
7
+ };
8
+ onSectionSelected: (sectionKey: string) => void;
9
+ onFieldCommit: (payload: PayazaInlineFieldCommitPayload) => void;
10
+ onSectionReorder: (sectionKey: string, direction: 'up' | 'down') => void;
11
+ onFocusSidebarSection: (sectionKey: string) => void;
12
+ onPreviewElementFocus?: (payload: Omit<PreviewElementFocusPayload, 'type'>) => void;
13
+ onRouteChange?: (route: string) => void;
14
+ onOpenAssetSelect?: (callback: (url: string) => void) => void;
15
+ }
16
+ /** Shared handler for iframe postMessage and inline preview events. */
17
+ export declare function processPreviewMessage(d: Record<string, unknown>, ctx: PreviewMessageHandlerContext): void;
18
+ //# sourceMappingURL=preview-message-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-message-handler.d.ts","sourceRoot":"","sources":["../../../src/editor/payaza-form/preview-message-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,0BAA0B,EAEhC,MAAM,uCAAuC,CAAC;AAE/C,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,8BAA8B,CAAC;AAEnF,MAAM,WAAW,4BAA4B;IAC3C,aAAa,EAAE;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,aAAa,EAAE,CAAC,OAAO,EAAE,8BAA8B,KAAK,IAAI,CAAC;IACjE,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC;IACzE,qBAAqB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,0BAA0B,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IACpF,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,KAAK,IAAI,CAAC;CAC/D;AAED,uEAAuE;AACvE,wBAAgB,qBAAqB,CACnC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1B,GAAG,EAAE,4BAA4B,GAChC,IAAI,CA0GN"}
@@ -0,0 +1,84 @@
1
+ import { PREVIEW_MSG, } from '../../preview/canvas/preview-messages.js';
2
+ import { previewCommitsMatchEditorContext } from '../../preview/canvas/preview-isolation.js';
3
+ /** Shared handler for iframe postMessage and inline preview events. */
4
+ export function processPreviewMessage(d, ctx) {
5
+ if (d.type === 'SECTION_SELECTED') {
6
+ const sectionKey = d.section || d.sectionKey;
7
+ if (typeof sectionKey === 'string' && sectionKey) {
8
+ ctx.onSectionSelected(sectionKey);
9
+ }
10
+ return;
11
+ }
12
+ if (d.type === PREVIEW_MSG.ELEMENT_FOCUS) {
13
+ const payload = d;
14
+ const ok = previewCommitsMatchEditorContext({ layoutId: payload.layoutId, pageId: payload.pageId }, { layoutId: ctx.editorContext.layoutId, pageId: ctx.editorContext.activePageId });
15
+ if (!ok)
16
+ return;
17
+ ctx.onPreviewElementFocus?.({
18
+ layoutId: payload.layoutId,
19
+ pageId: payload.pageId,
20
+ sectionKey: payload.sectionKey,
21
+ fieldPath: payload.fieldPath,
22
+ elementKind: payload.elementKind,
23
+ });
24
+ return;
25
+ }
26
+ if (d.type === PREVIEW_MSG.FIELD_COMMIT) {
27
+ const payload = d;
28
+ const ok = previewCommitsMatchEditorContext({ layoutId: payload.layoutId, pageId: payload.pageId }, { layoutId: ctx.editorContext.layoutId, pageId: ctx.editorContext.activePageId });
29
+ if (!ok)
30
+ return;
31
+ ctx.onFieldCommit({
32
+ sectionKey: payload.sectionKey,
33
+ fieldPath: payload.fieldPath,
34
+ valueText: String(payload.value ?? ''),
35
+ richText: payload.richText,
36
+ elementKind: payload.elementKind,
37
+ });
38
+ return;
39
+ }
40
+ if (d.type === PREVIEW_MSG.SECTION_REORDER) {
41
+ const { sectionKey, direction, layoutId, pageId } = d;
42
+ const ok = layoutId &&
43
+ pageId &&
44
+ previewCommitsMatchEditorContext({ layoutId, pageId }, { layoutId: ctx.editorContext.layoutId, pageId: ctx.editorContext.activePageId });
45
+ if (!ok)
46
+ return;
47
+ if (sectionKey && (direction === 'up' || direction === 'down')) {
48
+ ctx.onSectionReorder(sectionKey, direction);
49
+ }
50
+ return;
51
+ }
52
+ if (d.type === PREVIEW_MSG.IMAGE_ACTIVATE) {
53
+ const { sectionKey, fieldPath, layoutId, pageId } = d;
54
+ const ok = layoutId &&
55
+ pageId &&
56
+ previewCommitsMatchEditorContext({ layoutId, pageId }, { layoutId: ctx.editorContext.layoutId, pageId: ctx.editorContext.activePageId });
57
+ if (!ok)
58
+ return;
59
+ if (sectionKey && Array.isArray(fieldPath)) {
60
+ ctx.onOpenAssetSelect?.((url) => {
61
+ ctx.onFieldCommit({
62
+ sectionKey,
63
+ fieldPath,
64
+ valueText: url,
65
+ elementKind: 'image',
66
+ });
67
+ });
68
+ }
69
+ return;
70
+ }
71
+ if (d.type === 'PREVIEW_FOCUS_SIDEBAR') {
72
+ const sk = d.sectionKey;
73
+ if (typeof sk === 'string' && sk) {
74
+ ctx.onFocusSidebarSection(sk);
75
+ }
76
+ return;
77
+ }
78
+ if (d.type === 'ROUTE_CHANGED') {
79
+ const route = d.route;
80
+ if (typeof route === 'string' && route) {
81
+ ctx.onRouteChange?.(route);
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import { StoreConfig } from '../../lib/store-types';
3
+ import type { InteractionMode } from '../document/types';
4
+ import { type PreviewZoom } from './preview-viewport';
5
+ interface InlineCanvasPreviewProps {
6
+ layoutId: string;
7
+ config: StoreConfig;
8
+ activePageId: string;
9
+ viewMode: 'desktop' | 'tablet' | 'mobile';
10
+ selectedSection?: string | null;
11
+ focusedField?: {
12
+ sectionKey: string;
13
+ fieldPath: string[];
14
+ elementKind: string;
15
+ } | null;
16
+ interactionMode?: InteractionMode;
17
+ slideIndexSync?: {
18
+ sectionKey: string;
19
+ arrayField: string;
20
+ index: number;
21
+ } | null;
22
+ flushRequestId?: number;
23
+ previewZoom?: PreviewZoom;
24
+ desktopCanvasWidth?: number;
25
+ className?: string;
26
+ onPreviewEvent?: (payload: object) => void;
27
+ onReady?: () => void;
28
+ }
29
+ /**
30
+ * Same-document layout preview for mobile editors — avoids iframe memory overhead on WebKit.
31
+ */
32
+ export declare const InlineCanvasPreview: React.FC<InlineCanvasPreviewProps>;
33
+ export {};
34
+ //# sourceMappingURL=InlineCanvasPreview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InlineCanvasPreview.d.ts","sourceRoot":"","sources":["../../../src/editor/shared/InlineCanvasPreview.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA+C,MAAM,OAAO,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAOpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAEL,KAAK,WAAW,EAEjB,MAAM,oBAAoB,CAAC;AAE5B,UAAU,wBAAwB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC1C,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,YAAY,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACvF,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,cAAc,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAkNlE,CAAC"}
@@ -0,0 +1,145 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useRef, useState } from 'react';
4
+ import { LayoutPreview } from '../../preview/LayoutPreview.js';
5
+ import { PreviewCanvasEnhancer } from '../../preview/canvas/PreviewCanvasEnhancer.js';
6
+ import { PreviewCanvasStyles } from '../../preview/canvas/PreviewCanvasStyles.js';
7
+ import { previewPageIdToRoute } from '../../preview/canvas/preview-route.js';
8
+ import { PREVIEW_MSG } from '../../preview/canvas/preview-messages.js';
9
+ import { normalizeEditorLayoutId } from '../document/layout-id.js';
10
+ import { DESKTOP_PREVIEW_WIDTH, resolvePreviewScale, } from './preview-viewport.js';
11
+ /**
12
+ * Same-document layout preview for mobile editors — avoids iframe memory overhead on WebKit.
13
+ */
14
+ export const InlineCanvasPreview = ({ layoutId, config, activePageId, viewMode, selectedSection, focusedField = null, interactionMode = 'edit', slideIndexSync = null, flushRequestId = 0, previewZoom = 'fit', desktopCanvasWidth = DESKTOP_PREVIEW_WIDTH, className = '', onPreviewEvent, onReady, }) => {
15
+ const viewportContainerRef = useRef(null);
16
+ const [containerWidth, setContainerWidth] = useState(0);
17
+ const [currentRoute, setCurrentRoute] = useState('/');
18
+ const readyFiredRef = useRef(false);
19
+ const canonicalLayoutId = useMemo(() => normalizeEditorLayoutId(String(layoutId)) ?? String(layoutId), [layoutId]);
20
+ const route = useMemo(() => previewPageIdToRoute(layoutId, activePageId, config), [layoutId, activePageId, config]);
21
+ useEffect(() => {
22
+ setCurrentRoute(route);
23
+ }, [route]);
24
+ const previewContext = useMemo(() => ({
25
+ layoutId: canonicalLayoutId,
26
+ pageId: activePageId,
27
+ }), [canonicalLayoutId, activePageId]);
28
+ const previewData = useMemo(() => {
29
+ const base = {
30
+ ...config,
31
+ isPreview: true,
32
+ headerConfig: {
33
+ ...config.headerConfig,
34
+ show: config.headerConfig?.show ?? true,
35
+ },
36
+ };
37
+ if (typeof window !== 'undefined') {
38
+ window.__IS_PREVIEW__ = true;
39
+ }
40
+ return base;
41
+ }, [config]);
42
+ useEffect(() => {
43
+ if (readyFiredRef.current)
44
+ return;
45
+ readyFiredRef.current = true;
46
+ onReady?.();
47
+ }, [onReady]);
48
+ const lastPostedFocusRef = useRef(null);
49
+ useEffect(() => {
50
+ const key = focusedField
51
+ ? `${focusedField.sectionKey}|${focusedField.fieldPath.join('|')}|${focusedField.elementKind}`
52
+ : null;
53
+ if (lastPostedFocusRef.current === key)
54
+ return;
55
+ lastPostedFocusRef.current = key;
56
+ window.postMessage({ type: PREVIEW_MSG.FIELD_SELECTED_SYNC, field: focusedField }, '*');
57
+ }, [focusedField]);
58
+ useEffect(() => {
59
+ window.postMessage({ type: 'SECTION_SELECTED_SYNC', section: selectedSection ?? null }, '*');
60
+ }, [selectedSection]);
61
+ const lastPostedSlideSyncRef = useRef(null);
62
+ useEffect(() => {
63
+ const key = slideIndexSync
64
+ ? `${slideIndexSync.sectionKey}|${slideIndexSync.arrayField}|${slideIndexSync.index}`
65
+ : null;
66
+ if (lastPostedSlideSyncRef.current === key)
67
+ return;
68
+ lastPostedSlideSyncRef.current = key;
69
+ if (!slideIndexSync)
70
+ return;
71
+ const payload = {
72
+ type: PREVIEW_MSG.SLIDE_INDEX_SYNC,
73
+ sectionKey: slideIndexSync.sectionKey,
74
+ arrayField: slideIndexSync.arrayField,
75
+ index: slideIndexSync.index,
76
+ };
77
+ window.postMessage(payload, '*');
78
+ }, [slideIndexSync]);
79
+ const lastFlushRef = useRef(0);
80
+ useEffect(() => {
81
+ if (flushRequestId <= 0)
82
+ return;
83
+ if (lastFlushRef.current === flushRequestId)
84
+ return;
85
+ lastFlushRef.current = flushRequestId;
86
+ window.postMessage({ type: PREVIEW_MSG.FLUSH_EDITS }, '*');
87
+ }, [flushRequestId]);
88
+ useEffect(() => {
89
+ if (viewMode !== 'desktop')
90
+ return;
91
+ const el = viewportContainerRef.current;
92
+ if (!el)
93
+ return;
94
+ const updateWidth = () => setContainerWidth(el.clientWidth);
95
+ updateWidth();
96
+ const observer = new ResizeObserver(updateWidth);
97
+ observer.observe(el);
98
+ return () => observer.disconnect();
99
+ }, [viewMode]);
100
+ const desktopScale = useMemo(() => {
101
+ if (viewMode !== 'desktop')
102
+ return 1;
103
+ return resolvePreviewScale(previewZoom, containerWidth, desktopCanvasWidth);
104
+ }, [viewMode, previewZoom, containerWidth, desktopCanvasWidth]);
105
+ const scaledDesktopHeight = useMemo(() => {
106
+ if (viewMode !== 'desktop')
107
+ return 900;
108
+ const parent = viewportContainerRef.current?.parentElement;
109
+ const availableHeight = parent?.clientHeight ?? 0;
110
+ if (availableHeight <= 0)
111
+ return 900;
112
+ return Math.max(480, availableHeight / Math.max(desktopScale, 0.01));
113
+ }, [viewMode, desktopScale]);
114
+ const deviceDimensions = useMemo(() => {
115
+ switch (viewMode) {
116
+ case 'mobile':
117
+ return { width: 390, height: 844, rounded: 'rounded-[3.5rem]', border: 'border-[14px]' };
118
+ case 'tablet':
119
+ return { width: 768, height: 1024, rounded: 'rounded-2xl', border: 'border-[8px]' };
120
+ case 'desktop':
121
+ default:
122
+ return { width: '100%', height: '100%', rounded: '', border: '' };
123
+ }
124
+ }, [viewMode]);
125
+ const previewNode = (_jsx(PreviewCanvasEnhancer, { enabled: true, interactionMode: interactionMode, previewContext: previewContext, onPreviewEvent: onPreviewEvent, children: _jsx(LayoutPreview, { layout: layoutId, config: previewData, interactive: true, initialRoute: currentRoute, onRouteChange: (nextRoute) => {
126
+ setCurrentRoute(nextRoute);
127
+ onPreviewEvent?.({ type: 'ROUTE_CHANGED', route: nextRoute });
128
+ } }) }));
129
+ const chrome = (_jsxs(_Fragment, { children: [_jsx(PreviewCanvasStyles, {}), previewNode] }));
130
+ if (viewMode === 'desktop') {
131
+ return (_jsx("div", { ref: viewportContainerRef, className: `relative h-full w-full overflow-auto bg-[#e2e4e7] p-3 md:p-4 ${className}`, style: { WebkitOverflowScrolling: 'touch' }, children: _jsx("div", { className: "flex min-h-full justify-center py-1", children: _jsx("div", { className: "shrink-0 overflow-hidden rounded-lg bg-white shadow-[0_1px_2px_rgba(15,23,42,0.04),0_12px_32px_-8px_rgba(15,23,42,0.1)] transition-transform duration-200 ease-out", style: {
132
+ width: desktopCanvasWidth,
133
+ height: scaledDesktopHeight,
134
+ minHeight: 480,
135
+ transform: `scale(${desktopScale})`,
136
+ transformOrigin: 'top center',
137
+ }, children: _jsx("div", { className: "relative h-full w-full overflow-hidden rounded-lg", children: chrome }) }) }) }));
138
+ }
139
+ return (_jsx("div", { className: `relative flex h-full w-full items-start justify-center bg-[#dcdcde] p-2 ${className}`, children: _jsx("div", { className: `flex flex-col overflow-hidden bg-white shadow-2xl transition-all duration-500 ease-in-out ${deviceDimensions.rounded} ${deviceDimensions.border} border-[#c3c4c7]`, style: {
140
+ width: `${deviceDimensions.width}px`,
141
+ height: `${deviceDimensions.height}px`,
142
+ maxWidth: '100%',
143
+ maxHeight: '100%',
144
+ }, children: _jsx("div", { className: "relative min-h-0 flex-1 overflow-hidden bg-white", children: chrome }) }) }));
145
+ };
@@ -1,6 +1,7 @@
1
1
  export * from './EditorHeader';
2
2
  export * from './EditorNavigator';
3
3
  export * from './IframePreview';
4
+ export * from './InlineCanvasPreview';
4
5
  export * from './LayoutRenderer';
5
6
  export * from './PageTabs';
6
7
  export * from './ResizableSidebar';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/editor/shared/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/editor/shared/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC"}
@@ -1,6 +1,7 @@
1
1
  export * from './EditorHeader.js';
2
2
  export * from './EditorNavigator.js';
3
3
  export * from './IframePreview.js';
4
+ export * from './InlineCanvasPreview.js';
4
5
  export * from './LayoutRenderer.js';
5
6
  export * from './PageTabs.js';
6
7
  export * from './ResizableSidebar.js';
@@ -3,4 +3,6 @@
3
3
  * Real phones stay in mobile mode in landscape — viewport width alone is unreliable (e.g. iPhone ~844px wide).
4
4
  */
5
5
  export declare function isMobileEditorViewport(): boolean;
6
+ /** Inline same-document preview — avoids iframe double-runtime on mobile WebKit. */
7
+ export declare function shouldUseInlineEditorPreview(): boolean;
6
8
  //# sourceMappingURL=mobile-editor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-editor.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/mobile-editor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAUhD"}
1
+ {"version":3,"file":"mobile-editor.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/mobile-editor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAUhD;AAED,oFAAoF;AACpF,wBAAgB,4BAA4B,IAAI,OAAO,CAEtD"}
@@ -15,3 +15,7 @@ export function isMobileEditorViewport() {
15
15
  }
16
16
  return false;
17
17
  }
18
+ /** Inline same-document preview — avoids iframe double-runtime on mobile WebKit. */
19
+ export function shouldUseInlineEditorPreview() {
20
+ return isMobileEditorViewport();
21
+ }
@@ -5,6 +5,8 @@ export interface PreviewCanvasEnhancerProps {
5
5
  interactionMode: 'edit' | 'preview';
6
6
  children: React.ReactNode;
7
7
  previewContext?: PayazaPreviewContext | null;
8
+ /** When set (inline mobile preview), dispatch events directly instead of postMessage to parent. */
9
+ onPreviewEvent?: (payload: object) => void;
8
10
  }
9
- export declare function PreviewCanvasEnhancer({ enabled, interactionMode, children, previewContext, }: PreviewCanvasEnhancerProps): React.JSX.Element;
11
+ export declare function PreviewCanvasEnhancer({ enabled, interactionMode, children, previewContext, onPreviewEvent, }: PreviewCanvasEnhancerProps): React.JSX.Element;
10
12
  //# sourceMappingURL=PreviewCanvasEnhancer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PreviewCanvasEnhancer.d.ts","sourceRoot":"","sources":["../../../src/preview/canvas/PreviewCanvasEnhancer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAIxE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAShE,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,cAAc,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC9C;AAeD,wBAAgB,qBAAqB,CAAC,EACpC,OAAO,EACP,eAAe,EACf,QAAQ,EACR,cAAqB,GACtB,EAAE,0BAA0B,qBA2sB5B"}
1
+ {"version":3,"file":"PreviewCanvasEnhancer.d.ts","sourceRoot":"","sources":["../../../src/preview/canvas/PreviewCanvasEnhancer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAIxE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAShE,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,cAAc,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC7C,mGAAmG;IACnG,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAeD,wBAAgB,qBAAqB,CAAC,EACpC,OAAO,EACP,eAAe,EACf,QAAQ,EACR,cAAqB,EACrB,cAAc,GACf,EAAE,0BAA0B,qBAstB5B"}
@@ -18,7 +18,7 @@ function postToParent(payload) {
18
18
  window.parent.postMessage(payload, '*');
19
19
  }
20
20
  }
21
- export function PreviewCanvasEnhancer({ enabled, interactionMode, children, previewContext = null, }) {
21
+ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, previewContext = null, onPreviewEvent, }) {
22
22
  const [toolbarVisible, setToolbarVisible] = useState(false);
23
23
  const [toolbarRect, setToolbarRect] = useState(null);
24
24
  const [toolbarMode, setToolbarMode] = useState('text');
@@ -36,6 +36,14 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
36
36
  next;
37
37
  }
38
38
  }, [previewContext]);
39
+ const dispatchPreviewEvent = useCallback((payload) => {
40
+ if (onPreviewEvent) {
41
+ onPreviewEvent(payload);
42
+ }
43
+ else {
44
+ postToParent(payload);
45
+ }
46
+ }, [onPreviewEvent]);
39
47
  const resolveCtx = useCallback(() => {
40
48
  if (previewCtxRef.current?.layoutId && previewCtxRef.current.pageId)
41
49
  return previewCtxRef.current;
@@ -84,7 +92,7 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
84
92
  const ctx = resolveCtx();
85
93
  if (!ctx)
86
94
  return;
87
- postToParent({
95
+ dispatchPreviewEvent({
88
96
  type: PREVIEW_MSG.ELEMENT_FOCUS,
89
97
  layoutId: ctx.layoutId,
90
98
  pageId: ctx.pageId,
@@ -113,7 +121,7 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
113
121
  setToolbarEditableActive(false);
114
122
  if (rich) {
115
123
  const payload = buildPayazaRichCommitFromEditorHtml(inner, { keepRaw: true });
116
- postToParent({
124
+ dispatchPreviewEvent({
117
125
  type: PREVIEW_MSG.FIELD_COMMIT,
118
126
  layoutId: ctx.layoutId,
119
127
  pageId: ctx.pageId,
@@ -126,7 +134,7 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
126
134
  return;
127
135
  }
128
136
  const value = extractEditablePlainText(t);
129
- postToParent({
137
+ dispatchPreviewEvent({
130
138
  type: PREVIEW_MSG.FIELD_COMMIT,
131
139
  layoutId: ctx.layoutId,
132
140
  pageId: ctx.pageId,
@@ -213,12 +221,12 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
213
221
  const parsedIcon = iconKey ? parsePayazaFieldKey(iconKey) : null;
214
222
  if (parsedIcon && ctx) {
215
223
  postElementFocus(parsedIcon.sectionKey, parsedIcon.fieldPath, 'icon');
216
- postToParent({ type: 'SECTION_SELECTED', section: parsedIcon.sectionKey, sectionKey: parsedIcon.sectionKey });
224
+ dispatchPreviewEvent({ type: 'SECTION_SELECTED', section: parsedIcon.sectionKey, sectionKey: parsedIcon.sectionKey });
217
225
  }
218
226
  else {
219
227
  const iconSk = schemaSectionKeyFromElement(iconEl);
220
228
  if (iconSk)
221
- postToParent({ type: 'SECTION_SELECTED', section: iconSk, sectionKey: iconSk });
229
+ dispatchPreviewEvent({ type: 'SECTION_SELECTED', section: iconSk, sectionKey: iconSk });
222
230
  }
223
231
  return;
224
232
  }
@@ -240,7 +248,7 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
240
248
  const parsedRating = ratingKey ? parsePayazaFieldKey(ratingKey) : null;
241
249
  const ratingSk = schemaSectionKeyFromElement(ratingEl);
242
250
  if (ratingSk) {
243
- postToParent({ type: 'SECTION_SELECTED', section: ratingSk, sectionKey: ratingSk });
251
+ dispatchPreviewEvent({ type: 'SECTION_SELECTED', section: ratingSk, sectionKey: ratingSk });
244
252
  }
245
253
  if (parsedRating && ctx) {
246
254
  postElementFocus(parsedRating.sectionKey, parsedRating.fieldPath, 'rating');
@@ -248,7 +256,7 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
248
256
  ? Number(ratingStarEl.getAttribute('data-payaza-rating-star'))
249
257
  : NaN;
250
258
  if (Number.isFinite(starValue) && starValue >= 1 && starValue <= 5) {
251
- postToParent({
259
+ dispatchPreviewEvent({
252
260
  type: PREVIEW_MSG.FIELD_COMMIT,
253
261
  layoutId: ctx.layoutId,
254
262
  pageId: ctx.pageId,
@@ -283,7 +291,7 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
283
291
  setToolbarVisible(true);
284
292
  const imgSk = schemaSectionKeyFromElement(imgEl);
285
293
  if (imgSk)
286
- postToParent({ type: 'SECTION_SELECTED', section: imgSk, sectionKey: imgSk });
294
+ dispatchPreviewEvent({ type: 'SECTION_SELECTED', section: imgSk, sectionKey: imgSk });
287
295
  return;
288
296
  }
289
297
  if (textEl) {
@@ -303,7 +311,7 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
303
311
  const parsed = key ? parsePayazaFieldKey(key) : null;
304
312
  const sk = schemaSectionKeyFromElement(textEl);
305
313
  if (sk)
306
- postToParent({ type: 'SECTION_SELECTED', section: sk, sectionKey: sk });
314
+ dispatchPreviewEvent({ type: 'SECTION_SELECTED', section: sk, sectionKey: sk });
307
315
  if (parsed && ctx) {
308
316
  postElementFocus(parsed.sectionKey, parsed.fieldPath, richFmt ? 'richHtml' : 'plainText');
309
317
  }
@@ -330,12 +338,12 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
330
338
  const parsedGroup = groupKey ? parsePayazaFieldKey(groupKey) : null;
331
339
  if (parsedGroup && ctx) {
332
340
  postElementFocus(parsedGroup.sectionKey, parsedGroup.fieldPath, 'group');
333
- postToParent({ type: 'SECTION_SELECTED', section: parsedGroup.sectionKey, sectionKey: parsedGroup.sectionKey });
341
+ dispatchPreviewEvent({ type: 'SECTION_SELECTED', section: parsedGroup.sectionKey, sectionKey: parsedGroup.sectionKey });
334
342
  }
335
343
  else {
336
344
  const gs = schemaSectionKeyFromElement(groupEl);
337
345
  if (gs)
338
- postToParent({ type: 'SECTION_SELECTED', section: gs, sectionKey: gs });
346
+ dispatchPreviewEvent({ type: 'SECTION_SELECTED', section: gs, sectionKey: gs });
339
347
  }
340
348
  return;
341
349
  }
@@ -353,7 +361,7 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
353
361
  setToolbarVisible(true);
354
362
  const ss = schemaSectionKeyFromElement(sectionEl);
355
363
  if (ss) {
356
- postToParent({ type: 'SECTION_SELECTED', section: ss, sectionKey: ss });
364
+ dispatchPreviewEvent({ type: 'SECTION_SELECTED', section: ss, sectionKey: ss });
357
365
  if (ctx)
358
366
  postElementFocus(ss, [], 'section');
359
367
  }
@@ -454,7 +462,7 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
454
462
  const sk = schemaSectionKeyFromElement(selectedElRef.current);
455
463
  if (!sk)
456
464
  return;
457
- postToParent({
465
+ dispatchPreviewEvent({
458
466
  type: PREVIEW_MSG.SECTION_REORDER,
459
467
  layoutId: ctx.layoutId,
460
468
  pageId: ctx.pageId,
@@ -469,7 +477,7 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
469
477
  const sk = schemaSectionKeyFromElement(selectedElRef.current);
470
478
  if (!sk)
471
479
  return;
472
- postToParent({
480
+ dispatchPreviewEvent({
473
481
  type: PREVIEW_MSG.SECTION_REORDER,
474
482
  layoutId: ctx.layoutId,
475
483
  pageId: ctx.pageId,
@@ -482,7 +490,7 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
482
490
  if (!imageFieldRef.current || !ctx?.layoutId)
483
491
  return;
484
492
  const { sectionKey, fieldPath } = imageFieldRef.current;
485
- postToParent({
493
+ dispatchPreviewEvent({
486
494
  type: PREVIEW_MSG.IMAGE_ACTIVATE,
487
495
  layoutId: ctx.layoutId,
488
496
  pageId: ctx.pageId,
@@ -492,10 +500,10 @@ export function PreviewCanvasEnhancer({ enabled, interactionMode, children, prev
492
500
  }, [resolveCtx]);
493
501
  const onMore = useCallback(() => {
494
502
  const sk = schemaSectionKeyFromElement(selectedElRef.current);
495
- if (sk && typeof window !== 'undefined' && window.parent !== window) {
496
- window.parent.postMessage({ type: 'PREVIEW_FOCUS_SIDEBAR', sectionKey: sk }, '*');
503
+ if (sk) {
504
+ dispatchPreviewEvent({ type: 'PREVIEW_FOCUS_SIDEBAR', sectionKey: sk });
497
505
  }
498
- }, []);
506
+ }, [dispatchPreviewEvent]);
499
507
  // Sync canvas selection when List View clicks a section or block element.
500
508
  // Refs and setState setters are stable so empty deps is correct.
501
509
  useEffect(() => {
@@ -0,0 +1,4 @@
1
+ /** Section highlight styles shared by iframe preview and inline mobile canvas. */
2
+ export declare const PREVIEW_CANVAS_STYLES = "\n [data-section] {\n position: relative;\n cursor: pointer !important;\n }\n [data-section-hover=\"true\"]:not([data-section-selected=\"true\"])::after {\n content: '';\n position: absolute;\n inset: 0;\n border: 1px dashed rgba(68, 0, 102, 0.35);\n pointer-events: none;\n z-index: 50;\n background: rgba(68, 0, 102, 0.02);\n border-radius: 2px;\n }\n [data-section-selected=\"true\"]::after {\n content: '';\n position: absolute;\n inset: 0;\n border: 1px solid #3858e9;\n pointer-events: none;\n z-index: 51;\n background: rgba(56, 88, 233, 0.04);\n box-shadow: inset 0 0 0 1px rgba(56, 88, 233, 0.12);\n border-radius: 2px;\n }\n [data-section-selected=\"true\"]::before {\n content: attr(data-section);\n position: absolute;\n top: -24px;\n left: 0;\n background: #1e1e1e;\n color: #f0f0f0;\n font-size: 11px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 2px;\n text-transform: none;\n z-index: 52;\n max-width: 200px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n";
3
+ export declare function PreviewCanvasStyles(): import("react").JSX.Element;
4
+ //# sourceMappingURL=PreviewCanvasStyles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PreviewCanvasStyles.d.ts","sourceRoot":"","sources":["../../../src/preview/canvas/PreviewCanvasStyles.tsx"],"names":[],"mappings":"AAEA,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,0mCA4CjC,CAAC;AAEF,wBAAgB,mBAAmB,gCAElC"}
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /** Section highlight styles shared by iframe preview and inline mobile canvas. */
4
+ export const PREVIEW_CANVAS_STYLES = `
5
+ [data-section] {
6
+ position: relative;
7
+ cursor: pointer !important;
8
+ }
9
+ [data-section-hover="true"]:not([data-section-selected="true"])::after {
10
+ content: '';
11
+ position: absolute;
12
+ inset: 0;
13
+ border: 1px dashed rgba(68, 0, 102, 0.35);
14
+ pointer-events: none;
15
+ z-index: 50;
16
+ background: rgba(68, 0, 102, 0.02);
17
+ border-radius: 2px;
18
+ }
19
+ [data-section-selected="true"]::after {
20
+ content: '';
21
+ position: absolute;
22
+ inset: 0;
23
+ border: 1px solid #3858e9;
24
+ pointer-events: none;
25
+ z-index: 51;
26
+ background: rgba(56, 88, 233, 0.04);
27
+ box-shadow: inset 0 0 0 1px rgba(56, 88, 233, 0.12);
28
+ border-radius: 2px;
29
+ }
30
+ [data-section-selected="true"]::before {
31
+ content: attr(data-section);
32
+ position: absolute;
33
+ top: -24px;
34
+ left: 0;
35
+ background: #1e1e1e;
36
+ color: #f0f0f0;
37
+ font-size: 11px;
38
+ font-weight: 600;
39
+ padding: 2px 8px;
40
+ border-radius: 2px;
41
+ text-transform: none;
42
+ z-index: 52;
43
+ max-width: 200px;
44
+ overflow: hidden;
45
+ text-overflow: ellipsis;
46
+ white-space: nowrap;
47
+ }
48
+ `;
49
+ export function PreviewCanvasStyles() {
50
+ return _jsx("style", { dangerouslySetInnerHTML: { __html: PREVIEW_CANVAS_STYLES } });
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payaza-storefront-layouts",
3
- "version": "1.1.76",
3
+ "version": "1.1.78",
4
4
  "lts": true,
5
5
  "type": "module",
6
6
  "description": "Shared layout components for Storefront applications",