@standhigher/puck-page-builder 0.1.0 → 0.8.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/chunk-KYXIXIWK.js +25 -0
- package/dist/{chunk-G2LNCKYO.js → chunk-OBN4DMOR.js} +25 -12
- package/dist/{chunk-RGB53WE6.js → chunk-PUTYZI7B.js} +37 -4
- package/dist/chunk-YK2AUHF6.js +38 -0
- package/dist/extensions.d.ts +44 -4
- package/dist/extensions.js +7 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +160 -78
- package/dist/renderer.d.ts +1 -0
- package/dist/renderer.js +2 -1
- package/dist/runtime.d.ts +5 -0
- package/dist/runtime.js +36 -0
- package/dist/schema.d.ts +17 -8
- package/dist/schema.js +2 -1
- package/dist/theme.d.ts +14 -0
- package/dist/theme.js +12 -0
- package/package.json +10 -2
- package/src/styles.css +24 -1
- package/dist/chunk-72MFV6J7.js +0 -15
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mergeThemeTokens,
|
|
3
|
+
toThemeStyle
|
|
4
|
+
} from "./chunk-YK2AUHF6.js";
|
|
5
|
+
|
|
6
|
+
// src/renderer/web/WebRenderer.tsx
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
function WebRenderer({ document, className, registry }) {
|
|
9
|
+
const template = document.templateId ? registry?.getTemplate(document.templateId) : void 0;
|
|
10
|
+
const pageTheme = mergeThemeTokens(template?.theme, document.theme);
|
|
11
|
+
return /* @__PURE__ */ jsx("main", { className: className ?? "pb-web-renderer", "data-page-id": document.pageId, lang: document.settings.locale, style: toThemeStyle(pageTheme), children: document.blocks.map((block) => {
|
|
12
|
+
const definition = registry?.getBlock(block.type);
|
|
13
|
+
const variant = definition?.variants?.find((item) => item.id === block.variant);
|
|
14
|
+
const style = toThemeStyle(mergeThemeTokens(template?.theme, document.theme, variant?.theme, block.style));
|
|
15
|
+
if (block.type === "core.text") return /* @__PURE__ */ jsx("section", { "data-block-id": block.id, "data-block-variant": block.variant, style, className: "pb-web-renderer__text", children: /* @__PURE__ */ jsx("p", { children: typeof block.props.content === "string" ? block.props.content : "" }) }, block.id);
|
|
16
|
+
if (block.type === "core.image") return /* @__PURE__ */ jsx("figure", { "data-block-id": block.id, "data-block-variant": block.variant, style, className: "pb-web-renderer__image", children: /* @__PURE__ */ jsx("img", { src: typeof block.props.src === "string" ? block.props.src : "", alt: typeof block.props.alt === "string" ? block.props.alt : "" }) }, block.id);
|
|
17
|
+
const BlockRenderer = definition?.render.web;
|
|
18
|
+
if (BlockRenderer) return /* @__PURE__ */ jsx("section", { "data-block-id": block.id, "data-block-variant": block.variant, style, children: /* @__PURE__ */ jsx(BlockRenderer, { ...block.props }) }, block.id);
|
|
19
|
+
return null;
|
|
20
|
+
}) });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
WebRenderer
|
|
25
|
+
};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeThemeTokens
|
|
3
|
+
} from "./chunk-YK2AUHF6.js";
|
|
4
|
+
|
|
1
5
|
// src/core/schema/page-document.ts
|
|
2
|
-
var pageDocumentKeys = /* @__PURE__ */ new Set(["schemaVersion", "pageId", "target", "templateId", "root", "blocks", "settings"]);
|
|
3
|
-
var blockKeys = /* @__PURE__ */ new Set(["id", "type", "version", "props", "slots", "binding"]);
|
|
6
|
+
var pageDocumentKeys = /* @__PURE__ */ new Set(["schemaVersion", "pageId", "target", "templateId", "templateVersion", "theme", "root", "blocks", "settings"]);
|
|
7
|
+
var blockKeys = /* @__PURE__ */ new Set(["id", "type", "version", "props", "variant", "style", "slots", "binding"]);
|
|
4
8
|
function isRecord(value) {
|
|
5
9
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
10
|
}
|
|
@@ -9,6 +13,10 @@ function isJsonValue(value) {
|
|
|
9
13
|
if (Array.isArray(value)) return value.every(isJsonValue);
|
|
10
14
|
return isRecord(value) && Object.values(value).every(isJsonValue);
|
|
11
15
|
}
|
|
16
|
+
function themeTokensOrEmpty(value) {
|
|
17
|
+
const result = normalizeThemeTokens(value);
|
|
18
|
+
return result.success ? result.data : {};
|
|
19
|
+
}
|
|
12
20
|
function normalizeBlock(value, path, issues) {
|
|
13
21
|
if (!isRecord(value)) {
|
|
14
22
|
issues.push({ path, message: "\u5FC5\u987B\u662F\u5BF9\u8C61" });
|
|
@@ -19,6 +27,8 @@ function normalizeBlock(value, path, issues) {
|
|
|
19
27
|
if (typeof value.type !== "string" || !value.type) issues.push({ path: `${path}.type`, message: "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32" });
|
|
20
28
|
if (typeof value.version !== "number" || !Number.isInteger(value.version) || value.version < 1) issues.push({ path: `${path}.version`, message: "\u5FC5\u987B\u662F\u5927\u4E8E 0 \u7684\u6574\u6570" });
|
|
21
29
|
if (!isRecord(value.props) || !Object.values(value.props).every(isJsonValue)) issues.push({ path: `${path}.props`, message: "\u5FC5\u987B\u662F JSON \u5BF9\u8C61" });
|
|
30
|
+
if (typeof value.variant !== "string" || !value.variant) issues.push({ path: `${path}.variant`, message: "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32" });
|
|
31
|
+
if (!normalizeThemeTokens(value.style).success) issues.push({ path: `${path}.style`, message: "\u5FC5\u987B\u662F\u53D7\u63A7 Theme Token \u5BF9\u8C61" });
|
|
22
32
|
if (value.binding !== void 0 && (!isRecord(value.binding) || typeof value.binding.source !== "string" || value.binding.params !== void 0 && (!isRecord(value.binding.params) || !Object.values(value.binding.params).every(isJsonValue)))) issues.push({ path: `${path}.binding`, message: "\u5FC5\u987B\u5305\u542B source\uFF0C\u4E14 params \u5FC5\u987B\u662F JSON \u5BF9\u8C61" });
|
|
23
33
|
if (issues.some((issue) => issue.path.startsWith(path))) return null;
|
|
24
34
|
const slots = normalizeSlots(value.slots, `${path}.slots`, issues);
|
|
@@ -28,6 +38,8 @@ function normalizeBlock(value, path, issues) {
|
|
|
28
38
|
type: value.type,
|
|
29
39
|
version: value.version,
|
|
30
40
|
props: value.props,
|
|
41
|
+
variant: value.variant,
|
|
42
|
+
style: themeTokensOrEmpty(value.style),
|
|
31
43
|
...slots ? { slots } : {},
|
|
32
44
|
...value.binding ? { binding: value.binding } : {}
|
|
33
45
|
};
|
|
@@ -53,9 +65,10 @@ function createPageDocument(input) {
|
|
|
53
65
|
schemaVersion: 1,
|
|
54
66
|
pageId: input.pageId,
|
|
55
67
|
target: input.target ?? "web",
|
|
56
|
-
...input.templateId ? { templateId: input.templateId } : {},
|
|
68
|
+
...input.templateId ? { templateId: input.templateId, templateVersion: input.templateVersion ?? 1 } : {},
|
|
69
|
+
theme: themeTokensOrEmpty(input.theme),
|
|
57
70
|
root: input.root ?? {},
|
|
58
|
-
blocks: input.blocks ?? [],
|
|
71
|
+
blocks: (input.blocks ?? []).map((block) => ({ ...block, variant: block.variant ?? "default", style: themeTokensOrEmpty(block.style) })),
|
|
59
72
|
settings: { locale: input.settings?.locale ?? "en", ...input.settings?.seoTitle ? { seoTitle: input.settings.seoTitle } : {} }
|
|
60
73
|
};
|
|
61
74
|
}
|
|
@@ -67,6 +80,10 @@ function validatePageDocument(value) {
|
|
|
67
80
|
if (typeof value.pageId !== "string" || !value.pageId) issues.push({ path: "$.pageId", message: "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32" });
|
|
68
81
|
if (value.target !== "web" && value.target !== "email") issues.push({ path: "$.target", message: "\u5FC5\u987B\u662F web \u6216 email" });
|
|
69
82
|
if (value.templateId !== void 0 && typeof value.templateId !== "string") issues.push({ path: "$.templateId", message: "\u5FC5\u987B\u662F\u5B57\u7B26\u4E32" });
|
|
83
|
+
if (value.templateVersion !== void 0 && (typeof value.templateVersion !== "number" || !Number.isInteger(value.templateVersion) || value.templateVersion < 1)) issues.push({ path: "$.templateVersion", message: "\u5FC5\u987B\u662F\u5927\u4E8E 0 \u7684\u6574\u6570" });
|
|
84
|
+
if (typeof value.templateId === "string" && value.templateVersion === void 0) issues.push({ path: "$.templateVersion", message: "\u4F7F\u7528\u6A21\u677F\u65F6\u5FC5\u987B\u4FDD\u5B58\u6A21\u677F\u7248\u672C" });
|
|
85
|
+
if (value.templateId === void 0 && value.templateVersion !== void 0) issues.push({ path: "$.templateVersion", message: "\u672A\u4F7F\u7528\u6A21\u677F\u65F6\u4E0D\u5F97\u4FDD\u5B58\u6A21\u677F\u7248\u672C" });
|
|
86
|
+
if (!normalizeThemeTokens(value.theme).success) issues.push({ path: "$.theme", message: "\u5FC5\u987B\u662F\u53D7\u63A7 Theme Token \u5BF9\u8C61" });
|
|
70
87
|
if (!isRecord(value.root) || !Object.values(value.root).every(isJsonValue)) issues.push({ path: "$.root", message: "\u5FC5\u987B\u662F JSON \u5BF9\u8C61" });
|
|
71
88
|
if (!isRecord(value.settings) || typeof value.settings.locale !== "string") issues.push({ path: "$.settings.locale", message: "\u5FC5\u987B\u662F\u5B57\u7B26\u4E32" });
|
|
72
89
|
if (!Array.isArray(value.blocks)) issues.push({ path: "$.blocks", message: "\u5FC5\u987B\u662F\u6570\u7EC4" });
|
|
@@ -83,6 +100,8 @@ function validatePageDocument(value) {
|
|
|
83
100
|
pageId: value.pageId,
|
|
84
101
|
target: value.target,
|
|
85
102
|
...typeof value.templateId === "string" ? { templateId: value.templateId } : {},
|
|
103
|
+
...typeof value.templateVersion === "number" ? { templateVersion: value.templateVersion } : {},
|
|
104
|
+
theme: themeTokensOrEmpty(value.theme),
|
|
86
105
|
root: value.root,
|
|
87
106
|
blocks,
|
|
88
107
|
settings: value.settings
|
|
@@ -90,14 +109,8 @@ function validatePageDocument(value) {
|
|
|
90
109
|
};
|
|
91
110
|
}
|
|
92
111
|
function migratePageDocument(value) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return validation2.success ? { ...validation2, migrated: false } : validation2;
|
|
96
|
-
}
|
|
97
|
-
const migrated = value.schemaVersion === void 0;
|
|
98
|
-
const candidate = migrated ? { ...value, schemaVersion: 1 } : value;
|
|
99
|
-
const validation = validatePageDocument(candidate);
|
|
100
|
-
return validation.success ? { ...validation, migrated } : validation;
|
|
112
|
+
const validation = validatePageDocument(value);
|
|
113
|
+
return validation.success ? { ...validation, migrated: false } : validation;
|
|
101
114
|
}
|
|
102
115
|
|
|
103
116
|
export {
|
|
@@ -56,8 +56,31 @@ function resolveOrder(extensions, disabled) {
|
|
|
56
56
|
for (const extension of [...active].sort(compareByOrder)) visit(extension);
|
|
57
57
|
return sorted;
|
|
58
58
|
}
|
|
59
|
+
var TemplateRegistry = class _TemplateRegistry {
|
|
60
|
+
constructor(templateMap) {
|
|
61
|
+
this.templateMap = templateMap;
|
|
62
|
+
}
|
|
63
|
+
templateMap;
|
|
64
|
+
get templates() {
|
|
65
|
+
return Object.freeze([...this.templateMap.values()]);
|
|
66
|
+
}
|
|
67
|
+
get(id) {
|
|
68
|
+
return this.templateMap.get(id);
|
|
69
|
+
}
|
|
70
|
+
static create(templates, blocks) {
|
|
71
|
+
const blockTypes = new Set(blocks.map((block) => block.type));
|
|
72
|
+
for (const template of templates) {
|
|
73
|
+
if (template.source !== "built-in" && template.source !== "marketplace" && template.source !== "custom") throw new ExtensionRegistryError("invalid-identifier", `Template ${template.id} \u5FC5\u987B\u58F0\u660E built-in\u3001marketplace \u6216 custom \u6765\u6E90`);
|
|
74
|
+
if (template.target !== "web" && template.target !== "email") throw new ExtensionRegistryError("invalid-target", `Template ${template.id} \u5FC5\u987B\u58F0\u660E web \u6216 email target`);
|
|
75
|
+
for (const type of template.requiredBlocks ?? []) {
|
|
76
|
+
if (!blockTypes.has(type)) throw new ExtensionRegistryError("missing-template-dependency", `Template ${template.id} \u7F3A\u5C11\u5DF2\u6CE8\u518C\u533A\u5757\uFF1A${type}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return Object.freeze(new _TemplateRegistry(new Map(templates.map((template) => [template.id, template]))));
|
|
80
|
+
}
|
|
81
|
+
};
|
|
59
82
|
var ExtensionRegistry = class _ExtensionRegistry {
|
|
60
|
-
constructor(extensions, blockMap, fieldMap, actionMap, rendererMap, dataSourceMap, templateMap, slotMap, hooks) {
|
|
83
|
+
constructor(extensions, blockMap, fieldMap, actionMap, rendererMap, dataSourceMap, templateMap, templateRegistry, slotMap, hooks) {
|
|
61
84
|
this.extensions = extensions;
|
|
62
85
|
this.blockMap = blockMap;
|
|
63
86
|
this.fieldMap = fieldMap;
|
|
@@ -65,6 +88,7 @@ var ExtensionRegistry = class _ExtensionRegistry {
|
|
|
65
88
|
this.rendererMap = rendererMap;
|
|
66
89
|
this.dataSourceMap = dataSourceMap;
|
|
67
90
|
this.templateMap = templateMap;
|
|
91
|
+
this.templateRegistry = templateRegistry;
|
|
68
92
|
this.slotMap = slotMap;
|
|
69
93
|
this.hooks = hooks;
|
|
70
94
|
}
|
|
@@ -75,6 +99,7 @@ var ExtensionRegistry = class _ExtensionRegistry {
|
|
|
75
99
|
rendererMap;
|
|
76
100
|
dataSourceMap;
|
|
77
101
|
templateMap;
|
|
102
|
+
templateRegistry;
|
|
78
103
|
slotMap;
|
|
79
104
|
hooks;
|
|
80
105
|
get blocks() {
|
|
@@ -93,7 +118,7 @@ var ExtensionRegistry = class _ExtensionRegistry {
|
|
|
93
118
|
return Object.freeze([...this.dataSourceMap.values()]);
|
|
94
119
|
}
|
|
95
120
|
get templates() {
|
|
96
|
-
return
|
|
121
|
+
return this.templateRegistry.templates;
|
|
97
122
|
}
|
|
98
123
|
getBlock(type) {
|
|
99
124
|
return this.blockMap.get(type);
|
|
@@ -111,7 +136,7 @@ var ExtensionRegistry = class _ExtensionRegistry {
|
|
|
111
136
|
return this.dataSourceMap.get(key);
|
|
112
137
|
}
|
|
113
138
|
getTemplate(id) {
|
|
114
|
-
return this.
|
|
139
|
+
return this.templateRegistry.get(id);
|
|
115
140
|
}
|
|
116
141
|
getSlot(slot) {
|
|
117
142
|
return this.slotMap.get(slot) ?? [];
|
|
@@ -154,6 +179,7 @@ var ExtensionRegistry = class _ExtensionRegistry {
|
|
|
154
179
|
slots.set(slot.slot, current);
|
|
155
180
|
}
|
|
156
181
|
}
|
|
182
|
+
const templateRegistry = TemplateRegistry.create(templateItems.map((template) => templates.get(template.id)), blockItems);
|
|
157
183
|
const sortedSlots = /* @__PURE__ */ new Map();
|
|
158
184
|
for (const [slot, contributions] of slots) sortedSlots.set(slot, Object.freeze([...contributions].sort(compareByOrder)));
|
|
159
185
|
return Object.freeze(new _ExtensionRegistry(
|
|
@@ -164,6 +190,7 @@ var ExtensionRegistry = class _ExtensionRegistry {
|
|
|
164
190
|
renderers,
|
|
165
191
|
dataSources,
|
|
166
192
|
templates,
|
|
193
|
+
templateRegistry,
|
|
167
194
|
sortedSlots,
|
|
168
195
|
Object.freeze(resolved.flatMap((extension) => extension.hooks ? [extension.hooks] : []))
|
|
169
196
|
));
|
|
@@ -172,9 +199,15 @@ var ExtensionRegistry = class _ExtensionRegistry {
|
|
|
172
199
|
function createExtensionRegistry(extensions, options) {
|
|
173
200
|
return ExtensionRegistry.create(extensions, options);
|
|
174
201
|
}
|
|
202
|
+
function createTemplateRegistry(templates, blocks = []) {
|
|
203
|
+
const registered = templates.map((template) => Object.freeze({ ...template, extension: "standalone" }));
|
|
204
|
+
return TemplateRegistry.create(registered, blocks);
|
|
205
|
+
}
|
|
175
206
|
|
|
176
207
|
export {
|
|
177
208
|
ExtensionRegistryError,
|
|
209
|
+
TemplateRegistry,
|
|
178
210
|
ExtensionRegistry,
|
|
179
|
-
createExtensionRegistry
|
|
211
|
+
createExtensionRegistry,
|
|
212
|
+
createTemplateRegistry
|
|
180
213
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/core/theme/index.ts
|
|
2
|
+
var systemThemeTokens = Object.freeze({
|
|
3
|
+
"color.background": "#ffffff",
|
|
4
|
+
"color.surface": "#ffffff",
|
|
5
|
+
"color.text": "#202223",
|
|
6
|
+
"color.muted": "#6d7175",
|
|
7
|
+
"color.primary": "#005bd3",
|
|
8
|
+
"color.border": "#d2d5d8",
|
|
9
|
+
"font.family": "system-ui, sans-serif",
|
|
10
|
+
"font.size": "16px",
|
|
11
|
+
radius: "8px",
|
|
12
|
+
spacing: "16px"
|
|
13
|
+
});
|
|
14
|
+
var themeTokenNames = new Set(Object.keys(systemThemeTokens));
|
|
15
|
+
var unsafeCssValue = /[{};]|url\s*\(|expression\s*\(|@import/i;
|
|
16
|
+
function normalizeThemeTokens(value) {
|
|
17
|
+
if (value === void 0) return { success: true, data: {} };
|
|
18
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return { success: false };
|
|
19
|
+
const tokens = {};
|
|
20
|
+
for (const [name, tokenValue] of Object.entries(value)) {
|
|
21
|
+
if (!themeTokenNames.has(name) || typeof tokenValue !== "string" || !tokenValue || unsafeCssValue.test(tokenValue)) return { success: false };
|
|
22
|
+
tokens[name] = tokenValue;
|
|
23
|
+
}
|
|
24
|
+
return { success: true, data: tokens };
|
|
25
|
+
}
|
|
26
|
+
function mergeThemeTokens(...layers) {
|
|
27
|
+
return Object.freeze(Object.assign({}, systemThemeTokens, ...layers));
|
|
28
|
+
}
|
|
29
|
+
function toThemeStyle(tokens) {
|
|
30
|
+
return Object.fromEntries(Object.entries(tokens).map(([name, value]) => [`--pb-${name.replaceAll(".", "-")}`, value]));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
systemThemeTokens,
|
|
35
|
+
normalizeThemeTokens,
|
|
36
|
+
mergeThemeTokens,
|
|
37
|
+
toThemeStyle
|
|
38
|
+
};
|
package/dist/extensions.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ComponentType, ReactNode } from 'react';
|
|
2
|
-
import { RenderTarget, PageDocument } from './schema.js';
|
|
2
|
+
import { RenderTarget, JsonValue, PageDocument } from './schema.js';
|
|
3
|
+
import { ThemeTokens } from './theme.js';
|
|
3
4
|
|
|
4
5
|
type ExtensionTarget = RenderTarget | "all";
|
|
5
6
|
type EditorActionPosition = "left" | "center" | "right";
|
|
@@ -11,12 +12,26 @@ type ValidationIssue = {
|
|
|
11
12
|
type FieldConfig = {
|
|
12
13
|
field: string;
|
|
13
14
|
label?: string;
|
|
15
|
+
/** Product-facing editor metadata. Custom field components remain supported. */
|
|
16
|
+
control?: "text" | "textarea" | "url";
|
|
17
|
+
description?: string;
|
|
18
|
+
group?: string;
|
|
14
19
|
required?: boolean;
|
|
15
20
|
};
|
|
16
21
|
type FieldProps<T = unknown> = {
|
|
17
22
|
value: T;
|
|
18
23
|
onChange(value: T): void;
|
|
19
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Props available only to an extension's editor-canvas renderer.
|
|
27
|
+
* They are separate from Web rendering so editor interaction cannot invoke a
|
|
28
|
+
* host Runtime or become persisted operational data.
|
|
29
|
+
*/
|
|
30
|
+
type BlockEditorProps<P = Record<string, unknown>> = P & {
|
|
31
|
+
blockId: string;
|
|
32
|
+
selected: boolean;
|
|
33
|
+
onPropsChange(props: Record<string, JsonValue>): void;
|
|
34
|
+
};
|
|
20
35
|
interface BlockDefinition<P = Record<string, unknown>> {
|
|
21
36
|
type: string;
|
|
22
37
|
version: number;
|
|
@@ -25,10 +40,20 @@ interface BlockDefinition<P = Record<string, unknown>> {
|
|
|
25
40
|
targets: ExtensionTarget[];
|
|
26
41
|
defaultProps: P;
|
|
27
42
|
fields: Record<string, FieldConfig>;
|
|
28
|
-
render: Partial<Record<RenderTarget, ComponentType<P
|
|
43
|
+
render: Partial<Record<RenderTarget, ComponentType<P>>> & {
|
|
44
|
+
/** Optional edit-mode renderer. WebRenderer never uses this surface. */
|
|
45
|
+
editor?: ComponentType<BlockEditorProps<P>>;
|
|
46
|
+
};
|
|
47
|
+
defaultVariant?: string;
|
|
48
|
+
variants?: BlockVariantDefinition[];
|
|
29
49
|
dataSources?: string[];
|
|
30
50
|
validate?: (props: P) => ValidationIssue[];
|
|
31
51
|
}
|
|
52
|
+
interface BlockVariantDefinition {
|
|
53
|
+
id: string;
|
|
54
|
+
label: string;
|
|
55
|
+
theme?: ThemeTokens;
|
|
56
|
+
}
|
|
32
57
|
interface FieldDefinition<T = unknown> {
|
|
33
58
|
type: string;
|
|
34
59
|
component: ComponentType<FieldProps<T>>;
|
|
@@ -65,13 +90,18 @@ interface DataSourceDefinition<P = any, R = unknown> {
|
|
|
65
90
|
live: (params: P) => Promise<R>;
|
|
66
91
|
validateParams?: (params: P) => ValidationIssue[];
|
|
67
92
|
}
|
|
93
|
+
type TemplateSource = "built-in" | "marketplace" | "custom";
|
|
68
94
|
interface TemplateDefinition {
|
|
69
95
|
id: string;
|
|
70
96
|
version: number;
|
|
71
97
|
name: string;
|
|
72
98
|
target: RenderTarget;
|
|
99
|
+
source: TemplateSource;
|
|
73
100
|
thumbnail?: string;
|
|
74
101
|
create(): PageDocument;
|
|
102
|
+
/** Block types that must be registered before this template can be used. */
|
|
103
|
+
requiredBlocks?: string[];
|
|
104
|
+
theme?: ThemeTokens;
|
|
75
105
|
}
|
|
76
106
|
interface LifecycleHooks {
|
|
77
107
|
onChange?(document: PageDocument): void;
|
|
@@ -106,7 +136,7 @@ type ExtensionRegistryOptions = {
|
|
|
106
136
|
disabled?: string[];
|
|
107
137
|
};
|
|
108
138
|
|
|
109
|
-
type ExtensionRegistryErrorCode = "duplicate-extension" | "duplicate-definition" | "invalid-identifier" | "invalid-target" | "missing-dependency" | "dependency-cycle";
|
|
139
|
+
type ExtensionRegistryErrorCode = "duplicate-extension" | "duplicate-definition" | "invalid-identifier" | "invalid-target" | "missing-dependency" | "missing-template-dependency" | "dependency-cycle";
|
|
110
140
|
declare class ExtensionRegistryError extends Error {
|
|
111
141
|
readonly code: ExtensionRegistryErrorCode;
|
|
112
142
|
constructor(code: ExtensionRegistryErrorCode, message: string);
|
|
@@ -114,6 +144,14 @@ declare class ExtensionRegistryError extends Error {
|
|
|
114
144
|
type Registered<T> = T & {
|
|
115
145
|
extension: string;
|
|
116
146
|
};
|
|
147
|
+
/** Immutable template view with explicit source and block dependency validation. */
|
|
148
|
+
declare class TemplateRegistry {
|
|
149
|
+
private readonly templateMap;
|
|
150
|
+
private constructor();
|
|
151
|
+
get templates(): readonly Registered<TemplateDefinition>[];
|
|
152
|
+
get(id: string): Registered<TemplateDefinition> | undefined;
|
|
153
|
+
static create(templates: readonly Registered<TemplateDefinition>[], blocks: readonly BlockDefinition[]): TemplateRegistry;
|
|
154
|
+
}
|
|
117
155
|
/** Immutable compiled view of all enabled extensions. */
|
|
118
156
|
declare class ExtensionRegistry {
|
|
119
157
|
readonly extensions: readonly PageBuilderExtension[];
|
|
@@ -123,6 +161,7 @@ declare class ExtensionRegistry {
|
|
|
123
161
|
private readonly rendererMap;
|
|
124
162
|
private readonly dataSourceMap;
|
|
125
163
|
private readonly templateMap;
|
|
164
|
+
readonly templateRegistry: TemplateRegistry;
|
|
126
165
|
private readonly slotMap;
|
|
127
166
|
private readonly hooks;
|
|
128
167
|
private constructor();
|
|
@@ -144,5 +183,6 @@ declare class ExtensionRegistry {
|
|
|
144
183
|
static create(extensions: PageBuilderExtension[], options?: ExtensionRegistryOptions): ExtensionRegistry;
|
|
145
184
|
}
|
|
146
185
|
declare function createExtensionRegistry(extensions: PageBuilderExtension[], options?: ExtensionRegistryOptions): ExtensionRegistry;
|
|
186
|
+
declare function createTemplateRegistry(templates: TemplateDefinition[], blocks?: BlockDefinition[]): TemplateRegistry;
|
|
147
187
|
|
|
148
|
-
export { type BlockDefinition, type DataSourceDefinition, type EditorAction, type EditorActionPosition, type ExtensionActionContext, ExtensionRegistry, ExtensionRegistryError, type ExtensionRegistryErrorCode, type ExtensionRegistryOptions, type ExtensionTarget, type FieldConfig, type FieldDefinition, type FieldProps, type LifecycleHooks, type PageBuilderExtension, type RendererDefinition, type TemplateDefinition, type UISlotContribution, type UISlotName, type ValidationIssue, createExtensionRegistry };
|
|
188
|
+
export { type BlockDefinition, type BlockEditorProps, type BlockVariantDefinition, type DataSourceDefinition, type EditorAction, type EditorActionPosition, type ExtensionActionContext, ExtensionRegistry, ExtensionRegistryError, type ExtensionRegistryErrorCode, type ExtensionRegistryOptions, type ExtensionTarget, type FieldConfig, type FieldDefinition, type FieldProps, type LifecycleHooks, type PageBuilderExtension, type RendererDefinition, type TemplateDefinition, TemplateRegistry, type TemplateSource, type UISlotContribution, type UISlotName, type ValidationIssue, createExtensionRegistry, createTemplateRegistry };
|
package/dist/extensions.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ExtensionRegistry,
|
|
3
3
|
ExtensionRegistryError,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
TemplateRegistry,
|
|
5
|
+
createExtensionRegistry,
|
|
6
|
+
createTemplateRegistry
|
|
7
|
+
} from "./chunk-PUTYZI7B.js";
|
|
6
8
|
export {
|
|
7
9
|
ExtensionRegistry,
|
|
8
10
|
ExtensionRegistryError,
|
|
9
|
-
|
|
11
|
+
TemplateRegistry,
|
|
12
|
+
createExtensionRegistry,
|
|
13
|
+
createTemplateRegistry
|
|
10
14
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
import { ExtensionRegistry } from './extensions.js';
|
|
4
|
-
export { BlockDefinition, DataSourceDefinition, EditorAction, EditorActionPosition, ExtensionActionContext, ExtensionRegistryError, ExtensionRegistryErrorCode, ExtensionRegistryOptions, ExtensionTarget, FieldConfig, FieldDefinition, FieldProps, LifecycleHooks, PageBuilderExtension, RendererDefinition, TemplateDefinition, UISlotContribution, UISlotName, ValidationIssue, createExtensionRegistry } from './extensions.js';
|
|
4
|
+
export { BlockDefinition, BlockEditorProps, BlockVariantDefinition, DataSourceDefinition, EditorAction, EditorActionPosition, ExtensionActionContext, ExtensionRegistryError, ExtensionRegistryErrorCode, ExtensionRegistryOptions, ExtensionTarget, FieldConfig, FieldDefinition, FieldProps, LifecycleHooks, PageBuilderExtension, RendererDefinition, TemplateDefinition, TemplateRegistry, TemplateSource, UISlotContribution, UISlotName, ValidationIssue, createExtensionRegistry, createTemplateRegistry } from './extensions.js';
|
|
5
5
|
import { PageDocument, BlockNode, JsonValue } from './schema.js';
|
|
6
|
-
export { DataBinding, PageDocumentIssue, PageDocumentMigration, PageSettings, RenderTarget, createPageDocument, migratePageDocument, validatePageDocument } from './schema.js';
|
|
6
|
+
export { BlockStyleOverrides, DataBinding, PageDocumentIssue, PageDocumentMigration, PageDocumentSchemaVersion, PageSettings, RenderTarget, createPageDocument, migratePageDocument, validatePageDocument } from './schema.js';
|
|
7
|
+
export { ThemeTokenName, ThemeTokens, mergeThemeTokens, normalizeThemeTokens, systemThemeTokens, toThemeStyle } from './theme.js';
|
|
7
8
|
import { Data } from '@puckeditor/core';
|
|
8
9
|
export { WebRenderer, WebRendererProps } from './renderer.js';
|
|
9
10
|
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ExtensionRegistry,
|
|
3
3
|
ExtensionRegistryError,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
TemplateRegistry,
|
|
5
|
+
createExtensionRegistry,
|
|
6
|
+
createTemplateRegistry
|
|
7
|
+
} from "./chunk-PUTYZI7B.js";
|
|
6
8
|
import {
|
|
7
9
|
WebRenderer
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-KYXIXIWK.js";
|
|
9
11
|
import {
|
|
10
12
|
createPageDocument,
|
|
11
13
|
migratePageDocument,
|
|
12
14
|
validatePageDocument
|
|
13
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-OBN4DMOR.js";
|
|
16
|
+
import {
|
|
17
|
+
mergeThemeTokens,
|
|
18
|
+
normalizeThemeTokens,
|
|
19
|
+
systemThemeTokens,
|
|
20
|
+
toThemeStyle
|
|
21
|
+
} from "./chunk-YK2AUHF6.js";
|
|
14
22
|
|
|
15
23
|
// src/editor/shell/EditorShell.tsx
|
|
16
24
|
import { Puck } from "@puckeditor/core";
|
|
@@ -334,12 +342,35 @@ function PropertyPanel({
|
|
|
334
342
|
|
|
335
343
|
// src/editor/shell/PageDocumentEditorShell.tsx
|
|
336
344
|
import { Puck as Puck2, usePuck } from "@puckeditor/core";
|
|
337
|
-
import { Badge as Badge2, Banner,
|
|
345
|
+
import { Badge as Badge2, Banner, Button as Button2, ButtonGroup as ButtonGroup2, InlineStack as InlineStack2, Text as Text2, TextField } from "@shopify/polaris";
|
|
338
346
|
import { DragHandleIcon as DragHandleIcon2, LayoutSectionIcon as LayoutSectionIcon2, MenuIcon as MenuIcon2, RedoIcon as RedoIcon2, UndoIcon as UndoIcon2 } from "@shopify/polaris-icons";
|
|
339
|
-
import { useCallback as useCallback2, useEffect as
|
|
347
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef4, useState as useState3 } from "react";
|
|
348
|
+
|
|
349
|
+
// src/adapters/puck/canvas-extension-block.tsx
|
|
350
|
+
import { registerOverlayPortal } from "@puckeditor/core";
|
|
351
|
+
import { useEffect, useRef as useRef2 } from "react";
|
|
352
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
353
|
+
function CanvasExtensionBlock({ active, children, label, onSelect }) {
|
|
354
|
+
const editorRef = useRef2(null);
|
|
355
|
+
useEffect(() => active ? registerOverlayPortal(editorRef.current, { disableDrag: true }) : void 0, [active]);
|
|
356
|
+
return /* @__PURE__ */ jsx3("div", { ref: editorRef, className: "pb-document-canvas__extension", "aria-label": "Select " + label + " in canvas", role: "group", tabIndex: 0, onClick: onSelect, onKeyDown: (event) => {
|
|
357
|
+
if (event.key === "Enter" || event.key === " ") onSelect();
|
|
358
|
+
}, children });
|
|
359
|
+
}
|
|
340
360
|
|
|
341
361
|
// src/adapters/puck/page-document-config.tsx
|
|
342
|
-
import { jsx as
|
|
362
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
363
|
+
function canvasFieldFallback({ block, props, registry, onPropsChange }) {
|
|
364
|
+
const fields = Object.entries(block.fields);
|
|
365
|
+
if (!fields.length) return null;
|
|
366
|
+
return /* @__PURE__ */ jsx4("div", { "aria-label": "Edit " + block.label + " values in canvas", onClick: (event) => event.stopPropagation(), style: { display: "grid", gap: 8, marginTop: 12, padding: 12, border: "1px dashed #94a3b8", borderRadius: 6, background: "#f8fafc" }, children: fields.map(([name, field]) => {
|
|
367
|
+
const Field = registry?.getField(field.field)?.component;
|
|
368
|
+
return Field ? /* @__PURE__ */ jsxs3("label", { style: { display: "grid", gap: 4, fontSize: 12, fontWeight: 600 }, children: [
|
|
369
|
+
field.label ?? name,
|
|
370
|
+
/* @__PURE__ */ jsx4(Field, { value: props[name], onChange: (value) => onPropsChange({ [name]: value }) })
|
|
371
|
+
] }, name) : null;
|
|
372
|
+
}) });
|
|
373
|
+
}
|
|
343
374
|
function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId, registry) {
|
|
344
375
|
const extensionComponents = Object.fromEntries((registry?.blocks ?? []).flatMap((block) => {
|
|
345
376
|
const BlockRenderer = block.render.web;
|
|
@@ -347,9 +378,12 @@ function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId,
|
|
|
347
378
|
return [[block.type, {
|
|
348
379
|
render: (props) => {
|
|
349
380
|
const id = typeof props.id === "string" ? props.id : `unknown-${block.type}`;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
381
|
+
const EditorRenderer = block.render.editor;
|
|
382
|
+
const active = id === selectedBlockId;
|
|
383
|
+
return /* @__PURE__ */ jsx4(CanvasExtensionBlock, { active, label: block.label, onSelect: () => onSelect(id), children: EditorRenderer ? /* @__PURE__ */ jsx4(EditorRenderer, { ...props, blockId: id, selected: active, onPropsChange: (next) => onPropsChange(id, next) }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
384
|
+
/* @__PURE__ */ jsx4(BlockRenderer, { ...props }),
|
|
385
|
+
active ? canvasFieldFallback({ block, props, registry, onPropsChange: (next) => onPropsChange(id, next) }) : null
|
|
386
|
+
] }) });
|
|
353
387
|
}
|
|
354
388
|
}]];
|
|
355
389
|
}));
|
|
@@ -360,10 +394,10 @@ function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId,
|
|
|
360
394
|
const id = typeof props.id === "string" ? props.id : "unknown-text";
|
|
361
395
|
const content = typeof props.content === "string" ? props.content : "";
|
|
362
396
|
const editable = id === selectedBlockId;
|
|
363
|
-
return /* @__PURE__ */
|
|
397
|
+
return /* @__PURE__ */ jsx4("section", { className: "pb-document-canvas__text", "data-page-document-block-id": id, "aria-label": "Select Text in canvas", role: "button", tabIndex: 0, onClick: () => onSelect(id), onKeyDown: (event) => {
|
|
364
398
|
if (event.key === "Enter" || event.key === " ") onSelect(id);
|
|
365
|
-
}, children: /* @__PURE__ */
|
|
366
|
-
if (editable) onPropsChange(id, { content: event.currentTarget.textContent ?? "" });
|
|
399
|
+
}, children: /* @__PURE__ */ jsx4("p", { contentEditable: editable, suppressContentEditableWarning: true, onInput: (event) => {
|
|
400
|
+
if (editable) onPropsChange(id, { content: event.currentTarget.textContent ?? "" }, true);
|
|
367
401
|
}, children: content }) });
|
|
368
402
|
}
|
|
369
403
|
},
|
|
@@ -376,8 +410,8 @@ function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId,
|
|
|
376
410
|
return /* @__PURE__ */ jsxs3("figure", { className: "pb-document-canvas__image", "data-page-document-block-id": id, "aria-label": "Select Image in canvas", role: "button", tabIndex: 0, onClick: () => onSelect(id), onKeyDown: (event) => {
|
|
377
411
|
if (event.key === "Enter" || event.key === " ") onSelect(id);
|
|
378
412
|
}, children: [
|
|
379
|
-
/* @__PURE__ */
|
|
380
|
-
editable ? /* @__PURE__ */
|
|
413
|
+
/* @__PURE__ */ jsx4("img", { src, alt }),
|
|
414
|
+
editable ? /* @__PURE__ */ jsx4("input", { "aria-label": "\u753B\u5E03\u56FE\u7247 URL", value: src, onChange: (event) => onPropsChange(id, { src: event.currentTarget.value }, true), onClick: (event) => event.stopPropagation() }) : null
|
|
381
415
|
] });
|
|
382
416
|
}
|
|
383
417
|
},
|
|
@@ -399,7 +433,7 @@ function toEngineBlock(block, registry) {
|
|
|
399
433
|
}
|
|
400
434
|
function toEngineData(document, registry) {
|
|
401
435
|
return {
|
|
402
|
-
root: { ...document.root },
|
|
436
|
+
root: { props: { ...document.root } },
|
|
403
437
|
content: document.blocks.map((block) => toEngineBlock(block, registry))
|
|
404
438
|
};
|
|
405
439
|
}
|
|
@@ -416,6 +450,8 @@ function fromEngineData(data, base, registry) {
|
|
|
416
450
|
type,
|
|
417
451
|
version: previous?.version ?? 1,
|
|
418
452
|
props,
|
|
453
|
+
variant: previous?.variant ?? registry?.getBlock(type)?.defaultVariant ?? "default",
|
|
454
|
+
style: previous?.style ?? {},
|
|
419
455
|
...previous?.slots ? { slots: previous.slots } : {},
|
|
420
456
|
...previous?.binding ? { binding: previous.binding } : {}
|
|
421
457
|
}];
|
|
@@ -425,8 +461,8 @@ function fromEngineData(data, base, registry) {
|
|
|
425
461
|
}
|
|
426
462
|
|
|
427
463
|
// src/editor/context/EditorContext.tsx
|
|
428
|
-
import { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useRef as
|
|
429
|
-
import { jsx as
|
|
464
|
+
import { createContext, useCallback, useContext, useEffect as useEffect2, useMemo, useReducer, useRef as useRef3, useState as useState2 } from "react";
|
|
465
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
430
466
|
function historyReducer(state, action) {
|
|
431
467
|
if (action.type === "select") return { ...state, selectedBlockId: action.id };
|
|
432
468
|
if (action.type === "device") return { ...state, device: action.device };
|
|
@@ -452,16 +488,16 @@ function uniqueBlockId(type, blocks) {
|
|
|
452
488
|
return `${prefix}-${index}`;
|
|
453
489
|
}
|
|
454
490
|
function defaultBlock(type, blocks, registry) {
|
|
455
|
-
if (type === "core.text") return { id: uniqueBlockId(type, blocks), type, version: 1, props: { content: "New text block" } };
|
|
456
|
-
if (type === "core.image") return { id: uniqueBlockId(type, blocks), type, version: 1, props: { src: "https://images.unsplash.com/photo-1580674684081-7617fbf3d745?auto=format&fit=crop&w=1200&q=80", alt: "" } };
|
|
491
|
+
if (type === "core.text") return { id: uniqueBlockId(type, blocks), type, version: 1, props: { content: "New text block" }, variant: "default", style: {} };
|
|
492
|
+
if (type === "core.image") return { id: uniqueBlockId(type, blocks), type, version: 1, props: { src: "https://images.unsplash.com/photo-1580674684081-7617fbf3d745?auto=format&fit=crop&w=1200&q=80", alt: "" }, variant: "default", style: {} };
|
|
457
493
|
const definition = registry?.getBlock(type);
|
|
458
494
|
if (!definition) throw new Error(`Unknown PageDocument block type: ${type}`);
|
|
459
|
-
return { id: uniqueBlockId(type, blocks), type, version: definition.version, props: definition.defaultProps };
|
|
495
|
+
return { id: uniqueBlockId(type, blocks), type, version: definition.version, props: definition.defaultProps, variant: definition.defaultVariant ?? "default", style: {} };
|
|
460
496
|
}
|
|
461
497
|
function EditorProvider({ initialDocument, registry, loadState = "ready", leaveWarning = "You have unsaved changes.", onDocumentChange, children }) {
|
|
462
498
|
const [history, dispatch] = useReducer(historyReducer, initialDocument, (document) => ({ document, selectedBlockId: document.blocks[0]?.id ?? null, past: [], future: [], device: "desktop", savedDocument: document }));
|
|
463
|
-
const historyRef =
|
|
464
|
-
|
|
499
|
+
const historyRef = useRef3(history);
|
|
500
|
+
useEffect2(() => {
|
|
465
501
|
historyRef.current = history;
|
|
466
502
|
}, [history]);
|
|
467
503
|
const [canvasSelectionRequest, setCanvasSelectionRequest] = useState2(null);
|
|
@@ -486,11 +522,11 @@ function EditorProvider({ initialDocument, registry, loadState = "ready", leaveW
|
|
|
486
522
|
const blocks = current.document.blocks.map((block) => block.id === id ? { ...block, props: { ...block.props, ...props } } : block);
|
|
487
523
|
replace({ ...current.document, blocks }, id);
|
|
488
524
|
}, [editable, replace]);
|
|
489
|
-
|
|
525
|
+
useEffect2(() => {
|
|
490
526
|
onDocumentChange?.(history.document);
|
|
491
527
|
}, [history.document, onDocumentChange]);
|
|
492
528
|
const isDirty = JSON.stringify(history.document) !== JSON.stringify(history.savedDocument);
|
|
493
|
-
|
|
529
|
+
useEffect2(() => {
|
|
494
530
|
if (!isDirty || typeof window === "undefined") return;
|
|
495
531
|
const confirmLeave = (event) => {
|
|
496
532
|
event.preventDefault();
|
|
@@ -499,7 +535,7 @@ function EditorProvider({ initialDocument, registry, loadState = "ready", leaveW
|
|
|
499
535
|
window.addEventListener("beforeunload", confirmLeave);
|
|
500
536
|
return () => window.removeEventListener("beforeunload", confirmLeave);
|
|
501
537
|
}, [isDirty, leaveWarning]);
|
|
502
|
-
|
|
538
|
+
useEffect2(() => {
|
|
503
539
|
if (typeof window === "undefined") return;
|
|
504
540
|
const keydown = (event) => {
|
|
505
541
|
const target = event.target;
|
|
@@ -594,7 +630,7 @@ function EditorProvider({ initialDocument, registry, loadState = "ready", leaveW
|
|
|
594
630
|
markSaved: () => dispatch({ type: "saved" })
|
|
595
631
|
};
|
|
596
632
|
}, [canvasSelectionRequest, confirmCanvasSelection, editable, history, isDirty, loadState, registry, replace, requestCanvasSelection, selectedBlock, updateBlockProps, updateFromCanvas]);
|
|
597
|
-
return /* @__PURE__ */
|
|
633
|
+
return /* @__PURE__ */ jsx5(EditorContext.Provider, { value, children });
|
|
598
634
|
}
|
|
599
635
|
function useEditorContext() {
|
|
600
636
|
const context = useContext(EditorContext);
|
|
@@ -682,7 +718,7 @@ function blockIdAtRelativeY(ids, relativeY) {
|
|
|
682
718
|
}
|
|
683
719
|
|
|
684
720
|
// src/editor/shell/PageDocumentEditorShell.tsx
|
|
685
|
-
import {
|
|
721
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
686
722
|
var deviceLabels2 = { desktop: "desktop", tablet: "tablet", mobile: "mobile" };
|
|
687
723
|
function blockLabel(block, registry) {
|
|
688
724
|
return blockTypeLabel(block.type, registry);
|
|
@@ -693,25 +729,25 @@ function blockTypeLabel(type, registry) {
|
|
|
693
729
|
return registry?.getBlock(type)?.label ?? type;
|
|
694
730
|
}
|
|
695
731
|
function PageDocumentEditorShell(props) {
|
|
696
|
-
return /* @__PURE__ */
|
|
732
|
+
return /* @__PURE__ */ jsx6(EditorProvider, { initialDocument: props.initialDocument, registry: props.registry, loadState: props.loadState, leaveWarning: createAdminI18n(props.adminLocale).t("leaveWarning"), onDocumentChange: props.onDocumentChange, children: /* @__PURE__ */ jsx6(PageDocumentEditor, { ...props }) });
|
|
697
733
|
}
|
|
698
734
|
function PageDocumentEditor({ iframe = true, registry, adminLocale, onSave, onPublish }) {
|
|
699
735
|
const editor = useEditorContext();
|
|
700
736
|
const [blockView, setBlockView] = useState3("blocks");
|
|
701
737
|
const [draggingLibraryType, setDraggingLibraryType] = useState3(null);
|
|
702
|
-
const canvasFrameRef =
|
|
738
|
+
const canvasFrameRef = useRef4(null);
|
|
703
739
|
const [canvasMutationVersion, setCanvasMutationVersion] = useState3(0);
|
|
704
740
|
const [request, setRequest] = useState3("idle");
|
|
705
741
|
const [notice, setNotice] = useState3(null);
|
|
706
742
|
const i18n = createAdminI18n(adminLocale);
|
|
707
743
|
const engineData = useMemo2(() => toEngineData(editor.document, registry), [editor.document, registry]);
|
|
708
744
|
const { confirmCanvasSelection, selectedBlockId, updateBlockProps } = editor;
|
|
709
|
-
const updateFromCanvasInput = useCallback2((id, props) => {
|
|
710
|
-
setCanvasMutationVersion((version) => version + 1);
|
|
745
|
+
const updateFromCanvasInput = useCallback2((id, props, preserveCanvasValue = false) => {
|
|
746
|
+
if (preserveCanvasValue) setCanvasMutationVersion((version) => version + 1);
|
|
711
747
|
updateBlockProps(id, props);
|
|
712
748
|
}, [updateBlockProps]);
|
|
713
749
|
const config = useMemo2(() => createPageDocumentPuckConfig(confirmCanvasSelection, updateFromCanvasInput, selectedBlockId, registry), [confirmCanvasSelection, registry, selectedBlockId, updateFromCanvasInput]);
|
|
714
|
-
if (editor.loadState !== "ready" && editor.loadState !== "success") return /* @__PURE__ */
|
|
750
|
+
if (editor.loadState !== "ready" && editor.loadState !== "success") return /* @__PURE__ */ jsx6(EditorStatus, { state: editor.loadState });
|
|
715
751
|
const blockTypes = ["core.text", "core.image", ...registry?.blocks.map((block) => block.type) ?? []];
|
|
716
752
|
const addFromLibrary = (type, beforeId) => {
|
|
717
753
|
const id = editor.addBlock(type, beforeId);
|
|
@@ -775,12 +811,12 @@ function PageDocumentEditor({ iframe = true, registry, adminLocale, onSave, onPu
|
|
|
775
811
|
setRequest("idle");
|
|
776
812
|
}
|
|
777
813
|
};
|
|
778
|
-
return /* @__PURE__ */
|
|
779
|
-
/* @__PURE__ */
|
|
814
|
+
return /* @__PURE__ */ jsx6(Puck2, { config, data: engineData, iframe: { enabled: iframe }, onChange: (data) => editor.updateFromCanvas(fromEngineData(data, editor.document, registry)), children: /* @__PURE__ */ jsxs4(Puck2.Layout, { children: [
|
|
815
|
+
/* @__PURE__ */ jsx6(CanvasSelectionBridge, { data: engineData, requestedBlockId: editor.canvasSelectionRequest, onCanvasSelected: confirmCanvasSelection, canvasMutationVersion }),
|
|
780
816
|
/* @__PURE__ */ jsxs4("div", { className: "pb-shell pb-shell--v04", "data-testid": "page-document-editor", "data-page-id": editor.document.pageId, "data-dirty": editor.isDirty, "data-editor-state": editor.loadState, children: [
|
|
781
|
-
/* @__PURE__ */
|
|
817
|
+
/* @__PURE__ */ jsx6("header", { className: "pb-header", children: /* @__PURE__ */ jsxs4(InlineStack2, { align: "space-between", blockAlign: "center", gap: "300", wrap: false, children: [
|
|
782
818
|
/* @__PURE__ */ jsxs4("div", { className: "pb-page-title", children: [
|
|
783
|
-
/* @__PURE__ */
|
|
819
|
+
/* @__PURE__ */ jsx6(Text2, { as: "h1", variant: "headingSm", children: editor.document.settings.seoTitle ?? editor.document.pageId }),
|
|
784
820
|
/* @__PURE__ */ jsxs4(Text2, { as: "p", variant: "bodySm", tone: "subdued", children: [
|
|
785
821
|
"PageDocument V",
|
|
786
822
|
editor.document.schemaVersion,
|
|
@@ -789,54 +825,54 @@ function PageDocumentEditor({ iframe = true, registry, adminLocale, onSave, onPu
|
|
|
789
825
|
] })
|
|
790
826
|
] }),
|
|
791
827
|
/* @__PURE__ */ jsxs4(InlineStack2, { gap: "150", blockAlign: "center", wrap: false, children: [
|
|
792
|
-
/* @__PURE__ */
|
|
793
|
-
/* @__PURE__ */
|
|
794
|
-
/* @__PURE__ */
|
|
795
|
-
/* @__PURE__ */
|
|
796
|
-
/* @__PURE__ */
|
|
828
|
+
/* @__PURE__ */ jsx6(Badge2, { tone: editor.isDirty ? "attention" : "success", children: editor.isDirty ? i18n.t("unsaved") : i18n.t("saved") }),
|
|
829
|
+
/* @__PURE__ */ jsx6(Button2, { disabled: !onSave || !editor.isDirty || request !== "idle", onClick: () => void save(), children: request === "saving" ? i18n.t("saving") : i18n.t("save") }),
|
|
830
|
+
/* @__PURE__ */ jsx6(Button2, { variant: "primary", disabled: !onPublish || request !== "idle", onClick: () => void publish(), children: request === "publishing" ? i18n.t("publishing") : i18n.t("publish") }),
|
|
831
|
+
/* @__PURE__ */ jsx6(Button2, { accessibilityLabel: i18n.t("undo"), icon: UndoIcon2, variant: "tertiary", disabled: !editor.actionState.canUndo, onClick: editor.undo }),
|
|
832
|
+
/* @__PURE__ */ jsx6(Button2, { accessibilityLabel: i18n.t("redo"), icon: RedoIcon2, variant: "tertiary", disabled: !editor.actionState.canRedo, onClick: editor.redo })
|
|
797
833
|
] })
|
|
798
834
|
] }) }),
|
|
799
|
-
editor.loadState === "success" ? /* @__PURE__ */
|
|
800
|
-
notice ? /* @__PURE__ */
|
|
835
|
+
editor.loadState === "success" ? /* @__PURE__ */ jsx6(Banner, { tone: "success", children: i18n.t("success") }) : null,
|
|
836
|
+
notice ? /* @__PURE__ */ jsx6(Banner, { tone: notice === "published" ? "success" : "critical", children: i18n.t(notice) }) : null,
|
|
801
837
|
/* @__PURE__ */ jsxs4("div", { className: "pb-workspace pb-workspace--document", children: [
|
|
802
838
|
/* @__PURE__ */ jsxs4("nav", { className: "pb-tool-rail", "aria-label": "\u7F16\u8F91\u5668\u5DE5\u5177", children: [
|
|
803
|
-
/* @__PURE__ */
|
|
804
|
-
/* @__PURE__ */
|
|
839
|
+
/* @__PURE__ */ jsx6(Button2, { accessibilityLabel: i18n.t("blocks"), icon: LayoutSectionIcon2, pressed: blockView === "blocks", variant: "tertiary", onClick: () => setBlockView("blocks") }),
|
|
840
|
+
/* @__PURE__ */ jsx6(Button2, { accessibilityLabel: i18n.t("outline"), icon: MenuIcon2, pressed: blockView === "outline", variant: "tertiary", onClick: () => setBlockView("outline") })
|
|
805
841
|
] }),
|
|
806
842
|
/* @__PURE__ */ jsxs4("aside", { className: "pb-left-panel", "aria-label": "PageDocument \u533A\u5757", children: [
|
|
807
|
-
/* @__PURE__ */
|
|
808
|
-
blockView === "blocks" ? /* @__PURE__ */
|
|
843
|
+
/* @__PURE__ */ jsx6(InlineStack2, { align: "space-between", blockAlign: "center", children: /* @__PURE__ */ jsx6(Text2, { as: "h2", variant: "headingSm", children: blockView === "blocks" ? i18n.t("blocks") : i18n.t("outline") }) }),
|
|
844
|
+
blockView === "blocks" ? /* @__PURE__ */ jsx6("div", { className: "pb-block-list", "data-testid": "blocks-view", "aria-label": "\u533A\u5757\u7C7B\u578B\u5E93", role: "list", onDrop: cancelLibraryDrop, children: blockTypes.map((type) => /* @__PURE__ */ jsxs4("div", { className: `pb-document-block-row pb-document-block-row--library ${editor.selectedBlock?.type === type ? "pb-document-block-row--selected" : ""}`, "data-block-type": type, "data-selected": editor.selectedBlock?.type === type, role: "listitem", draggable: editor.actionState.canAdd, "aria-label": `${blockTypeLabel(type, registry)}\uFF0C\u62D6\u62FD\u81F3\u753B\u5E03\u4EE5\u6DFB\u52A0${editor.selectedBlock?.type === type ? "\uFF0C\u5F53\u524D\u9009\u4E2D\u7C7B\u578B" : ""}`, onDragStart: (event) => {
|
|
809
845
|
event.dataTransfer.setData("application/x-page-document-block", type);
|
|
810
846
|
event.dataTransfer.effectAllowed = "copy";
|
|
811
847
|
setDraggingLibraryType(type);
|
|
812
848
|
}, onDragEnd: () => setDraggingLibraryType(null), children: [
|
|
813
849
|
/* @__PURE__ */ jsxs4("span", { className: "pb-library-block-title", children: [
|
|
814
|
-
/* @__PURE__ */
|
|
815
|
-
/* @__PURE__ */
|
|
850
|
+
/* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockTypeLabel(type, registry) }),
|
|
851
|
+
/* @__PURE__ */ jsx6("span", { className: "pb-library-block-drag-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(DragHandleIcon2, {}) })
|
|
816
852
|
] }),
|
|
817
|
-
/* @__PURE__ */
|
|
818
|
-
] }, type)) }) : editor.document.blocks.length === 0 ? /* @__PURE__ */
|
|
819
|
-
/* @__PURE__ */
|
|
820
|
-
/* @__PURE__ */
|
|
853
|
+
/* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: type })
|
|
854
|
+
] }, type)) }) : editor.document.blocks.length === 0 ? /* @__PURE__ */ jsx6(Text2, { as: "p", tone: "subdued", children: i18n.t("empty") }) : /* @__PURE__ */ jsx6("div", { className: "pb-block-list", "data-testid": "outline-view", children: editor.document.blocks.map((block) => /* @__PURE__ */ jsxs4("button", { type: "button", className: `pb-document-block-row pb-document-block-row--library ${block.id === editor.selectedBlockId ? "pb-document-block-row--selected" : ""}`, "aria-pressed": block.id === editor.selectedBlockId, onClick: () => editor.requestCanvasSelection(block.id), children: [
|
|
855
|
+
/* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockLabel(block, registry) }),
|
|
856
|
+
/* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: block.id })
|
|
821
857
|
] }, block.id)) })
|
|
822
858
|
] }),
|
|
823
859
|
/* @__PURE__ */ jsxs4("main", { className: "pb-canvas-area", children: [
|
|
824
|
-
/* @__PURE__ */
|
|
860
|
+
/* @__PURE__ */ jsx6("div", { className: "pb-canvas-toolbar", children: /* @__PURE__ */ jsx6(ButtonGroup2, { variant: "segmented", children: Object.keys(deviceLabels2).map((device) => /* @__PURE__ */ jsx6(Button2, { pressed: editor.device === device, onClick: () => editor.setDevice(device), children: i18n.t(deviceLabels2[device]) }, device)) }) }),
|
|
825
861
|
/* @__PURE__ */ jsxs4("div", { className: "pb-canvas-stage", children: [
|
|
826
|
-
/* @__PURE__ */
|
|
862
|
+
/* @__PURE__ */ jsx6("div", { ref: canvasFrameRef, className: `pb-canvas-frame pb-canvas-frame--${editor.device}`, "data-device": editor.device, children: /* @__PURE__ */ jsx6(Puck2.Preview, {}) }),
|
|
827
863
|
draggingLibraryType ? /* @__PURE__ */ jsxs4("div", { className: "pb-canvas-drop-target", "data-testid": "canvas-drop-target", role: "region", "aria-label": "\u533A\u5757\u6295\u653E\u533A", onDragOver: (event) => event.preventDefault(), onDrop: dropFromLibrary, children: [
|
|
828
864
|
"\u677E\u5F00\u4EE5\u6DFB\u52A0 ",
|
|
829
865
|
blockTypeLabel(draggingLibraryType, registry)
|
|
830
866
|
] }) : null,
|
|
831
867
|
editor.selectedBlock ? /* @__PURE__ */ jsxs4("div", { className: "pb-canvas-overlay", "aria-label": `\u5DF2\u9009\u62E9 ${blockLabel(editor.selectedBlock, registry)}`, children: [
|
|
832
|
-
/* @__PURE__ */
|
|
833
|
-
/* @__PURE__ */
|
|
868
|
+
/* @__PURE__ */ jsx6("span", { children: blockLabel(editor.selectedBlock, registry) }),
|
|
869
|
+
/* @__PURE__ */ jsx6("span", { children: "Selected" })
|
|
834
870
|
] }) : null
|
|
835
871
|
] })
|
|
836
872
|
] }),
|
|
837
873
|
/* @__PURE__ */ jsxs4("aside", { className: "pb-right-panel", "aria-label": "PageDocument \u5C5E\u6027", children: [
|
|
838
|
-
/* @__PURE__ */
|
|
839
|
-
editor.selectedBlock ? /* @__PURE__ */
|
|
874
|
+
/* @__PURE__ */ jsx6(Text2, { as: "h2", variant: "headingSm", children: i18n.t("properties") }),
|
|
875
|
+
editor.selectedBlock ? /* @__PURE__ */ jsx6(DocumentInspector, { block: editor.selectedBlock, registry, disabled: !editor.actionState.canEdit, onChange: (props) => editor.updateBlockProps(editor.selectedBlock.id, props) }) : /* @__PURE__ */ jsx6(Text2, { as: "p", tone: "subdued", children: i18n.t("selectBlock") })
|
|
840
876
|
] })
|
|
841
877
|
] })
|
|
842
878
|
] })
|
|
@@ -844,11 +880,11 @@ function PageDocumentEditor({ iframe = true, registry, adminLocale, onSave, onPu
|
|
|
844
880
|
}
|
|
845
881
|
function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canvasMutationVersion }) {
|
|
846
882
|
const puck = usePuck();
|
|
847
|
-
const lastSelectedId =
|
|
848
|
-
const lastSyncedData =
|
|
849
|
-
const lastCanvasMutationVersion =
|
|
883
|
+
const lastSelectedId = useRef4(null);
|
|
884
|
+
const lastSyncedData = useRef4(null);
|
|
885
|
+
const lastCanvasMutationVersion = useRef4(0);
|
|
850
886
|
const serializedData = JSON.stringify(data);
|
|
851
|
-
|
|
887
|
+
useEffect3(() => {
|
|
852
888
|
if (lastSyncedData.current === serializedData) return;
|
|
853
889
|
lastSyncedData.current = serializedData;
|
|
854
890
|
if (canvasMutationVersion > lastCanvasMutationVersion.current) {
|
|
@@ -857,13 +893,13 @@ function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canva
|
|
|
857
893
|
}
|
|
858
894
|
puck.dispatch({ type: "setData", data });
|
|
859
895
|
}, [canvasMutationVersion, data, puck, serializedData]);
|
|
860
|
-
|
|
896
|
+
useEffect3(() => {
|
|
861
897
|
if (!requestedBlockId) return;
|
|
862
898
|
const selector = puck.getSelectorForId(requestedBlockId);
|
|
863
899
|
if (selector) puck.dispatch({ type: "setUi", ui: { itemSelector: selector } });
|
|
864
900
|
}, [puck, requestedBlockId]);
|
|
865
901
|
const selectedId = typeof puck.selectedItem?.props.id === "string" ? puck.selectedItem.props.id : null;
|
|
866
|
-
|
|
902
|
+
useEffect3(() => {
|
|
867
903
|
if (selectedId && lastSelectedId.current !== selectedId) {
|
|
868
904
|
lastSelectedId.current = selectedId;
|
|
869
905
|
onCanvasSelected(selectedId);
|
|
@@ -875,22 +911,62 @@ function EditorStatus({ state }) {
|
|
|
875
911
|
const i18n = createAdminI18n();
|
|
876
912
|
const tone = state === "error" ? "critical" : state === "disabled" ? "warning" : "info";
|
|
877
913
|
const message = state === "loading" ? i18n.t("loading") : state === "empty" ? i18n.t("empty") : state === "error" ? i18n.t("error") : i18n.t("disabled");
|
|
878
|
-
return /* @__PURE__ */
|
|
914
|
+
return /* @__PURE__ */ jsx6("div", { className: "pb-editor-status", "data-testid": "page-document-editor-state", "data-editor-state": state, children: /* @__PURE__ */ jsx6(Banner, { tone, title: message, children: state === "disabled" ? i18n.t("disabled") : message }) });
|
|
915
|
+
}
|
|
916
|
+
function InspectorSection({ title, children, defaultOpen = true }) {
|
|
917
|
+
return /* @__PURE__ */ jsxs4("details", { className: "pb-inspector-section", open: defaultOpen, children: [
|
|
918
|
+
/* @__PURE__ */ jsxs4("summary", { children: [
|
|
919
|
+
/* @__PURE__ */ jsx6("span", { children: title }),
|
|
920
|
+
/* @__PURE__ */ jsx6("span", { "aria-hidden": "true", children: "\u2304" })
|
|
921
|
+
] }),
|
|
922
|
+
/* @__PURE__ */ jsx6("div", { className: "pb-inspector-section__body", children })
|
|
923
|
+
] });
|
|
924
|
+
}
|
|
925
|
+
function InspectorTextControl({ label, value, control, disabled, onChange }) {
|
|
926
|
+
const stringValue = typeof value === "string" ? value : "";
|
|
927
|
+
return /* @__PURE__ */ jsx6(TextField, { label, labelHidden: true, value: stringValue, onChange, autoComplete: "off", disabled, multiline: control === "textarea" ? 4 : false, type: control === "url" ? "url" : "text" });
|
|
928
|
+
}
|
|
929
|
+
function InspectorField({ name, field, value, registry, disabled, onChange }) {
|
|
930
|
+
const label = field.label ?? name;
|
|
931
|
+
const Field = registry?.getField(field.field)?.component;
|
|
932
|
+
return /* @__PURE__ */ jsxs4("div", { className: "pb-inspector-field", "data-control": field.control ?? "custom", children: [
|
|
933
|
+
/* @__PURE__ */ jsxs4("div", { className: "pb-inspector-field__heading", children: [
|
|
934
|
+
/* @__PURE__ */ jsx6(Text2, { as: "p", variant: "bodySm", fontWeight: "semibold", children: label }),
|
|
935
|
+
field.description ? /* @__PURE__ */ jsx6(Text2, { as: "p", variant: "bodySm", tone: "subdued", children: field.description }) : null
|
|
936
|
+
] }),
|
|
937
|
+
field.control ? /* @__PURE__ */ jsx6(InspectorTextControl, { label, value, control: field.control, disabled, onChange: (next) => onChange(next) }) : Field ? /* @__PURE__ */ jsx6(Field, { value, onChange }) : null
|
|
938
|
+
] });
|
|
939
|
+
}
|
|
940
|
+
function inspectorFieldConfig(name, field) {
|
|
941
|
+
const key = name.toLowerCase();
|
|
942
|
+
const group = /(?:href|url)/.test(key) ? "Links" : /(?:default|shipment|query|hide)/.test(key) ? "Tracking settings" : /(?:id|variant|theme)/.test(key) ? "Advanced" : "Content";
|
|
943
|
+
const description = field.description ?? (field.control === "textarea" ? "\u9002\u5408\u8F83\u957F\u6216\u591A\u884C\u7684\u5C55\u793A\u6587\u6848\u3002" : field.control === "url" ? "\u4F7F\u7528\u7AD9\u5185\u76F8\u5BF9\u8DEF\u5F84\u6216 HTTPS \u5730\u5740\u3002" : key.includes("shipment") ? "\u591A\u4E2A\u5305\u88F9\u6807\u7B7E\u4F7F\u7528 | \u5206\u9694\u3002" : key.includes("default") ? "\u4EC5\u7528\u4E8E\u7F16\u8F91\u5668\u548C\u7A7A\u72B6\u6001\u9884\u89C8\u3002" : void 0);
|
|
944
|
+
return { ...field, group: field.group ?? group, description };
|
|
879
945
|
}
|
|
880
946
|
function DocumentInspector({ block, registry, disabled, onChange }) {
|
|
881
947
|
const definition = registry?.getBlock(block.type);
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
948
|
+
const groupedFields = definition ? Object.entries(definition.fields).reduce((groups, entry) => {
|
|
949
|
+
const [name, field] = entry;
|
|
950
|
+
const configuredField = inspectorFieldConfig(name, field);
|
|
951
|
+
const group = configuredField.group ?? "Content";
|
|
952
|
+
(groups[group] ??= []).push([name, configuredField]);
|
|
953
|
+
return groups;
|
|
954
|
+
}, {}) : {};
|
|
955
|
+
return /* @__PURE__ */ jsxs4("div", { className: "pb-inspector", "data-testid": "document-inspector", children: [
|
|
956
|
+
/* @__PURE__ */ jsxs4("header", { className: "pb-inspector__header", children: [
|
|
957
|
+
/* @__PURE__ */ jsx6(Badge2, { children: block.type }),
|
|
958
|
+
/* @__PURE__ */ jsxs4("div", { children: [
|
|
959
|
+
/* @__PURE__ */ jsx6(Text2, { as: "p", variant: "headingSm", children: blockLabel(block, registry) }),
|
|
960
|
+
/* @__PURE__ */ jsx6(Text2, { as: "p", variant: "bodySm", tone: "subdued", children: definition?.category ?? "Core block" })
|
|
961
|
+
] })
|
|
962
|
+
] }),
|
|
963
|
+
block.type === "core.text" ? /* @__PURE__ */ jsx6(InspectorSection, { title: "Content", children: /* @__PURE__ */ jsx6(InspectorField, { name: "content", field: { field: "", label: "\u6587\u672C\u5185\u5BB9", control: "textarea", description: "\u652F\u6301\u8F83\u957F\u7684\u6B63\u6587\u5185\u5BB9\u3002" }, value: block.props.content, registry, disabled, onChange: (content) => onChange({ content }) }) }) : null,
|
|
964
|
+
block.type === "core.image" ? /* @__PURE__ */ jsxs4(InspectorSection, { title: "Image", children: [
|
|
965
|
+
/* @__PURE__ */ jsx6(InspectorField, { name: "src", field: { field: "", label: "\u56FE\u7247 URL", control: "url", description: "\u4F7F\u7528 HTTPS \u56FE\u7247\u5730\u5740\u3002" }, value: block.props.src, registry, disabled, onChange: (src) => onChange({ src }) }),
|
|
966
|
+
/* @__PURE__ */ jsx6(InspectorField, { name: "alt", field: { field: "", label: "\u66FF\u4EE3\u6587\u672C", control: "text", description: "\u7528\u4E8E\u65E0\u969C\u788D\u9605\u8BFB\u548C\u56FE\u7247\u52A0\u8F7D\u5931\u8D25\u573A\u666F\u3002" }, value: block.props.alt, registry, disabled, onChange: (alt) => onChange({ alt }) })
|
|
889
967
|
] }) : null,
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
return Field ? /* @__PURE__ */ jsx5(Field, { value: block.props[name], onChange: (value) => onChange({ [name]: value }) }, name) : null;
|
|
893
|
-
}) : null
|
|
968
|
+
Object.entries(groupedFields).map(([group, fields]) => /* @__PURE__ */ jsx6(InspectorSection, { title: group, defaultOpen: group !== "Advanced", children: fields.map(([name, field]) => /* @__PURE__ */ jsx6(InspectorField, { name, field, value: block.props[name], registry, disabled, onChange: (value) => onChange({ ...block.props, [name]: value }) }, name)) }, group)),
|
|
969
|
+
definition ? /* @__PURE__ */ jsx6("p", { className: "pb-inspector__hint", children: "\u753B\u5E03\u4E2D\u5E26\u865A\u7EBF\u8FB9\u6846\u7684\u5185\u5BB9\u53EF\u76F4\u63A5\u7F16\u8F91\u3002" }) : null
|
|
894
970
|
] });
|
|
895
971
|
}
|
|
896
972
|
export {
|
|
@@ -899,13 +975,19 @@ export {
|
|
|
899
975
|
ExtensionRegistry,
|
|
900
976
|
ExtensionRegistryError,
|
|
901
977
|
PageDocumentEditorShell,
|
|
978
|
+
TemplateRegistry,
|
|
902
979
|
WebRenderer,
|
|
903
980
|
createAdminI18n,
|
|
904
981
|
createExtensionRegistry,
|
|
905
982
|
createPageDocument,
|
|
983
|
+
createTemplateRegistry,
|
|
906
984
|
fromEngineData,
|
|
985
|
+
mergeThemeTokens,
|
|
907
986
|
migratePageDocument,
|
|
987
|
+
normalizeThemeTokens,
|
|
988
|
+
systemThemeTokens,
|
|
908
989
|
toEngineData,
|
|
990
|
+
toThemeStyle,
|
|
909
991
|
useEditorContext,
|
|
910
992
|
validatePageDocument
|
|
911
993
|
};
|
package/dist/renderer.d.ts
CHANGED
package/dist/renderer.js
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { WebRenderer, WebRendererProps } from './renderer.js';
|
|
2
|
+
export { BlockDefinition, BlockEditorProps, BlockVariantDefinition, DataSourceDefinition, EditorAction, EditorActionPosition, ExtensionActionContext, ExtensionRegistry, ExtensionRegistryError, ExtensionRegistryErrorCode, ExtensionRegistryOptions, ExtensionTarget, FieldConfig, FieldDefinition, FieldProps, LifecycleHooks, PageBuilderExtension, RendererDefinition, TemplateDefinition, TemplateRegistry, TemplateSource, UISlotContribution, UISlotName, ValidationIssue, createExtensionRegistry, createTemplateRegistry } from './extensions.js';
|
|
3
|
+
export { BlockNode, BlockStyleOverrides, DataBinding, JsonPrimitive, JsonValue, PageDocument, PageDocumentIssue, PageDocumentMigration, PageDocumentSchemaVersion, PageDocumentValidation, PageSettings, RenderTarget, createPageDocument, migratePageDocument, validatePageDocument } from './schema.js';
|
|
4
|
+
export { ThemeTokenName, ThemeTokenValidation, ThemeTokens, mergeThemeTokens, normalizeThemeTokens, systemThemeTokens, toThemeStyle } from './theme.js';
|
|
5
|
+
import 'react';
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ExtensionRegistry,
|
|
3
|
+
ExtensionRegistryError,
|
|
4
|
+
TemplateRegistry,
|
|
5
|
+
createExtensionRegistry,
|
|
6
|
+
createTemplateRegistry
|
|
7
|
+
} from "./chunk-PUTYZI7B.js";
|
|
8
|
+
import {
|
|
9
|
+
WebRenderer
|
|
10
|
+
} from "./chunk-KYXIXIWK.js";
|
|
11
|
+
import {
|
|
12
|
+
createPageDocument,
|
|
13
|
+
migratePageDocument,
|
|
14
|
+
validatePageDocument
|
|
15
|
+
} from "./chunk-OBN4DMOR.js";
|
|
16
|
+
import {
|
|
17
|
+
mergeThemeTokens,
|
|
18
|
+
normalizeThemeTokens,
|
|
19
|
+
systemThemeTokens,
|
|
20
|
+
toThemeStyle
|
|
21
|
+
} from "./chunk-YK2AUHF6.js";
|
|
22
|
+
export {
|
|
23
|
+
ExtensionRegistry,
|
|
24
|
+
ExtensionRegistryError,
|
|
25
|
+
TemplateRegistry,
|
|
26
|
+
WebRenderer,
|
|
27
|
+
createExtensionRegistry,
|
|
28
|
+
createPageDocument,
|
|
29
|
+
createTemplateRegistry,
|
|
30
|
+
mergeThemeTokens,
|
|
31
|
+
migratePageDocument,
|
|
32
|
+
normalizeThemeTokens,
|
|
33
|
+
systemThemeTokens,
|
|
34
|
+
toThemeStyle,
|
|
35
|
+
validatePageDocument
|
|
36
|
+
};
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { ThemeTokens } from './theme.js';
|
|
2
|
+
|
|
1
3
|
type JsonPrimitive = string | number | boolean | null;
|
|
2
4
|
type JsonValue = JsonPrimitive | JsonValue[] | {
|
|
3
5
|
[key: string]: JsonValue;
|
|
4
6
|
};
|
|
5
7
|
type RenderTarget = "web" | "email";
|
|
8
|
+
type PageDocumentSchemaVersion = 1;
|
|
6
9
|
type PageSettings = {
|
|
7
10
|
locale: string;
|
|
8
11
|
seoTitle?: string;
|
|
@@ -11,19 +14,25 @@ type DataBinding = {
|
|
|
11
14
|
source: string;
|
|
12
15
|
params?: Record<string, JsonValue>;
|
|
13
16
|
};
|
|
17
|
+
/** Token-only block styling. Arbitrary CSS properties are deliberately not persisted. */
|
|
18
|
+
type BlockStyleOverrides = ThemeTokens;
|
|
14
19
|
type BlockNode = {
|
|
15
20
|
id: string;
|
|
16
21
|
type: string;
|
|
17
22
|
version: number;
|
|
18
23
|
props: Record<string, JsonValue>;
|
|
24
|
+
variant: string;
|
|
25
|
+
style: BlockStyleOverrides;
|
|
19
26
|
slots?: Record<string, BlockNode[]>;
|
|
20
27
|
binding?: DataBinding;
|
|
21
28
|
};
|
|
22
29
|
type PageDocument = {
|
|
23
|
-
schemaVersion:
|
|
30
|
+
schemaVersion: PageDocumentSchemaVersion;
|
|
24
31
|
pageId: string;
|
|
25
32
|
target: RenderTarget;
|
|
26
33
|
templateId?: string;
|
|
34
|
+
templateVersion?: number;
|
|
35
|
+
theme: ThemeTokens;
|
|
27
36
|
root: Record<string, JsonValue>;
|
|
28
37
|
blocks: BlockNode[];
|
|
29
38
|
settings: PageSettings;
|
|
@@ -47,13 +56,13 @@ type PageDocumentMigration = {
|
|
|
47
56
|
success: false;
|
|
48
57
|
issues: PageDocumentIssue[];
|
|
49
58
|
};
|
|
50
|
-
declare function createPageDocument(input: Partial<PageDocument
|
|
59
|
+
declare function createPageDocument(input: Omit<Partial<PageDocument>, "schemaVersion" | "blocks" | "theme"> & {
|
|
60
|
+
pageId: string;
|
|
61
|
+
blocks?: Array<Omit<BlockNode, "variant" | "style"> & Partial<Pick<BlockNode, "variant" | "style">>>;
|
|
62
|
+
theme?: ThemeTokens;
|
|
63
|
+
}): PageDocument;
|
|
51
64
|
declare function validatePageDocument(value: unknown): PageDocumentValidation;
|
|
52
|
-
/**
|
|
53
|
-
* Accept the pre-versioned shape produced by the early Demo and normalize it
|
|
54
|
-
* to the first persisted PageDocument schema. Future schema migrations belong
|
|
55
|
-
* here so storage callers have one validation boundary.
|
|
56
|
-
*/
|
|
65
|
+
/** V0.6.1 starts from this schema; no pre-integration document compatibility is needed. */
|
|
57
66
|
declare function migratePageDocument(value: unknown): PageDocumentMigration;
|
|
58
67
|
|
|
59
|
-
export { type BlockNode, type DataBinding, type JsonPrimitive, type JsonValue, type PageDocument, type PageDocumentIssue, type PageDocumentMigration, type PageDocumentValidation, type PageSettings, type RenderTarget, createPageDocument, migratePageDocument, validatePageDocument };
|
|
68
|
+
export { type BlockNode, type BlockStyleOverrides, type DataBinding, type JsonPrimitive, type JsonValue, type PageDocument, type PageDocumentIssue, type PageDocumentMigration, type PageDocumentSchemaVersion, type PageDocumentValidation, type PageSettings, type RenderTarget, createPageDocument, migratePageDocument, validatePageDocument };
|
package/dist/schema.js
CHANGED
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type ThemeTokenName = "color.background" | "color.surface" | "color.text" | "color.muted" | "color.primary" | "color.border" | "font.family" | "font.size" | "radius" | "spacing";
|
|
2
|
+
type ThemeTokens = Partial<Record<ThemeTokenName, string>>;
|
|
3
|
+
type ThemeTokenValidation = {
|
|
4
|
+
success: true;
|
|
5
|
+
data: ThemeTokens;
|
|
6
|
+
} | {
|
|
7
|
+
success: false;
|
|
8
|
+
};
|
|
9
|
+
declare const systemThemeTokens: Readonly<Required<ThemeTokens>>;
|
|
10
|
+
declare function normalizeThemeTokens(value: unknown): ThemeTokenValidation;
|
|
11
|
+
declare function mergeThemeTokens(...layers: Array<ThemeTokens | undefined>): Required<ThemeTokens>;
|
|
12
|
+
declare function toThemeStyle(tokens: ThemeTokens): Record<string, string>;
|
|
13
|
+
|
|
14
|
+
export { type ThemeTokenName, type ThemeTokenValidation, type ThemeTokens, mergeThemeTokens, normalizeThemeTokens, systemThemeTokens, toThemeStyle };
|
package/dist/theme.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@standhigher/puck-page-builder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "React PageDocument editor, extension registry, and web renderer for BestTrack page experiences.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -40,6 +40,14 @@
|
|
|
40
40
|
"types": "./dist/schema.d.ts",
|
|
41
41
|
"import": "./dist/schema.js"
|
|
42
42
|
},
|
|
43
|
+
"./runtime": {
|
|
44
|
+
"types": "./dist/runtime.d.ts",
|
|
45
|
+
"import": "./dist/runtime.js"
|
|
46
|
+
},
|
|
47
|
+
"./theme": {
|
|
48
|
+
"types": "./dist/theme.d.ts",
|
|
49
|
+
"import": "./dist/theme.js"
|
|
50
|
+
},
|
|
43
51
|
"./styles.css": "./src/styles.css"
|
|
44
52
|
},
|
|
45
53
|
"publishConfig": {
|
|
@@ -62,7 +70,7 @@
|
|
|
62
70
|
"typescript": "^5.9.3"
|
|
63
71
|
},
|
|
64
72
|
"scripts": {
|
|
65
|
-
"build": "tsup src/index.ts src/renderer.ts src/extensions.ts src/schema.ts --format esm --dts --clean",
|
|
73
|
+
"build": "tsup src/index.ts src/renderer.ts src/runtime.ts src/extensions.ts src/schema.ts src/theme.ts --format esm --dts --clean && node scripts/assert-runtime-boundary.mjs",
|
|
66
74
|
"typecheck": "tsc --noEmit"
|
|
67
75
|
}
|
|
68
76
|
}
|
package/src/styles.css
CHANGED
|
@@ -13,7 +13,30 @@ button:focus-visible, [role="button"]:focus-visible { outline: 2px solid var(--b
|
|
|
13
13
|
.pb-tool-rail .Polaris-Button { min-width: 44px; min-height: 44px; }
|
|
14
14
|
.pb-left-panel, .pb-right-panel { display: flex; flex-direction: column; gap: var(--builder-space-4); min-width: 0; padding: var(--builder-space-4); overflow: auto; background: var(--builder-surface); }
|
|
15
15
|
.pb-left-panel { border-right: 1px solid var(--builder-border); }
|
|
16
|
-
.pb-right-panel { border-left: 1px solid var(--builder-border); }
|
|
16
|
+
.pb-right-panel { border-left: 1px solid var(--builder-border); background: #f8fafc; }
|
|
17
|
+
.pb-right-panel > h2 { margin: 0; color: #475569; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; }
|
|
18
|
+
.pb-inspector { display: grid; gap: var(--builder-space-3); }
|
|
19
|
+
.pb-inspector__header { display: grid; gap: var(--builder-space-2); padding: var(--builder-space-3); border: 1px solid #e2e8f0; border-radius: 12px; background: #fff; box-shadow: 0 1px 2px rgb(15 23 42 / 4%); }
|
|
20
|
+
.pb-inspector__header .Polaris-Badge { width: fit-content; max-width: 100%; overflow: hidden; color: #1d4ed8; background: #eff6ff; font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
|
|
21
|
+
.pb-inspector__header p { margin: 0; }
|
|
22
|
+
.pb-inspector-section { overflow: hidden; border: 1px solid #e2e8f0; border-radius: 12px; background: #fff; box-shadow: 0 1px 2px rgb(15 23 42 / 3%); }
|
|
23
|
+
.pb-inspector-section > summary { display: flex; align-items: center; justify-content: space-between; min-height: 44px; padding: 0 var(--builder-space-3); color: #0f172a; font-size: 13px; font-weight: 650; cursor: pointer; list-style: none; }
|
|
24
|
+
.pb-inspector-section > summary::-webkit-details-marker { display: none; }
|
|
25
|
+
.pb-inspector-section > summary > span:last-child { color: #64748b; font-size: 16px; transition: transform 140ms ease; }
|
|
26
|
+
.pb-inspector-section:not([open]) > summary > span:last-child { transform: rotate(-90deg); }
|
|
27
|
+
.pb-inspector-section__body { display: grid; gap: var(--builder-space-3); padding: 0 var(--builder-space-3) var(--builder-space-3); border-top: 1px solid #f1f5f9; }
|
|
28
|
+
.pb-inspector-field { display: grid; gap: 6px; }
|
|
29
|
+
.pb-inspector-field__heading { display: grid; gap: 2px; }
|
|
30
|
+
.pb-inspector-field__heading p { margin: 0; }
|
|
31
|
+
.pb-inspector-field__heading p:first-child { color: #334155; font-size: 13px; }
|
|
32
|
+
.pb-inspector-field__heading p:last-child { color: #64748b; font-size: 12px; line-height: 1.45; }
|
|
33
|
+
.pb-inspector-field input, .pb-inspector-field textarea { width: 100%; min-height: 40px; padding: 9px 11px; border: 1px solid #cbd5e1; border-radius: 8px; outline: 0; color: #0f172a; background: #fff; font: inherit; font-size: 14px; line-height: 1.4; transition: border-color 140ms ease, box-shadow 140ms ease; }
|
|
34
|
+
.pb-inspector-field textarea { min-height: 96px; resize: vertical; }
|
|
35
|
+
.pb-inspector-field input[type="url"] { color: #334155; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
|
|
36
|
+
.pb-inspector-field input:hover, .pb-inspector-field textarea:hover { border-color: #94a3b8; }
|
|
37
|
+
.pb-inspector-field input:focus, .pb-inspector-field textarea:focus { border-color: #2563eb; box-shadow: 0 0 0 3px rgb(37 99 235 / 12%); }
|
|
38
|
+
.pb-inspector-field input:disabled, .pb-inspector-field textarea:disabled { color: #94a3b8; background: #f8fafc; cursor: not-allowed; }
|
|
39
|
+
.pb-inspector__hint { margin: 0; padding: 0 var(--builder-space-2); color: #64748b; font-size: 12px; line-height: 1.5; }
|
|
17
40
|
.pb-panel--closed { display: none; }
|
|
18
41
|
.pb-block-list { display: flex; flex: 1; flex-direction: column; gap: var(--builder-space-2); min-height: 0; }
|
|
19
42
|
.pb-block-row { display: flex; align-items: center; gap: var(--builder-space-2); padding: var(--builder-space-2); border: 1px solid transparent; border-radius: var(--builder-panel-radius); background: transparent; transition: background-color 120ms ease, border-color 120ms ease; }
|
package/dist/chunk-72MFV6J7.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// src/renderer/web/WebRenderer.tsx
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
function WebRenderer({ document, className, registry }) {
|
|
4
|
-
return /* @__PURE__ */ jsx("main", { className: className ?? "pb-web-renderer", "data-page-id": document.pageId, lang: document.settings.locale, children: document.blocks.map((block) => {
|
|
5
|
-
if (block.type === "core.text") return /* @__PURE__ */ jsx("section", { "data-block-id": block.id, className: "pb-web-renderer__text", children: /* @__PURE__ */ jsx("p", { children: typeof block.props.content === "string" ? block.props.content : "" }) }, block.id);
|
|
6
|
-
if (block.type === "core.image") return /* @__PURE__ */ jsx("figure", { "data-block-id": block.id, className: "pb-web-renderer__image", children: /* @__PURE__ */ jsx("img", { src: typeof block.props.src === "string" ? block.props.src : "", alt: typeof block.props.alt === "string" ? block.props.alt : "" }) }, block.id);
|
|
7
|
-
const BlockRenderer = registry?.getBlock(block.type)?.render.web;
|
|
8
|
-
if (BlockRenderer) return /* @__PURE__ */ jsx("section", { "data-block-id": block.id, children: /* @__PURE__ */ jsx(BlockRenderer, { ...block.props }) }, block.id);
|
|
9
|
-
return null;
|
|
10
|
-
}) });
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
WebRenderer
|
|
15
|
-
};
|