@zalify/storefront-kit 0.1.12 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commerce/disclosure-motion.d.ts +2 -0
- package/dist/commerce/disclosure-motion.js +69 -0
- package/dist/commerce/image-url.d.ts +2 -0
- package/dist/commerce/image-url.js +15 -0
- package/dist/commerce/index.d.ts +1 -0
- package/dist/commerce/index.js +1 -0
- package/dist/editor/bootstrap.d.ts +15 -0
- package/dist/editor/bootstrap.js +42 -0
- package/dist/editor/draft.d.ts +17 -0
- package/dist/editor/draft.js +32 -0
- package/dist/editor/frame.d.ts +2 -0
- package/dist/editor/frame.js +7 -4
- package/dist/react/adapter.d.ts +3 -0
- package/dist/react/adapter.js +1 -0
- package/dist/react/blocks/_slide.js +2 -1
- package/dist/react/blocks/_video-card.js +2 -1
- package/dist/react/blocks/image.js +2 -1
- package/dist/react/blocks/story.js +2 -1
- package/dist/react/blocks/video.js +2 -1
- package/dist/react/components/ProductCard.js +4 -3
- package/dist/react/components/StoreImage.d.ts +2 -0
- package/dist/react/components/StoreImage.js +6 -0
- package/dist/react/components/VideoModal.js +2 -1
- package/dist/react/engine/CssVariables.js +5 -4
- package/dist/react/engine/images.d.ts +1 -1
- package/dist/react/engine/images.js +2 -6
- package/dist/react/engine/render.js +3 -1
- package/dist/react/engine/store.d.ts +6 -0
- package/dist/react/engine/store.js +34 -3
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +1 -0
- package/dist/react/sections/article.js +2 -1
- package/dist/react/sections/custom-section.js +2 -1
- package/dist/react/sections/image-with-text.js +2 -1
- package/dist/react/sections/video-bubble.js +2 -1
- package/dist/react/useEditorTemplate.d.ts +15 -0
- package/dist/react/useEditorTemplate.js +114 -0
- package/dist/schemas/bridge.d.ts +1 -0
- package/package.json +1 -1
- package/src/commerce/disclosure-motion.ts +60 -0
- package/src/commerce/image-url.ts +11 -0
- package/src/commerce/index.ts +2 -0
- package/src/editor/bootstrap.ts +61 -0
- package/src/editor/draft.ts +54 -0
- package/src/editor/frame.ts +7 -3
- package/src/react/adapter.tsx +4 -0
- package/src/react/blocks/_slide.tsx +2 -1
- package/src/react/blocks/_video-card.tsx +3 -2
- package/src/react/blocks/image.tsx +2 -1
- package/src/react/blocks/story.tsx +2 -1
- package/src/react/blocks/video.tsx +2 -1
- package/src/react/components/ProductCard.tsx +6 -5
- package/src/react/components/StoreImage.tsx +6 -0
- package/src/react/components/VideoModal.tsx +3 -2
- package/src/react/engine/CssVariables.tsx +34 -10
- package/src/react/engine/images.tsx +2 -5
- package/src/react/engine/render.tsx +14 -1
- package/src/react/engine/store.ts +43 -4
- package/src/react/index.ts +2 -0
- package/src/react/sections/article.tsx +2 -1
- package/src/react/sections/custom-section.tsx +2 -1
- package/src/react/sections/image-with-text.tsx +2 -1
- package/src/react/sections/video-bubble.tsx +2 -1
- package/src/react/useEditorTemplate.ts +136 -0
- package/src/schemas/bridge.ts +1 -0
|
@@ -7,9 +7,15 @@
|
|
|
7
7
|
* Static token scales (spacing, type, radii) live in the app's
|
|
8
8
|
* critical.css (synced from the theme).
|
|
9
9
|
*/
|
|
10
|
-
import {useMemo} from 'react';
|
|
10
|
+
import {useMemo, useSyncExternalStore} from 'react';
|
|
11
11
|
import {colorSchemes, themeSettings} from './settings';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getThemeStoreVersion,
|
|
14
|
+
subscribePreview,
|
|
15
|
+
getPreviewVersion,
|
|
16
|
+
getServerPreviewVersion,
|
|
17
|
+
hasThemePreview,
|
|
18
|
+
} from './store';
|
|
13
19
|
import {
|
|
14
20
|
googleFontsHrefsFromSettings,
|
|
15
21
|
parseFontHandle,
|
|
@@ -35,7 +41,9 @@ function buildCss(): string {
|
|
|
35
41
|
const lines: string[] = [];
|
|
36
42
|
lines.push(':root {');
|
|
37
43
|
lines.push(` --font-body--family: ${bodyFamily};`);
|
|
38
|
-
lines.push(
|
|
44
|
+
lines.push(
|
|
45
|
+
` --font-body--style: ${bodyGoogle ? 'normal' : bodyFont.style};`,
|
|
46
|
+
);
|
|
39
47
|
lines.push(` --font-body--weight: ${bodyGoogle ? 400 : bodyFont.weight};`);
|
|
40
48
|
lines.push(` --font-heading--family: ${headingFamily};`);
|
|
41
49
|
if (monoGoogle) {
|
|
@@ -52,15 +60,23 @@ function buildCss(): string {
|
|
|
52
60
|
}
|
|
53
61
|
lines.push(` --page-width: ${s.max_page_width ?? '90rem'};`);
|
|
54
62
|
lines.push(` --page-margin: ${s.min_page_margin ?? 20}px;`);
|
|
55
|
-
lines.push(
|
|
56
|
-
|
|
63
|
+
lines.push(
|
|
64
|
+
` --style-border-radius-inputs: ${s.input_corner_radius ?? 0}px;`,
|
|
65
|
+
);
|
|
66
|
+
lines.push(
|
|
67
|
+
` --style-border-radius-buttons: ${s.button_corner_radius ?? 0}px;`,
|
|
68
|
+
);
|
|
57
69
|
lines.push(` --style-border-radius-cards: ${s.card_corner_radius ?? 0}px;`);
|
|
58
|
-
lines.push(
|
|
70
|
+
lines.push(
|
|
71
|
+
` --style-badge-background: ${s.card_badge_background ?? '#F9EDEF'};`,
|
|
72
|
+
);
|
|
59
73
|
lines.push(` --style-badge-text: ${s.card_badge_text ?? '#1A1A1A'};`);
|
|
60
74
|
lines.push(
|
|
61
75
|
` --style-badge-sale-background: ${s.card_badge_sale_background ?? '#F26B8A'};`,
|
|
62
76
|
);
|
|
63
|
-
lines.push(
|
|
77
|
+
lines.push(
|
|
78
|
+
` --style-badge-sale-text: ${s.card_badge_sale_text ?? '#FFFFFF'};`,
|
|
79
|
+
);
|
|
64
80
|
lines.push(
|
|
65
81
|
` --style-badge-1-background: ${s.badge_style_1_background ?? '#1A1A1A'};`,
|
|
66
82
|
);
|
|
@@ -137,6 +153,11 @@ export function CssVariables({
|
|
|
137
153
|
nonce,
|
|
138
154
|
fonts,
|
|
139
155
|
}: {nonce?: string; fonts?: ResolvedGoogleFontCss | null} = {}) {
|
|
156
|
+
useSyncExternalStore(
|
|
157
|
+
subscribePreview,
|
|
158
|
+
getPreviewVersion,
|
|
159
|
+
getServerPreviewVersion,
|
|
160
|
+
);
|
|
140
161
|
const themeVersion = getThemeStoreVersion();
|
|
141
162
|
const {css, links} = useMemo(
|
|
142
163
|
() => ({css: buildCss(), links: googleFontsHrefs()}),
|
|
@@ -147,7 +168,7 @@ export function CssVariables({
|
|
|
147
168
|
// inlined and the latin woff2 files preloaded, so the font downloads
|
|
148
169
|
// with the HTML and usually beats first paint — no async stylesheet,
|
|
149
170
|
// no FOUT, no request to fonts.googleapis.com at all.
|
|
150
|
-
if (fonts) {
|
|
171
|
+
if (fonts && !hasThemePreview()) {
|
|
151
172
|
return (
|
|
152
173
|
<>
|
|
153
174
|
<link
|
|
@@ -182,13 +203,16 @@ export function CssVariables({
|
|
|
182
203
|
{links.map((href) => (
|
|
183
204
|
<link
|
|
184
205
|
key={href}
|
|
185
|
-
rel=
|
|
206
|
+
rel={hasThemePreview() ? 'stylesheet' : 'preload'}
|
|
186
207
|
as="style"
|
|
187
208
|
href={href}
|
|
188
209
|
data-zfy-font-css=""
|
|
189
210
|
/>
|
|
190
211
|
))}
|
|
191
|
-
<script
|
|
212
|
+
<script
|
|
213
|
+
nonce={nonce}
|
|
214
|
+
dangerouslySetInnerHTML={{__html: FONT_CSS_LOADER}}
|
|
215
|
+
/>
|
|
192
216
|
<noscript>
|
|
193
217
|
{links.map((href) => (
|
|
194
218
|
<link key={href} rel="stylesheet" href={href} />
|
|
@@ -18,11 +18,8 @@ export function settingImageUrl(value: unknown): string | null {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/** Append a width to a Shopify CDN URL (mirror of `| image_url: width`). */
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
const separator = url.includes('?') ? '&' : '?';
|
|
24
|
-
return `${url}${separator}width=${width}`;
|
|
25
|
-
}
|
|
21
|
+
export {imageUrl} from '../../commerce/image-url';
|
|
22
|
+
import {imageUrl} from '../../commerce/image-url';
|
|
26
23
|
|
|
27
24
|
/** Srcset across the theme's standard width ladder. */
|
|
28
25
|
export function imageSrcSet(
|
|
@@ -15,7 +15,15 @@
|
|
|
15
15
|
* emitted unconditionally so server and client markup stay identical.
|
|
16
16
|
*/
|
|
17
17
|
import {formatThemePath, DATA_PATH_ATTR} from '../../schemas/index.ts';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
getSectionGroup,
|
|
20
|
+
getTemplate,
|
|
21
|
+
getThemeStore,
|
|
22
|
+
subscribePreview,
|
|
23
|
+
getPreviewVersion,
|
|
24
|
+
getServerPreviewVersion,
|
|
25
|
+
} from './store';
|
|
26
|
+
import {useSyncExternalStore} from 'react';
|
|
19
27
|
import {SectionProvider, TemplateProvider} from './context';
|
|
20
28
|
import type {BlockData, SectionData, TemplateData} from './types';
|
|
21
29
|
|
|
@@ -181,6 +189,11 @@ export function SectionGroup({
|
|
|
181
189
|
name: string;
|
|
182
190
|
sectionData?: Record<string, unknown>;
|
|
183
191
|
}) {
|
|
192
|
+
useSyncExternalStore(
|
|
193
|
+
subscribePreview,
|
|
194
|
+
getPreviewVersion,
|
|
195
|
+
getServerPreviewVersion,
|
|
196
|
+
);
|
|
184
197
|
const group = getSectionGroup(name);
|
|
185
198
|
return (
|
|
186
199
|
<TemplateProvider value={{name, kind: 'group', resources: {}, sectionData}}>
|
|
@@ -26,7 +26,10 @@ export interface ThemeSchema {
|
|
|
26
26
|
/** sections/*-group.json keyed by file name ("header-group"…). */
|
|
27
27
|
sectionGroups: Record<string, SectionGroupData>;
|
|
28
28
|
/** config/settings_schema.json. */
|
|
29
|
-
settingsSchema: Array<{
|
|
29
|
+
settingsSchema: Array<{
|
|
30
|
+
name: string;
|
|
31
|
+
settings?: Array<Record<string, unknown>>;
|
|
32
|
+
}>;
|
|
30
33
|
/** config/settings_data.json. */
|
|
31
34
|
settingsData: {
|
|
32
35
|
current: string | Record<string, unknown>;
|
|
@@ -57,6 +60,40 @@ interface ThemeStore extends InstallThemeOptions {}
|
|
|
57
60
|
|
|
58
61
|
let store: ThemeStore | null = null;
|
|
59
62
|
let version = 0;
|
|
63
|
+
let preview: {owner: ThemeStore; value: ThemeStore} | null = null;
|
|
64
|
+
let previewVersion = 0;
|
|
65
|
+
const previewListeners = new Set<() => void>();
|
|
66
|
+
|
|
67
|
+
export function subscribePreview(listener: () => void): () => void {
|
|
68
|
+
previewListeners.add(listener);
|
|
69
|
+
return () => {
|
|
70
|
+
previewListeners.delete(listener);
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function getPreviewVersion(): number {
|
|
74
|
+
return previewVersion;
|
|
75
|
+
}
|
|
76
|
+
export function getServerPreviewVersion(): number {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
export function hasThemePreview(): boolean {
|
|
80
|
+
return (
|
|
81
|
+
typeof window !== 'undefined' && preview !== null && preview.owner === store
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Browser-only overlay; the installed schema and SSR state remain untouched. */
|
|
86
|
+
export function setThemePreview(
|
|
87
|
+
owner: ThemeStore,
|
|
88
|
+
schema: ThemeSchema | null,
|
|
89
|
+
): void {
|
|
90
|
+
if (typeof window === 'undefined' || owner !== store) return;
|
|
91
|
+
preview = schema
|
|
92
|
+
? {owner, value: {...owner, schema, settingsOverride: undefined}}
|
|
93
|
+
: null;
|
|
94
|
+
previewVersion++;
|
|
95
|
+
for (const listener of previewListeners) listener();
|
|
96
|
+
}
|
|
60
97
|
|
|
61
98
|
export function installTheme(options: InstallThemeOptions): void {
|
|
62
99
|
store = options;
|
|
@@ -67,17 +104,19 @@ export function installTheme(options: InstallThemeOptions): void {
|
|
|
67
104
|
|
|
68
105
|
/** Monotonic install counter — lets derived caches detect re-installs. */
|
|
69
106
|
export function getThemeStoreVersion(): number {
|
|
70
|
-
return version;
|
|
107
|
+
return version + (typeof window === 'undefined' ? 0 : previewVersion);
|
|
71
108
|
}
|
|
72
109
|
|
|
73
110
|
export function getThemeStore(): ThemeStore {
|
|
74
111
|
if (!store) {
|
|
75
112
|
throw new Error(
|
|
76
|
-
|
|
113
|
+
"[storefront-kit] installTheme() has not been called. Import your app's " +
|
|
77
114
|
'theme-setup module (which calls installTheme) before rendering.',
|
|
78
115
|
);
|
|
79
116
|
}
|
|
80
|
-
return store
|
|
117
|
+
return typeof window !== 'undefined' && preview?.owner === store
|
|
118
|
+
? preview.value
|
|
119
|
+
: store;
|
|
81
120
|
}
|
|
82
121
|
|
|
83
122
|
/** Look up a page template ("index", "product", "customers/login"…). */
|
package/src/react/index.ts
CHANGED
|
@@ -77,3 +77,5 @@ export {
|
|
|
77
77
|
// Built-in registries (spread into installTheme with app extras).
|
|
78
78
|
// Server-only loaders live in '@zalify/storefront-kit/react/server'.
|
|
79
79
|
export {builtinSections, builtinBlocks} from './registries';
|
|
80
|
+
|
|
81
|
+
export {useEditorTemplate} from './useEditorTemplate';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {StoreImage} from '../components/StoreImage';
|
|
1
2
|
/**
|
|
2
3
|
* Port of sections/article.liquid. CSS: app/styles/components/sections-article.css
|
|
3
4
|
*
|
|
@@ -75,7 +76,7 @@ export default function ArticleSection({
|
|
|
75
76
|
</header>
|
|
76
77
|
|
|
77
78
|
{article.image?.url && (
|
|
78
|
-
<
|
|
79
|
+
<StoreImage
|
|
79
80
|
className="article__image"
|
|
80
81
|
src={imageUrl(article.image.url, 1500)}
|
|
81
82
|
srcSet={imageSrcSet(article.image.url)}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {StoreImage} from '../components/StoreImage';
|
|
1
2
|
/**
|
|
2
3
|
* Port of sections/custom-section.liquid — the canonical v3 section
|
|
3
4
|
* pattern: color_scheme + section_spacing on the wrapper, optional
|
|
@@ -34,7 +35,7 @@ export default function CustomSection({
|
|
|
34
35
|
>
|
|
35
36
|
{backgroundImage ? (
|
|
36
37
|
<div className="custom-section__background">
|
|
37
|
-
<
|
|
38
|
+
<StoreImage src={imageUrl(backgroundImage, 2000)} alt="" />
|
|
38
39
|
</div>
|
|
39
40
|
) : null}
|
|
40
41
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {StoreImage} from '../components/StoreImage';
|
|
1
2
|
/**
|
|
2
3
|
* Port of sections/image-with-text.liquid — media column + block stack.
|
|
3
4
|
* CSS: app/styles/components/sections-image-with-text.css
|
|
@@ -42,7 +43,7 @@ export default function ImageWithTextSection({
|
|
|
42
43
|
>
|
|
43
44
|
<div className="image-with-text__media">
|
|
44
45
|
{image ? (
|
|
45
|
-
<
|
|
46
|
+
<StoreImage
|
|
46
47
|
className="image-with-text__image"
|
|
47
48
|
src={imageUrl(image, 1500)}
|
|
48
49
|
srcSet={imageSrcSet(image, [400, 700, 1000, 1500])}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {StoreImage} from '../components/StoreImage';
|
|
1
2
|
/**
|
|
2
3
|
* Port of sections/video-bubble.liquid — floating video bubble: a small
|
|
3
4
|
* muted preview loop pinned to a bottom corner that expands into the
|
|
@@ -107,7 +108,7 @@ export default function VideoBubbleSection({
|
|
|
107
108
|
<source src={previewMp4.url} type={previewMp4.mime_type} />
|
|
108
109
|
</video>
|
|
109
110
|
) : poster ? (
|
|
110
|
-
<
|
|
111
|
+
<StoreImage
|
|
111
112
|
className="video-bubble__media"
|
|
112
113
|
src={imageUrl(poster, 400)}
|
|
113
114
|
loading="lazy"
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import type { TemplateData } from "../schemas/data";
|
|
4
|
+
import type { ThemeEditorManifest } from "../schemas/manifest";
|
|
5
|
+
import type { FrameBridgeController } from "../editor/frame";
|
|
6
|
+
import type { PreviewContext } from "../schemas/bridge";
|
|
7
|
+
import type { EditorDocuments } from "../editor/bootstrap";
|
|
8
|
+
import { getThemeStore, setThemePreview } from "./engine/store";
|
|
9
|
+
|
|
10
|
+
type Options = {
|
|
11
|
+
origins: readonly string[];
|
|
12
|
+
loadManifest: () => Promise<ThemeEditorManifest>;
|
|
13
|
+
/** Authoritative, repository-relative merchant data targets. */
|
|
14
|
+
paths: EditorDocuments;
|
|
15
|
+
/** Real route targets keyed by template name. */
|
|
16
|
+
previews: Record<string, PreviewContext>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** No editor code or schema is fetched outside an explicitly allowed preview. */
|
|
20
|
+
export function useEditorTemplate(
|
|
21
|
+
name: string,
|
|
22
|
+
options: Options,
|
|
23
|
+
): TemplateData | null {
|
|
24
|
+
const [draft, setDraft] = useState<{
|
|
25
|
+
name: string;
|
|
26
|
+
template: TemplateData;
|
|
27
|
+
} | null>(null);
|
|
28
|
+
const controller = useRef<FrameBridgeController | null>(null);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
setDraft(null);
|
|
31
|
+
if (
|
|
32
|
+
!options.origins.length ||
|
|
33
|
+
window.self === window.top ||
|
|
34
|
+
new URL(window.location.href).searchParams.get("zalify-editor") !== "1"
|
|
35
|
+
)
|
|
36
|
+
return;
|
|
37
|
+
let parentOrigin: string;
|
|
38
|
+
try {
|
|
39
|
+
parentOrigin = new URL(document.referrer).origin;
|
|
40
|
+
} catch {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!options.origins.includes(parentOrigin)) return;
|
|
44
|
+
let disposed = false;
|
|
45
|
+
const owner = getThemeStore();
|
|
46
|
+
void Promise.all([
|
|
47
|
+
import("../editor/frame"),
|
|
48
|
+
options.loadManifest(),
|
|
49
|
+
import("../editor/bootstrap"),
|
|
50
|
+
import("../editor/draft"),
|
|
51
|
+
])
|
|
52
|
+
.then(([bridge, manifest, bootstrapModule, draftModule]) => {
|
|
53
|
+
if (disposed) return;
|
|
54
|
+
const settings = owner.schema.settingsData;
|
|
55
|
+
const current =
|
|
56
|
+
typeof settings.current === "string"
|
|
57
|
+
? (settings.presets?.[settings.current] ?? {})
|
|
58
|
+
: settings.current;
|
|
59
|
+
const schema = {
|
|
60
|
+
...owner.schema,
|
|
61
|
+
settingsData: {
|
|
62
|
+
...settings,
|
|
63
|
+
current: { ...current, ...owner.settingsOverride },
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const previews = { ...options.previews };
|
|
67
|
+
// The currently rendered route is also a verified context, including locale.
|
|
68
|
+
const url = new URL(window.location.href);
|
|
69
|
+
url.searchParams.delete("zalify-editor");
|
|
70
|
+
const kind = name.split(".")[0];
|
|
71
|
+
const resourceType = (
|
|
72
|
+
[
|
|
73
|
+
"index",
|
|
74
|
+
"product",
|
|
75
|
+
"collection",
|
|
76
|
+
"page",
|
|
77
|
+
"blog",
|
|
78
|
+
"article",
|
|
79
|
+
"cart",
|
|
80
|
+
"search",
|
|
81
|
+
"list-collections",
|
|
82
|
+
"404",
|
|
83
|
+
].includes(kind)
|
|
84
|
+
? kind
|
|
85
|
+
: "page"
|
|
86
|
+
) as PreviewContext["resourceType"];
|
|
87
|
+
previews[name] = {
|
|
88
|
+
...previews[name],
|
|
89
|
+
id: name,
|
|
90
|
+
title: previews[name]?.title ?? name,
|
|
91
|
+
resourceType,
|
|
92
|
+
url: url.pathname + url.search,
|
|
93
|
+
};
|
|
94
|
+
const bootstrap = bootstrapModule.createEditorBootstrap({
|
|
95
|
+
schema,
|
|
96
|
+
manifest,
|
|
97
|
+
paths: options.paths,
|
|
98
|
+
previews,
|
|
99
|
+
});
|
|
100
|
+
const draft = draftModule.createEditorDraft(schema);
|
|
101
|
+
controller.current = bridge.mountFrameBridge({
|
|
102
|
+
editorOrigin: parentOrigin,
|
|
103
|
+
templateName: name,
|
|
104
|
+
hash: manifest.hash,
|
|
105
|
+
getManifest: () => manifest,
|
|
106
|
+
capabilities: [
|
|
107
|
+
"editor-bootstrap-v1",
|
|
108
|
+
"apply-template-v1",
|
|
109
|
+
"apply-groups-v1",
|
|
110
|
+
"apply-settings-v1",
|
|
111
|
+
"preview-navigation-v1",
|
|
112
|
+
],
|
|
113
|
+
getBootstrap: () => bootstrap,
|
|
114
|
+
applyTemplate(payload) {
|
|
115
|
+
if (payload.templateName !== name) return false;
|
|
116
|
+
setThemePreview(owner, { ...schema, ...draft.apply(payload) });
|
|
117
|
+
setDraft({ name, template: payload.template });
|
|
118
|
+
return true;
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
})
|
|
122
|
+
.catch(() => {
|
|
123
|
+
/* The storefront remains usable if preview assets fail. */
|
|
124
|
+
});
|
|
125
|
+
return () => {
|
|
126
|
+
disposed = true;
|
|
127
|
+
controller.current?.unmount();
|
|
128
|
+
controller.current = null;
|
|
129
|
+
setThemePreview(owner, null);
|
|
130
|
+
};
|
|
131
|
+
}, [name, options]);
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
controller.current?.reportRects();
|
|
134
|
+
}, [draft]);
|
|
135
|
+
return draft?.name === name ? draft.template : null;
|
|
136
|
+
}
|