@standhigher/puck-page-builder 0.12.4 → 0.12.6

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 CHANGED
@@ -260,13 +260,7 @@ function EditorShell({ initialState, iframe = true }) {
260
260
  ] }),
261
261
  /* @__PURE__ */ jsxs2("main", { className: "pb-canvas-area", children: [
262
262
  /* @__PURE__ */ jsx2(CanvasToolbar, { device: state.device, zoom: state.zoom, onDevice: (device) => update({ device }), onZoom: (zoom) => update({ zoom }) }),
263
- /* @__PURE__ */ jsxs2("div", { className: "pb-canvas-stage", children: [
264
- /* @__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, {}) }),
265
- selectedBlock ? /* @__PURE__ */ jsxs2("div", { className: "pb-canvas-overlay", "aria-label": `\u5DF2\u9009\u62E9 ${selectedBlock.label}`, children: [
266
- /* @__PURE__ */ jsx2("span", { children: selectedBlock.label }),
267
- /* @__PURE__ */ jsx2("span", { children: "Selected" })
268
- ] }) : null
269
- ] }),
263
+ /* @__PURE__ */ jsx2("div", { className: "pb-canvas-stage", children: /* @__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, {}) }) }),
270
264
  !state.isLeftRailOpen || !state.isRightPanelOpen ? /* @__PURE__ */ jsxs2("div", { className: "pb-collapsed-actions", children: [
271
265
  !state.isLeftRailOpen ? /* @__PURE__ */ jsx2(Button, { onClick: () => update({ isLeftRailOpen: true }), children: "\u6253\u5F00\u533A\u5757\u9762\u677F" }) : null,
272
266
  !state.isRightPanelOpen ? /* @__PURE__ */ jsx2(Button, { onClick: () => update({ isRightPanelOpen: true }), children: "\u6253\u5F00\u5C5E\u6027\u9762\u677F" }) : null
@@ -353,77 +347,132 @@ function PropertyPanel({
353
347
  import { Puck as Puck2, usePuck } from "@puckeditor/core";
354
348
  import { Badge as Badge3, Banner, Button as Button2, ButtonGroup as ButtonGroup2, Frame, InlineStack as InlineStack3, Select as Select2, Text as Text3, TextField, Toast } from "@shopify/polaris";
355
349
  import { DragHandleIcon as DragHandleIcon2, LayoutSectionIcon as LayoutSectionIcon2, MenuIcon as MenuIcon2, ProductIcon, RedoIcon as RedoIcon2, UndoIcon as UndoIcon2, XIcon as XIcon2 } from "@shopify/polaris-icons";
356
- import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef4, useState as useState3 } from "react";
350
+ import { useCallback as useCallback2, useEffect as useEffect4, useLayoutEffect, useMemo as useMemo2, useRef as useRef5, useState as useState4 } from "react";
357
351
 
358
- // src/adapters/puck/canvas-extension-block.tsx
352
+ // src/adapters/puck/use-canvas-editing.ts
359
353
  import { registerOverlayPortal } from "@puckeditor/core";
360
354
  import { useEffect, useRef as useRef2 } from "react";
355
+ var editingControlSelector = "input, textarea, select, button, a[href], [contenteditable]:not([contenteditable=false]), [role=textbox]";
356
+ function useCanvasEditing(active) {
357
+ const ref = useRef2(null);
358
+ useEffect(() => {
359
+ const root = ref.current;
360
+ if (!active || !root) return;
361
+ const cleanupPortal = registerOverlayPortal(root, { disableDragOnFocus: false });
362
+ const protectEditingControl = (event) => {
363
+ const target = event.target;
364
+ const control = typeof target?.closest === "function" ? target.closest(editingControlSelector) : null;
365
+ if (control && root.contains(control)) event.stopPropagation();
366
+ };
367
+ root.addEventListener("pointerdown", protectEditingControl, { capture: true });
368
+ return () => {
369
+ root.removeEventListener("pointerdown", protectEditingControl, { capture: true });
370
+ cleanupPortal?.();
371
+ };
372
+ }, [active]);
373
+ return ref;
374
+ }
375
+
376
+ // src/adapters/puck/canvas-extension-block.tsx
361
377
  import { jsx as jsx3 } from "react/jsx-runtime";
362
- function CanvasExtensionBlock({ active, children, label, onSelect }) {
363
- const editorRef = useRef2(null);
364
- useEffect(() => active ? registerOverlayPortal(editorRef.current, { disableDrag: true }) : void 0, [active]);
365
- 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) => {
366
- if (event.key === "Enter" || event.key === " ") onSelect();
378
+ function CanvasExtensionBlock({ active, blockId, children, label, onSelect }) {
379
+ const editorRef = useCanvasEditing(active);
380
+ return /* @__PURE__ */ jsx3("div", { ref: editorRef, className: "pb-document-canvas__extension", "data-page-document-block-id": blockId, "aria-label": "Select " + label + " in canvas", role: "group", tabIndex: 0, onClick: onSelect, onKeyDown: (event) => {
381
+ if (event.target === event.currentTarget && (event.key === "Enter" || event.key === " ")) {
382
+ event.preventDefault();
383
+ onSelect();
384
+ }
367
385
  }, children });
368
386
  }
369
387
 
388
+ // src/adapters/puck/canvas-core-blocks.tsx
389
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
390
+ function CanvasTextBlock({ id, content, editable, onSelect, onPropsChange }) {
391
+ const ref = useCanvasEditing(editable);
392
+ return /* @__PURE__ */ jsx4("section", { ref, 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) => {
393
+ if (event.target === event.currentTarget && (event.key === "Enter" || event.key === " ")) {
394
+ event.preventDefault();
395
+ onSelect(id);
396
+ }
397
+ }, children: /* @__PURE__ */ jsx4("p", { contentEditable: editable, suppressContentEditableWarning: true, onInput: (event) => {
398
+ if (editable) onPropsChange(id, { content: event.currentTarget.textContent ?? "" }, true);
399
+ }, children: content }) });
400
+ }
401
+ function CanvasImageBlock({ id, src, alt, editable, onSelect, onPropsChange }) {
402
+ const ref = useCanvasEditing(editable);
403
+ return /* @__PURE__ */ jsxs3("figure", { ref, 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) => {
404
+ if (event.target === event.currentTarget && (event.key === "Enter" || event.key === " ")) {
405
+ event.preventDefault();
406
+ onSelect(id);
407
+ }
408
+ }, children: [
409
+ /* @__PURE__ */ jsx4("img", { src, alt }),
410
+ 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
411
+ ] });
412
+ }
413
+
370
414
  // src/adapters/puck/page-document-config.tsx
371
- import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
415
+ import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
416
+ function withBlockPermissions(component, type, resolvePermissions) {
417
+ if (!resolvePermissions) return component;
418
+ return {
419
+ ...component,
420
+ // Keep actions hidden until the policy resolver has produced the current
421
+ // per-block result. This prevents a one-frame clickable action that may be
422
+ // rejected by the PageDocument policy after Puck emits onChange.
423
+ permissions: { drag: false, duplicate: false, delete: false, edit: false, insert: false },
424
+ resolvePermissions: (item) => {
425
+ const id = typeof item?.props?.id === "string" ? item.props.id : "";
426
+ return resolvePermissions({ id, type });
427
+ }
428
+ };
429
+ }
372
430
  function canvasFieldFallback({ block, props, registry, onPropsChange }) {
373
431
  const fields = Object.entries(block.fields);
374
432
  if (!fields.length) return null;
375
- 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]) => {
433
+ return /* @__PURE__ */ jsx5("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]) => {
376
434
  const Field = registry?.getField(field.field)?.component;
377
- return Field ? /* @__PURE__ */ jsxs3("label", { style: { display: "grid", gap: 4, fontSize: 12, fontWeight: 600 }, children: [
435
+ return Field ? /* @__PURE__ */ jsxs4("label", { style: { display: "grid", gap: 4, fontSize: 12, fontWeight: 600 }, children: [
378
436
  field.label ?? name,
379
- /* @__PURE__ */ jsx4(Field, { value: props[name], onChange: (value) => onPropsChange({ [name]: value }) })
437
+ /* @__PURE__ */ jsx5(Field, { value: props[name], onChange: (value) => onPropsChange({ [name]: value }) })
380
438
  ] }, name) : null;
381
439
  }) });
382
440
  }
383
- function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId, registry) {
441
+ function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId, registry, resolveBlockPermissions) {
384
442
  const extensionComponents = Object.fromEntries((registry?.blocks ?? []).flatMap((block) => {
385
443
  const BlockRenderer = block.render.web;
386
444
  if (!BlockRenderer) return [];
387
- return [[block.type, {
445
+ return [[block.type, withBlockPermissions({
388
446
  render: (props) => {
389
447
  const id = typeof props.id === "string" ? props.id : `unknown-${block.type}`;
390
448
  const EditorRenderer = block.render.editor;
391
449
  const active = id === selectedBlockId;
392
- 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: [
393
- /* @__PURE__ */ jsx4(BlockRenderer, { ...props }),
450
+ return /* @__PURE__ */ jsx5(CanvasExtensionBlock, { active, blockId: id, label: block.label, onSelect: () => onSelect(id), children: EditorRenderer ? /* @__PURE__ */ jsx5(EditorRenderer, { ...props, blockId: id, selected: active, onPropsChange: (next) => onPropsChange(id, next) }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
451
+ /* @__PURE__ */ jsx5(BlockRenderer, { ...props }),
394
452
  active ? canvasFieldFallback({ block, props, registry, onPropsChange: (next) => onPropsChange(id, next) }) : null
395
453
  ] }) });
396
454
  }
397
- }]];
455
+ }, block.type, resolveBlockPermissions)]];
398
456
  }));
399
457
  return {
400
458
  components: {
401
- Text: {
459
+ Text: withBlockPermissions({
402
460
  render: (props) => {
403
461
  const id = typeof props.id === "string" ? props.id : "unknown-text";
404
462
  const content = typeof props.content === "string" ? props.content : "";
405
463
  const editable = id === selectedBlockId;
406
- 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) => {
407
- if (event.key === "Enter" || event.key === " ") onSelect(id);
408
- }, children: /* @__PURE__ */ jsx4("p", { contentEditable: editable, suppressContentEditableWarning: true, onInput: (event) => {
409
- if (editable) onPropsChange(id, { content: event.currentTarget.textContent ?? "" }, true);
410
- }, children: content }) });
464
+ return /* @__PURE__ */ jsx5(CanvasTextBlock, { id, content, editable, onSelect, onPropsChange });
411
465
  }
412
- },
413
- Image: {
466
+ }, "core.text", resolveBlockPermissions),
467
+ Image: withBlockPermissions({
414
468
  render: (props) => {
415
469
  const id = typeof props.id === "string" ? props.id : "unknown-image";
416
470
  const src = typeof props.src === "string" ? props.src : "";
417
471
  const alt = typeof props.alt === "string" ? props.alt : "";
418
472
  const editable = id === selectedBlockId;
419
- 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) => {
420
- if (event.key === "Enter" || event.key === " ") onSelect(id);
421
- }, children: [
422
- /* @__PURE__ */ jsx4("img", { src, alt }),
423
- 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
424
- ] });
473
+ return /* @__PURE__ */ jsx5(CanvasImageBlock, { id, src, alt, editable, onSelect, onPropsChange });
425
474
  }
426
- },
475
+ }, "core.image", resolveBlockPermissions),
427
476
  ...extensionComponents
428
477
  }
429
478
  };
@@ -440,10 +489,10 @@ function toEngineBlock(block, registry) {
440
489
  if (!type) throw new Error(`V0.2 \u4E0D\u652F\u6301\u8F6C\u6362\u533A\u5757\u7C7B\u578B\uFF1A${block.type}`);
441
490
  return { type, props: { id: block.id, ...block.props } };
442
491
  }
