@standhigher/puck-page-builder 0.1.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/index.js ADDED
@@ -0,0 +1,911 @@
1
+ import {
2
+ ExtensionRegistry,
3
+ ExtensionRegistryError,
4
+ createExtensionRegistry
5
+ } from "./chunk-RGB53WE6.js";
6
+ import {
7
+ WebRenderer
8
+ } from "./chunk-72MFV6J7.js";
9
+ import {
10
+ createPageDocument,
11
+ migratePageDocument,
12
+ validatePageDocument
13
+ } from "./chunk-G2LNCKYO.js";
14
+
15
+ // src/editor/shell/EditorShell.tsx
16
+ import { Puck } from "@puckeditor/core";
17
+ import {
18
+ Badge,
19
+ BlockStack,
20
+ Box,
21
+ Button,
22
+ ButtonGroup,
23
+ Divider,
24
+ InlineStack,
25
+ Modal,
26
+ Select,
27
+ Text,
28
+ Tooltip
29
+ } from "@shopify/polaris";
30
+ import {
31
+ ArrowLeftIcon,
32
+ DeleteIcon,
33
+ DragHandleIcon,
34
+ DuplicateIcon,
35
+ LayoutSectionIcon,
36
+ MenuIcon,
37
+ PlusIcon,
38
+ RedoIcon,
39
+ UndoIcon,
40
+ ViewIcon,
41
+ XIcon
42
+ } from "@shopify/polaris-icons";
43
+ import { useRef, useState } from "react";
44
+
45
+ // src/adapters/puck/demo-preview.tsx
46
+ import { jsx, jsxs } from "react/jsx-runtime";
47
+ function toDemoPuckData(blocks) {
48
+ return {
49
+ root: {},
50
+ content: blocks.map((block) => ({
51
+ type: block.type,
52
+ props: { id: block.id, title: block.title, body: block.body }
53
+ }))
54
+ };
55
+ }
56
+ function createDemoPuckConfig(onSelect) {
57
+ const renderBlock = (kind) => (props) => {
58
+ const id = typeof props.id === "string" ? props.id : "unknown-block";
59
+ const title = typeof props.title === "string" ? props.title : "Untitled block";
60
+ const body = typeof props.body === "string" ? props.body : "";
61
+ return /* @__PURE__ */ jsxs(
62
+ "section",
63
+ {
64
+ "aria-label": `Select ${kind} in canvas`,
65
+ className: `bt-storefront__section bt-storefront__section--${kind.toLowerCase()}`,
66
+ "data-block-id": id,
67
+ onClick: () => onSelect(id),
68
+ onKeyDown: (event) => {
69
+ if (event.key === "Enter" || event.key === " ") onSelect(id);
70
+ },
71
+ role: "button",
72
+ tabIndex: 0,
73
+ children: [
74
+ /* @__PURE__ */ jsx("p", { className: "bt-storefront__eyebrow", children: kind }),
75
+ kind === "Hero" ? /* @__PURE__ */ jsx("h1", { children: title }) : /* @__PURE__ */ jsx("h2", { children: title }),
76
+ /* @__PURE__ */ jsx("p", { children: body }),
77
+ kind === "TrackingForm" ? /* @__PURE__ */ jsx("div", { className: "bt-storefront__tracking", children: /* @__PURE__ */ jsxs("div", { className: "bt-storefront__form", children: [
78
+ /* @__PURE__ */ jsx("input", { "aria-label": "Tracking number", placeholder: "Enter tracking number" }),
79
+ /* @__PURE__ */ jsx("button", { type: "button", children: "Track order" })
80
+ ] }) }) : null
81
+ ]
82
+ }
83
+ );
84
+ };
85
+ return {
86
+ components: {
87
+ Hero: { render: (props) => /* @__PURE__ */ jsxs("div", { className: "bt-storefront", children: [
88
+ /* @__PURE__ */ jsxs("header", { className: "bt-storefront__header", children: [
89
+ /* @__PURE__ */ jsx("span", { className: "bt-storefront__brand", children: "BESTTRACK" }),
90
+ /* @__PURE__ */ jsx("span", { className: "bt-storefront__link", children: "Help center" })
91
+ ] }),
92
+ renderBlock("Hero")(props)
93
+ ] }) },
94
+ TrackingForm: { render: renderBlock("TrackingForm") },
95
+ Text: { render: renderBlock("Text") }
96
+ }
97
+ };
98
+ }
99
+
100
+ // src/editor/state/types.ts
101
+ var initialBlocks = [
102
+ {
103
+ id: "hero-1",
104
+ type: "Hero",
105
+ label: "\u6B22\u8FCE\u533A\u5757",
106
+ description: "\u9875\u9762\u6807\u9898\u4E0E\u8BF4\u660E",
107
+ title: "Track every order with confidence",
108
+ body: "A local V0.1 preview block rendered by Puck."
109
+ },
110
+ {
111
+ id: "tracking-form-1",
112
+ type: "TrackingForm",
113
+ label: "\u7269\u6D41\u67E5\u8BE2",
114
+ description: "\u67E5\u8BE2\u8868\u5355\u5360\u4F4D",
115
+ title: "Where is my order?",
116
+ body: "Enter a tracking number to view its progress."
117
+ },
118
+ {
119
+ id: "text-1",
120
+ type: "Text",
121
+ label: "\u5E2E\u52A9\u6587\u672C",
122
+ description: "\u5BCC\u6587\u672C\u5185\u5BB9\u5360\u4F4D",
123
+ title: "Need help?",
124
+ body: "Contact the store for order and delivery questions."
125
+ }
126
+ ];
127
+ var initialEditorState = {
128
+ blocks: initialBlocks,
129
+ selectedBlockId: initialBlocks[0].id,
130
+ blockView: "blocks",
131
+ device: "desktop",
132
+ zoom: "auto",
133
+ saveState: "saved",
134
+ previewMode: "editor",
135
+ publishState: "draft",
136
+ editorLocale: "zh-CN",
137
+ pageLocale: "en-US",
138
+ isPickerOpen: false,
139
+ isLeftRailOpen: true,
140
+ isRightPanelOpen: true
141
+ };
142
+
143
+ // src/editor/shell/EditorShell.tsx
144
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
145
+ var deviceLabels = {
146
+ desktop: "Desktop",
147
+ tablet: "Tablet",
148
+ mobile: "Mobile",
149
+ full: "Full"
150
+ };
151
+ var saveLabels = {
152
+ clean: "\u65E0\u672A\u4FDD\u5B58\u53D8\u66F4",
153
+ dirty: "\u672A\u4FDD\u5B58\u53D8\u66F4",
154
+ saving: "\u4FDD\u5B58\u4E2D",
155
+ saved: "\u5DF2\u4FDD\u5B58",
156
+ failed: "\u4FDD\u5B58\u5931\u8D25",
157
+ conflict: "\u5185\u5BB9\u51B2\u7A81"
158
+ };
159
+ function IconButton({
160
+ label,
161
+ icon,
162
+ onClick,
163
+ disabled = false,
164
+ pressed = false
165
+ }) {
166
+ return /* @__PURE__ */ jsx2(Tooltip, { content: label, children: /* @__PURE__ */ jsx2(Button, { accessibilityLabel: label, icon, onClick, disabled, pressed, variant: "tertiary" }) });
167
+ }
168
+ function EditorShell({ initialState, iframe = true }) {
169
+ const [state, setState] = useState({ ...initialEditorState, ...initialState });
170
+ const [undoDepth, setUndoDepth] = useState(0);
171
+ const nextBlockId = useRef(state.blocks.length);
172
+ const selectedBlock = state.blocks.find((block) => block.id === state.selectedBlockId) ?? null;
173
+ const update = (patch) => setState((current) => ({ ...current, ...patch }));
174
+ const selectBlock = (id) => update({ selectedBlockId: id, saveState: "dirty" });
175
+ const puckConfig = createDemoPuckConfig(selectBlock);
176
+ const puckData = toDemoPuckData(state.blocks);
177
+ const addBlock = (type) => {
178
+ const next = {
179
+ Hero: { label: "\u6B22\u8FCE\u533A\u5757", description: "\u9875\u9762\u6807\u9898\u4E0E\u8BF4\u660E", title: "A new welcome", body: "Local demo content." },
180
+ TrackingForm: { label: "\u7269\u6D41\u67E5\u8BE2", description: "\u67E5\u8BE2\u8868\u5355\u5360\u4F4D", title: "Track an order", body: "Use a tracking number." },
181
+ Text: { label: "\u5E2E\u52A9\u6587\u672C", description: "\u5BCC\u6587\u672C\u5185\u5BB9\u5360\u4F4D", title: "Helpful information", body: "Local demo content." }
182
+ }[type];
183
+ const id = `${type.toLowerCase()}-${nextBlockId.current++}`;
184
+ update({ blocks: [...state.blocks, { id, type, ...next }], selectedBlockId: id, saveState: "dirty", isPickerOpen: false });
185
+ };
186
+ const duplicateBlock = (block) => {
187
+ const id = `${block.type.toLowerCase()}-${nextBlockId.current++}`;
188
+ const duplicate = { ...block, id, label: `${block.label} \u526F\u672C` };
189
+ const index = state.blocks.findIndex((item) => item.id === block.id);
190
+ const blocks = [...state.blocks];
191
+ blocks.splice(index + 1, 0, duplicate);
192
+ update({ blocks, selectedBlockId: id, saveState: "dirty" });
193
+ };
194
+ const deleteBlock = (block) => {
195
+ const blocks = state.blocks.filter((item) => item.id !== block.id);
196
+ update({ blocks, selectedBlockId: blocks[0]?.id ?? null, saveState: "dirty" });
197
+ };
198
+ const moveBlock = (id, direction) => {
199
+ const from = state.blocks.findIndex((block) => block.id === id);
200
+ const to = from + direction;
201
+ if (to < 0 || to >= state.blocks.length) return;
202
+ const blocks = [...state.blocks];
203
+ [blocks[from], blocks[to]] = [blocks[to], blocks[from]];
204
+ update({ blocks, saveState: "dirty" });
205
+ };
206
+ const savePresentationState = () => {
207
+ update({ saveState: "saving" });
208
+ window.setTimeout(() => update({ saveState: "saved" }), 350);
209
+ };
210
+ return /* @__PURE__ */ jsx2(Puck, { config: puckConfig, data: puckData, iframe: { enabled: iframe }, children: /* @__PURE__ */ jsx2(Puck.Layout, { children: /* @__PURE__ */ jsxs2("div", { className: "pb-shell", "data-testid": "editor-shell", "data-preview-mode": state.previewMode, children: [
211
+ /* @__PURE__ */ jsx2("header", { className: "pb-header", children: /* @__PURE__ */ jsxs2(InlineStack, { align: "space-between", blockAlign: "center", gap: "300", wrap: false, children: [
212
+ /* @__PURE__ */ jsxs2(InlineStack, { gap: "200", blockAlign: "center", wrap: false, children: [
213
+ /* @__PURE__ */ jsx2(IconButton, { label: "\u8FD4\u56DE\u9875\u9762\u5217\u8868", icon: ArrowLeftIcon }),
214
+ /* @__PURE__ */ jsxs2("div", { className: "pb-page-title", children: [
215
+ /* @__PURE__ */ jsx2(Text, { as: "h1", variant: "headingSm", children: "Tracking page" }),
216
+ /* @__PURE__ */ jsx2(Text, { as: "p", variant: "bodySm", tone: "subdued", children: "English (US)" })
217
+ ] }),
218
+ /* @__PURE__ */ jsx2(Badge, { tone: state.saveState === "failed" || state.saveState === "conflict" ? "critical" : "info", children: saveLabels[state.saveState] })
219
+ ] }),
220
+ /* @__PURE__ */ jsxs2(InlineStack, { gap: "150", blockAlign: "center", wrap: false, children: [
221
+ /* @__PURE__ */ jsx2(IconButton, { label: "\u64A4\u9500", icon: UndoIcon, disabled: undoDepth === 0, onClick: () => setUndoDepth((depth) => Math.max(0, depth - 1)) }),
222
+ /* @__PURE__ */ jsx2(IconButton, { label: "\u6062\u590D", icon: RedoIcon, disabled: true }),
223
+ /* @__PURE__ */ jsx2(Button, { onClick: savePresentationState, children: "\u4FDD\u5B58\u8349\u7A3F" }),
224
+ /* @__PURE__ */ jsx2(Button, { onClick: () => update({ previewMode: "mock-preview" }), icon: ViewIcon, children: "\u9884\u89C8" }),
225
+ /* @__PURE__ */ jsx2(Button, { variant: "primary", disabled: true, children: "\u53D1\u5E03" })
226
+ ] })
227
+ ] }) }),
228
+ /* @__PURE__ */ jsxs2("div", { className: "pb-workspace", children: [
229
+ /* @__PURE__ */ jsxs2("nav", { className: "pb-tool-rail", "aria-label": "\u7F16\u8F91\u5668\u5DE5\u5177", children: [
230
+ /* @__PURE__ */ jsx2(IconButton, { label: "\u533A\u5757", icon: LayoutSectionIcon, pressed: state.blockView === "blocks", onClick: () => update({ blockView: "blocks", isLeftRailOpen: true }) }),
231
+ /* @__PURE__ */ jsx2(IconButton, { label: "\u7ED3\u6784", icon: MenuIcon, pressed: state.blockView === "outline", onClick: () => update({ blockView: "outline", isLeftRailOpen: true }) })
232
+ ] }),
233
+ /* @__PURE__ */ jsxs2("aside", { className: `pb-left-panel ${state.isLeftRailOpen ? "" : "pb-panel--closed"}`, "aria-label": "Block navigation", children: [
234
+ /* @__PURE__ */ jsxs2(InlineStack, { align: "space-between", blockAlign: "center", children: [
235
+ /* @__PURE__ */ jsxs2("div", { children: [
236
+ /* @__PURE__ */ jsx2(Text, { as: "h2", variant: "headingSm", children: state.blockView === "blocks" ? "\u533A\u5757" : "\u7ED3\u6784" }),
237
+ /* @__PURE__ */ jsx2(Text, { as: "p", variant: "bodySm", tone: "subdued", children: state.blockView === "blocks" ? "\u9875\u9762\u533A\u5757\u4E0E\u5FEB\u6377\u64CD\u4F5C" : "\u5F53\u524D\u9875\u9762\u5C42\u7EA7" })
238
+ ] }),
239
+ /* @__PURE__ */ jsx2(IconButton, { label: "\u6536\u8D77\u5DE6\u4FA7\u9762\u677F", icon: XIcon, onClick: () => update({ isLeftRailOpen: false }) })
240
+ ] }),
241
+ /* @__PURE__ */ jsx2(BlockList, { view: state.blockView, blocks: state.blocks, selectedBlockId: state.selectedBlockId, onSelect: selectBlock, onMove: moveBlock, onDuplicate: duplicateBlock, onDelete: deleteBlock }),
242
+ /* @__PURE__ */ jsx2(Button, { fullWidth: true, icon: PlusIcon, onClick: () => update({ isPickerOpen: true }), children: "\u6DFB\u52A0\u6A21\u5757" })
243
+ ] }),
244
+ /* @__PURE__ */ jsxs2("main", { className: "pb-canvas-area", children: [
245
+ /* @__PURE__ */ jsx2(CanvasToolbar, { device: state.device, zoom: state.zoom, onDevice: (device) => update({ device }), onZoom: (zoom) => update({ zoom }) }),
246
+ /* @__PURE__ */ jsxs2("div", { className: "pb-canvas-stage", children: [
247
+ /* @__PURE__ */ jsx2("div", { className: `pb-canvas-frame pb-canvas-frame--${state.device} pb-canvas-frame--zoom-${state.zoom}`, "data-device": state.device, "data-zoom": state.zoom, children: /* @__PURE__ */ jsx2(Puck.Preview, {}) }),
248
+ selectedBlock ? /* @__PURE__ */ jsxs2("div", { className: "pb-canvas-overlay", "aria-label": `\u5DF2\u9009\u62E9 ${selectedBlock.label}`, children: [
249
+ /* @__PURE__ */ jsx2("span", { children: selectedBlock.label }),
250
+ /* @__PURE__ */ jsx2("span", { children: "Selected" })
251
+ ] }) : null
252
+ ] }),
253
+ !state.isLeftRailOpen || !state.isRightPanelOpen ? /* @__PURE__ */ jsxs2("div", { className: "pb-collapsed-actions", children: [
254
+ !state.isLeftRailOpen ? /* @__PURE__ */ jsx2(Button, { onClick: () => update({ isLeftRailOpen: true }), children: "\u6253\u5F00\u533A\u5757\u9762\u677F" }) : null,
255
+ !state.isRightPanelOpen ? /* @__PURE__ */ jsx2(Button, { onClick: () => update({ isRightPanelOpen: true }), children: "\u6253\u5F00\u5C5E\u6027\u9762\u677F" }) : null
256
+ ] }) : null
257
+ ] }),
258
+ /* @__PURE__ */ jsxs2("aside", { className: `pb-right-panel ${state.isRightPanelOpen ? "" : "pb-panel--closed"}`, "aria-label": "Block properties", children: [
259
+ /* @__PURE__ */ jsxs2(InlineStack, { align: "space-between", blockAlign: "center", children: [
260
+ /* @__PURE__ */ jsxs2("div", { children: [
261
+ /* @__PURE__ */ jsx2(Text, { as: "h2", variant: "headingSm", children: "\u5C5E\u6027" }),
262
+ /* @__PURE__ */ jsx2(Text, { as: "p", variant: "bodySm", tone: "subdued", children: selectedBlock?.label ?? "\u672A\u9009\u62E9\u533A\u5757" })
263
+ ] }),
264
+ /* @__PURE__ */ jsx2(IconButton, { label: "\u6536\u8D77\u5C5E\u6027\u9762\u677F", icon: XIcon, onClick: () => update({ isRightPanelOpen: false }) })
265
+ ] }),
266
+ selectedBlock ? /* @__PURE__ */ jsx2(PropertyPanel, { block: selectedBlock, editorLocale: state.editorLocale, pageLocale: state.pageLocale, onEditorLocale: (editorLocale) => update({ editorLocale }), onPageLocale: (pageLocale) => update({ pageLocale }) }) : /* @__PURE__ */ jsx2(Text, { as: "p", tone: "subdued", children: "\u9009\u62E9\u4E00\u4E2A\u533A\u5757\u4EE5\u67E5\u770B\u5C5E\u6027\u3002" })
267
+ ] })
268
+ ] }),
269
+ /* @__PURE__ */ jsx2(Modal, { instant: true, open: state.isPickerOpen, onClose: () => update({ isPickerOpen: false }), title: "\u6DFB\u52A0\u6A21\u5757", primaryAction: { content: "\u5173\u95ED", onAction: () => update({ isPickerOpen: false }) }, children: /* @__PURE__ */ jsx2(Modal.Section, { children: /* @__PURE__ */ jsxs2(BlockStack, { gap: "300", children: [
270
+ /* @__PURE__ */ jsx2(Text, { as: "p", tone: "subdued", children: "\u672C\u7248\u4EC5\u64CD\u4F5C\u672C\u5730\u6F14\u793A\u533A\u5757\uFF0C\u4E0D\u4F1A\u5199\u5165\u9875\u9762\u6570\u636E\u6216\u8C03\u7528\u4E1A\u52A1\u63A5\u53E3\u3002" }),
271
+ /* @__PURE__ */ jsx2(InlineStack, { gap: "200", wrap: true, children: ["Hero", "TrackingForm", "Text"].map((type) => /* @__PURE__ */ jsx2(Button, { onClick: () => addBlock(type), children: type }, type)) })
272
+ ] }) }) })
273
+ ] }) }) });
274
+ }
275
+ function BlockList({
276
+ view,
277
+ blocks,
278
+ selectedBlockId,
279
+ onSelect,
280
+ onMove,
281
+ onDuplicate,
282
+ onDelete
283
+ }) {
284
+ return /* @__PURE__ */ jsx2("div", { className: `pb-block-list pb-block-list--${view}`, "data-testid": `${view}-view`, children: blocks.map((block, index) => /* @__PURE__ */ jsxs2("article", { className: `pb-block-row ${block.id === selectedBlockId ? "pb-block-row--selected" : ""}`, children: [
285
+ /* @__PURE__ */ jsx2("span", { className: "pb-block-handle", "aria-label": `${block.label} \u62D6\u52A8\u6392\u5E8F`, children: /* @__PURE__ */ jsx2(DragHandleIcon, {}) }),
286
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "pb-block-select", onClick: () => onSelect(block.id), "aria-pressed": block.id === selectedBlockId, children: [
287
+ /* @__PURE__ */ jsx2(Text, { as: "span", variant: "bodySm", fontWeight: "semibold", children: block.label }),
288
+ view === "blocks" ? /* @__PURE__ */ jsx2(Text, { as: "span", variant: "bodySm", tone: "subdued", children: block.description }) : /* @__PURE__ */ jsx2(Text, { as: "span", variant: "bodySm", tone: "subdued", children: block.type })
289
+ ] }),
290
+ /* @__PURE__ */ jsxs2("div", { className: "pb-block-actions", "aria-label": `${block.label} \u64CD\u4F5C`, children: [
291
+ /* @__PURE__ */ jsx2(Button, { size: "slim", onClick: () => onMove(block.id, -1), disabled: index === 0, children: "\u4E0A\u79FB" }),
292
+ /* @__PURE__ */ jsx2(Button, { size: "slim", onClick: () => onMove(block.id, 1), disabled: index === blocks.length - 1, children: "\u4E0B\u79FB" }),
293
+ /* @__PURE__ */ jsx2(IconButton, { label: `\u590D\u5236 ${block.label}`, icon: DuplicateIcon, onClick: () => onDuplicate(block) }),
294
+ /* @__PURE__ */ jsx2(IconButton, { label: `\u5220\u9664 ${block.label}`, icon: DeleteIcon, onClick: () => onDelete(block) })
295
+ ] })
296
+ ] }, block.id)) });
297
+ }
298
+ function CanvasToolbar({ device, zoom, onDevice, onZoom }) {
299
+ return /* @__PURE__ */ jsx2("div", { className: "pb-canvas-toolbar", children: /* @__PURE__ */ jsxs2(InlineStack, { align: "space-between", blockAlign: "center", gap: "200", wrap: true, children: [
300
+ /* @__PURE__ */ jsx2(ButtonGroup, { variant: "segmented", children: Object.keys(deviceLabels).map((item) => /* @__PURE__ */ jsx2(Button, { pressed: device === item, onClick: () => onDevice(item), children: deviceLabels[item] }, item)) }),
301
+ /* @__PURE__ */ jsx2(Select, { label: "\u7F29\u653E", labelHidden: true, value: zoom, options: [{ label: "Auto", value: "auto" }, { label: "50%", value: "50" }, { label: "70%", value: "70" }, { label: "100%", value: "100" }], onChange: (value) => onZoom(value) })
302
+ ] }) });
303
+ }
304
+ function PropertyPanel({
305
+ block,
306
+ editorLocale,
307
+ pageLocale,
308
+ onEditorLocale,
309
+ onPageLocale
310
+ }) {
311
+ const [tab, setTab] = useState(0);
312
+ const tabs = ["\u5185\u5BB9", "\u6837\u5F0F", "\u9AD8\u7EA7"];
313
+ return /* @__PURE__ */ jsxs2(BlockStack, { gap: "300", "data-testid": "property-panel", children: [
314
+ /* @__PURE__ */ jsx2("div", { className: "pb-inspector-tabs", role: "tablist", "aria-label": "\u5C5E\u6027\u5206\u7C7B", children: tabs.map((label, index) => /* @__PURE__ */ jsx2("button", { type: "button", role: "tab", "aria-selected": tab === index, className: tab === index ? "pb-inspector-tab--selected" : "", onClick: () => setTab(index), children: label }, label)) }),
315
+ tab === 0 ? /* @__PURE__ */ jsxs2(BlockStack, { gap: "300", children: [
316
+ /* @__PURE__ */ jsx2(Badge, { children: block.type }),
317
+ /* @__PURE__ */ jsx2(Text, { as: "p", variant: "headingSm", children: block.label }),
318
+ /* @__PURE__ */ jsx2(Divider, {}),
319
+ /* @__PURE__ */ jsxs2(Text, { as: "p", tone: "subdued", children: [
320
+ "\u5F53\u524D\u9009\u4E2D\uFF1A",
321
+ block.id
322
+ ] }),
323
+ /* @__PURE__ */ jsx2(Text, { as: "p", children: block.description }),
324
+ /* @__PURE__ */ jsx2(Box, { padding: "300", background: "bg-surface-secondary", borderRadius: "200", children: /* @__PURE__ */ jsx2(Text, { as: "p", variant: "bodySm", tone: "subdued", children: "\u5B57\u6BB5\u7F16\u8F91\u5C06\u5728 PageDocument \u7248\u672C\u5B9E\u73B0\uFF1BV0.1.1 \u4EC5\u63D0\u4F9B\u771F\u5B9E\u9875\u9762\u9884\u89C8\u548C\u9009\u62E9\u53CD\u9988\u3002" }) })
325
+ ] }) : null,
326
+ tab === 1 ? /* @__PURE__ */ jsx2(BlockStack, { gap: "300", children: /* @__PURE__ */ jsx2(Text, { as: "p", variant: "bodySm", tone: "subdued", children: "\u6837\u5F0F\u914D\u7F6E\u5C06\u7531 Block Field \u4E0E Theme Token \u63D0\u4F9B\u3002\u672C\u7248\u4FDD\u6301 Storefront \u9875\u9762\u6837\u5F0F\u4E0E\u7F16\u8F91\u5668 UI \u9694\u79BB\u3002" }) }) : null,
327
+ tab === 2 ? /* @__PURE__ */ jsxs2(BlockStack, { gap: "300", children: [
328
+ /* @__PURE__ */ jsx2(Text, { as: "p", variant: "bodySm", tone: "subdued", children: "\u8BED\u8A00\u5165\u53E3\u5C5E\u4E8E Inspector\uFF0C\u907F\u514D\u5728\u4E3B\u5DE5\u5177\u680F\u91CD\u590D\u5360\u4F4D\u3002" }),
329
+ /* @__PURE__ */ jsx2(Select, { label: "Editor Language", options: [{ label: "\u4E2D\u6587", value: "zh-CN" }, { label: "English", value: "en-US" }], value: editorLocale, onChange: onEditorLocale }),
330
+ /* @__PURE__ */ jsx2(Select, { label: "Page Locale", options: [{ label: "English (US)", value: "en-US" }, { label: "\u4E2D\u6587", value: "zh-CN" }], value: pageLocale, onChange: onPageLocale })
331
+ ] }) : null
332
+ ] });
333
+ }
334
+
335
+ // src/editor/shell/PageDocumentEditorShell.tsx
336
+ import { Puck as Puck2, usePuck } from "@puckeditor/core";
337
+ import { Badge as Badge2, Banner, BlockStack as BlockStack2, Button as Button2, ButtonGroup as ButtonGroup2, InlineStack as InlineStack2, Text as Text2, TextField } from "@shopify/polaris";
338
+ 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 useEffect2, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
340
+
341
+ // src/adapters/puck/page-document-config.tsx
342
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
343
+ function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId, registry) {
344
+ const extensionComponents = Object.fromEntries((registry?.blocks ?? []).flatMap((block) => {
345
+ const BlockRenderer = block.render.web;
346
+ if (!BlockRenderer) return [];
347
+ return [[block.type, {
348
+ render: (props) => {
349
+ const id = typeof props.id === "string" ? props.id : `unknown-${block.type}`;
350
+ return /* @__PURE__ */ jsx3("section", { className: "pb-document-canvas__extension", "aria-label": `Select ${block.label} in canvas`, role: "button", tabIndex: 0, onClick: () => onSelect(id), onKeyDown: (event) => {
351
+ if (event.key === "Enter" || event.key === " ") onSelect(id);
352
+ }, children: /* @__PURE__ */ jsx3(BlockRenderer, { ...props }) });
353
+ }
354
+ }]];
355
+ }));
356
+ return {
357
+ components: {
358
+ Text: {
359
+ render: (props) => {
360
+ const id = typeof props.id === "string" ? props.id : "unknown-text";
361
+ const content = typeof props.content === "string" ? props.content : "";
362
+ const editable = id === selectedBlockId;
363
+ return /* @__PURE__ */ jsx3("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
+ if (event.key === "Enter" || event.key === " ") onSelect(id);
365
+ }, children: /* @__PURE__ */ jsx3("p", { contentEditable: editable, suppressContentEditableWarning: true, onInput: (event) => {
366
+ if (editable) onPropsChange(id, { content: event.currentTarget.textContent ?? "" });
367
+ }, children: content }) });
368
+ }
369
+ },
370
+ Image: {
371
+ render: (props) => {
372
+ const id = typeof props.id === "string" ? props.id : "unknown-image";
373
+ const src = typeof props.src === "string" ? props.src : "";
374
+ const alt = typeof props.alt === "string" ? props.alt : "";
375
+ const editable = id === selectedBlockId;
376
+ 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
+ if (event.key === "Enter" || event.key === " ") onSelect(id);
378
+ }, children: [
379
+ /* @__PURE__ */ jsx3("img", { src, alt }),
380
+ editable ? /* @__PURE__ */ jsx3("input", { "aria-label": "\u753B\u5E03\u56FE\u7247 URL", value: src, onChange: (event) => onPropsChange(id, { src: event.currentTarget.value }), onClick: (event) => event.stopPropagation() }) : null
381
+ ] });
382
+ }
383
+ },
384
+ ...extensionComponents
385
+ }
386
+ };
387
+ }
388
+
389
+ // src/adapters/puck/page-document.ts
390
+ var documentToPuckType = {
391
+ "core.text": "Text",
392
+ "core.image": "Image"
393
+ };
394
+ var puckToDocumentType = Object.fromEntries(Object.entries(documentToPuckType).map(([documentType, puckType]) => [puckType, documentType]));
395
+ function toEngineBlock(block, registry) {
396
+ const type = documentToPuckType[block.type] ?? (registry?.getBlock(block.type) ? block.type : void 0);
397
+ if (!type) throw new Error(`V0.2 \u4E0D\u652F\u6301\u8F6C\u6362\u533A\u5757\u7C7B\u578B\uFF1A${block.type}`);
398
+ return { type, props: { id: block.id, ...block.props } };
399
+ }
400
+ function toEngineData(document, registry) {
401
+ return {
402
+ root: { ...document.root },
403
+ content: document.blocks.map((block) => toEngineBlock(block, registry))
404
+ };
405
+ }
406
+ function fromEngineData(data, base, registry) {
407
+ const previousBlocks = new Map(base.blocks.map((block) => [block.id, block]));
408
+ const blocks = data.content.flatMap((item) => {
409
+ const type = puckToDocumentType[item.type] ?? (registry?.getBlock(item.type) ? item.type : void 0);
410
+ const id = typeof item.props.id === "string" ? item.props.id : null;
411
+ if (!type || !id) return [];
412
+ const previous = previousBlocks.get(id);
413
+ const props = Object.fromEntries(Object.entries(item.props).filter(([key]) => key !== "id"));
414
+ return [{
415
+ id,
416
+ type,
417
+ version: previous?.version ?? 1,
418
+ props,
419
+ ...previous?.slots ? { slots: previous.slots } : {},
420
+ ...previous?.binding ? { binding: previous.binding } : {}
421
+ }];
422
+ });
423
+ const root = typeof data.root === "object" && data.root !== null && "props" in data.root ? data.root.props : data.root;
424
+ return createPageDocument({ ...base, root, blocks });
425
+ }
426
+
427
+ // src/editor/context/EditorContext.tsx
428
+ import { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useRef as useRef2, useState as useState2 } from "react";
429
+ import { jsx as jsx4 } from "react/jsx-runtime";
430
+ function historyReducer(state, action) {
431
+ if (action.type === "select") return { ...state, selectedBlockId: action.id };
432
+ if (action.type === "device") return { ...state, device: action.device };
433
+ if (action.type === "undo") {
434
+ const previous = state.past.at(-1);
435
+ if (!previous) return state;
436
+ return { ...state, ...previous, past: state.past.slice(0, -1), future: [{ document: state.document, selectedBlockId: state.selectedBlockId }, ...state.future] };
437
+ }
438
+ if (action.type === "redo") {
439
+ const next = state.future[0];
440
+ if (!next) return state;
441
+ return { ...state, ...next, past: [...state.past, { document: state.document, selectedBlockId: state.selectedBlockId }], future: state.future.slice(1) };
442
+ }
443
+ if (action.type === "saved") return { ...state, savedDocument: state.document };
444
+ const current = { document: state.document, selectedBlockId: state.selectedBlockId };
445
+ return { ...state, document: action.document, selectedBlockId: action.selectedBlockId, past: [...state.past, current], future: [] };
446
+ }
447
+ var EditorContext = createContext(null);
448
+ function uniqueBlockId(type, blocks) {
449
+ const prefix = type.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "") || "block";
450
+ let index = blocks.length + 1;
451
+ while (blocks.some((block) => block.id === `${prefix}-${index}`)) index += 1;
452
+ return `${prefix}-${index}`;
453
+ }
454
+ 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: "" } };
457
+ const definition = registry?.getBlock(type);
458
+ if (!definition) throw new Error(`Unknown PageDocument block type: ${type}`);
459
+ return { id: uniqueBlockId(type, blocks), type, version: definition.version, props: definition.defaultProps };
460
+ }
461
+ function EditorProvider({ initialDocument, registry, loadState = "ready", leaveWarning = "You have unsaved changes.", onDocumentChange, children }) {
462
+ const [history, dispatch] = useReducer(historyReducer, initialDocument, (document) => ({ document, selectedBlockId: document.blocks[0]?.id ?? null, past: [], future: [], device: "desktop", savedDocument: document }));
463
+ const historyRef = useRef2(history);
464
+ useEffect(() => {
465
+ historyRef.current = history;
466
+ }, [history]);
467
+ const [canvasSelectionRequest, setCanvasSelectionRequest] = useState2(null);
468
+ const editable = loadState === "ready" || loadState === "success";
469
+ const selectedBlock = history.document.blocks.find((block) => block.id === history.selectedBlockId) ?? null;
470
+ const replace = useCallback((document, selectedBlockId) => dispatch({ type: "replace", document, selectedBlockId }), []);
471
+ const requestCanvasSelection = useCallback((id) => {
472
+ if (editable) setCanvasSelectionRequest(id);
473
+ }, [editable]);
474
+ const confirmCanvasSelection = useCallback((id) => {
475
+ setCanvasSelectionRequest(null);
476
+ dispatch({ type: "select", id });
477
+ }, []);
478
+ const updateFromCanvas = useCallback((document) => {
479
+ const current = historyRef.current;
480
+ if (!editable || JSON.stringify(document) === JSON.stringify(current.document)) return;
481
+ replace(document, current.selectedBlockId);
482
+ }, [editable, replace]);
483
+ const updateBlockProps = useCallback((id, props) => {
484
+ if (!editable) return;
485
+ const current = historyRef.current;
486
+ const blocks = current.document.blocks.map((block) => block.id === id ? { ...block, props: { ...block.props, ...props } } : block);
487
+ replace({ ...current.document, blocks }, id);
488
+ }, [editable, replace]);
489
+ useEffect(() => {
490
+ onDocumentChange?.(history.document);
491
+ }, [history.document, onDocumentChange]);
492
+ const isDirty = JSON.stringify(history.document) !== JSON.stringify(history.savedDocument);
493
+ useEffect(() => {
494
+ if (!isDirty || typeof window === "undefined") return;
495
+ const confirmLeave = (event) => {
496
+ event.preventDefault();
497
+ event.returnValue = leaveWarning;
498
+ };
499
+ window.addEventListener("beforeunload", confirmLeave);
500
+ return () => window.removeEventListener("beforeunload", confirmLeave);
501
+ }, [isDirty, leaveWarning]);
502
+ useEffect(() => {
503
+ if (typeof window === "undefined") return;
504
+ const keydown = (event) => {
505
+ const target = event.target;
506
+ if (target?.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(target?.tagName ?? "")) return;
507
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "z") {
508
+ event.preventDefault();
509
+ dispatch({ type: event.shiftKey ? "redo" : "undo" });
510
+ } else if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "y") {
511
+ event.preventDefault();
512
+ dispatch({ type: "redo" });
513
+ }
514
+ };
515
+ window.addEventListener("keydown", keydown);
516
+ return () => window.removeEventListener("keydown", keydown);
517
+ }, []);
518
+ const value = useMemo(() => {
519
+ const actionState = {
520
+ canUndo: editable && history.past.length > 0,
521
+ canRedo: editable && history.future.length > 0,
522
+ canAdd: editable,
523
+ canEdit: editable && selectedBlock !== null,
524
+ canDelete: editable && selectedBlock !== null,
525
+ canDuplicate: editable && selectedBlock !== null,
526
+ canReorder: editable && history.document.blocks.length > 1
527
+ };
528
+ return {
529
+ document: history.document,
530
+ selectedBlockId: history.selectedBlockId,
531
+ selectedBlock,
532
+ canvasSelectionRequest,
533
+ device: history.device,
534
+ loadState,
535
+ isDirty,
536
+ actionState,
537
+ requestCanvasSelection,
538
+ confirmCanvasSelection,
539
+ updateFromCanvas,
540
+ setDevice: (device) => dispatch({ type: "device", device }),
541
+ addBlock: (type, beforeId) => {
542
+ if (!editable) return null;
543
+ const block = defaultBlock(type, history.document.blocks, registry);
544
+ const blocks = [...history.document.blocks];
545
+ const targetIndex = beforeId ? blocks.findIndex((item) => item.id === beforeId) : -1;
546
+ if (targetIndex < 0) blocks.push(block);
547
+ else blocks.splice(targetIndex, 0, block);
548
+ replace({ ...history.document, blocks }, block.id);
549
+ return block.id;
550
+ },
551
+ duplicateBlock: (id) => {
552
+ if (!editable) return;
553
+ const index = history.document.blocks.findIndex((block2) => block2.id === id);
554
+ const source = history.document.blocks[index];
555
+ if (!source) return;
556
+ const block = { ...source, id: uniqueBlockId(source.type, history.document.blocks), props: { ...source.props } };
557
+ const blocks = [...history.document.blocks];
558
+ blocks.splice(index + 1, 0, block);
559
+ replace({ ...history.document, blocks }, block.id);
560
+ },
561
+ deleteBlock: (id) => {
562
+ if (!editable) return;
563
+ const index = history.document.blocks.findIndex((block) => block.id === id);
564
+ if (index < 0) return;
565
+ const blocks = history.document.blocks.filter((block) => block.id !== id);
566
+ replace({ ...history.document, blocks }, blocks[index]?.id ?? blocks[index - 1]?.id ?? null);
567
+ },
568
+ moveBlock: (id, direction) => {
569
+ if (!editable) return;
570
+ const from = history.document.blocks.findIndex((block) => block.id === id);
571
+ const to = from + direction;
572
+ if (from < 0 || to < 0 || to >= history.document.blocks.length) return;
573
+ const blocks = [...history.document.blocks];
574
+ [blocks[from], blocks[to]] = [blocks[to], blocks[from]];
575
+ replace({ ...history.document, blocks }, id);
576
+ },
577
+ reorderBlock: (id, beforeId) => {
578
+ if (!editable || id === beforeId) return;
579
+ const source = history.document.blocks.find((block) => block.id === id);
580
+ const withoutSource = history.document.blocks.filter((block) => block.id !== id);
581
+ const targetIndex = withoutSource.findIndex((block) => block.id === beforeId);
582
+ if (!source || targetIndex < 0) return;
583
+ const blocks = [...withoutSource];
584
+ blocks.splice(targetIndex, 0, source);
585
+ replace({ ...history.document, blocks }, id);
586
+ },
587
+ updateBlockProps,
588
+ undo: () => {
589
+ if (editable) dispatch({ type: "undo" });
590
+ },
591
+ redo: () => {
592
+ if (editable) dispatch({ type: "redo" });
593
+ },
594
+ markSaved: () => dispatch({ type: "saved" })
595
+ };
596
+ }, [canvasSelectionRequest, confirmCanvasSelection, editable, history, isDirty, loadState, registry, replace, requestCanvasSelection, selectedBlock, updateBlockProps, updateFromCanvas]);
597
+ return /* @__PURE__ */ jsx4(EditorContext.Provider, { value, children });
598
+ }
599
+ function useEditorContext() {
600
+ const context = useContext(EditorContext);
601
+ if (!context) throw new Error("useEditorContext must be used within EditorProvider");
602
+ return context;
603
+ }
604
+
605
+ // src/editor/i18n/admin.ts
606
+ var messages = {
607
+ "zh-CN": {
608
+ blocks: "\u533A\u5757",
609
+ outline: "\u7ED3\u6784",
610
+ properties: "\u5C5E\u6027",
611
+ addBlock: "\u6DFB\u52A0\u533A\u5757",
612
+ undo: "\u64A4\u9500",
613
+ redo: "\u6062\u590D",
614
+ duplicate: "\u590D\u5236",
615
+ remove: "\u5220\u9664",
616
+ moveUp: "\u4E0A\u79FB",
617
+ moveDown: "\u4E0B\u79FB",
618
+ loading: "\u6B63\u5728\u52A0\u8F7D\u7F16\u8F91\u5668\u2026",
619
+ empty: "\u9875\u9762\u8FD8\u6CA1\u6709\u533A\u5757",
620
+ error: "\u7F16\u8F91\u5668\u65E0\u6CD5\u52A0\u8F7D",
621
+ disabled: "\u7F16\u8F91\u5668\u5F53\u524D\u4E0D\u53EF\u7F16\u8F91",
622
+ success: "\u64CD\u4F5C\u5DF2\u5B8C\u6210",
623
+ unsaved: "\u672A\u4FDD\u5B58\u53D8\u66F4",
624
+ saved: "\u6240\u6709\u66F4\u6539\u5DF2\u4FDD\u7559\u5728\u5F53\u524D\u4F1A\u8BDD\u4E2D",
625
+ save: "\u4FDD\u5B58\u8349\u7A3F",
626
+ saving: "\u6B63\u5728\u4FDD\u5B58\u2026",
627
+ publish: "\u53D1\u5E03",
628
+ publishing: "\u6B63\u5728\u53D1\u5E03\u2026",
629
+ saveFailed: "\u4FDD\u5B58\u8349\u7A3F\u5931\u8D25",
630
+ publishFailed: "\u53D1\u5E03\u5931\u8D25",
631
+ published: "\u5DF2\u53D1\u5E03",
632
+ leaveWarning: "\u4F60\u6709\u672A\u4FDD\u5B58\u7684\u66F4\u6539\u3002\u786E\u5B9A\u8981\u79BB\u5F00\u5417\uFF1F",
633
+ selectBlock: "\u9009\u62E9\u4E00\u4E2A\u533A\u5757\u4EE5\u7F16\u8F91\u3002",
634
+ desktop: "\u684C\u9762",
635
+ tablet: "\u5E73\u677F",
636
+ mobile: "\u624B\u673A"
637
+ },
638
+ en: {
639
+ blocks: "Blocks",
640
+ outline: "Outline",
641
+ properties: "Properties",
642
+ addBlock: "Add block",
643
+ undo: "Undo",
644
+ redo: "Redo",
645
+ duplicate: "Duplicate",
646
+ remove: "Delete",
647
+ moveUp: "Move up",
648
+ moveDown: "Move down",
649
+ loading: "Loading editor\u2026",
650
+ empty: "This page has no blocks",
651
+ error: "The editor could not load",
652
+ disabled: "The editor is currently read-only",
653
+ success: "Action completed",
654
+ unsaved: "Unsaved changes",
655
+ saved: "Changes are kept in this session",
656
+ save: "Save draft",
657
+ saving: "Saving\u2026",
658
+ publish: "Publish",
659
+ publishing: "Publishing\u2026",
660
+ saveFailed: "Could not save draft",
661
+ publishFailed: "Could not publish",
662
+ published: "Published",
663
+ leaveWarning: "You have unsaved changes. Are you sure you want to leave?",
664
+ selectBlock: "Select a block to edit it.",
665
+ desktop: "Desktop",
666
+ tablet: "Tablet",
667
+ mobile: "Mobile"
668
+ }
669
+ };
670
+ function createAdminI18n(locale) {
671
+ const resolvedLocale = locale === "en" || locale === "en-US" ? "en" : "zh-CN";
672
+ return { locale: resolvedLocale, t: (key) => messages[resolvedLocale][key] };
673
+ }
674
+
675
+ // src/editor/shell/drop-position.ts
676
+ function nearestBlockIdAtY(blocks, y) {
677
+ return blocks.find((block) => y < block.top + block.height / 2)?.id;
678
+ }
679
+ function blockIdAtRelativeY(ids, relativeY) {
680
+ const index = Math.min(ids.length, Math.max(0, Math.round(relativeY * ids.length)));
681
+ return ids[index];
682
+ }
683
+
684
+ // src/editor/shell/PageDocumentEditorShell.tsx
685
+ import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
686
+ var deviceLabels2 = { desktop: "desktop", tablet: "tablet", mobile: "mobile" };
687
+ function blockLabel(block, registry) {
688
+ return blockTypeLabel(block.type, registry);
689
+ }
690
+ function blockTypeLabel(type, registry) {
691
+ if (type === "core.text") return "\u6587\u672C";
692
+ if (type === "core.image") return "\u56FE\u7247";
693
+ return registry?.getBlock(type)?.label ?? type;
694
+ }
695
+ function PageDocumentEditorShell(props) {
696
+ return /* @__PURE__ */ jsx5(EditorProvider, { initialDocument: props.initialDocument, registry: props.registry, loadState: props.loadState, leaveWarning: createAdminI18n(props.adminLocale).t("leaveWarning"), onDocumentChange: props.onDocumentChange, children: /* @__PURE__ */ jsx5(PageDocumentEditor, { ...props }) });
697
+ }
698
+ function PageDocumentEditor({ iframe = true, registry, adminLocale, onSave, onPublish }) {
699
+ const editor = useEditorContext();
700
+ const [blockView, setBlockView] = useState3("blocks");
701
+ const [draggingLibraryType, setDraggingLibraryType] = useState3(null);
702
+ const canvasFrameRef = useRef3(null);
703
+ const [canvasMutationVersion, setCanvasMutationVersion] = useState3(0);
704
+ const [request, setRequest] = useState3("idle");
705
+ const [notice, setNotice] = useState3(null);
706
+ const i18n = createAdminI18n(adminLocale);
707
+ const engineData = useMemo2(() => toEngineData(editor.document, registry), [editor.document, registry]);
708
+ const { confirmCanvasSelection, selectedBlockId, updateBlockProps } = editor;
709
+ const updateFromCanvasInput = useCallback2((id, props) => {
710
+ setCanvasMutationVersion((version) => version + 1);
711
+ updateBlockProps(id, props);
712
+ }, [updateBlockProps]);
713
+ const config = useMemo2(() => createPageDocumentPuckConfig(confirmCanvasSelection, updateFromCanvasInput, selectedBlockId, registry), [confirmCanvasSelection, registry, selectedBlockId, updateFromCanvasInput]);
714
+ if (editor.loadState !== "ready" && editor.loadState !== "success") return /* @__PURE__ */ jsx5(EditorStatus, { state: editor.loadState });
715
+ const blockTypes = ["core.text", "core.image", ...registry?.blocks.map((block) => block.type) ?? []];
716
+ const addFromLibrary = (type, beforeId) => {
717
+ const id = editor.addBlock(type, beforeId);
718
+ if (id) editor.requestCanvasSelection(id);
719
+ };
720
+ const getDropBeforeId = (clientY) => {
721
+ const frame = canvasFrameRef.current;
722
+ if (!frame) return void 0;
723
+ const iframe2 = frame.querySelector("iframe");
724
+ const root = iframe2?.contentDocument ?? frame;
725
+ const offsetTop = iframe2?.getBoundingClientRect().top ?? 0;
726
+ const elements = Array.from(root.querySelectorAll("[data-page-document-block-id]"));
727
+ const puckElements = elements.length > 0 ? elements : Array.from(root.querySelectorAll("[data-puck-dnd]"));
728
+ const blockElements = puckElements.length > 0 ? puckElements : Array.from(window.document.querySelectorAll("[data-page-document-block-id], [data-puck-dnd]"));
729
+ const candidates = blockElements.map((element) => ({
730
+ id: element.dataset.pageDocumentBlockId ?? element.dataset.puckDnd,
731
+ top: element.getBoundingClientRect().top,
732
+ height: element.getBoundingClientRect().height
733
+ })).filter((item) => typeof item.id === "string");
734
+ const nearestId = nearestBlockIdAtY(candidates, clientY - offsetTop);
735
+ if (nearestId || candidates.length > 0) return nearestId;
736
+ const rect = frame.getBoundingClientRect();
737
+ if (rect.height <= 0) return void 0;
738
+ return blockIdAtRelativeY(editor.document.blocks.map((block) => block.id), (clientY - rect.top) / rect.height);
739
+ };
740
+ const dropFromLibrary = (event) => {
741
+ event.preventDefault();
742
+ const type = event.dataTransfer.getData("application/x-page-document-block") || draggingLibraryType;
743
+ if (type && blockTypes.includes(type)) addFromLibrary(type, getDropBeforeId(event.clientY));
744
+ setDraggingLibraryType(null);
745
+ };
746
+ const cancelLibraryDrop = (event) => {
747
+ event.preventDefault();
748
+ event.stopPropagation();
749
+ setDraggingLibraryType(null);
750
+ };
751
+ const save = async () => {
752
+ if (!onSave || request !== "idle") return;
753
+ setRequest("saving");
754
+ setNotice(null);
755
+ try {
756
+ await onSave(editor.document);
757
+ editor.markSaved();
758
+ } catch {
759
+ setNotice("saveFailed");
760
+ } finally {
761
+ setRequest("idle");
762
+ }
763
+ };
764
+ const publish = async () => {
765
+ if (!onPublish || request !== "idle") return;
766
+ setRequest("publishing");
767
+ setNotice(null);
768
+ try {
769
+ await onPublish(editor.document);
770
+ editor.markSaved();
771
+ setNotice("published");
772
+ } catch {
773
+ setNotice("publishFailed");
774
+ } finally {
775
+ setRequest("idle");
776
+ }
777
+ };
778
+ return /* @__PURE__ */ jsx5(Puck2, { config, data: engineData, iframe: { enabled: iframe }, onChange: (data) => editor.updateFromCanvas(fromEngineData(data, editor.document, registry)), children: /* @__PURE__ */ jsxs4(Puck2.Layout, { children: [
779
+ /* @__PURE__ */ jsx5(CanvasSelectionBridge, { data: engineData, requestedBlockId: editor.canvasSelectionRequest, onCanvasSelected: confirmCanvasSelection, canvasMutationVersion }),
780
+ /* @__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__ */ jsx5("header", { className: "pb-header", children: /* @__PURE__ */ jsxs4(InlineStack2, { align: "space-between", blockAlign: "center", gap: "300", wrap: false, children: [
782
+ /* @__PURE__ */ jsxs4("div", { className: "pb-page-title", children: [
783
+ /* @__PURE__ */ jsx5(Text2, { as: "h1", variant: "headingSm", children: editor.document.settings.seoTitle ?? editor.document.pageId }),
784
+ /* @__PURE__ */ jsxs4(Text2, { as: "p", variant: "bodySm", tone: "subdued", children: [
785
+ "PageDocument V",
786
+ editor.document.schemaVersion,
787
+ " \xB7 ",
788
+ editor.document.target
789
+ ] })
790
+ ] }),
791
+ /* @__PURE__ */ jsxs4(InlineStack2, { gap: "150", blockAlign: "center", wrap: false, children: [
792
+ /* @__PURE__ */ jsx5(Badge2, { tone: editor.isDirty ? "attention" : "success", children: editor.isDirty ? i18n.t("unsaved") : i18n.t("saved") }),
793
+ /* @__PURE__ */ jsx5(Button2, { disabled: !onSave || !editor.isDirty || request !== "idle", onClick: () => void save(), children: request === "saving" ? i18n.t("saving") : i18n.t("save") }),
794
+ /* @__PURE__ */ jsx5(Button2, { variant: "primary", disabled: !onPublish || request !== "idle", onClick: () => void publish(), children: request === "publishing" ? i18n.t("publishing") : i18n.t("publish") }),
795
+ /* @__PURE__ */ jsx5(Button2, { accessibilityLabel: i18n.t("undo"), icon: UndoIcon2, variant: "tertiary", disabled: !editor.actionState.canUndo, onClick: editor.undo }),
796
+ /* @__PURE__ */ jsx5(Button2, { accessibilityLabel: i18n.t("redo"), icon: RedoIcon2, variant: "tertiary", disabled: !editor.actionState.canRedo, onClick: editor.redo })
797
+ ] })
798
+ ] }) }),
799
+ editor.loadState === "success" ? /* @__PURE__ */ jsx5(Banner, { tone: "success", children: i18n.t("success") }) : null,
800
+ notice ? /* @__PURE__ */ jsx5(Banner, { tone: notice === "published" ? "success" : "critical", children: i18n.t(notice) }) : null,
801
+ /* @__PURE__ */ jsxs4("div", { className: "pb-workspace pb-workspace--document", children: [
802
+ /* @__PURE__ */ jsxs4("nav", { className: "pb-tool-rail", "aria-label": "\u7F16\u8F91\u5668\u5DE5\u5177", children: [
803
+ /* @__PURE__ */ jsx5(Button2, { accessibilityLabel: i18n.t("blocks"), icon: LayoutSectionIcon2, pressed: blockView === "blocks", variant: "tertiary", onClick: () => setBlockView("blocks") }),
804
+ /* @__PURE__ */ jsx5(Button2, { accessibilityLabel: i18n.t("outline"), icon: MenuIcon2, pressed: blockView === "outline", variant: "tertiary", onClick: () => setBlockView("outline") })
805
+ ] }),
806
+ /* @__PURE__ */ jsxs4("aside", { className: "pb-left-panel", "aria-label": "PageDocument \u533A\u5757", children: [
807
+ /* @__PURE__ */ jsx5(InlineStack2, { align: "space-between", blockAlign: "center", children: /* @__PURE__ */ jsx5(Text2, { as: "h2", variant: "headingSm", children: blockView === "blocks" ? i18n.t("blocks") : i18n.t("outline") }) }),
808
+ blockView === "blocks" ? /* @__PURE__ */ jsx5("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
+ event.dataTransfer.setData("application/x-page-document-block", type);
810
+ event.dataTransfer.effectAllowed = "copy";
811
+ setDraggingLibraryType(type);
812
+ }, onDragEnd: () => setDraggingLibraryType(null), children: [
813
+ /* @__PURE__ */ jsxs4("span", { className: "pb-library-block-title", children: [
814
+ /* @__PURE__ */ jsx5(Text2, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockTypeLabel(type, registry) }),
815
+ /* @__PURE__ */ jsx5("span", { className: "pb-library-block-drag-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(DragHandleIcon2, {}) })
816
+ ] }),
817
+ /* @__PURE__ */ jsx5(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: type })
818
+ ] }, type)) }) : editor.document.blocks.length === 0 ? /* @__PURE__ */ jsx5(Text2, { as: "p", tone: "subdued", children: i18n.t("empty") }) : /* @__PURE__ */ jsx5("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: [
819
+ /* @__PURE__ */ jsx5(Text2, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockLabel(block, registry) }),
820
+ /* @__PURE__ */ jsx5(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: block.id })
821
+ ] }, block.id)) })
822
+ ] }),
823
+ /* @__PURE__ */ jsxs4("main", { className: "pb-canvas-area", children: [
824
+ /* @__PURE__ */ jsx5("div", { className: "pb-canvas-toolbar", children: /* @__PURE__ */ jsx5(ButtonGroup2, { variant: "segmented", children: Object.keys(deviceLabels2).map((device) => /* @__PURE__ */ jsx5(Button2, { pressed: editor.device === device, onClick: () => editor.setDevice(device), children: i18n.t(deviceLabels2[device]) }, device)) }) }),
825
+ /* @__PURE__ */ jsxs4("div", { className: "pb-canvas-stage", children: [
826
+ /* @__PURE__ */ jsx5("div", { ref: canvasFrameRef, className: `pb-canvas-frame pb-canvas-frame--${editor.device}`, "data-device": editor.device, children: /* @__PURE__ */ jsx5(Puck2.Preview, {}) }),
827
+ 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
+ "\u677E\u5F00\u4EE5\u6DFB\u52A0 ",
829
+ blockTypeLabel(draggingLibraryType, registry)
830
+ ] }) : null,
831
+ editor.selectedBlock ? /* @__PURE__ */ jsxs4("div", { className: "pb-canvas-overlay", "aria-label": `\u5DF2\u9009\u62E9 ${blockLabel(editor.selectedBlock, registry)}`, children: [
832
+ /* @__PURE__ */ jsx5("span", { children: blockLabel(editor.selectedBlock, registry) }),
833
+ /* @__PURE__ */ jsx5("span", { children: "Selected" })
834
+ ] }) : null
835
+ ] })
836
+ ] }),
837
+ /* @__PURE__ */ jsxs4("aside", { className: "pb-right-panel", "aria-label": "PageDocument \u5C5E\u6027", children: [
838
+ /* @__PURE__ */ jsx5(Text2, { as: "h2", variant: "headingSm", children: i18n.t("properties") }),
839
+ editor.selectedBlock ? /* @__PURE__ */ jsx5(DocumentInspector, { block: editor.selectedBlock, registry, disabled: !editor.actionState.canEdit, onChange: (props) => editor.updateBlockProps(editor.selectedBlock.id, props) }) : /* @__PURE__ */ jsx5(Text2, { as: "p", tone: "subdued", children: i18n.t("selectBlock") })
840
+ ] })
841
+ ] })
842
+ ] })
843
+ ] }) });
844
+ }
845
+ function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canvasMutationVersion }) {
846
+ const puck = usePuck();
847
+ const lastSelectedId = useRef3(null);
848
+ const lastSyncedData = useRef3(null);
849
+ const lastCanvasMutationVersion = useRef3(0);
850
+ const serializedData = JSON.stringify(data);
851
+ useEffect2(() => {
852
+ if (lastSyncedData.current === serializedData) return;
853
+ lastSyncedData.current = serializedData;
854
+ if (canvasMutationVersion > lastCanvasMutationVersion.current) {
855
+ lastCanvasMutationVersion.current = canvasMutationVersion;
856
+ return;
857
+ }
858
+ puck.dispatch({ type: "setData", data });
859
+ }, [canvasMutationVersion, data, puck, serializedData]);
860
+ useEffect2(() => {
861
+ if (!requestedBlockId) return;
862
+ const selector = puck.getSelectorForId(requestedBlockId);
863
+ if (selector) puck.dispatch({ type: "setUi", ui: { itemSelector: selector } });
864
+ }, [puck, requestedBlockId]);
865
+ const selectedId = typeof puck.selectedItem?.props.id === "string" ? puck.selectedItem.props.id : null;
866
+ useEffect2(() => {
867
+ if (selectedId && lastSelectedId.current !== selectedId) {
868
+ lastSelectedId.current = selectedId;
869
+ onCanvasSelected(selectedId);
870
+ }
871
+ }, [onCanvasSelected, selectedId]);
872
+ return null;
873
+ }
874
+ function EditorStatus({ state }) {
875
+ const i18n = createAdminI18n();
876
+ const tone = state === "error" ? "critical" : state === "disabled" ? "warning" : "info";
877
+ const message = state === "loading" ? i18n.t("loading") : state === "empty" ? i18n.t("empty") : state === "error" ? i18n.t("error") : i18n.t("disabled");
878
+ return /* @__PURE__ */ jsx5("div", { className: "pb-editor-status", "data-testid": "page-document-editor-state", "data-editor-state": state, children: /* @__PURE__ */ jsx5(Banner, { tone, title: message, children: state === "disabled" ? i18n.t("disabled") : message }) });
879
+ }
880
+ function DocumentInspector({ block, registry, disabled, onChange }) {
881
+ const definition = registry?.getBlock(block.type);
882
+ return /* @__PURE__ */ jsxs4(BlockStack2, { gap: "300", "data-testid": "document-inspector", children: [
883
+ /* @__PURE__ */ jsx5(Badge2, { children: block.type }),
884
+ /* @__PURE__ */ jsx5(Text2, { as: "p", variant: "headingSm", children: blockLabel(block, registry) }),
885
+ block.type === "core.text" ? /* @__PURE__ */ jsx5(TextField, { label: "\u6587\u672C\u5185\u5BB9", value: typeof block.props.content === "string" ? block.props.content : "", onChange: (content) => onChange({ content }), autoComplete: "off", multiline: 4, disabled }) : null,
886
+ block.type === "core.image" ? /* @__PURE__ */ jsxs4(Fragment, { children: [
887
+ /* @__PURE__ */ jsx5(TextField, { label: "\u56FE\u7247 URL", value: typeof block.props.src === "string" ? block.props.src : "", onChange: (src) => onChange({ src }), autoComplete: "off", disabled }),
888
+ /* @__PURE__ */ jsx5(TextField, { label: "\u66FF\u4EE3\u6587\u672C", value: typeof block.props.alt === "string" ? block.props.alt : "", onChange: (alt) => onChange({ alt }), autoComplete: "off", disabled })
889
+ ] }) : null,
890
+ definition ? Object.entries(definition.fields).map(([name, field]) => {
891
+ const Field = registry?.getField(field.field)?.component;
892
+ return Field ? /* @__PURE__ */ jsx5(Field, { value: block.props[name], onChange: (value) => onChange({ [name]: value }) }, name) : null;
893
+ }) : null
894
+ ] });
895
+ }
896
+ export {
897
+ EditorProvider,
898
+ EditorShell,
899
+ ExtensionRegistry,
900
+ ExtensionRegistryError,
901
+ PageDocumentEditorShell,
902
+ WebRenderer,
903
+ createAdminI18n,
904
+ createExtensionRegistry,
905
+ createPageDocument,
906
+ fromEngineData,
907
+ migratePageDocument,
908
+ toEngineData,
909
+ useEditorContext,
910
+ validatePageDocument
911
+ };