443
- function toEngineData(document, registry) {
492
+ function toEngineData(document2, registry) {
444
493
  return {
445
- root: { props: { ...document.root } },
446
- content: document.blocks.map((block) => toEngineBlock(block, registry))
494
+ root: { props: { ...document2.root } },
495
+ content: document2.blocks.map((block) => toEngineBlock(block, registry))
447
496
  };
448
497
  }
449
498
  function fromEngineData(data, base, registry) {
@@ -530,7 +579,7 @@ function canApplyCanvasDocument(previous, next, getDefinition, policy) {
530
579
  }
531
580
 
532
581
  // src/editor/context/EditorContext.tsx
533
- import { jsx as jsx5 } from "react/jsx-runtime";
582
+ import { jsx as jsx6 } from "react/jsx-runtime";
534
583
  function historyReducer(state, action) {
535
584
  if (action.type === "select") return { ...state, selectedBlockId: action.id };
536
585
  if (action.type === "device") return { ...state, device: action.device };
@@ -563,7 +612,7 @@ function defaultBlock(type, blocks, registry) {
563
612
  return { id: uniqueBlockId(type, blocks), type, version: definition.version, props: definition.defaultProps, variant: definition.defaultVariant ?? "default", style: {} };
564
613
  }
565
614
  function EditorProvider({ initialDocument, registry, policy, loadState = "ready", sessionState = "active", session, sessionMessage, leaveWarning = "You have unsaved changes.", onDocumentChange, children }) {
566
- const [history, dispatch] = useReducer(historyReducer, initialDocument, (document) => ({ document, selectedBlockId: document.blocks[0]?.id ?? null, past: [], future: [], device: "desktop", savedDocument: document }));
615
+ const [history, dispatch] = useReducer(historyReducer, initialDocument, (document2) => ({ document: document2, selectedBlockId: document2.blocks[0]?.id ?? null, past: [], future: [], device: "desktop", savedDocument: document2 }));
567
616
  const historyRef = useRef3(history);
568
617
  useEffect2(() => {
569
618
  historyRef.current = history;
@@ -573,7 +622,7 @@ function EditorProvider({ initialDocument, registry, policy, loadState = "ready"
573
622
  const editable = (loadState === "ready" || loadState === "success") && sessionState === "active";
574
623
  const selectedBlock = history.document.blocks.find((block) => block.id === history.selectedBlockId) ?? null;
575
624
  const pendingDeleteBlock = history.document.blocks.find((block) => block.id === pendingDeleteBlockId) ?? null;
576
- const replace = useCallback((document, selectedBlockId) => dispatch({ type: "replace", document, selectedBlockId }), []);
625
+ const replace = useCallback((document2, selectedBlockId) => dispatch({ type: "replace", document: document2, selectedBlockId }), []);
577
626
  const requestCanvasSelection = useCallback((id) => {
578
627
  if (editable) setCanvasSelectionRequest(id);
579
628
  }, [editable]);
@@ -581,11 +630,11 @@ function EditorProvider({ initialDocument, registry, policy, loadState = "ready"
581
630
  setCanvasSelectionRequest(null);
582
631
  dispatch({ type: "select", id });
583
632
  }, []);
584
- const updateFromCanvas = useCallback((document) => {
633
+ const updateFromCanvas = useCallback((document2) => {
585
634
  const current = historyRef.current;
586
- if (!editable || JSON.stringify(document) === JSON.stringify(current.document)) return false;
587
- if (!canApplyCanvasDocument(current.document, document, (type) => registry?.getBlock(type), policy)) return false;
588
- replace(document, current.selectedBlockId);
635
+ if (!editable || JSON.stringify(document2) === JSON.stringify(current.document)) return false;
636
+ if (!canApplyCanvasDocument(current.document, document2, (type) => registry?.getBlock(type), policy)) return false;
637
+ replace(document2, current.selectedBlockId);
589
638
  return true;
590
639
  }, [editable, policy, registry, replace]);
591
640
  const updateBlockProps = useCallback((id, props) => {
@@ -658,6 +707,14 @@ function EditorProvider({ initialDocument, registry, policy, loadState = "ready"
658
707
  const block = history.document.blocks.find((item) => item.id === id);
659
708
  return editable && canDragBlock(block, registry?.getBlock(block?.type ?? ""), policy);
660
709
  },
710
+ canDeleteBlock: (id) => {
711
+ const block = history.document.blocks.find((item) => item.id === id);
712
+ return editable && canDeleteBlock(block, history.document.blocks, registry?.getBlock(block?.type ?? ""), policy);
713
+ },
714
+ canDuplicateBlock: (id) => {
715
+ const block = history.document.blocks.find((item) => item.id === id);
716
+ return editable && canDuplicateBlock(block, history.document.blocks, registry?.getBlock(block?.type ?? ""), policy);
717
+ },
661
718
  requestCanvasSelection,
662
719
  confirmCanvasSelection,
663
720
  updateFromCanvas,
@@ -733,10 +790,10 @@ function EditorProvider({ initialDocument, registry, policy, loadState = "ready"
733
790
  redo: () => {
734
791
  if (editable) dispatch({ type: "redo" });
735
792
  },
736
- markSaved: (document = historyRef.current.document) => dispatch({ type: "saved", document })
793
+ markSaved: (document2 = historyRef.current.document) => dispatch({ type: "saved", document: document2 })
737
794
  };
738
795
  }, [canvasSelectionRequest, confirmCanvasSelection, editable, history, isDirty, loadState, pendingDeleteBlock, pendingDeleteBlockId, policy, registry, replace, requestCanvasSelection, selectedBlock, session, sessionMessage, sessionState, updateBlockPresentation, updateBlockProps, updateFromCanvas]);
739
- return /* @__PURE__ */ jsx5(EditorContext.Provider, { value, children });
796
+ return /* @__PURE__ */ jsx6(EditorContext.Provider, { value, children });
740
797
  }
741
798
  function useEditorContext() {
742
799
  const context = useContext(EditorContext);
@@ -746,18 +803,18 @@ function useEditorContext() {
746
803
 
747
804
  // src/editor/components/PageStatusCard.tsx
748
805
  import { Badge as Badge2, InlineStack as InlineStack2, Text as Text2 } from "@shopify/polaris";
749
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
806
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
750
807
  function PageStatusCard({ status, sessionState }) {
751
808
  const publicationTone = status.publicationStatus === "published" ? "success" : "attention";
752
809
  const sessionLabel = sessionState === "locked" ? `${status.editingBy ?? "\u5176\u4ED6\u4EBA"}\u6B63\u5728\u7F16\u8F91` : sessionState === "lost" ? "\u7F16\u8F91\u9501\u5DF2\u5931\u6548" : status.editingBy ? `${status.editingBy}\u6B63\u5728\u7F16\u8F91` : void 0;
753
- return /* @__PURE__ */ jsxs4("div", { className: "pb-page-status-card", "data-testid": "page-status-card", "data-publication-status": status.publicationStatus, "data-session-state": sessionState, children: [
754
- /* @__PURE__ */ jsxs4(InlineStack2, { gap: "150", blockAlign: "center", wrap: true, children: [
755
- /* @__PURE__ */ jsx6(Badge2, { tone: publicationTone, children: status.publicationStatus === "published" ? "\u7EBF\u4E0A" : "\u672A\u53D1\u5E03" }),
756
- status.draftLabel ? /* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: status.draftLabel }) : null,
757
- status.publishedVersionLabel ? /* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: status.publishedVersionLabel }) : null,
758
- sessionLabel ? /* @__PURE__ */ jsx6(Badge2, { tone: sessionState === "lost" ? "critical" : "attention", children: sessionLabel }) : null
810
+ return /* @__PURE__ */ jsxs5("div", { className: "pb-page-status-card", "data-testid": "page-status-card", "data-publication-status": status.publicationStatus, "data-session-state": sessionState, children: [
811
+ /* @__PURE__ */ jsxs5(InlineStack2, { gap: "150", blockAlign: "center", wrap: true, children: [
812
+ /* @__PURE__ */ jsx7(Badge2, { tone: publicationTone, children: status.publicationStatus === "published" ? "\u7EBF\u4E0A" : "\u672A\u53D1\u5E03" }),
813
+ status.draftLabel ? /* @__PURE__ */ jsx7(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: status.draftLabel }) : null,
814
+ status.publishedVersionLabel ? /* @__PURE__ */ jsx7(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: status.publishedVersionLabel }) : null,
815
+ sessionLabel ? /* @__PURE__ */ jsx7(Badge2, { tone: sessionState === "lost" ? "critical" : "attention", children: sessionLabel }) : null
759
816
  ] }),
760
- status.lastSavedAt ? /* @__PURE__ */ jsxs4(Text2, { as: "p", variant: "bodySm", tone: "subdued", children: [
817
+ status.lastSavedAt ? /* @__PURE__ */ jsxs5(Text2, { as: "p", variant: "bodySm", tone: "subdued", children: [
761
818
  "\u6700\u540E\u4FDD\u5B58\uFF1A",
762
819
  status.lastSavedAt
763
820
  ] }) : null
@@ -774,6 +831,7 @@ var messages = {
774
831
  undo: "\u64A4\u9500",
775
832
  redo: "\u6062\u590D",
776
833
  duplicate: "\u590D\u5236",
834
+ duplicateUnavailable: "\u6B64\u533A\u5757\u5DF2\u8FBE\u5230\u5141\u8BB8\u6570\u91CF\u4E0A\u9650\uFF0C\u6216\u590D\u5236\u64CD\u4F5C\u5DF2\u88AB\u7981\u7528\u3002",
777
835
  remove: "\u5220\u9664",
778
836
  confirmDeleteTitle: "\u786E\u8BA4\u5220\u9664\u533A\u5757\uFF1F",
779
837
  confirmDeleteMessage: "\u5220\u9664\u540E\u53EF\u4F7F\u7528\u64A4\u9500\u6062\u590D\u3002",
@@ -838,6 +896,7 @@ var messages = {
838
896
  undo: "Undo",
839
897
  redo: "Redo",
840
898
  duplicate: "Duplicate",
899
+ duplicateUnavailable: "This block has reached its limit or duplication is disabled.",
841
900
  remove: "Delete",
842
901
  confirmDeleteTitle: "Delete this block?",
843
902
  confirmDeleteMessage: "You can restore it with Undo after deleting.",
@@ -900,17 +959,197 @@ function createAdminI18n(locale) {
900
959
  return { locale: resolvedLocale, t: (key) => messages[resolvedLocale][key] };
901
960
  }
902
961
 
962
+ // src/editor/shell/CanvasLibraryDropTarget.tsx
963
+ import { useEffect as useEffect3, useRef as useRef4, useState as useState3 } from "react";
964
+
903
965
  // src/editor/shell/drop-position.ts
904
966
  function nearestBlockIdAtY(blocks, y) {
905
967
  return blocks.find((block) => y < block.top + block.height / 2)?.id;
906
968
  }
907
- function blockIdAtRelativeY(ids, relativeY) {
908
- const index = Math.min(ids.length, Math.max(0, Math.round(relativeY * ids.length)));
909
- return ids[index];
969
+
970
+ // src/editor/shell/CanvasLibraryDropTarget.tsx
971
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
972
+ function uniqueBlocks(blocks) {
973
+ const seen = /* @__PURE__ */ new Set();
974
+ return blocks.filter((block) => !seen.has(block.id) && seen.add(block.id));
975
+ }
976
+ function scrollableAncestors(start) {
977
+ const result = [];
978
+ let node = start;
979
+ while (node) {
980
+ const style = window.getComputedStyle(node);
981
+ if (["auto", "scroll", "overlay"].includes(style.overflowY) && node.scrollHeight > node.clientHeight) result.push(node);
982
+ node = node.parentElement;
983
+ }
984
+ return result;
985
+ }
986
+ function scrollDelta(pointerY, rect, elapsedMs) {
987
+ const top = Math.max(0, rect.top);
988
+ const bottom = Math.min(window.innerHeight, rect.bottom);
989
+ const height = bottom - top;
990
+ if (height <= 0 || pointerY < top || pointerY > bottom) return 0;
991
+ const edge = Math.min(96, Math.max(36, height * 0.24));
992
+ const speed = 900;
993
+ if (pointerY < top + edge) return -speed * Math.min(1, (top + edge - pointerY) / edge) * elapsedMs / 1e3;
994
+ if (pointerY > bottom - edge) return speed * Math.min(1, (pointerY - (bottom - edge)) / edge) * elapsedMs / 1e3;
995
+ return 0;
996
+ }
997
+ function scrollElement(element, delta) {
998
+ const canScroll = delta < 0 ? element.scrollTop > 0 : element.scrollTop < element.scrollHeight - element.clientHeight;
999
+ if (!delta || !canScroll) return false;
1000
+ if (typeof element.scrollBy === "function") element.scrollBy({ top: delta, behavior: "auto" });
1001
+ else element.scrollTop += delta;
1002
+ return true;
1003
+ }
1004
+ function CanvasLibraryDropTarget({ frameRef, blocks, label, onDrop, onCancel, adminLocale }) {
1005
+ const targetRef = useRef4(null);
1006
+ const latestPreview = useRef4(null);
1007
+ const dragging = useRef4(false);
1008
+ const completed = useRef4(false);
1009
+ const pointerY = useRef4(0);
1010
+ const animationFrame = useRef4(null);
1011
+ const lastFrameTime = useRef4(null);
1012
+ const callbacks = useRef4({ onDrop, onCancel });
1013
+ useEffect3(() => {
1014
+ callbacks.current = { onDrop, onCancel };
1015
+ }, [onDrop, onCancel]);
1016
+ const orderedBlocks = uniqueBlocks(blocks);
1017
+ const [preview, setPreview] = useState3(null);
1018
+ const english = adminLocale?.toLowerCase().startsWith("en") ?? false;
1019
+ const stopAutoScroll = () => {
1020
+ if (animationFrame.current !== null) window.cancelAnimationFrame(animationFrame.current);
1021
+ animationFrame.current = null;
1022
+ lastFrameTime.current = null;
1023
+ };
1024
+ const clear = (cancel) => {
1025
+ stopAutoScroll();
1026
+ dragging.current = false;
1027
+ latestPreview.current = null;
1028
+ setPreview(null);
1029
+ if (cancel) {
1030
+ completed.current = true;
1031
+ callbacks.current.onCancel();
1032
+ }
1033
+ };
1034
+ const calculatePreview = (clientY) => {
1035
+ const target = targetRef.current;
1036
+ const frame = frameRef.current;
1037
+ if (!target || !frame) return null;
1038
+ const targetRect = target.getBoundingClientRect();
1039
+ const frameRect = frame.getBoundingClientRect();
1040
+ const iframe = frame.querySelector("iframe");
1041
+ const iframeDocument = iframe?.contentDocument;
1042
+ const root = iframeDocument ?? frame;
1043
+ const allowed = new Set(orderedBlocks.map((block) => block.id));
1044
+ const byId = /* @__PURE__ */ new Map();
1045
+ const iframeRect = iframeDocument ? iframe?.getBoundingClientRect() : void 0;
1046
+ const scaleY = iframe && iframeRect && iframeRect.height > 0 && iframe.clientHeight > 0 ? iframeRect.height / iframe.clientHeight : 1;
1047
+ const elements = Array.from(root.querySelectorAll("[data-page-document-block-id], [data-puck-dnd]"));
1048
+ for (const element of elements) {
1049
+ const id = element.dataset.pageDocumentBlockId ?? element.dataset.puckDnd;
1050
+ if (!id || !allowed.has(id) || byId.has(id) || element.closest('[aria-hidden="true"], [class*="DragOverlay"]')) continue;
1051
+ const rect = element.getBoundingClientRect();
1052
+ byId.set(id, { id, top: iframeRect ? iframeRect.top + rect.top * scaleY : rect.top, height: iframeRect ? rect.height * scaleY : rect.height });
1053
+ }
1054
+ const candidates = orderedBlocks.flatMap((block) => byId.get(block.id) ?? []);
1055
+ const beforeId = nearestBlockIdAtY(candidates, clientY);
1056
+ const markerCandidate = beforeId ? candidates.find((candidate) => candidate.id === beforeId) : candidates.at(-1);
1057
+ const markerViewportTop = markerCandidate ? beforeId ? markerCandidate.top : markerCandidate.top + markerCandidate.height : Math.min(frameRect.bottom, Math.max(frameRect.top, clientY));
1058
+ return { beforeId, markerTop: Math.max(0, markerViewportTop - targetRect.top), markerLeft: Math.max(0, frameRect.left - targetRect.left), markerWidth: frameRect.width };
1059
+ };
1060
+ const updatePreview = (clientY) => {
1061
+ pointerY.current = clientY;
1062
+ const next = calculatePreview(clientY);
1063
+ const previous = latestPreview.current;
1064
+ latestPreview.current = next;
1065
+ if (previous?.beforeId !== next?.beforeId || previous?.markerTop !== next?.markerTop || previous?.markerLeft !== next?.markerLeft || previous?.markerWidth !== next?.markerWidth) setPreview(next);
1066
+ };
1067
+ const autoScroll = (timestamp) => {
1068
+ if (!dragging.current) return;
1069
+ const elapsedMs = Math.min(64, Math.max(0, timestamp - (lastFrameTime.current ?? timestamp)));
1070
+ lastFrameTime.current = timestamp;
1071
+ const stage = targetRef.current?.parentElement;
1072
+ if (stage) {
1073
+ let scrolled = false;
1074
+ for (const ancestor of scrollableAncestors(stage)) {
1075
+ const delta = scrollDelta(pointerY.current, ancestor.getBoundingClientRect(), elapsedMs);
1076
+ if (scrollElement(ancestor, delta)) {
1077
+ scrolled = true;
1078
+ break;
1079
+ }
1080
+ }
1081
+ const documentScroller = document.scrollingElement;
1082
+ if (!scrolled && documentScroller) {
1083
+ const viewport = new DOMRect(0, 0, window.innerWidth, window.innerHeight);
1084
+ scrollElement(documentScroller, scrollDelta(pointerY.current, viewport, elapsedMs));
1085
+ }
1086
+ updatePreview(pointerY.current);
1087
+ }
1088
+ animationFrame.current = window.requestAnimationFrame(autoScroll);
1089
+ };
1090
+ const startAutoScroll = () => {
1091
+ if (animationFrame.current === null) animationFrame.current = window.requestAnimationFrame(autoScroll);
1092
+ };
1093
+ useEffect3(() => {
1094
+ const cancelOutside = (event) => {
1095
+ if (event.type === "drop" && targetRef.current?.contains(event.target)) return;
1096
+ if (!completed.current) clear(true);
1097
+ };
1098
+ const onKeyDown = (event) => {
1099
+ if (event.key === "Escape" && !completed.current) clear(true);
1100
+ };
1101
+ document.addEventListener("keydown", onKeyDown);
1102
+ document.addEventListener("dragend", cancelOutside, true);
1103
+ document.addEventListener("drop", cancelOutside, true);
1104
+ return () => {
1105
+ document.removeEventListener("keydown", onKeyDown);
1106
+ document.removeEventListener("dragend", cancelOutside, true);
1107
+ document.removeEventListener("drop", cancelOutside, true);
1108
+ stopAutoScroll();
1109
+ };
1110
+ }, []);
1111
+ const handleDragOver = (event) => {
1112
+ event.preventDefault();
1113
+ event.stopPropagation();
1114
+ event.dataTransfer.dropEffect = "copy";
1115
+ completed.current = false;
1116
+ dragging.current = true;
1117
+ updatePreview(event.clientY);
1118
+ startAutoScroll();
1119
+ };
1120
+ const handleDragLeave = (event) => {
1121
+ if (!event.relatedTarget || !event.currentTarget.contains(event.relatedTarget)) clear(false);
1122
+ };
1123
+ const handleDrop = (event) => {
1124
+ event.preventDefault();
1125
+ event.stopPropagation();
1126
+ const next = latestPreview.current ?? calculatePreview(event.clientY);
1127
+ completed.current = true;
1128
+ clear(false);
1129
+ callbacks.current.onDrop(next?.beforeId);
1130
+ };
1131
+ const position = preview?.beforeId ? orderedBlocks.findIndex((block) => block.id === preview.beforeId) + 1 : orderedBlocks.length + 1;
1132
+ const hint = english ? `Release to add ${label}${preview ? ` \xB7 position ${position}` : ""}` : `\u677E\u5F00\u4EE5\u6DFB\u52A0 ${label}${preview ? ` \xB7 \u7B2C ${position} \u4E2A\u4F4D\u7F6E` : ""}`;
1133
+ const style = { position: "absolute", inset: 0, zIndex: 2 };
1134
+ const markerLabel = english ? `Position ${position}` : `\u7B2C ${position} \u4E2A\u4F4D\u7F6E`;
1135
+ return /* @__PURE__ */ jsxs6("div", { ref: targetRef, className: "pb-canvas-drop-target", "data-testid": "canvas-drop-target", role: "region", "aria-label": english ? "Block drop area" : "\u533A\u5757\u6295\u653E\u533A", style, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [
1136
+ /* @__PURE__ */ jsx8("span", { className: "pb-canvas-drop-target__hint", "aria-live": "polite", children: hint }),
1137
+ preview ? /* @__PURE__ */ jsx8("span", { className: "pb-canvas-drop-marker", "aria-hidden": "true", style: { top: preview.markerTop, left: preview.markerLeft, width: preview.markerWidth }, children: /* @__PURE__ */ jsx8("span", { className: "pb-canvas-drop-marker__label", children: markerLabel }) }) : null
1138
+ ] });
910
1139
  }
911
1140
 
912
1141
  // src/editor/shell/PageDocumentEditorShell.tsx
913
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1142
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1143
+ function scrollCanvasBlockIntoView(blockId, frame) {
1144
+ const selector = `[data-page-document-block-id="${CSS.escape(blockId)}"]`;
1145
+ const scroll = () => {
1146
+ const iframe = frame?.querySelector("iframe");
1147
+ const element = iframe?.contentDocument?.querySelector(selector) ?? document.querySelector(selector);
1148
+ element?.scrollIntoView({ block: "start", inline: "nearest" });
1149
+ };
1150
+ if (typeof requestAnimationFrame === "function") requestAnimationFrame(scroll);
1151
+ else scroll();
1152
+ }
914
1153
  var deviceLabels2 = { desktop: "desktop", tablet: "tablet", mobile: "mobile" };
915
1154
  function blockLabel(block, registry) {
916
1155
  return blockTypeLabel(block.type, registry);
@@ -920,12 +1159,12 @@ function blockTypeLabel(type, registry) {
920
1159
  if (type === "core.image") return "\u56FE\u7247";
921
1160
  return registry?.getBlock(type)?.label ?? type;
922
1161
  }
923
- function validateDocumentBlocks(document, registry) {
924
- return registry ? validatePageDocumentWithRegistry(document, registry) : [];
1162
+ function validateDocumentBlocks(document2, registry) {
1163
+ return registry ? validatePageDocumentWithRegistry(document2, registry) : [];
925
1164
  }
926
1165
  function useEditorSession(adapter, pageId) {
927
- const [managed, setManaged] = useState3(() => adapter ? { state: "acquiring" } : { state: "active" });
928
- useEffect3(() => {
1166
+ const [managed, setManaged] = useState4(() => adapter ? { state: "acquiring" } : { state: "active" });
1167
+ useEffect4(() => {
929
1168
  let cancelled = false;
930
1169
  let session;
931
1170
  let heartbeatTimer;
@@ -967,27 +1206,31 @@ function useEditorSession(adapter, pageId) {
967
1206
  function PageDocumentEditorShell(props) {
968
1207
  const { sessionAdapter, initialDocument, onSessionStateChange } = props;
969
1208
  const managedSession = useEditorSession(sessionAdapter, initialDocument.pageId);
970
- useEffect3(() => {
1209
+ useEffect4(() => {
971
1210
  onSessionStateChange?.(managedSession.state);
972
1211
  }, [managedSession.state, onSessionStateChange]);
973
- return /* @__PURE__ */ jsx7(EditorProvider, { initialDocument: props.initialDocument, registry: props.registry, policy: props.policy, loadState: props.loadState, sessionState: managedSession.state, session: managedSession.session, sessionMessage: managedSession.message, leaveWarning: createAdminI18n(props.adminLocale).t("leaveWarning"), onDocumentChange: props.onDocumentChange, children: /* @__PURE__ */ jsx7(PageDocumentEditor, { ...props }) });
1212
+ return /* @__PURE__ */ jsx9(EditorProvider, { initialDocument: props.initialDocument, registry: props.registry, policy: props.policy, loadState: props.loadState, sessionState: managedSession.state, session: managedSession.session, sessionMessage: managedSession.message, leaveWarning: createAdminI18n(props.adminLocale).t("leaveWarning"), onDocumentChange: props.onDocumentChange, children: /* @__PURE__ */ jsx9(PageDocumentEditor, { ...props }) });
974
1213
  }
975
1214
  function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onPublish, draftPersistence, draftRevision: initialDraftRevision, autoSave = true, autoSaveDelayMs = 800, publishAction, assetPicker, inspectorSettings, productPicker, pageStatus, onBack, onHistory, onPreview, onAddToStore, deleteConfirmation, availableBlockTypes, appearanceControls = false }) {
976
1215
  const editor = useEditorContext();
977
- const [blockView, setBlockView] = useState3("blocks");
978
- const [leftRailOpen, setLeftRailOpen] = useState3(true);
979
- const [rightPanelOpen, setRightPanelOpen] = useState3(true);
980
- const [draggingLibraryType, setDraggingLibraryType] = useState3(null);
981
- const canvasFrameRef = useRef4(null);
982
- const [canvasMutationVersion, setCanvasMutationVersion] = useState3(0);
983
- const [canvasResetVersion, setCanvasResetVersion] = useState3(0);
984
- const [request, setRequest] = useState3("idle");
985
- const [notice, setNotice] = useState3(null);
986
- const [draftRevision, setDraftRevision] = useState3(initialDraftRevision);
987
- const [isOnline, setIsOnline] = useState3(() => typeof navigator === "undefined" || navigator.onLine);
988
- const [saveState, setSaveState] = useState3("saved");
989
- const [zoom, setZoom] = useState3("auto");
990
- const [confirmBack, setConfirmBack] = useState3(false);
1216
+ const editorRef = useRef5(editor);
1217
+ useLayoutEffect(() => {
1218
+ editorRef.current = editor;
1219
+ }, [editor]);
1220
+ const [blockView, setBlockView] = useState4("blocks");
1221
+ const [leftRailOpen, setLeftRailOpen] = useState4(true);
1222
+ const [rightPanelOpen, setRightPanelOpen] = useState4(true);
1223
+ const [draggingLibraryType, setDraggingLibraryType] = useState4(null);
1224
+ const canvasFrameRef = useRef5(null);
1225
+ const [canvasMutationVersion, setCanvasMutationVersion] = useState4(0);
1226
+ const [canvasResetVersion, setCanvasResetVersion] = useState4(0);
1227
+ const [request, setRequest] = useState4("idle");
1228
+ const [notice, setNotice] = useState4(null);
1229
+ const [draftRevision, setDraftRevision] = useState4(initialDraftRevision);
1230
+ const [isOnline, setIsOnline] = useState4(() => typeof navigator === "undefined" || navigator.onLine);
1231
+ const [saveState, setSaveState] = useState4("saved");
1232
+ const [zoom, setZoom] = useState4("auto");
1233
+ const [confirmBack, setConfirmBack] = useState4(false);
991
1234
  const i18n = createAdminI18n(adminLocale);
992
1235
  const engineData = useMemo2(() => toEngineData(editor.document, registry), [editor.document, registry]);
993
1236
  const canvasThemeStyle = useMemo2(() => {
@@ -1000,8 +1243,15 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1000
1243
  if (preserveCanvasValue) setCanvasMutationVersion((version) => version + 1);
1001
1244
  updateBlockProps(id, props);
1002
1245
  }, [updateBlockProps]);
1003
- const config = useMemo2(() => createPageDocumentPuckConfig(confirmCanvasSelection, updateFromCanvasInput, selectedBlockId, registry), [confirmCanvasSelection, registry, selectedBlockId, updateFromCanvasInput]);
1004
- useEffect3(() => {
1246
+ const resolveBlockPermissions = useCallback2(({ id, type }) => ({
1247
+ duplicate: editorRef.current.canDuplicateBlock(id),
1248
+ delete: editorRef.current.canDeleteBlock(id),
1249
+ drag: editorRef.current.canDragBlock(id),
1250
+ edit: editorRef.current.isEditable,
1251
+ insert: editorRef.current.canAddBlock(type)
1252
+ }), []);
1253
+ const config = useMemo2(() => createPageDocumentPuckConfig(confirmCanvasSelection, updateFromCanvasInput, selectedBlockId, registry, resolveBlockPermissions), [confirmCanvasSelection, registry, resolveBlockPermissions, selectedBlockId, updateFromCanvasInput]);
1254
+ useEffect4(() => {
1005
1255
  const online = () => setIsOnline(true);
1006
1256
  const offline = () => setIsOnline(false);
1007
1257
  window.addEventListener("online", online);
@@ -1019,14 +1269,14 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1019
1269
  setSaveState("offline");
1020
1270
  return;
1021
1271
  }
1022
- const document = editor.document;
1272
+ const document2 = editor.document;
1023
1273
  setRequest("saving");
1024
1274
  setSaveState("saving");
1025
1275
  setNotice(null);
1026
1276
  try {
1027
- const result = draftPersistence ? await draftPersistence.saveDraft({ document, expectedRevision: draftRevision, session: editor.session }) : await onSave?.(document);
1277
+ const result = draftPersistence ? await draftPersistence.saveDraft({ document: document2, expectedRevision: draftRevision, session: editor.session }) : await onSave?.(document2);
1028
1278
  if (result?.revision !== void 0) setDraftRevision(result.revision);
1029
- editor.markSaved(document);
1279
+ editor.markSaved(document2);
1030
1280
  setSaveState("saved");
1031
1281
  } catch {
1032
1282
  setNotice("saveFailed");
@@ -1035,7 +1285,7 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1035
1285
  setRequest("idle");
1036
1286
  }
1037
1287
  }, [canPersistDraft, draftPersistence, draftRevision, editor, isOnline, onSave, request, validationIssues.length]);
1038
- useEffect3(() => {
1288
+ useEffect4(() => {
1039
1289
  if (!autoSave || !canPersistDraft || !editor.isDirty || resolvedSaveState !== "dirty" && resolvedSaveState !== "failed" || !isOnline || request !== "idle" || validationIssues.length > 0 || editor.sessionState !== "active") return;
1040
1290
  const timer = window.setTimeout(() => {
1041
1291
  void save();
@@ -1044,13 +1294,13 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1044
1294
  }, [autoSave, autoSaveDelayMs, canPersistDraft, editor.isDirty, editor.sessionState, isOnline, request, resolvedSaveState, save, validationIssues.length]);
1045
1295
  const publish = async () => {
1046
1296
  if (!publishAction && !onPublish || request !== "idle" || validationIssues.length > 0 || !isOnline) return;
1047
- const document = editor.document;
1297
+ const document2 = editor.document;
1048
1298
  setRequest("publishing");
1049
1299
  setNotice(null);
1050
1300
  try {
1051
- if (publishAction) await publishAction.publish({ document, expectedRevision: draftRevision, session: editor.session, validationIssues });
1052
- else await onPublish?.(document);
1053
- editor.markSaved(document);
1301
+ if (publishAction) await publishAction.publish({ document: document2, expectedRevision: draftRevision, session: editor.session, validationIssues });
1302
+ else await onPublish?.(document2);
1303
+ editor.markSaved(document2);
1054
1304
  setSaveState("saved");
1055
1305
  setNotice("published");
1056
1306
  } catch {
@@ -1059,40 +1309,14 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1059
1309
  setRequest("idle");
1060
1310
  }
1061
1311
  };
1062
- if (editor.loadState !== "ready" && editor.loadState !== "success") return /* @__PURE__ */ jsx7(EditorStatus, { state: editor.loadState });
1063
- if (editor.sessionState !== "active") return /* @__PURE__ */ jsx7(EditorSessionStatus, { state: editor.sessionState, message: editor.sessionMessage });
1312
+ if (editor.loadState !== "ready" && editor.loadState !== "success") return /* @__PURE__ */ jsx9(EditorStatus, { state: editor.loadState });
1313
+ if (editor.sessionState !== "active") return /* @__PURE__ */ jsx9(EditorSessionStatus, { state: editor.sessionState, message: editor.sessionMessage });
1064
1314
  const allBlockTypes = ["core.text", "core.image", ...registry?.blocks.map((block) => block.type) ?? []];
1065
1315
  const blockTypes = availableBlockTypes ? allBlockTypes.filter((type) => availableBlockTypes.includes(type)) : allBlockTypes;
1066
1316
  const addFromLibrary = (type, beforeId) => {
1067
1317
  const id = editor.addBlock(type, beforeId);
1068
1318
  if (id) editor.requestCanvasSelection(id);
1069
1319
  };
1070
- const getDropBeforeId = (clientY) => {
1071
- const frame = canvasFrameRef.current;
1072
- if (!frame) return void 0;
1073
- const iframe2 = frame.querySelector("iframe");
1074
- const root = iframe2?.contentDocument ?? frame;
1075
- const offsetTop = iframe2?.getBoundingClientRect().top ?? 0;
1076
- const elements = Array.from(root.querySelectorAll("[data-page-document-block-id]"));
1077
- const puckElements = elements.length > 0 ? elements : Array.from(root.querySelectorAll("[data-puck-dnd]"));
1078
- const blockElements = puckElements.length > 0 ? puckElements : Array.from(window.document.querySelectorAll("[data-page-document-block-id], [data-puck-dnd]"));
1079
- const candidates = blockElements.map((element) => ({
1080
- id: element.dataset.pageDocumentBlockId ?? element.dataset.puckDnd,
1081
- top: element.getBoundingClientRect().top,
1082
- height: element.getBoundingClientRect().height
1083
- })).filter((item) => typeof item.id === "string");
1084
- const nearestId = nearestBlockIdAtY(candidates, clientY - offsetTop);
1085
- if (nearestId || candidates.length > 0) return nearestId;
1086
- const rect = frame.getBoundingClientRect();
1087
- if (rect.height <= 0) return void 0;
1088
- return blockIdAtRelativeY(editor.document.blocks.map((block) => block.id), (clientY - rect.top) / rect.height);
1089
- };
1090
- const dropFromLibrary = (event) => {
1091
- event.preventDefault();
1092
- const type = event.dataTransfer.getData("application/x-page-document-block") || draggingLibraryType;
1093
- if (type && blockTypes.includes(type)) addFromLibrary(type, getDropBeforeId(event.clientY));
1094
- setDraggingLibraryType(null);
1095
- };
1096
1320
  const cancelLibraryDrop = (event) => {
1097
1321
  event.preventDefault();
1098
1322
  event.stopPropagation();
@@ -1106,58 +1330,63 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1106
1330
  else onBack();
1107
1331
  };
1108
1332
  const dismissPublishedNotice = () => setNotice((current) => current === "published" ? null : current);
1109
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1110
- /* @__PURE__ */ jsx7(Puck2, { config, data: engineData, iframe: { enabled: iframe }, onChange: (data) => {
1333
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
1334
+ /* @__PURE__ */ jsx9(Puck2, { config, data: engineData, iframe: { enabled: iframe }, dnd: { behavior: "static" }, overrides: { actionBar: CanvasSelectionActionBar }, onChange: (data) => {
1111
1335
  if (!editor.updateFromCanvas(fromEngineData(data, editor.document, registry))) setCanvasResetVersion((version) => version + 1);
1112
- }, children: /* @__PURE__ */ jsxs5(Puck2.Layout, { children: [
1113
- /* @__PURE__ */ jsx7(CanvasSelectionBridge, { data: engineData, requestedBlockId: editor.canvasSelectionRequest, onCanvasSelected: confirmCanvasSelection, canvasMutationVersion, canvasResetVersion }),
1114
- /* @__PURE__ */ jsxs5("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, "data-editor-session-state": editor.sessionState, "data-save-state": resolvedSaveState, "data-left-panel": leftRailOpen ? "open" : "closed", "data-right-panel": rightPanelOpen ? "open" : "closed", children: [
1115
- /* @__PURE__ */ jsx7("header", { className: "pb-header", children: /* @__PURE__ */ jsxs5("div", { className: "pb-header-content", children: [
1116
- /* @__PURE__ */ jsxs5("div", { className: "pb-header-title-group", children: [
1117
- onBack ? /* @__PURE__ */ jsx7(Button2, { variant: "tertiary", onClick: requestBack, children: i18n.t("back") }) : null,
1118
- /* @__PURE__ */ jsxs5("div", { className: "pb-page-title", children: [
1119
- /* @__PURE__ */ jsx7(Text3, { as: "h1", variant: "headingSm", children: editor.document.settings.seoTitle ?? editor.document.pageId }),
1120
- pageStatus ? /* @__PURE__ */ jsx7(PageStatusCard, { status: pageStatus, sessionState: editor.sessionState }) : null
1336
+ }, children: /* @__PURE__ */ jsxs7(Puck2.Layout, { children: [
1337
+ /* @__PURE__ */ jsx9(CanvasSelectionBridge, { data: engineData, requestedBlockId: editor.canvasSelectionRequest, onCanvasSelected: confirmCanvasSelection, canvasMutationVersion, canvasResetVersion, permissionsVersion: `${editor.isEditable}:${editor.document.blocks.map((block) => `${block.id}:${block.type}`).join("|")}` }),
1338
+ /* @__PURE__ */ jsxs7("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, "data-editor-session-state": editor.sessionState, "data-save-state": resolvedSaveState, "data-left-panel": leftRailOpen ? "open" : "closed", "data-right-panel": rightPanelOpen ? "open" : "closed", children: [
1339
+ /* @__PURE__ */ jsx9("header", { className: "pb-header", children: /* @__PURE__ */ jsxs7("div", { className: "pb-header-content", children: [
1340
+ /* @__PURE__ */ jsxs7("div", { className: "pb-header-title-group", children: [
1341
+ onBack ? /* @__PURE__ */ jsx9(Button2, { variant: "tertiary", onClick: requestBack, children: i18n.t("back") }) : null,
1342
+ /* @__PURE__ */ jsxs7("div", { className: "pb-page-title", children: [
1343
+ /* @__PURE__ */ jsx9(Text3, { as: "h1", variant: "headingSm", children: editor.document.settings.seoTitle ?? editor.document.pageId }),
1344
+ pageStatus ? /* @__PURE__ */ jsx9(PageStatusCard, { status: pageStatus, sessionState: editor.sessionState }) : null
1121
1345
  ] })
1122
1346
  ] }),
1123
- /* @__PURE__ */ jsx7("div", { className: "pb-header-controls", children: /* @__PURE__ */ jsxs5("div", { className: "pb-header-device-toolbar", children: [
1124
- /* @__PURE__ */ jsx7(ButtonGroup2, { variant: "segmented", children: Object.keys(deviceLabels2).map((device) => /* @__PURE__ */ jsx7(Button2, { pressed: editor.device === device, onClick: () => editor.setDevice(device), children: i18n.t(deviceLabels2[device]) }, device)) }),
1125
- /* @__PURE__ */ jsx7("div", { className: "pb-zoom-control", children: /* @__PURE__ */ jsx7(Select2, { label: i18n.t("zoom"), labelHidden: true, options: [{ label: i18n.t("zoomAuto"), value: "auto" }, { label: "50%", value: "50" }, { label: "70%", value: "70" }, { label: "100%", value: "100" }], value: zoom, onChange: (value) => setZoom(value) }) })
1347
+ /* @__PURE__ */ jsx9("div", { className: "pb-header-controls", children: /* @__PURE__ */ jsxs7("div", { className: "pb-header-device-toolbar", children: [
1348
+ /* @__PURE__ */ jsx9(ButtonGroup2, { variant: "segmented", children: Object.keys(deviceLabels2).map((device) => /* @__PURE__ */ jsx9(Button2, { pressed: editor.device === device, onClick: () => editor.setDevice(device), children: i18n.t(deviceLabels2[device]) }, device)) }),
1349
+ /* @__PURE__ */ jsx9("div", { className: "pb-zoom-control", children: /* @__PURE__ */ jsx9(Select2, { label: i18n.t("zoom"), labelHidden: true, options: [{ label: i18n.t("zoomAuto"), value: "auto" }, { label: "50%", value: "50" }, { label: "70%", value: "70" }, { label: "100%", value: "100" }], value: zoom, onChange: (value) => setZoom(value) }) })
1126
1350
  ] }) }),
1127
- /* @__PURE__ */ jsx7("div", { className: "pb-header-actions", children: /* @__PURE__ */ jsxs5(InlineStack3, { gap: "150", blockAlign: "center", wrap: true, children: [
1128
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("undo"), icon: UndoIcon2, variant: "tertiary", disabled: !editor.actionState.canUndo, onClick: editor.undo }),
1129
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("redo"), icon: RedoIcon2, variant: "tertiary", disabled: !editor.actionState.canRedo, onClick: editor.redo }),
1130
- resolvedSaveState === "saved" ? null : /* @__PURE__ */ jsx7(Badge3, { tone: saveBadgeTone, children: saveBadgeLabel }),
1131
- onHistory ? /* @__PURE__ */ jsx7("span", { "data-editor-history": "", children: /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle", onClick: () => onHistory({ document: editor.document, draftRevision, session: editor.session }), children: i18n.t("history") }) }) : null,
1132
- onPreview ? /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle" || !isOnline, onClick: () => void onPreview({ document: editor.document, draftRevision, session: editor.session }), children: i18n.t("preview") }) : null,
1133
- onAddToStore ? /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle" || pageStatus?.publicationStatus === "unpublished", onClick: () => void onAddToStore({ document: editor.document, session: editor.session }), children: i18n.t("addToStore") }) : null,
1134
- /* @__PURE__ */ jsx7(Button2, { variant: "primary", disabled: !publishAction && !onPublish || request !== "idle" || validationIssues.length > 0 || !isOnline, onClick: () => void publish(), children: request === "publishing" ? i18n.t("publishing") : i18n.t("publish") })
1351
+ /* @__PURE__ */ jsx9("div", { className: "pb-header-actions", children: /* @__PURE__ */ jsxs7(InlineStack3, { gap: "150", blockAlign: "center", wrap: true, children: [
1352
+ /* @__PURE__ */ jsx9(Button2, { accessibilityLabel: i18n.t("undo"), icon: UndoIcon2, variant: "tertiary", disabled: !editor.actionState.canUndo, onClick: editor.undo }),
1353
+ /* @__PURE__ */ jsx9(Button2, { accessibilityLabel: i18n.t("redo"), icon: RedoIcon2, variant: "tertiary", disabled: !editor.actionState.canRedo, onClick: editor.redo }),
1354
+ resolvedSaveState === "saved" ? null : /* @__PURE__ */ jsx9(Badge3, { tone: saveBadgeTone, children: saveBadgeLabel }),
1355
+ onHistory ? /* @__PURE__ */ jsx9("span", { "data-editor-history": "", children: /* @__PURE__ */ jsx9(Button2, { disabled: request !== "idle", onClick: () => onHistory({ document: editor.document, draftRevision, session: editor.session }), children: i18n.t("history") }) }) : null,
1356
+ onPreview ? /* @__PURE__ */ jsx9(Button2, { disabled: request !== "idle" || !isOnline, onClick: () => void onPreview({ document: editor.document, draftRevision, session: editor.session }), children: i18n.t("preview") }) : null,
1357
+ onAddToStore ? /* @__PURE__ */ jsx9(Button2, { disabled: request !== "idle" || pageStatus?.publicationStatus === "unpublished", onClick: () => void onAddToStore({ document: editor.document, session: editor.session }), children: i18n.t("addToStore") }) : null,
1358
+ /* @__PURE__ */ jsx9(Button2, { variant: "primary", disabled: !publishAction && !onPublish || request !== "idle" || validationIssues.length > 0 || !isOnline, onClick: () => void publish(), children: request === "publishing" ? i18n.t("publishing") : i18n.t("publish") })
1135
1359
  ] }) })
1136
1360
  ] }) }),
1137
- editor.loadState === "success" ? /* @__PURE__ */ jsx7(Banner, { tone: "success", children: i18n.t("success") }) : null,
1138
- notice && notice !== "published" ? /* @__PURE__ */ jsx7("div", { className: `pb-editor-notice pb-editor-notice--${notice}`, "data-testid": "editor-notice", "data-notice": notice, children: /* @__PURE__ */ jsx7(Banner, { tone: "critical", children: i18n.t(notice) }) }) : null,
1139
- validationIssues.length > 0 ? /* @__PURE__ */ jsx7(Banner, { tone: "critical", title: "\u533A\u5757\u5C5E\u6027\u672A\u901A\u8FC7\u6821\u9A8C", children: /* @__PURE__ */ jsx7("ul", { children: validationIssues.map((issue) => /* @__PURE__ */ jsxs5("li", { children: [
1361
+ editor.loadState === "success" ? /* @__PURE__ */ jsx9(Banner, { tone: "success", children: i18n.t("success") }) : null,
1362
+ notice && notice !== "published" ? /* @__PURE__ */ jsx9("div", { className: `pb-editor-notice pb-editor-notice--${notice}`, "data-testid": "editor-notice", "data-notice": notice, children: /* @__PURE__ */ jsx9(Banner, { tone: "critical", children: i18n.t(notice) }) }) : null,
1363
+ validationIssues.length > 0 ? /* @__PURE__ */ jsx9(Banner, { tone: "critical", title: "\u533A\u5757\u5C5E\u6027\u672A\u901A\u8FC7\u6821\u9A8C", children: /* @__PURE__ */ jsx9("ul", { children: validationIssues.map((issue) => /* @__PURE__ */ jsxs7("li", { children: [
1140
1364
  issue.path,
1141
1365
  ": ",
1142
1366
  issue.message
1143
1367
  ] }, `${issue.path}-${issue.message}`)) }) }) : null,
1144
- /* @__PURE__ */ jsxs5("div", { className: `pb-workspace pb-workspace--document${leftRailOpen ? "" : " pb-workspace--left-closed"}${rightPanelOpen ? "" : " pb-workspace--right-closed"}`, "data-left-panel": leftRailOpen ? "open" : "closed", "data-right-panel": rightPanelOpen ? "open" : "closed", children: [
1145
- /* @__PURE__ */ jsxs5("nav", { className: "pb-tool-rail", "aria-label": "\u7F16\u8F91\u5668\u5DE5\u5177", children: [
1146
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("blocks"), icon: LayoutSectionIcon2, pressed: blockView === "blocks" && leftRailOpen, variant: "tertiary", onClick: () => {
1368
+ /* @__PURE__ */ jsxs7("div", { className: `pb-workspace pb-workspace--document${leftRailOpen ? "" : " pb-workspace--left-closed"}${rightPanelOpen ? "" : " pb-workspace--right-closed"}`, "data-left-panel": leftRailOpen ? "open" : "closed", "data-right-panel": rightPanelOpen ? "open" : "closed", children: [
1369
+ /* @__PURE__ */ jsxs7("nav", { className: "pb-tool-rail", "aria-label": "\u7F16\u8F91\u5668\u5DE5\u5177", children: [
1370
+ /* @__PURE__ */ jsx9(Button2, { accessibilityLabel: i18n.t("blocks"), icon: LayoutSectionIcon2, pressed: blockView === "blocks" && leftRailOpen, variant: "tertiary", onClick: () => {
1147
1371
  setBlockView("blocks");
1148
1372
  setLeftRailOpen(true);
1149
1373
  } }),
1150
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("outline"), icon: MenuIcon2, pressed: blockView === "outline" && leftRailOpen, variant: "tertiary", onClick: () => {
1374
+ /* @__PURE__ */ jsx9(Button2, { accessibilityLabel: i18n.t("outline"), icon: MenuIcon2, pressed: blockView === "outline" && leftRailOpen, variant: "tertiary", onClick: () => {
1151
1375
  setBlockView("outline");
1152
1376
  setLeftRailOpen(true);
1153
1377
  } })
1154
1378
  ] }),
1155
- /* @__PURE__ */ jsxs5("aside", { className: `pb-left-panel${leftRailOpen ? "" : " pb-panel--closed"}`, "aria-label": "PageDocument \u533A\u5757", children: [
1156
- /* @__PURE__ */ jsxs5(InlineStack3, { align: "space-between", blockAlign: "center", children: [
1157
- /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingSm", children: blockView === "blocks" ? i18n.t("blocks") : i18n.t("outline") }),
1158
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("collapseLeft"), icon: XIcon2, variant: "tertiary", onClick: () => setLeftRailOpen(false) })
1379
+ /* @__PURE__ */ jsxs7("aside", { className: `pb-left-panel${leftRailOpen ? "" : " pb-panel--closed"}`, "aria-label": "PageDocument \u533A\u5757", children: [
1380
+ /* @__PURE__ */ jsxs7(InlineStack3, { align: "space-between", blockAlign: "center", children: [
1381
+ /* @__PURE__ */ jsx9(Text3, { as: "h2", variant: "headingSm", children: blockView === "blocks" ? i18n.t("blocks") : i18n.t("outline") }),
1382
+ /* @__PURE__ */ jsx9(Button2, { accessibilityLabel: i18n.t("collapseLeft"), icon: XIcon2, variant: "tertiary", onClick: () => setLeftRailOpen(false) })
1159
1383
  ] }),
1160
- blockView === "blocks" ? /* @__PURE__ */ jsx7("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__ */ jsxs5("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.canAddBlock(type), "aria-disabled": !editor.canAddBlock(type), "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) => {
1384
+ blockView === "blocks" ? /* @__PURE__ */ jsx9("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__ */ jsxs7("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.canAddBlock(type), "aria-disabled": !editor.canAddBlock(type), "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" : ""}`, onClick: () => {
1385
+ const block = editor.document.blocks.find((item) => item.type === type);
1386
+ if (!block) return;
1387
+ editor.requestCanvasSelection(block.id);
1388
+ scrollCanvasBlockIntoView(block.id, canvasFrameRef.current);
1389
+ }, onDragStart: (event) => {
1161
1390
  if (!editor.canAddBlock(type)) {
1162
1391
  event.preventDefault();
1163
1392
  return;
@@ -1166,62 +1395,68 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1166
1395
  event.dataTransfer.effectAllowed = "copy";
1167
1396
  setDraggingLibraryType(type);
1168
1397
  }, onDragEnd: () => setDraggingLibraryType(null), children: [
1169
- /* @__PURE__ */ jsxs5("span", { className: "pb-library-block-title", children: [
1170
- /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockTypeLabel(type, registry) }),
1171
- /* @__PURE__ */ jsx7("span", { className: "pb-library-block-drag-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsx7(DragHandleIcon2, {}) })
1398
+ /* @__PURE__ */ jsxs7("span", { className: "pb-library-block-title", children: [
1399
+ /* @__PURE__ */ jsx9(Text3, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockTypeLabel(type, registry) }),
1400
+ /* @__PURE__ */ jsx9("span", { className: "pb-library-block-drag-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsx9(DragHandleIcon2, {}) })
1172
1401
  ] }),
1173
- /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", tone: "subdued", children: type })
1174
- ] }, type)) }) : editor.document.blocks.length === 0 ? /* @__PURE__ */ jsx7(Text3, { as: "p", tone: "subdued", children: i18n.t("empty") }) : /* @__PURE__ */ jsx7("div", { className: "pb-block-list", "data-testid": "outline-view", children: editor.document.blocks.map((block) => /* @__PURE__ */ jsxs5("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: [
1175
- /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockLabel(block, registry) }),
1176
- /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", tone: "subdued", children: block.id })
1402
+ /* @__PURE__ */ jsx9(Text3, { as: "span", variant: "bodySm", tone: "subdued", children: type })
1403
+ ] }, type)) }) : editor.document.blocks.length === 0 ? /* @__PURE__ */ jsx9(Text3, { as: "p", tone: "subdued", children: i18n.t("empty") }) : /* @__PURE__ */ jsx9("div", { className: "pb-block-list", "data-testid": "outline-view", children: editor.document.blocks.map((block) => /* @__PURE__ */ jsxs7("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: () => {
1404
+ editor.requestCanvasSelection(block.id);
1405
+ scrollCanvasBlockIntoView(block.id, canvasFrameRef.current);
1406
+ }, children: [
1407
+ /* @__PURE__ */ jsx9(Text3, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockLabel(block, registry) }),
1408
+ /* @__PURE__ */ jsx9(Text3, { as: "span", variant: "bodySm", tone: "subdued", children: block.id })
1177
1409
  ] }, block.id)) })
1178
1410
  ] }),
1179
- /* @__PURE__ */ jsxs5("main", { className: "pb-canvas-area", children: [
1180
- /* @__PURE__ */ jsxs5("div", { className: "pb-canvas-stage", children: [
1181
- /* @__PURE__ */ jsx7("div", { ref: canvasFrameRef, className: `pb-canvas-frame pb-canvas-frame--${editor.device}${zoom === "auto" ? "" : ` pb-canvas-frame--zoom-${zoom}`}`, "data-device": editor.device, "data-zoom": zoom, style: canvasThemeStyle, children: /* @__PURE__ */ jsx7(Puck2.Preview, {}) }),
1182
- draggingLibraryType ? /* @__PURE__ */ jsxs5("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: [
1183
- "\u677E\u5F00\u4EE5\u6DFB\u52A0 ",
1184
- blockTypeLabel(draggingLibraryType, registry)
1185
- ] }) : null,
1186
- editor.selectedBlock ? /* @__PURE__ */ jsxs5("div", { className: "pb-canvas-overlay", "aria-label": `\u5DF2\u9009\u62E9 ${blockLabel(editor.selectedBlock, registry)}`, children: [
1187
- /* @__PURE__ */ jsx7("span", { children: blockLabel(editor.selectedBlock, registry) }),
1188
- /* @__PURE__ */ jsx7("span", { children: "Selected" })
1189
- ] }) : null
1411
+ /* @__PURE__ */ jsxs7("main", { className: "pb-canvas-area", children: [
1412
+ /* @__PURE__ */ jsxs7("div", { className: "pb-canvas-stage", children: [
1413
+ /* @__PURE__ */ jsx9("div", { ref: canvasFrameRef, className: `pb-canvas-frame pb-canvas-frame--${editor.device}${zoom === "auto" ? "" : ` pb-canvas-frame--zoom-${zoom}`}`, "data-device": editor.device, "data-zoom": zoom, style: canvasThemeStyle, children: /* @__PURE__ */ jsx9(Puck2.Preview, {}) }),
1414
+ draggingLibraryType ? /* @__PURE__ */ jsx9(CanvasLibraryDropTarget, { frameRef: canvasFrameRef, blocks: editor.document.blocks.map((block) => ({ id: block.id, label: blockLabel(block, registry) })), label: blockTypeLabel(draggingLibraryType, registry), adminLocale, onDrop: (beforeId) => {
1415
+ addFromLibrary(draggingLibraryType, beforeId);
1416
+ setDraggingLibraryType(null);
1417
+ }, onCancel: () => setDraggingLibraryType(null) }) : null
1190
1418
  ] }),
1191
- !leftRailOpen || !rightPanelOpen ? /* @__PURE__ */ jsxs5("div", { className: "pb-collapsed-actions", children: [
1192
- !leftRailOpen ? /* @__PURE__ */ jsx7(Button2, { onClick: () => setLeftRailOpen(true), children: i18n.t("expandLeft") }) : null,
1193
- !rightPanelOpen ? /* @__PURE__ */ jsx7(Button2, { onClick: () => setRightPanelOpen(true), children: i18n.t("expandRight") }) : null
1419
+ !leftRailOpen || !rightPanelOpen ? /* @__PURE__ */ jsxs7("div", { className: "pb-collapsed-actions", children: [
1420
+ !leftRailOpen ? /* @__PURE__ */ jsx9(Button2, { onClick: () => setLeftRailOpen(true), children: i18n.t("expandLeft") }) : null,
1421
+ !rightPanelOpen ? /* @__PURE__ */ jsx9(Button2, { onClick: () => setRightPanelOpen(true), children: i18n.t("expandRight") }) : null
1194
1422
  ] }) : null
1195
1423
  ] }),
1196
- /* @__PURE__ */ jsxs5("aside", { className: `pb-right-panel${rightPanelOpen ? "" : " pb-panel--closed"}`, "aria-label": "PageDocument \u5C5E\u6027", children: [
1197
- /* @__PURE__ */ jsxs5(InlineStack3, { align: "space-between", blockAlign: "center", children: [
1198
- /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingSm", children: i18n.t("properties") }),
1199
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("collapseRight"), icon: XIcon2, variant: "tertiary", onClick: () => setRightPanelOpen(false) })
1424
+ /* @__PURE__ */ jsxs7("aside", { className: `pb-right-panel${rightPanelOpen ? "" : " pb-panel--closed"}`, "aria-label": "PageDocument \u5C5E\u6027", children: [
1425
+ /* @__PURE__ */ jsxs7(InlineStack3, { align: "space-between", blockAlign: "center", children: [
1426
+ /* @__PURE__ */ jsx9(Text3, { as: "h2", variant: "headingSm", children: i18n.t("properties") }),
1427
+ /* @__PURE__ */ jsx9(Button2, { accessibilityLabel: i18n.t("collapseRight"), icon: XIcon2, variant: "tertiary", onClick: () => setRightPanelOpen(false) })
1200
1428
  ] }),
1201
- editor.selectedBlock ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1202
- /* @__PURE__ */ jsx7(DocumentInspector, { settings: inspectorSettings?.[editor.selectedBlock.id] ?? inspectorSettings?.[editor.selectedBlock.type], pageId: editor.document.pageId, assetPicker, productPicker, i18n, block: editor.selectedBlock, registry, disabled: !editor.actionState.canEdit, appearanceControls, onChange: (props) => editor.updateBlockProps(editor.selectedBlock.id, props), onPresentationChange: (presentation) => updateBlockPresentation(editor.selectedBlock.id, presentation) }, editor.selectedBlock.id),
1203
- /* @__PURE__ */ jsx7(InspectorActions, { block: editor.selectedBlock, canDuplicate: editor.actionState.canDuplicate, canDelete: editor.actionState.canDelete, canMove: editor.actionState.canReorder, canMoveUp: editor.document.blocks[0]?.id !== editor.selectedBlock.id, canMoveDown: editor.document.blocks.at(-1)?.id !== editor.selectedBlock.id, i18n, onDuplicate: () => editor.duplicateBlock(editor.selectedBlock.id), onDelete: () => editor.requestDeleteBlock(editor.selectedBlock.id), onMove: (direction) => editor.moveBlock(editor.selectedBlock.id, direction) })
1204
- ] }) : /* @__PURE__ */ jsx7(Text3, { as: "p", tone: "subdued", children: i18n.t("selectBlock") })
1429
+ editor.selectedBlock ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
1430
+ /* @__PURE__ */ jsx9(DocumentInspector, { settings: inspectorSettings?.[editor.selectedBlock.id] ?? inspectorSettings?.[editor.selectedBlock.type], pageId: editor.document.pageId, assetPicker, productPicker, i18n, block: editor.selectedBlock, registry, disabled: !editor.actionState.canEdit, appearanceControls, onChange: (props) => editor.updateBlockProps(editor.selectedBlock.id, props), onPresentationChange: (presentation) => updateBlockPresentation(editor.selectedBlock.id, presentation) }, editor.selectedBlock.id),
1431
+ /* @__PURE__ */ jsx9(InspectorActions, { block: editor.selectedBlock, canDuplicate: editor.actionState.canDuplicate, canDelete: editor.actionState.canDelete, canMove: editor.actionState.canReorder, canMoveUp: editor.document.blocks[0]?.id !== editor.selectedBlock.id, canMoveDown: editor.document.blocks.at(-1)?.id !== editor.selectedBlock.id, i18n, onDuplicate: () => editor.duplicateBlock(editor.selectedBlock.id), onDelete: () => editor.requestDeleteBlock(editor.selectedBlock.id), onMove: (direction) => editor.moveBlock(editor.selectedBlock.id, direction) })
1432
+ ] }) : /* @__PURE__ */ jsx9(Text3, { as: "p", tone: "subdued", children: i18n.t("selectBlock") })
1205
1433
  ] })
1206
1434
  ] }),
1207
- editor.pendingDeleteBlock ? /* @__PURE__ */ jsx7(DeleteConfirmation, { block: editor.pendingDeleteBlock, config: deleteConfirmation, i18n, onCancel: editor.cancelDeleteBlock, onConfirm: editor.confirmDeleteBlock }) : null,
1208
- confirmBack ? /* @__PURE__ */ jsx7(LeaveConfirmation, { i18n, onCancel: () => setConfirmBack(false), onConfirm: () => {
1435
+ editor.pendingDeleteBlock ? /* @__PURE__ */ jsx9(DeleteConfirmation, { block: editor.pendingDeleteBlock, config: deleteConfirmation, i18n, onCancel: editor.cancelDeleteBlock, onConfirm: editor.confirmDeleteBlock }) : null,
1436
+ confirmBack ? /* @__PURE__ */ jsx9(LeaveConfirmation, { i18n, onCancel: () => setConfirmBack(false), onConfirm: () => {
1209
1437
  setConfirmBack(false);
1210
1438
  onBack?.();
1211
1439
  } }) : null
1212
1440
  ] })
1213
1441
  ] }) }),
1214
- /* @__PURE__ */ jsx7("div", { className: "pb-toast-host", ...notice === "published" ? { "data-testid": "editor-notice", "data-notice": "published" } : {}, children: /* @__PURE__ */ jsx7(Frame, { children: notice === "published" ? /* @__PURE__ */ jsx7(Toast, { content: i18n.t("publishSuccessTitle"), onDismiss: dismissPublishedNotice }) : null }) })
1442
+ /* @__PURE__ */ jsx9("div", { className: "pb-toast-host", ...notice === "published" ? { "data-testid": "editor-notice", "data-notice": "published" } : {}, children: /* @__PURE__ */ jsx9(Frame, { children: notice === "published" ? /* @__PURE__ */ jsx9(Toast, { content: i18n.t("publishSuccessTitle"), onDismiss: dismissPublishedNotice }) : null }) })
1215
1443
  ] });
1216
1444
  }
1217
- function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canvasMutationVersion, canvasResetVersion }) {
1445
+ function CanvasSelectionActionBar({ label, children, parentAction }) {
1446
+ void label;
1447
+ void children;
1448
+ void parentAction;
1449
+ return /* @__PURE__ */ jsx9(Fragment2, {});
1450
+ }
1451
+ function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canvasMutationVersion, canvasResetVersion, permissionsVersion }) {
1218
1452
  const puck = usePuck();
1219
- const lastSelectedId = useRef4(null);
1220
- const lastSyncedData = useRef4(null);
1221
- const lastCanvasMutationVersion = useRef4(0);
1222
- const lastCanvasResetVersion = useRef4(0);
1453
+ const lastSelectedId = useRef5(null);
1454
+ const lastSyncedData = useRef5(null);
1455
+ const lastCanvasMutationVersion = useRef5(0);
1456
+ const lastCanvasResetVersion = useRef5(0);
1223
1457
  const serializedData = JSON.stringify(data);
1224
- useEffect3(() => {
1458
+ const refreshPermissions = puck.refreshPermissions;
1459
+ useEffect4(() => {
1225
1460
  const forceReset = canvasResetVersion > lastCanvasResetVersion.current;
1226
1461
  if (lastSyncedData.current === serializedData && !forceReset) return;
1227
1462
  lastSyncedData.current = serializedData;
@@ -1232,13 +1467,17 @@ function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canva
1232
1467
  }
1233
1468
  puck.dispatch({ type: "setData", data });
1234
1469
  }, [canvasMutationVersion, canvasResetVersion, data, puck, serializedData]);
1235
- useEffect3(() => {
1470
+ useEffect4(() => {
1471
+ refreshPermissions?.({}, true);
1472
+ }, [permissionsVersion, refreshPermissions]);
1473
+ useEffect4(() => {
1236
1474
  if (!requestedBlockId) return;
1237
1475
  const selector = puck.getSelectorForId(requestedBlockId);
1238
1476
  if (selector) puck.dispatch({ type: "setUi", ui: { itemSelector: selector } });
1477
+ scrollCanvasBlockIntoView(requestedBlockId);
1239
1478
  }, [puck, requestedBlockId]);
1240
1479
  const selectedId = typeof puck.selectedItem?.props.id === "string" ? puck.selectedItem.props.id : null;
1241
- useEffect3(() => {
1480
+ useEffect4(() => {
1242
1481
  if (selectedId && lastSelectedId.current !== selectedId) {
1243
1482
  lastSelectedId.current = selectedId;
1244
1483
  onCanvasSelected(selectedId);
@@ -1250,43 +1489,53 @@ function EditorStatus({ state }) {
1250
1489
  const i18n = createAdminI18n();
1251
1490
  const tone = state === "error" ? "critical" : state === "disabled" ? "warning" : "info";
1252
1491
  const message = state === "loading" ? i18n.t("loading") : state === "empty" ? i18n.t("empty") : state === "error" ? i18n.t("error") : i18n.t("disabled");
1253
- return /* @__PURE__ */ jsx7("div", { className: "pb-editor-status", "data-testid": "page-document-editor-state", "data-editor-state": state, children: /* @__PURE__ */ jsx7(Banner, { tone, title: message, children: state === "disabled" ? i18n.t("disabled") : message }) });
1492
+ return /* @__PURE__ */ jsx9("div", { className: "pb-editor-status", "data-testid": "page-document-editor-state", "data-editor-state": state, children: /* @__PURE__ */ jsx9(Banner, { tone, title: message, children: state === "disabled" ? i18n.t("disabled") : message }) });
1254
1493
  }
1255
1494
  function EditorSessionStatus({ state, message }) {
1256
1495
  const i18n = createAdminI18n();
1257
1496
  const tone = state === "locked" ? "warning" : state === "readonly" ? "info" : "critical";
1258
1497
  const title = state === "acquiring" ? i18n.t("acquiringLock") : state === "locked" ? i18n.t("locked") : state === "readonly" ? i18n.t("readonly") : i18n.t("lockLost");
1259
- return /* @__PURE__ */ jsx7("div", { className: "pb-editor-status", "data-testid": "page-document-editor-session-state", "data-editor-session-state": state, children: /* @__PURE__ */ jsx7(Banner, { tone, title, children: message ?? title }) });
1498
+ return /* @__PURE__ */ jsx9("div", { className: "pb-editor-status", "data-testid": "page-document-editor-session-state", "data-editor-session-state": state, children: /* @__PURE__ */ jsx9(Banner, { tone, title, children: message ?? title }) });
1260
1499
  }
1261
1500
  function InspectorSection({ title, children, defaultOpen = true }) {
1262
- return /* @__PURE__ */ jsxs5("details", { className: "pb-inspector-section", open: defaultOpen, children: [
1263
- /* @__PURE__ */ jsxs5("summary", { children: [
1264
- /* @__PURE__ */ jsx7("span", { children: title }),
1265
- /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\u2304" })
1501
+ return /* @__PURE__ */ jsxs7("details", { className: "pb-inspector-section", open: defaultOpen, children: [
1502
+ /* @__PURE__ */ jsxs7("summary", { children: [
1503
+ /* @__PURE__ */ jsx9("span", { children: title }),
1504
+ /* @__PURE__ */ jsx9("span", { "aria-hidden": "true", children: "\u2304" })
1266
1505
  ] }),
1267
- /* @__PURE__ */ jsx7("div", { className: "pb-inspector-section__body", children })
1506
+ /* @__PURE__ */ jsx9("div", { className: "pb-inspector-section__body", children })
1268
1507
  ] });
1269
1508
  }
1270
- function InspectorTextControl({ label, value, control, disabled, onChange }) {
1509
+ function InspectorTextControl({ label, value, control, maxLength, disabled, onChange }) {
1271
1510
  const stringValue = typeof value === "string" ? value : "";
1272
- if (control === "color") return /* @__PURE__ */ jsx7("input", { "aria-label": label, type: "color", value: /^#[\da-f]{6}$/i.test(stringValue) ? stringValue : "#000000", disabled, onChange: (event) => onChange(event.currentTarget.value) });
1273
- return /* @__PURE__ */ jsx7(TextField, { label, labelHidden: true, value: stringValue, onChange, autoComplete: "off", disabled, multiline: control === "textarea" ? 4 : false, type: control === "url" ? "url" : "text" });
1511
+ if (control === "color") return /* @__PURE__ */ jsx9("input", { "aria-label": label, type: "color", value: /^#[\da-f]{6}$/i.test(stringValue) ? stringValue : "#000000", disabled, onChange: (event) => onChange(event.currentTarget.value) });
1512
+ const length = Array.from(stringValue).length;
1513
+ const field = /* @__PURE__ */ jsx9(TextField, { label, labelHidden: true, value: stringValue, onChange: (next) => onChange(maxLength === void 0 ? next : Array.from(next).slice(0, maxLength).join("")), autoComplete: "off", disabled, multiline: control === "textarea" ? 4 : false, type: control === "url" ? "url" : "text", maxLength });
1514
+ if (maxLength === void 0) return field;
1515
+ return /* @__PURE__ */ jsxs7("div", { className: "pb-inspector-field__limited", children: [
1516
+ field,
1517
+ /* @__PURE__ */ jsxs7("span", { className: "pb-inspector-field__limit", "aria-label": `Limit ${maxLength}`, children: [
1518
+ length,
1519
+ "/",
1520
+ maxLength
1521
+ ] })
1522
+ ] });
1274
1523
  }
1275
1524
  function InspectorAssetControl({ value, disabled, picker, pageId, blockId, i18n, onChange }) {
1276
- const [busy, setBusy] = useState3(false);
1277
- const [error, setError] = useState3(null);
1278
- const active = useRef4(true);
1279
- useEffect3(() => {
1525
+ const [busy, setBusy] = useState4(false);
1526
+ const [error, setError] = useState4(null);
1527
+ const active = useRef5(true);
1528
+ useEffect4(() => {
1280
1529
  active.current = !disabled;
1281
1530
  return () => {
1282
1531
  active.current = false;
1283
1532
  };
1284
1533
  }, [disabled]);
1285
1534
  const url = typeof value === "string" && validateFieldValue({ field: "", control: "asset" }, value).length === 0 ? value : "";
1286
- return /* @__PURE__ */ jsxs5("div", { className: "pb-asset-picker", "data-testid": "asset-picker", children: [
1287
- url ? /* @__PURE__ */ jsx7("img", { src: url, alt: "", className: "pb-asset-picker__preview" }) : /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: i18n.t("emptyAsset") }),
1288
- url ? /* @__PURE__ */ jsx7(Button2, { fullWidth: true, variant: "tertiary", disabled: disabled || busy, onClick: () => onChange(""), children: i18n.t("removeAsset") }) : null,
1289
- /* @__PURE__ */ jsx7(Button2, { fullWidth: true, disabled: disabled || busy || !picker, onClick: () => void (async () => {
1535
+ return /* @__PURE__ */ jsxs7("div", { className: "pb-asset-picker", "data-testid": "asset-picker", children: [
1536
+ url ? /* @__PURE__ */ jsx9("img", { src: url, alt: "", className: "pb-asset-picker__preview" }) : /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: i18n.t("emptyAsset") }),
1537
+ url ? /* @__PURE__ */ jsx9(Button2, { fullWidth: true, variant: "tertiary", disabled: disabled || busy, onClick: () => onChange(""), children: i18n.t("removeAsset") }) : null,
1538
+ /* @__PURE__ */ jsx9(Button2, { fullWidth: true, disabled: disabled || busy || !picker, onClick: () => void (async () => {
1290
1539
  if (!picker) return;
1291
1540
  setBusy(true);
1292
1541
  setError(null);
@@ -1302,20 +1551,20 @@ function InspectorAssetControl({ value, disabled, picker, pageId, blockId, i18n,
1302
1551
  setBusy(false);
1303
1552
  }
1304
1553
  })(), children: busy ? i18n.t("selectingAsset") : i18n.t("selectAsset") }),
1305
- error ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: error }) : null
1554
+ error ? /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "critical", children: error }) : null
1306
1555
  ] });
1307
1556
  }
1308
1557
  function InspectorProductsControl({ value, disabled, picker, pageId, blockId, i18n, onChange }) {
1309
1558
  const products = parseProductReferences(value);
1310
- const [busy, setBusy] = useState3(false);
1311
- const [error, setError] = useState3(null);
1559
+ const [busy, setBusy] = useState4(false);
1560
+ const [error, setError] = useState4(null);
1312
1561
  const preview = products.slice(0, 4);
1313
- return /* @__PURE__ */ jsxs5("div", { className: "pb-product-picker", "data-testid": "product-picker", children: [
1314
- /* @__PURE__ */ jsxs5("div", { className: "pb-product-picker__summary", children: [
1315
- preview.length ? /* @__PURE__ */ jsx7("span", { className: "pb-product-picker__thumbs", "aria-hidden": "true", children: preview.map((product, index) => /* @__PURE__ */ jsx7("span", { className: "pb-product-picker__thumb", style: { zIndex: preview.length - index }, children: product.imageUrl ? /* @__PURE__ */ jsx7("img", { src: product.imageUrl, alt: "" }) : /* @__PURE__ */ jsx7("span", { className: "pb-product-picker__thumb-fallback" }) }, product.id)) }) : null,
1316
- /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", children: products.length ? `${products.length} ${i18n.t("productCountSuffix")}` : i18n.t("emptyProducts") })
1562
+ return /* @__PURE__ */ jsxs7("div", { className: "pb-product-picker", "data-testid": "product-picker", children: [
1563
+ /* @__PURE__ */ jsxs7("div", { className: "pb-product-picker__summary", children: [
1564
+ preview.length ? /* @__PURE__ */ jsx9("span", { className: "pb-product-picker__thumbs", "aria-hidden": "true", children: preview.map((product, index) => /* @__PURE__ */ jsx9("span", { className: "pb-product-picker__thumb", style: { zIndex: preview.length - index }, children: product.imageUrl ? /* @__PURE__ */ jsx9("img", { src: product.imageUrl, alt: "" }) : /* @__PURE__ */ jsx9("span", { className: "pb-product-picker__thumb-fallback" }) }, product.id)) }) : null,
1565
+ /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", children: products.length ? `${products.length} ${i18n.t("productCountSuffix")}` : i18n.t("emptyProducts") })
1317
1566
  ] }),
1318
- /* @__PURE__ */ jsx7(Button2, { fullWidth: true, icon: ProductIcon, variant: "secondary", disabled: disabled || busy || !picker, onClick: () => void (async () => {
1567
+ /* @__PURE__ */ jsx9(Button2, { fullWidth: true, icon: ProductIcon, variant: "secondary", disabled: disabled || busy || !picker, onClick: () => void (async () => {
1319
1568
  if (!picker) return;
1320
1569
  setBusy(true);
1321
1570
  setError(null);
@@ -1328,47 +1577,50 @@ function InspectorProductsControl({ value, disabled, picker, pageId, blockId, i1
1328
1577
  setBusy(false);
1329
1578
  }
1330
1579
  })(), children: busy ? i18n.t("selectingProducts") : i18n.t("selectProducts") }),
1331
- error ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: error }) : null
1580
+ error ? /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "critical", children: error }) : null
1332
1581
  ] });
1333
1582
  }
1334
1583
  function InspectorField({ name, field, value, registry, disabled, assetPicker, productPicker, pageId, blockId, i18n, onChange }) {
1335
1584
  const label = field.label ?? name;
1336
1585
  const issues = validateFieldValue(field, value);
1337
1586
  const Field = registry?.getField(field.field)?.component;
1338
- return /* @__PURE__ */ jsxs5("div", { className: "pb-inspector-field", "data-control": field.control ?? "custom", children: [
1339
- /* @__PURE__ */ jsxs5("div", { className: "pb-inspector-field__heading", children: [
1340
- /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", fontWeight: "semibold", children: label }),
1341
- field.description ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: field.description }) : null
1587
+ return /* @__PURE__ */ jsxs7("div", { className: "pb-inspector-field", "data-control": field.control ?? "custom", children: [
1588
+ /* @__PURE__ */ jsxs7("div", { className: "pb-inspector-field__heading", children: [
1589
+ /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", fontWeight: "semibold", children: label }),
1590
+ field.description ? /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: field.description }) : null
1342
1591
  ] }),
1343
- field.control === "products" ? /* @__PURE__ */ jsx7(InspectorProductsControl, { value, disabled, picker: productPicker, pageId, blockId, i18n, onChange }) : field.control === "asset" ? /* @__PURE__ */ jsx7(InspectorAssetControl, { value, disabled, picker: assetPicker, pageId, blockId, i18n, onChange }) : field.control ? /* @__PURE__ */ jsx7(InspectorTextControl, { label, value, control: field.control, disabled, onChange: (next) => onChange(next) }) : Field ? /* @__PURE__ */ jsx7(Field, { value, onChange }) : null,
1344
- issues.map((issue) => /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: issue.message }, issue.message))
1592
+ field.control === "products" ? /* @__PURE__ */ jsx9(InspectorProductsControl, { value, disabled, picker: productPicker, pageId, blockId, i18n, onChange }) : field.control === "asset" ? /* @__PURE__ */ jsx9(InspectorAssetControl, { value, disabled, picker: assetPicker, pageId, blockId, i18n, onChange }) : field.control ? /* @__PURE__ */ jsx9(InspectorTextControl, { label, value, control: field.control, maxLength: field.validation?.maxLength, disabled, onChange: (next) => onChange(next) }) : Field ? /* @__PURE__ */ jsx9(Field, { value, onChange }) : null,
1593
+ issues.map((issue) => /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "critical", children: issue.message }, issue.message))
1345
1594
  ] });
1346
1595
  }
1347
1596
  function InspectorActions({ block, canDuplicate, canDelete, canMove, canMoveUp, canMoveDown, i18n, onDuplicate, onDelete, onMove }) {
1348
- return /* @__PURE__ */ jsx7("div", { className: "pb-inspector-actions", "aria-label": `Actions for ${block.id}`, children: /* @__PURE__ */ jsxs5(ButtonGroup2, { children: [
1349
- /* @__PURE__ */ jsx7(Button2, { disabled: !canMove || !canMoveUp, onClick: () => onMove(-1), children: i18n.t("moveUp") }),
1350
- /* @__PURE__ */ jsx7(Button2, { disabled: !canMove || !canMoveDown, onClick: () => onMove(1), children: i18n.t("moveDown") }),
1351
- /* @__PURE__ */ jsx7(Button2, { disabled: !canDuplicate, onClick: onDuplicate, children: i18n.t("duplicate") }),
1352
- /* @__PURE__ */ jsx7(Button2, { disabled: !canDelete, tone: "critical", onClick: onDelete, children: i18n.t("remove") })
1353
- ] }) });
1597
+ return /* @__PURE__ */ jsxs7("div", { className: "pb-inspector-actions", "aria-label": `Actions for ${block.id}`, children: [
1598
+ /* @__PURE__ */ jsxs7(ButtonGroup2, { children: [
1599
+ /* @__PURE__ */ jsx9(Button2, { disabled: !canMove || !canMoveUp, onClick: () => onMove(-1), children: i18n.t("moveUp") }),
1600
+ /* @__PURE__ */ jsx9(Button2, { disabled: !canMove || !canMoveDown, onClick: () => onMove(1), children: i18n.t("moveDown") }),
1601
+ /* @__PURE__ */ jsx9(Button2, { disabled: !canDuplicate, accessibilityLabel: canDuplicate ? i18n.t("duplicate") : i18n.t("duplicateUnavailable"), onClick: onDuplicate, children: i18n.t("duplicate") }),
1602
+ /* @__PURE__ */ jsx9(Button2, { disabled: !canDelete, tone: "critical", onClick: onDelete, children: i18n.t("remove") })
1603
+ ] }),
1604
+ !canDuplicate ? /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: i18n.t("duplicateUnavailable") }) : null
1605
+ ] });
1354
1606
  }
1355
1607
  function DeleteConfirmation({ block, config, i18n, onCancel, onConfirm }) {
1356
- return /* @__PURE__ */ jsx7("div", { className: "pb-delete-confirmation-backdrop", role: "presentation", children: /* @__PURE__ */ jsxs5("section", { className: "pb-delete-confirmation", role: "dialog", "aria-modal": "true", "aria-labelledby": "pb-delete-confirmation-title", children: [
1357
- /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingMd", id: "pb-delete-confirmation-title", children: config?.title ?? i18n.t("confirmDeleteTitle") }),
1358
- /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodyMd", children: config?.message?.(block) ?? i18n.t("confirmDeleteMessage") }),
1359
- /* @__PURE__ */ jsxs5(ButtonGroup2, { children: [
1360
- /* @__PURE__ */ jsx7(Button2, { onClick: onCancel, children: config?.cancelLabel ?? i18n.t("cancel") }),
1361
- /* @__PURE__ */ jsx7(Button2, { tone: "critical", onClick: onConfirm, children: config?.confirmLabel ?? i18n.t("confirm") })
1608
+ return /* @__PURE__ */ jsx9("div", { className: "pb-delete-confirmation-backdrop", role: "presentation", children: /* @__PURE__ */ jsxs7("section", { className: "pb-delete-confirmation", role: "dialog", "aria-modal": "true", "aria-labelledby": "pb-delete-confirmation-title", children: [
1609
+ /* @__PURE__ */ jsx9(Text3, { as: "h2", variant: "headingMd", id: "pb-delete-confirmation-title", children: config?.title ?? i18n.t("confirmDeleteTitle") }),
1610
+ /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodyMd", children: config?.message?.(block) ?? i18n.t("confirmDeleteMessage") }),
1611
+ /* @__PURE__ */ jsxs7(ButtonGroup2, { children: [
1612
+ /* @__PURE__ */ jsx9(Button2, { onClick: onCancel, children: config?.cancelLabel ?? i18n.t("cancel") }),
1613
+ /* @__PURE__ */ jsx9(Button2, { tone: "critical", onClick: onConfirm, children: config?.confirmLabel ?? i18n.t("confirm") })
1362
1614
  ] })
1363
1615
  ] }) });
1364
1616
  }
1365
1617
  function LeaveConfirmation({ i18n, onCancel, onConfirm }) {
1366
- return /* @__PURE__ */ jsx7("div", { className: "pb-delete-confirmation-backdrop", role: "presentation", children: /* @__PURE__ */ jsxs5("section", { className: "pb-delete-confirmation", role: "dialog", "aria-modal": "true", "aria-labelledby": "pb-leave-confirmation-title", children: [
1367
- /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingMd", id: "pb-leave-confirmation-title", children: i18n.t("confirmLeaveTitle") }),
1368
- /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodyMd", children: i18n.t("leaveWarning") }),
1369
- /* @__PURE__ */ jsxs5(ButtonGroup2, { children: [
1370
- /* @__PURE__ */ jsx7(Button2, { onClick: onCancel, children: i18n.t("cancel") }),
1371
- /* @__PURE__ */ jsx7(Button2, { tone: "critical", onClick: onConfirm, children: i18n.t("leave") })
1618
+ return /* @__PURE__ */ jsx9("div", { className: "pb-delete-confirmation-backdrop", role: "presentation", children: /* @__PURE__ */ jsxs7("section", { className: "pb-delete-confirmation", role: "dialog", "aria-modal": "true", "aria-labelledby": "pb-leave-confirmation-title", children: [
1619
+ /* @__PURE__ */ jsx9(Text3, { as: "h2", variant: "headingMd", id: "pb-leave-confirmation-title", children: i18n.t("confirmLeaveTitle") }),
1620
+ /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodyMd", children: i18n.t("leaveWarning") }),
1621
+ /* @__PURE__ */ jsxs7(ButtonGroup2, { children: [
1622
+ /* @__PURE__ */ jsx9(Button2, { onClick: onCancel, children: i18n.t("cancel") }),
1623
+ /* @__PURE__ */ jsx9(Button2, { tone: "critical", onClick: onConfirm, children: i18n.t("leave") })
1372
1624
  ] })
1373
1625
  ] }) });
1374
1626
  }
@@ -1380,8 +1632,8 @@ function inspectorFieldConfig(name, field) {
1380
1632
  }
1381
1633
  var appearanceTokens = [["color.primary", "\u4E3B\u8272"], ["color.surface", "\u8868\u9762\u8272"], ["radius", "\u5706\u89D2"], ["spacing", "\u95F4\u8DDD"]];
1382
1634
  function DocumentInspector({ settings, pageId, assetPicker, productPicker, i18n, block, registry, disabled, appearanceControls, onChange, onPresentationChange }) {
1383
- const [assetError, setAssetError] = useState3(null);
1384
- const [selectingAsset, setSelectingAsset] = useState3(false);
1635
+ const [assetError, setAssetError] = useState4(null);
1636
+ const [selectingAsset, setSelectingAsset] = useState4(false);
1385
1637
  const definition = registry?.getBlock(block.type);
1386
1638
  const groupedFields = definition ? Object.entries(definition.fields).reduce((groups, entry) => {
1387
1639
  const [name, field] = entry;
@@ -1390,20 +1642,20 @@ function DocumentInspector({ settings, pageId, assetPicker, productPicker, i18n,
1390
1642
  (groups[group] ??= []).push([name, configuredField]);
1391
1643
  return groups;
1392
1644
  }, {}) : {};
1393
- return /* @__PURE__ */ jsxs5("div", { className: "pb-inspector", "data-testid": "document-inspector", children: [
1394
- /* @__PURE__ */ jsxs5("header", { className: "pb-inspector__header", children: [
1395
- /* @__PURE__ */ jsx7(Badge3, { children: block.type }),
1396
- /* @__PURE__ */ jsxs5("div", { children: [
1397
- /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "headingSm", children: blockLabel(block, registry) }),
1398
- /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: definition?.category ?? "Core block" })
1645
+ return /* @__PURE__ */ jsxs7("div", { className: "pb-inspector", "data-testid": "document-inspector", children: [
1646
+ /* @__PURE__ */ jsxs7("header", { className: "pb-inspector__header", children: [
1647
+ /* @__PURE__ */ jsx9(Badge3, { children: block.type }),
1648
+ /* @__PURE__ */ jsxs7("div", { children: [
1649
+ /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "headingSm", children: blockLabel(block, registry) }),
1650
+ /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: definition?.category ?? "Core block" })
1399
1651
  ] })
1400
1652
  ] }),
1401
- block.type === "core.text" ? /* @__PURE__ */ jsx7(InspectorSection, { title: "Content", children: /* @__PURE__ */ jsx7(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, productPicker, pageId, blockId: block.id, i18n, onChange: (content) => onChange({ content }) }) }) : null,
1402
- block.type === "core.image" ? /* @__PURE__ */ jsxs5(InspectorSection, { title: "Image", children: [
1403
- /* @__PURE__ */ jsx7(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, productPicker, pageId, blockId: block.id, i18n, onChange: (src) => onChange({ src }) }),
1404
- /* @__PURE__ */ jsx7(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, productPicker, pageId, blockId: block.id, i18n, onChange: (alt) => onChange({ alt }) }),
1405
- assetPicker ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1406
- /* @__PURE__ */ jsx7(Button2, { disabled: disabled || selectingAsset, onClick: () => void (async () => {
1653
+ block.type === "core.text" ? /* @__PURE__ */ jsx9(InspectorSection, { title: "Content", children: /* @__PURE__ */ jsx9(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, productPicker, pageId, blockId: block.id, i18n, onChange: (content) => onChange({ content }) }) }) : null,
1654
+ block.type === "core.image" ? /* @__PURE__ */ jsxs7(InspectorSection, { title: "Image", children: [
1655
+ /* @__PURE__ */ jsx9(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, productPicker, pageId, blockId: block.id, i18n, onChange: (src) => onChange({ src }) }),
1656
+ /* @__PURE__ */ jsx9(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, productPicker, pageId, blockId: block.id, i18n, onChange: (alt) => onChange({ alt }) }),
1657
+ assetPicker ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
1658
+ /* @__PURE__ */ jsx9(Button2, { disabled: disabled || selectingAsset, onClick: () => void (async () => {
1407
1659
  setSelectingAsset(true);
1408
1660
  setAssetError(null);
1409
1661
  try {
@@ -1415,13 +1667,13 @@ function DocumentInspector({ settings, pageId, assetPicker, productPicker, i18n,
1415
1667
  setSelectingAsset(false);
1416
1668
  }
1417
1669
  })(), children: selectingAsset ? "\u6B63\u5728\u9009\u62E9\u7D20\u6750\u2026" : "\u9009\u62E9\u7D20\u6750" }),
1418
- assetError ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: assetError }) : null
1670
+ assetError ? /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "critical", children: assetError }) : null
1419
1671
  ] }) : null
1420
1672
  ] }) : null,
1421
- Object.entries(groupedFields).map(([group, fields]) => /* @__PURE__ */ jsx7(InspectorSection, { title: group, defaultOpen: group !== "Advanced", children: fields.map(([name, field]) => /* @__PURE__ */ jsx7(InspectorField, { name, field, value: field.persist === false ? settings?.values[name] : block.props[name], registry, disabled: disabled || field.persist === false && (!settings || Boolean(settings.disabled)), assetPicker, productPicker, pageId, blockId: block.id, i18n, onChange: (value) => field.persist === false ? settings?.onChange(name, value) : onChange({ [name]: value }) }, name)) }, group)),
1673
+ Object.entries(groupedFields).map(([group, fields]) => /* @__PURE__ */ jsx9(InspectorSection, { title: group, defaultOpen: group !== "Advanced", children: fields.map(([name, field]) => /* @__PURE__ */ jsx9(InspectorField, { name, field, value: field.persist === false ? settings?.values[name] : block.props[name], registry, disabled: disabled || field.persist === false && (!settings || Boolean(settings.disabled)), assetPicker, productPicker, pageId, blockId: block.id, i18n, onChange: (value) => field.persist === false ? settings?.onChange(name, value) : onChange({ [name]: value }) }, name)) }, group)),
1422
1674
  Object.values(definition?.fields ?? {}).some((field) => field.persist === false) ? settings?.footer : null,
1423
- appearanceControls && definition?.variants?.length ? /* @__PURE__ */ jsx7(InspectorSection, { title: "\u5916\u89C2", children: /* @__PURE__ */ jsx7(Select2, { label: "\u6837\u5F0F\u53D8\u4F53", options: definition.variants.map((variant) => ({ label: variant.label, value: variant.id })), value: block.variant, disabled, onChange: (variant) => onPresentationChange({ variant, style: block.style }) }) }) : null,
1424
- appearanceControls && definition ? /* @__PURE__ */ jsx7(InspectorSection, { title: "\u6837\u5F0F\u8986\u76D6", defaultOpen: false, children: appearanceTokens.map(([token, label]) => /* @__PURE__ */ jsx7(TextField, { label, value: block.style[token] ?? "", placeholder: "\u7EE7\u627F\u9875\u9762\u6216\u6A21\u677F\u8BBE\u7F6E", autoComplete: "off", disabled, onChange: (value) => {
1675
+ appearanceControls && definition?.variants?.length ? /* @__PURE__ */ jsx9(InspectorSection, { title: "\u5916\u89C2", children: /* @__PURE__ */ jsx9(Select2, { label: "\u6837\u5F0F\u53D8\u4F53", options: definition.variants.map((variant) => ({ label: variant.label, value: variant.id })), value: block.variant, disabled, onChange: (variant) => onPresentationChange({ variant, style: block.style }) }) }) : null,
1676
+ appearanceControls && definition ? /* @__PURE__ */ jsx9(InspectorSection, { title: "\u6837\u5F0F\u8986\u76D6", defaultOpen: false, children: appearanceTokens.map(([token, label]) => /* @__PURE__ */ jsx9(TextField, { label, value: block.style[token] ?? "", placeholder: "\u7EE7\u627F\u9875\u9762\u6216\u6A21\u677F\u8BBE\u7F6E", autoComplete: "off", disabled, onChange: (value) => {
1425
1677
  const style = { ...block.style };
1426
1678
  if (value.trim()) style[token] = value;
1427
1679
  else delete style[token];