@standhigher/puck-page-builder 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,8 +3,14 @@ import {
3
3
  ExtensionRegistryError,
4
4
  TemplateRegistry,
5
5
  createExtensionRegistry,
6
- createTemplateRegistry
7
- } from "./chunk-PUTYZI7B.js";
6
+ createTemplateRegistry,
7
+ maximumBlockInstances,
8
+ mergeBlockPolicies,
9
+ minimumBlockInstances,
10
+ validateBlockPolicy,
11
+ validateFieldValue,
12
+ validatePageDocumentWithRegistry
13
+ } from "./chunk-PFCWXLWN.js";
8
14
  import {
9
15
  WebRenderer
10
16
  } from "./chunk-KYXIXIWK.js";
@@ -341,9 +347,9 @@ function PropertyPanel({
341
347
  }
342
348
 
343
349
  // src/editor/shell/PageDocumentEditorShell.tsx
344
- import { Puck as Puck2, usePuck } from "@puckeditor/core";
345
- import { Badge as Badge2, Banner, Button as Button2, ButtonGroup as ButtonGroup2, InlineStack as InlineStack2, Text as Text2, TextField } from "@shopify/polaris";
346
- import { DragHandleIcon as DragHandleIcon2, LayoutSectionIcon as LayoutSectionIcon2, MenuIcon as MenuIcon2, RedoIcon as RedoIcon2, UndoIcon as UndoIcon2 } from "@shopify/polaris-icons";
350
+ import { IconButton as IconButton2, Puck as Puck2, usePuck } from "@puckeditor/core";
351
+ import { Badge as Badge3, Banner, Button as Button2, ButtonGroup as ButtonGroup2, InlineStack as InlineStack3, Select as Select2, Text as Text3, TextField } from "@shopify/polaris";
352
+ import { DragHandleIcon as DragHandleIcon2, LayoutSectionIcon as LayoutSectionIcon2, MenuIcon as MenuIcon2, RedoIcon as RedoIcon2, UndoIcon as UndoIcon2, XIcon as XIcon2 } from "@shopify/polaris-icons";
347
353
  import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef4, useState as useState3 } from "react";
348
354
 
349
355
  // src/adapters/puck/canvas-extension-block.tsx
@@ -462,6 +468,65 @@ function fromEngineData(data, base, registry) {
462
468
 
463
469
  // src/editor/context/EditorContext.tsx
464
470
  import { createContext, useCallback, useContext, useEffect as useEffect2, useMemo, useReducer, useRef as useRef3, useState as useState2 } from "react";
471
+
472
+ // src/editor/policy.ts
473
+ function operationEnabled(operation, policy, blockPolicy) {
474
+ const key = `allow${operation[0].toUpperCase()}${operation.slice(1)}`;
475
+ return policy?.operations?.[key] !== false && blockPolicy[key] !== false;
476
+ }
477
+ function resolvedBlockPolicy(type, definition, policy) {
478
+ return mergeBlockPolicies(definition?.policy, policy?.blocks?.[type]);
479
+ }
480
+ function blockCount(blocks, type) {
481
+ return blocks.filter((block) => block.type === type).length;
482
+ }
483
+ function canAddBlock(type, blocks, definition, policy) {
484
+ const blockPolicy = resolvedBlockPolicy(type, definition, policy);
485
+ return operationEnabled("add", policy, blockPolicy) && blockCount(blocks, type) < maximumBlockInstances(blockPolicy);
486
+ }
487
+ function canDeleteBlock(block, blocks, definition, policy) {
488
+ if (!block) return false;
489
+ const blockPolicy = resolvedBlockPolicy(block.type, definition, policy);
490
+ return operationEnabled("delete", policy, blockPolicy) && blockCount(blocks, block.type) > minimumBlockInstances(blockPolicy);
491
+ }
492
+ function canDuplicateBlock(block, blocks, definition, policy) {
493
+ if (!block) return false;
494
+ const blockPolicy = resolvedBlockPolicy(block.type, definition, policy);
495
+ return operationEnabled("duplicate", policy, blockPolicy) && canAddBlock(block.type, blocks, definition, policy);
496
+ }
497
+ function canDragBlock(block, definition, policy) {
498
+ if (!block) return false;
499
+ return operationEnabled("drag", policy, resolvedBlockPolicy(block.type, definition, policy));
500
+ }
501
+ function canApplyCanvasDocument(previous, next, getDefinition, policy) {
502
+ const previousById = new Map(previous.blocks.map((block) => [block.id, block]));
503
+ const nextById = new Map(next.blocks.map((block) => [block.id, block]));
504
+ for (const block of next.blocks) {
505
+ const previousBlock = previousById.get(block.id);
506
+ if (previousBlock && previousBlock.type !== block.type) return false;
507
+ }
508
+ for (const block of next.blocks) {
509
+ if (!previousById.has(block.id) && !canAddBlock(block.type, previous.blocks, getDefinition(block.type), policy)) return false;
510
+ }
511
+ for (const block of previous.blocks) {
512
+ if (!nextById.has(block.id) && !canDeleteBlock(block, previous.blocks, getDefinition(block.type), policy)) return false;
513
+ }
514
+ const types = new Set([...previous.blocks, ...next.blocks].map((block) => block.type));
515
+ for (const type of types) {
516
+ const definition = getDefinition(type);
517
+ const blockPolicy = resolvedBlockPolicy(type, definition, policy);
518
+ const nextCount = blockCount(next.blocks, type);
519
+ if (nextCount > maximumBlockInstances(blockPolicy) || nextCount < minimumBlockInstances(blockPolicy)) return false;
520
+ }
521
+ if (previous.blocks.length === next.blocks.length && previous.blocks.every((block) => nextById.has(block.id))) {
522
+ for (let index = 0; index < previous.blocks.length; index += 1) {
523
+ if (previous.blocks[index]?.id !== next.blocks[index]?.id && !canDragBlock(previous.blocks[index], getDefinition(previous.blocks[index].type), policy)) return false;
524
+ }
525
+ }
526
+ return true;
527
+ }
528
+
529
+ // src/editor/context/EditorContext.tsx
465
530
  import { jsx as jsx5 } from "react/jsx-runtime";
466
531
  function historyReducer(state, action) {
467
532
  if (action.type === "select") return { ...state, selectedBlockId: action.id };
@@ -476,7 +541,7 @@ function historyReducer(state, action) {
476
541
  if (!next) return state;
477
542
  return { ...state, ...next, past: [...state.past, { document: state.document, selectedBlockId: state.selectedBlockId }], future: state.future.slice(1) };
478
543
  }
479
- if (action.type === "saved") return { ...state, savedDocument: state.document };
544
+ if (action.type === "saved") return { ...state, savedDocument: action.document };
480
545
  const current = { document: state.document, selectedBlockId: state.selectedBlockId };
481
546
  return { ...state, document: action.document, selectedBlockId: action.selectedBlockId, past: [...state.past, current], future: [] };
482
547
  }
@@ -494,15 +559,17 @@ function defaultBlock(type, blocks, registry) {
494
559
  if (!definition) throw new Error(`Unknown PageDocument block type: ${type}`);
495
560
  return { id: uniqueBlockId(type, blocks), type, version: definition.version, props: definition.defaultProps, variant: definition.defaultVariant ?? "default", style: {} };
496
561
  }
497
- function EditorProvider({ initialDocument, registry, loadState = "ready", leaveWarning = "You have unsaved changes.", onDocumentChange, children }) {
562
+ function EditorProvider({ initialDocument, registry, policy, loadState = "ready", sessionState = "active", session, sessionMessage, leaveWarning = "You have unsaved changes.", onDocumentChange, children }) {
498
563
  const [history, dispatch] = useReducer(historyReducer, initialDocument, (document) => ({ document, selectedBlockId: document.blocks[0]?.id ?? null, past: [], future: [], device: "desktop", savedDocument: document }));
499
564
  const historyRef = useRef3(history);
500
565
  useEffect2(() => {
501
566
  historyRef.current = history;
502
567
  }, [history]);
503
568
  const [canvasSelectionRequest, setCanvasSelectionRequest] = useState2(null);
504
- const editable = loadState === "ready" || loadState === "success";
569
+ const [pendingDeleteBlockId, setPendingDeleteBlockId] = useState2(null);
570
+ const editable = (loadState === "ready" || loadState === "success") && sessionState === "active";
505
571
  const selectedBlock = history.document.blocks.find((block) => block.id === history.selectedBlockId) ?? null;
572
+ const pendingDeleteBlock = history.document.blocks.find((block) => block.id === pendingDeleteBlockId) ?? null;
506
573
  const replace = useCallback((document, selectedBlockId) => dispatch({ type: "replace", document, selectedBlockId }), []);
507
574
  const requestCanvasSelection = useCallback((id) => {
508
575
  if (editable) setCanvasSelectionRequest(id);
@@ -513,15 +580,23 @@ function EditorProvider({ initialDocument, registry, loadState = "ready", leaveW
513
580
  }, []);
514
581
  const updateFromCanvas = useCallback((document) => {
515
582
  const current = historyRef.current;
516
- if (!editable || JSON.stringify(document) === JSON.stringify(current.document)) return;
583
+ if (!editable || JSON.stringify(document) === JSON.stringify(current.document)) return false;
584
+ if (!canApplyCanvasDocument(current.document, document, (type) => registry?.getBlock(type), policy)) return false;
517
585
  replace(document, current.selectedBlockId);
518
- }, [editable, replace]);
586
+ return true;
587
+ }, [editable, policy, registry, replace]);
519
588
  const updateBlockProps = useCallback((id, props) => {
520
589
  if (!editable) return;
521
590
  const current = historyRef.current;
522
591
  const blocks = current.document.blocks.map((block) => block.id === id ? { ...block, props: { ...block.props, ...props } } : block);
523
592
  replace({ ...current.document, blocks }, id);
524
593
  }, [editable, replace]);
594
+ const updateBlockPresentation = useCallback((id, presentation) => {
595
+ if (!editable) return;
596
+ const current = historyRef.current;
597
+ const blocks = current.document.blocks.map((block) => block.id === id ? { ...block, ...presentation } : block);
598
+ replace({ ...current.document, blocks }, id);
599
+ }, [editable, replace]);
525
600
  useEffect2(() => {
526
601
  onDocumentChange?.(history.document);
527
602
  }, [history.document, onDocumentChange]);
@@ -555,27 +630,37 @@ function EditorProvider({ initialDocument, registry, loadState = "ready", leaveW
555
630
  const actionState = {
556
631
  canUndo: editable && history.past.length > 0,
557
632
  canRedo: editable && history.future.length > 0,
558
- canAdd: editable,
633
+ canAdd: editable && ["core.text", "core.image", ...registry?.blocks.map((block) => block.type) ?? []].some((type) => canAddBlock(type, history.document.blocks, registry?.getBlock(type), policy)),
559
634
  canEdit: editable && selectedBlock !== null,
560
- canDelete: editable && selectedBlock !== null,
561
- canDuplicate: editable && selectedBlock !== null,
562
- canReorder: editable && history.document.blocks.length > 1
635
+ canDelete: editable && canDeleteBlock(selectedBlock, history.document.blocks, registry?.getBlock(selectedBlock?.type ?? ""), policy),
636
+ canDuplicate: editable && canDuplicateBlock(selectedBlock, history.document.blocks, registry?.getBlock(selectedBlock?.type ?? ""), policy),
637
+ canReorder: editable && history.document.blocks.length > 1 && canDragBlock(selectedBlock, registry?.getBlock(selectedBlock?.type ?? ""), policy)
563
638
  };
564
639
  return {
565
640
  document: history.document,
566
641
  selectedBlockId: history.selectedBlockId,
567
642
  selectedBlock,
643
+ pendingDeleteBlock,
568
644
  canvasSelectionRequest,
569
645
  device: history.device,
570
646
  loadState,
647
+ sessionState,
648
+ session,
649
+ sessionMessage,
650
+ isEditable: editable,
571
651
  isDirty,
572
652
  actionState,
653
+ canAddBlock: (type) => editable && canAddBlock(type, history.document.blocks, registry?.getBlock(type), policy),
654
+ canDragBlock: (id) => {
655
+ const block = history.document.blocks.find((item) => item.id === id);
656
+ return editable && canDragBlock(block, registry?.getBlock(block?.type ?? ""), policy);
657
+ },
573
658
  requestCanvasSelection,
574
659
  confirmCanvasSelection,
575
660
  updateFromCanvas,
576
661
  setDevice: (device) => dispatch({ type: "device", device }),
577
662
  addBlock: (type, beforeId) => {
578
- if (!editable) return null;
663
+ if (!editable || !canAddBlock(type, history.document.blocks, registry?.getBlock(type), policy)) return null;
579
664
  const block = defaultBlock(type, history.document.blocks, registry);
580
665
  const blocks = [...history.document.blocks];
581
666
  const targetIndex = beforeId ? blocks.findIndex((item) => item.id === beforeId) : -1;
@@ -588,7 +673,7 @@ function EditorProvider({ initialDocument, registry, loadState = "ready", leaveW
588
673
  if (!editable) return;
589
674
  const index = history.document.blocks.findIndex((block2) => block2.id === id);
590
675
  const source = history.document.blocks[index];
591
- if (!source) return;
676
+ if (!canDuplicateBlock(source, history.document.blocks, registry?.getBlock(source?.type ?? ""), policy)) return;
592
677
  const block = { ...source, id: uniqueBlockId(source.type, history.document.blocks), props: { ...source.props } };
593
678
  const blocks = [...history.document.blocks];
594
679
  blocks.splice(index + 1, 0, block);
@@ -597,7 +682,8 @@ function EditorProvider({ initialDocument, registry, loadState = "ready", leaveW
597
682
  deleteBlock: (id) => {
598
683
  if (!editable) return;
599
684
  const index = history.document.blocks.findIndex((block) => block.id === id);
600
- if (index < 0) return;
685
+ const source = history.document.blocks[index];
686
+ if (!canDeleteBlock(source, history.document.blocks, registry?.getBlock(source?.type ?? ""), policy)) return;
601
687
  const blocks = history.document.blocks.filter((block) => block.id !== id);
602
688
  replace({ ...history.document, blocks }, blocks[index]?.id ?? blocks[index - 1]?.id ?? null);
603
689
  },
@@ -605,7 +691,7 @@ function EditorProvider({ initialDocument, registry, loadState = "ready", leaveW
605
691
  if (!editable) return;
606
692
  const from = history.document.blocks.findIndex((block) => block.id === id);
607
693
  const to = from + direction;
608
- if (from < 0 || to < 0 || to >= history.document.blocks.length) return;
694
+ if (from < 0 || to < 0 || to >= history.document.blocks.length || !canDragBlock(history.document.blocks[from], registry?.getBlock(history.document.blocks[from]?.type ?? ""), policy)) return;
609
695
  const blocks = [...history.document.blocks];
610
696
  [blocks[from], blocks[to]] = [blocks[to], blocks[from]];
611
697
  replace({ ...history.document, blocks }, id);
@@ -615,21 +701,38 @@ function EditorProvider({ initialDocument, registry, loadState = "ready", leaveW
615
701
  const source = history.document.blocks.find((block) => block.id === id);
616
702
  const withoutSource = history.document.blocks.filter((block) => block.id !== id);
617
703
  const targetIndex = withoutSource.findIndex((block) => block.id === beforeId);
618
- if (!source || targetIndex < 0) return;
704
+ if (!source || targetIndex < 0 || !canDragBlock(source, registry?.getBlock(source.type), policy)) return;
619
705
  const blocks = [...withoutSource];
620
706
  blocks.splice(targetIndex, 0, source);
621
707
  replace({ ...history.document, blocks }, id);
622
708
  },
623
709
  updateBlockProps,
710
+ updateBlockPresentation,
711
+ requestDeleteBlock: (id) => {
712
+ const block = history.document.blocks.find((item) => item.id === id);
713
+ if (editable && canDeleteBlock(block, history.document.blocks, registry?.getBlock(block?.type ?? ""), policy)) setPendingDeleteBlockId(id);
714
+ },
715
+ cancelDeleteBlock: () => setPendingDeleteBlockId(null),
716
+ confirmDeleteBlock: () => {
717
+ if (pendingDeleteBlockId) {
718
+ const id = pendingDeleteBlockId;
719
+ setPendingDeleteBlockId(null);
720
+ const index = history.document.blocks.findIndex((block) => block.id === id);
721
+ const source = history.document.blocks[index];
722
+ if (!canDeleteBlock(source, history.document.blocks, registry?.getBlock(source?.type ?? ""), policy)) return;
723
+ const blocks = history.document.blocks.filter((block) => block.id !== id);
724
+ replace({ ...history.document, blocks }, blocks[index]?.id ?? blocks[index - 1]?.id ?? null);
725
+ }
726
+ },
624
727
  undo: () => {
625
728
  if (editable) dispatch({ type: "undo" });
626
729
  },
627
730
  redo: () => {
628
731
  if (editable) dispatch({ type: "redo" });
629
732
  },
630
- markSaved: () => dispatch({ type: "saved" })
733
+ markSaved: (document = historyRef.current.document) => dispatch({ type: "saved", document })
631
734
  };
632
- }, [canvasSelectionRequest, confirmCanvasSelection, editable, history, isDirty, loadState, registry, replace, requestCanvasSelection, selectedBlock, updateBlockProps, updateFromCanvas]);
735
+ }, [canvasSelectionRequest, confirmCanvasSelection, editable, history, isDirty, loadState, pendingDeleteBlock, pendingDeleteBlockId, policy, registry, replace, requestCanvasSelection, selectedBlock, session, sessionMessage, sessionState, updateBlockPresentation, updateBlockProps, updateFromCanvas]);
633
736
  return /* @__PURE__ */ jsx5(EditorContext.Provider, { value, children });
634
737
  }
635
738
  function useEditorContext() {
@@ -638,6 +741,26 @@ function useEditorContext() {
638
741
  return context;
639
742
  }
640
743
 
744
+ // src/editor/components/PageStatusCard.tsx
745
+ import { Badge as Badge2, InlineStack as InlineStack2, Text as Text2 } from "@shopify/polaris";
746
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
747
+ function PageStatusCard({ status, sessionState }) {
748
+ const publicationTone = status.publicationStatus === "published" ? "success" : "attention";
749
+ 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;
750
+ return /* @__PURE__ */ jsxs4("div", { className: "pb-page-status-card", "data-testid": "page-status-card", "data-publication-status": status.publicationStatus, "data-session-state": sessionState, children: [
751
+ /* @__PURE__ */ jsxs4(InlineStack2, { gap: "150", blockAlign: "center", wrap: true, children: [
752
+ /* @__PURE__ */ jsx6(Badge2, { tone: publicationTone, children: status.publicationStatus === "published" ? "\u7EBF\u4E0A" : "\u672A\u53D1\u5E03" }),
753
+ status.draftLabel ? /* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: status.draftLabel }) : null,
754
+ status.publishedVersionLabel ? /* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: status.publishedVersionLabel }) : null,
755
+ sessionLabel ? /* @__PURE__ */ jsx6(Badge2, { tone: sessionState === "lost" ? "critical" : "attention", children: sessionLabel }) : null
756
+ ] }),
757
+ status.lastSavedAt ? /* @__PURE__ */ jsxs4(Text2, { as: "p", variant: "bodySm", tone: "subdued", children: [
758
+ "\u6700\u540E\u4FDD\u5B58\uFF1A",
759
+ status.lastSavedAt
760
+ ] }) : null
761
+ ] });
762
+ }
763
+
641
764
  // src/editor/i18n/admin.ts
642
765
  var messages = {
643
766
  "zh-CN": {
@@ -649,6 +772,10 @@ var messages = {
649
772
  redo: "\u6062\u590D",
650
773
  duplicate: "\u590D\u5236",
651
774
  remove: "\u5220\u9664",
775
+ confirmDeleteTitle: "\u786E\u8BA4\u5220\u9664\u533A\u5757\uFF1F",
776
+ confirmDeleteMessage: "\u5220\u9664\u540E\u53EF\u4F7F\u7528\u64A4\u9500\u6062\u590D\u3002",
777
+ cancel: "\u53D6\u6D88",
778
+ confirm: "\u786E\u8BA4\u5220\u9664",
652
779
  moveUp: "\u4E0A\u79FB",
653
780
  moveDown: "\u4E0B\u79FB",
654
781
  loading: "\u6B63\u5728\u52A0\u8F7D\u7F16\u8F91\u5668\u2026",
@@ -663,13 +790,33 @@ var messages = {
663
790
  publish: "\u53D1\u5E03",
664
791
  publishing: "\u6B63\u5728\u53D1\u5E03\u2026",
665
792
  saveFailed: "\u4FDD\u5B58\u8349\u7A3F\u5931\u8D25",
793
+ retrySave: "\u91CD\u8BD5\u4FDD\u5B58",
794
+ offline: "\u5F53\u524D\u79BB\u7EBF\uFF0C\u6062\u590D\u7F51\u7EDC\u540E\u5C06\u81EA\u52A8\u4FDD\u5B58",
666
795
  publishFailed: "\u53D1\u5E03\u5931\u8D25",
667
796
  published: "\u5DF2\u53D1\u5E03",
797
+ back: "\u8FD4\u56DE",
798
+ confirmLeaveTitle: "\u79BB\u5F00\u7F16\u8F91\u5668\uFF1F",
799
+ leave: "\u79BB\u5F00",
800
+ zoom: "\u7F29\u653E",
801
+ zoomAuto: "\u81EA\u52A8\u7F29\u653E",
802
+ preview: "\u6B63\u5F0F\u9884\u89C8",
803
+ addToStore: "\u6DFB\u52A0\u5230\u5E97\u94FA",
804
+ acquiringLock: "\u6B63\u5728\u83B7\u53D6\u7F16\u8F91\u9501\u2026",
805
+ locked: "\u8BE5\u9875\u9762\u6B63\u5728\u88AB\u7F16\u8F91",
806
+ lockLost: "\u7F16\u8F91\u9501\u5DF2\u5931\u6548",
807
+ readonly: "\u9875\u9762\u4E3A\u53EA\u8BFB\u72B6\u6001",
668
808
  leaveWarning: "\u4F60\u6709\u672A\u4FDD\u5B58\u7684\u66F4\u6539\u3002\u786E\u5B9A\u8981\u79BB\u5F00\u5417\uFF1F",
669
809
  selectBlock: "\u9009\u62E9\u4E00\u4E2A\u533A\u5757\u4EE5\u7F16\u8F91\u3002",
670
810
  desktop: "\u684C\u9762",
671
811
  tablet: "\u5E73\u677F",
672
- mobile: "\u624B\u673A"
812
+ mobile: "\u624B\u673A",
813
+ collapseLeft: "\u6536\u8D77\u5DE6\u4FA7\u9762\u677F",
814
+ collapseRight: "\u6536\u8D77\u5C5E\u6027\u9762\u677F",
815
+ expandLeft: "\u6253\u5F00\u533A\u5757\u9762\u677F",
816
+ expandRight: "\u6253\u5F00\u5C5E\u6027\u9762\u677F",
817
+ toggleSidebars: "\u4FA7\u680F\u663E\u793A",
818
+ toggleLeftSidebar: "\u5207\u6362\u5DE6\u4FA7\u9762\u677F",
819
+ toggleRightSidebar: "\u5207\u6362\u5C5E\u6027\u9762\u677F"
673
820
  },
674
821
  en: {
675
822
  blocks: "Blocks",
@@ -680,6 +827,10 @@ var messages = {
680
827
  redo: "Redo",
681
828
  duplicate: "Duplicate",
682
829
  remove: "Delete",
830
+ confirmDeleteTitle: "Delete this block?",
831
+ confirmDeleteMessage: "You can restore it with Undo after deleting.",
832
+ cancel: "Cancel",
833
+ confirm: "Delete block",
683
834
  moveUp: "Move up",
684
835
  moveDown: "Move down",
685
836
  loading: "Loading editor\u2026",
@@ -694,13 +845,33 @@ var messages = {
694
845
  publish: "Publish",
695
846
  publishing: "Publishing\u2026",
696
847
  saveFailed: "Could not save draft",
848
+ retrySave: "Retry save",
849
+ offline: "Offline \u2014 changes will save when connection returns",
697
850
  publishFailed: "Could not publish",
698
851
  published: "Published",
852
+ back: "Back",
853
+ confirmLeaveTitle: "Leave the editor?",
854
+ leave: "Leave",
855
+ zoom: "Zoom",
856
+ zoomAuto: "Auto zoom",
857
+ preview: "Preview",
858
+ addToStore: "Add to store",
859
+ acquiringLock: "Acquiring edit lock\u2026",
860
+ locked: "This page is being edited",
861
+ lockLost: "The edit lock has expired",
862
+ readonly: "This page is read-only",
699
863
  leaveWarning: "You have unsaved changes. Are you sure you want to leave?",
700
864
  selectBlock: "Select a block to edit it.",
701
865
  desktop: "Desktop",
702
866
  tablet: "Tablet",
703
- mobile: "Mobile"
867
+ mobile: "Mobile",
868
+ collapseLeft: "Collapse blocks panel",
869
+ collapseRight: "Collapse properties panel",
870
+ expandLeft: "Open blocks panel",
871
+ expandRight: "Open properties panel",
872
+ toggleSidebars: "Sidebar visibility",
873
+ toggleLeftSidebar: "Toggle left sidebar",
874
+ toggleRightSidebar: "Toggle right sidebar"
704
875
  }
705
876
  };
706
877
  function createAdminI18n(locale) {
@@ -718,7 +889,7 @@ function blockIdAtRelativeY(ids, relativeY) {
718
889
  }
719
890
 
720
891
  // src/editor/shell/PageDocumentEditorShell.tsx
721
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
892
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
722
893
  var deviceLabels2 = { desktop: "desktop", tablet: "tablet", mobile: "mobile" };
723
894
  function blockLabel(block, registry) {
724
895
  return blockTypeLabel(block.type, registry);
@@ -728,40 +899,151 @@ function blockTypeLabel(type, registry) {
728
899
  if (type === "core.image") return "\u56FE\u7247";
729
900
  return registry?.getBlock(type)?.label ?? type;
730
901
  }
902
+ function PuckPanelIcon({ side }) {
903
+ return /* @__PURE__ */ jsxs5("svg", { xmlns: "http://www.w3.org/2000/svg", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", children: [
904
+ /* @__PURE__ */ jsx7("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
905
+ /* @__PURE__ */ jsx7("path", { d: side === "left" ? "M9 3v18" : "M15 3v18" })
906
+ ] });
907
+ }
731
908
  function validateDocumentBlocks(document, registry) {
732
- if (!registry) return [];
733
- return document.blocks.flatMap((block) => {
734
- const definition = registry.getBlock(block.type);
735
- if (!definition) return [];
736
- const issues = [...definition.validate?.(block.props) ?? []];
737
- if (definition.variants?.length && !definition.variants.some((variant) => variant.id === block.variant)) {
738
- issues.push({ path: "variant", message: `Unsupported variant: ${block.variant}.` });
739
- }
740
- return issues.map((issue) => ({ ...issue, path: `${block.id}.${issue.path}` }));
741
- });
909
+ return registry ? validatePageDocumentWithRegistry(document, registry) : [];
910
+ }
911
+ function useEditorSession(adapter, pageId) {
912
+ const [managed, setManaged] = useState3(() => adapter ? { state: "acquiring" } : { state: "active" });
913
+ useEffect3(() => {
914
+ let cancelled = false;
915
+ let session;
916
+ let heartbeatTimer;
917
+ const stopHeartbeat = () => {
918
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
919
+ heartbeatTimer = void 0;
920
+ };
921
+ if (!adapter) return;
922
+ void adapter.acquire({ pageId }).then((result) => {
923
+ if (cancelled) return;
924
+ if (result.state !== "active") {
925
+ setManaged({ state: result.state, message: result.message ?? (result.state === "locked" && result.editorName ? `${result.editorName} \u6B63\u5728\u7F16\u8F91` : void 0) });
926
+ return;
927
+ }
928
+ session = result.session;
929
+ setManaged({ state: "active", session });
930
+ if (!adapter.heartbeat) return;
931
+ const heartbeat = () => {
932
+ if (typeof navigator !== "undefined" && !navigator.onLine) return;
933
+ void adapter.heartbeat?.({ pageId, session: result.session }).catch(() => {
934
+ if (!cancelled) {
935
+ stopHeartbeat();
936
+ setManaged({ state: "lost", message: "\u7F16\u8F91\u9501\u5DF2\u5931\u6548\uFF0C\u8BF7\u6062\u590D\u7F51\u7EDC\u540E\u91CD\u65B0\u8FDB\u5165\u7F16\u8F91\u5668\u3002" });
937
+ }
938
+ });
939
+ };
940
+ heartbeatTimer = setInterval(heartbeat, adapter.heartbeatIntervalMs ?? 3e4);
941
+ }).catch(() => {
942
+ if (!cancelled) setManaged({ state: "lost", message: "\u65E0\u6CD5\u83B7\u53D6\u7F16\u8F91\u9501\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002" });
943
+ });
944
+ return () => {
945
+ cancelled = true;
946
+ stopHeartbeat();
947
+ if (session) void adapter.release?.({ pageId, session });
948
+ };
949
+ }, [adapter, pageId]);
950
+ return managed;
742
951
  }
743
952
  function PageDocumentEditorShell(props) {
744
- return /* @__PURE__ */ jsx6(EditorProvider, { initialDocument: props.initialDocument, registry: props.registry, loadState: props.loadState, leaveWarning: createAdminI18n(props.adminLocale).t("leaveWarning"), onDocumentChange: props.onDocumentChange, children: /* @__PURE__ */ jsx6(PageDocumentEditor, { ...props }) });
953
+ const { sessionAdapter, initialDocument, onSessionStateChange } = props;
954
+ const managedSession = useEditorSession(sessionAdapter, initialDocument.pageId);
955
+ useEffect3(() => {
956
+ onSessionStateChange?.(managedSession.state);
957
+ }, [managedSession.state, onSessionStateChange]);
958
+ 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 }) });
745
959
  }
746
- function PageDocumentEditor({ iframe = true, registry, adminLocale, onSave, onPublish }) {
960
+ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onPublish, draftPersistence, draftRevision: initialDraftRevision, autoSave = true, autoSaveDelayMs = 800, publishAction, assetPicker, pageStatus, onBack, onPreview, onAddToStore, deleteConfirmation, availableBlockTypes, appearanceControls = false }) {
747
961
  const editor = useEditorContext();
748
962
  const [blockView, setBlockView] = useState3("blocks");
963
+ const [leftRailOpen, setLeftRailOpen] = useState3(true);
964
+ const [rightPanelOpen, setRightPanelOpen] = useState3(true);
749
965
  const [draggingLibraryType, setDraggingLibraryType] = useState3(null);
750
966
  const canvasFrameRef = useRef4(null);
751
967
  const [canvasMutationVersion, setCanvasMutationVersion] = useState3(0);
968
+ const [canvasResetVersion, setCanvasResetVersion] = useState3(0);
752
969
  const [request, setRequest] = useState3("idle");
753
970
  const [notice, setNotice] = useState3(null);
971
+ const [draftRevision, setDraftRevision] = useState3(initialDraftRevision);
972
+ const [isOnline, setIsOnline] = useState3(() => typeof navigator === "undefined" || navigator.onLine);
973
+ const [saveState, setSaveState] = useState3("saved");
974
+ const [zoom, setZoom] = useState3("auto");
975
+ const [confirmBack, setConfirmBack] = useState3(false);
754
976
  const i18n = createAdminI18n(adminLocale);
755
977
  const engineData = useMemo2(() => toEngineData(editor.document, registry), [editor.document, registry]);
756
978
  const validationIssues = useMemo2(() => validateDocumentBlocks(editor.document, registry), [editor.document, registry]);
757
- const { confirmCanvasSelection, selectedBlockId, updateBlockProps } = editor;
979
+ const { confirmCanvasSelection, selectedBlockId, updateBlockProps, updateBlockPresentation } = editor;
758
980
  const updateFromCanvasInput = useCallback2((id, props, preserveCanvasValue = false) => {
759
981
  if (preserveCanvasValue) setCanvasMutationVersion((version) => version + 1);
760
982
  updateBlockProps(id, props);
761
983
  }, [updateBlockProps]);
762
984
  const config = useMemo2(() => createPageDocumentPuckConfig(confirmCanvasSelection, updateFromCanvasInput, selectedBlockId, registry), [confirmCanvasSelection, registry, selectedBlockId, updateFromCanvasInput]);
763
- if (editor.loadState !== "ready" && editor.loadState !== "success") return /* @__PURE__ */ jsx6(EditorStatus, { state: editor.loadState });
764
- const blockTypes = ["core.text", "core.image", ...registry?.blocks.map((block) => block.type) ?? []];
985
+ useEffect3(() => {
986
+ const online = () => setIsOnline(true);
987
+ const offline = () => setIsOnline(false);
988
+ window.addEventListener("online", online);
989
+ window.addEventListener("offline", offline);
990
+ return () => {
991
+ window.removeEventListener("online", online);
992
+ window.removeEventListener("offline", offline);
993
+ };
994
+ }, []);
995
+ const resolvedSaveState = !isOnline && editor.isDirty ? "offline" : saveState === "offline" && editor.isDirty ? "dirty" : saveState === "saved" && editor.isDirty ? "dirty" : saveState;
996
+ const canPersistDraft = Boolean(draftPersistence || onSave);
997
+ const save = useCallback2(async () => {
998
+ if (!canPersistDraft || request !== "idle" || validationIssues.length > 0) return;
999
+ if (!isOnline) {
1000
+ setSaveState("offline");
1001
+ return;
1002
+ }
1003
+ const document = editor.document;
1004
+ setRequest("saving");
1005
+ setSaveState("saving");
1006
+ setNotice(null);
1007
+ try {
1008
+ const result = draftPersistence ? await draftPersistence.saveDraft({ document, expectedRevision: draftRevision, session: editor.session }) : await onSave?.(document);
1009
+ if (result?.revision !== void 0) setDraftRevision(result.revision);
1010
+ editor.markSaved(document);
1011
+ setSaveState("saved");
1012
+ } catch {
1013
+ setNotice("saveFailed");
1014
+ setSaveState(typeof navigator !== "undefined" && !navigator.onLine ? "offline" : "failed");
1015
+ } finally {
1016
+ setRequest("idle");
1017
+ }
1018
+ }, [canPersistDraft, draftPersistence, draftRevision, editor, isOnline, onSave, request, validationIssues.length]);
1019
+ useEffect3(() => {
1020
+ if (!autoSave || !canPersistDraft || !editor.isDirty || resolvedSaveState !== "dirty" || !isOnline || request !== "idle" || validationIssues.length > 0 || editor.sessionState !== "active") return;
1021
+ const timer = window.setTimeout(() => {
1022
+ void save();
1023
+ }, autoSaveDelayMs);
1024
+ return () => window.clearTimeout(timer);
1025
+ }, [autoSave, autoSaveDelayMs, canPersistDraft, editor.isDirty, editor.sessionState, isOnline, request, resolvedSaveState, save, validationIssues.length]);
1026
+ const publish = async () => {
1027
+ if (!publishAction && !onPublish || request !== "idle" || validationIssues.length > 0 || !isOnline) return;
1028
+ const document = editor.document;
1029
+ setRequest("publishing");
1030
+ setNotice(null);
1031
+ try {
1032
+ if (publishAction) await publishAction.publish({ document, expectedRevision: draftRevision, session: editor.session, validationIssues });
1033
+ else await onPublish?.(document);
1034
+ editor.markSaved(document);
1035
+ setSaveState("saved");
1036
+ setNotice("published");
1037
+ } catch {
1038
+ setNotice("publishFailed");
1039
+ } finally {
1040
+ setRequest("idle");
1041
+ }
1042
+ };
1043
+ if (editor.loadState !== "ready" && editor.loadState !== "success") return /* @__PURE__ */ jsx7(EditorStatus, { state: editor.loadState });
1044
+ if (editor.sessionState !== "active") return /* @__PURE__ */ jsx7(EditorSessionStatus, { state: editor.sessionState, message: editor.sessionMessage });
1045
+ const allBlockTypes = ["core.text", "core.image", ...registry?.blocks.map((block) => block.type) ?? []];
1046
+ const blockTypes = availableBlockTypes ? allBlockTypes.filter((type) => availableBlockTypes.includes(type)) : allBlockTypes;
765
1047
  const addFromLibrary = (type, beforeId) => {
766
1048
  const id = editor.addBlock(type, beforeId);
767
1049
  if (id) editor.requestCanvasSelection(id);
@@ -797,120 +1079,147 @@ function PageDocumentEditor({ iframe = true, registry, adminLocale, onSave, onPu
797
1079
  event.stopPropagation();
798
1080
  setDraggingLibraryType(null);
799
1081
  };
800
- const save = async () => {
801
- if (!onSave || request !== "idle" || validationIssues.length > 0) return;
802
- setRequest("saving");
803
- setNotice(null);
804
- try {
805
- await onSave(editor.document);
806
- editor.markSaved();
807
- } catch {
808
- setNotice("saveFailed");
809
- } finally {
810
- setRequest("idle");
811
- }
1082
+ const saveBadgeTone = resolvedSaveState === "failed" ? "critical" : resolvedSaveState === "offline" || resolvedSaveState === "dirty" ? "attention" : "success";
1083
+ const saveBadgeLabel = resolvedSaveState === "saving" ? i18n.t("saving") : resolvedSaveState === "failed" ? i18n.t("saveFailed") : resolvedSaveState === "offline" ? i18n.t("offline") : resolvedSaveState === "dirty" ? i18n.t("unsaved") : i18n.t("saved");
1084
+ const saveButtonLabel = request === "saving" ? i18n.t("saving") : resolvedSaveState === "failed" ? i18n.t("retrySave") : i18n.t("save");
1085
+ const requestBack = () => {
1086
+ if (!onBack) return;
1087
+ if (editor.isDirty || request === "saving") setConfirmBack(true);
1088
+ else onBack();
812
1089
  };
813
- const publish = async () => {
814
- if (!onPublish || request !== "idle" || validationIssues.length > 0) return;
815
- setRequest("publishing");
816
- setNotice(null);
817
- try {
818
- await onPublish(editor.document);
819
- editor.markSaved();
820
- setNotice("published");
821
- } catch {
822
- setNotice("publishFailed");
823
- } finally {
824
- setRequest("idle");
825
- }
826
- };
827
- return /* @__PURE__ */ jsx6(Puck2, { config, data: engineData, iframe: { enabled: iframe }, onChange: (data) => editor.updateFromCanvas(fromEngineData(data, editor.document, registry)), children: /* @__PURE__ */ jsxs4(Puck2.Layout, { children: [
828
- /* @__PURE__ */ jsx6(CanvasSelectionBridge, { data: engineData, requestedBlockId: editor.canvasSelectionRequest, onCanvasSelected: confirmCanvasSelection, canvasMutationVersion }),
829
- /* @__PURE__ */ jsxs4("div", { className: "pb-shell pb-shell--v04", "data-testid": "page-document-editor", "data-page-id": editor.document.pageId, "data-dirty": editor.isDirty, "data-editor-state": editor.loadState, children: [
830
- /* @__PURE__ */ jsx6("header", { className: "pb-header", children: /* @__PURE__ */ jsxs4(InlineStack2, { align: "space-between", blockAlign: "center", gap: "300", wrap: false, children: [
831
- /* @__PURE__ */ jsxs4("div", { className: "pb-page-title", children: [
832
- /* @__PURE__ */ jsx6(Text2, { as: "h1", variant: "headingSm", children: editor.document.settings.seoTitle ?? editor.document.pageId }),
833
- /* @__PURE__ */ jsxs4(Text2, { as: "p", variant: "bodySm", tone: "subdued", children: [
834
- "PageDocument V",
835
- editor.document.schemaVersion,
836
- " \xB7 ",
837
- editor.document.target
1090
+ return /* @__PURE__ */ jsx7(Puck2, { config, data: engineData, iframe: { enabled: iframe }, onChange: (data) => {
1091
+ if (!editor.updateFromCanvas(fromEngineData(data, editor.document, registry))) setCanvasResetVersion((version) => version + 1);
1092
+ }, children: /* @__PURE__ */ jsxs5(Puck2.Layout, { children: [
1093
+ /* @__PURE__ */ jsx7(CanvasSelectionBridge, { data: engineData, requestedBlockId: editor.canvasSelectionRequest, onCanvasSelected: confirmCanvasSelection, canvasMutationVersion, canvasResetVersion }),
1094
+ /* @__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: [
1095
+ /* @__PURE__ */ jsx7("header", { className: "pb-header", children: /* @__PURE__ */ jsxs5("div", { className: "pb-header-content", children: [
1096
+ /* @__PURE__ */ jsxs5("div", { className: "pb-header-title-group", children: [
1097
+ onBack ? /* @__PURE__ */ jsx7(Button2, { variant: "tertiary", onClick: requestBack, children: i18n.t("back") }) : null,
1098
+ /* @__PURE__ */ jsxs5("div", { className: "pb-page-title", children: [
1099
+ /* @__PURE__ */ jsx7(Text3, { as: "h1", variant: "headingSm", children: editor.document.settings.seoTitle ?? editor.document.pageId }),
1100
+ /* @__PURE__ */ jsxs5(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: [
1101
+ "PageDocument V",
1102
+ editor.document.schemaVersion,
1103
+ " \xB7 ",
1104
+ editor.document.target
1105
+ ] }),
1106
+ pageStatus ? /* @__PURE__ */ jsx7(PageStatusCard, { status: pageStatus, sessionState: editor.sessionState }) : null
1107
+ ] }),
1108
+ /* @__PURE__ */ jsxs5("div", { className: "pb-header-sidebar-toggles", role: "group", "aria-label": i18n.t("toggleSidebars"), "data-testid": "sidebar-toggles", children: [
1109
+ /* @__PURE__ */ jsx7(IconButton2, { type: "button", title: i18n.t("toggleLeftSidebar"), onClick: () => setLeftRailOpen((open) => !open), children: /* @__PURE__ */ jsx7(PuckPanelIcon, { side: "left" }) }),
1110
+ /* @__PURE__ */ jsx7(IconButton2, { type: "button", title: i18n.t("toggleRightSidebar"), onClick: () => setRightPanelOpen((open) => !open), children: /* @__PURE__ */ jsx7(PuckPanelIcon, { side: "right" }) })
838
1111
  ] })
839
1112
  ] }),
840
- /* @__PURE__ */ jsxs4(InlineStack2, { gap: "150", blockAlign: "center", wrap: false, children: [
841
- /* @__PURE__ */ jsx6(Badge2, { tone: editor.isDirty ? "attention" : "success", children: editor.isDirty ? i18n.t("unsaved") : i18n.t("saved") }),
842
- /* @__PURE__ */ jsx6(Button2, { disabled: !onSave || !editor.isDirty || request !== "idle" || validationIssues.length > 0, onClick: () => void save(), children: request === "saving" ? i18n.t("saving") : i18n.t("save") }),
843
- /* @__PURE__ */ jsx6(Button2, { variant: "primary", disabled: !onPublish || request !== "idle" || validationIssues.length > 0, onClick: () => void publish(), children: request === "publishing" ? i18n.t("publishing") : i18n.t("publish") }),
844
- /* @__PURE__ */ jsx6(Button2, { accessibilityLabel: i18n.t("undo"), icon: UndoIcon2, variant: "tertiary", disabled: !editor.actionState.canUndo, onClick: editor.undo }),
845
- /* @__PURE__ */ jsx6(Button2, { accessibilityLabel: i18n.t("redo"), icon: RedoIcon2, variant: "tertiary", disabled: !editor.actionState.canRedo, onClick: editor.redo })
846
- ] })
1113
+ /* @__PURE__ */ jsxs5("div", { className: "pb-header-device-toolbar", children: [
1114
+ /* @__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)) }),
1115
+ /* @__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) }) })
1116
+ ] }),
1117
+ /* @__PURE__ */ jsx7("div", { className: "pb-header-actions", children: /* @__PURE__ */ jsxs5(InlineStack3, { gap: "150", blockAlign: "center", wrap: false, children: [
1118
+ /* @__PURE__ */ jsx7(Badge3, { tone: saveBadgeTone, children: saveBadgeLabel }),
1119
+ /* @__PURE__ */ jsx7(Button2, { disabled: !canPersistDraft || !editor.isDirty || request !== "idle" || validationIssues.length > 0 || !isOnline, onClick: () => void save(), children: saveButtonLabel }),
1120
+ onPreview ? /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle" || !isOnline, onClick: () => void onPreview({ document: editor.document, draftRevision, session: editor.session }), children: i18n.t("preview") }) : null,
1121
+ onAddToStore ? /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle" || pageStatus?.publicationStatus === "unpublished", onClick: () => void onAddToStore({ document: editor.document, session: editor.session }), children: i18n.t("addToStore") }) : null,
1122
+ /* @__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") }),
1123
+ /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("undo"), icon: UndoIcon2, variant: "tertiary", disabled: !editor.actionState.canUndo, onClick: editor.undo }),
1124
+ /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("redo"), icon: RedoIcon2, variant: "tertiary", disabled: !editor.actionState.canRedo, onClick: editor.redo })
1125
+ ] }) })
847
1126
  ] }) }),
848
- editor.loadState === "success" ? /* @__PURE__ */ jsx6(Banner, { tone: "success", children: i18n.t("success") }) : null,
849
- notice ? /* @__PURE__ */ jsx6(Banner, { tone: notice === "published" ? "success" : "critical", children: i18n.t(notice) }) : null,
850
- validationIssues.length > 0 ? /* @__PURE__ */ jsx6(Banner, { tone: "critical", title: "\u533A\u5757\u5C5E\u6027\u672A\u901A\u8FC7\u6821\u9A8C", children: /* @__PURE__ */ jsx6("ul", { children: validationIssues.map((issue) => /* @__PURE__ */ jsxs4("li", { children: [
1127
+ editor.loadState === "success" ? /* @__PURE__ */ jsx7(Banner, { tone: "success", children: i18n.t("success") }) : null,
1128
+ notice ? /* @__PURE__ */ jsx7(Banner, { tone: notice === "published" ? "success" : "critical", children: i18n.t(notice) }) : null,
1129
+ 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: [
851
1130
  issue.path,
852
1131
  ": ",
853
1132
  issue.message
854
1133
  ] }, `${issue.path}-${issue.message}`)) }) }) : null,
855
- /* @__PURE__ */ jsxs4("div", { className: "pb-workspace pb-workspace--document", children: [
856
- /* @__PURE__ */ jsxs4("nav", { className: "pb-tool-rail", "aria-label": "\u7F16\u8F91\u5668\u5DE5\u5177", children: [
857
- /* @__PURE__ */ jsx6(Button2, { accessibilityLabel: i18n.t("blocks"), icon: LayoutSectionIcon2, pressed: blockView === "blocks", variant: "tertiary", onClick: () => setBlockView("blocks") }),
858
- /* @__PURE__ */ jsx6(Button2, { accessibilityLabel: i18n.t("outline"), icon: MenuIcon2, pressed: blockView === "outline", variant: "tertiary", onClick: () => setBlockView("outline") })
1134
+ /* @__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: [
1135
+ /* @__PURE__ */ jsxs5("nav", { className: "pb-tool-rail", "aria-label": "\u7F16\u8F91\u5668\u5DE5\u5177", children: [
1136
+ /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("blocks"), icon: LayoutSectionIcon2, pressed: blockView === "blocks" && leftRailOpen, variant: "tertiary", onClick: () => {
1137
+ setBlockView("blocks");
1138
+ setLeftRailOpen(true);
1139
+ } }),
1140
+ /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("outline"), icon: MenuIcon2, pressed: blockView === "outline" && leftRailOpen, variant: "tertiary", onClick: () => {
1141
+ setBlockView("outline");
1142
+ setLeftRailOpen(true);
1143
+ } })
859
1144
  ] }),
860
- /* @__PURE__ */ jsxs4("aside", { className: "pb-left-panel", "aria-label": "PageDocument \u533A\u5757", children: [
861
- /* @__PURE__ */ jsx6(InlineStack2, { align: "space-between", blockAlign: "center", children: /* @__PURE__ */ jsx6(Text2, { as: "h2", variant: "headingSm", children: blockView === "blocks" ? i18n.t("blocks") : i18n.t("outline") }) }),
862
- blockView === "blocks" ? /* @__PURE__ */ jsx6("div", { className: "pb-block-list", "data-testid": "blocks-view", "aria-label": "\u533A\u5757\u7C7B\u578B\u5E93", role: "list", onDrop: cancelLibraryDrop, children: blockTypes.map((type) => /* @__PURE__ */ jsxs4("div", { className: `pb-document-block-row pb-document-block-row--library ${editor.selectedBlock?.type === type ? "pb-document-block-row--selected" : ""}`, "data-block-type": type, "data-selected": editor.selectedBlock?.type === type, role: "listitem", draggable: editor.actionState.canAdd, "aria-label": `${blockTypeLabel(type, registry)}\uFF0C\u62D6\u62FD\u81F3\u753B\u5E03\u4EE5\u6DFB\u52A0${editor.selectedBlock?.type === type ? "\uFF0C\u5F53\u524D\u9009\u4E2D\u7C7B\u578B" : ""}`, onDragStart: (event) => {
1145
+ /* @__PURE__ */ jsxs5("aside", { className: `pb-left-panel${leftRailOpen ? "" : " pb-panel--closed"}`, "aria-label": "PageDocument \u533A\u5757", children: [
1146
+ /* @__PURE__ */ jsxs5(InlineStack3, { align: "space-between", blockAlign: "center", children: [
1147
+ /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingSm", children: blockView === "blocks" ? i18n.t("blocks") : i18n.t("outline") }),
1148
+ /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("collapseLeft"), icon: XIcon2, variant: "tertiary", onClick: () => setLeftRailOpen(false) })
1149
+ ] }),
1150
+ 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) => {
1151
+ if (!editor.canAddBlock(type)) {
1152
+ event.preventDefault();
1153
+ return;
1154
+ }
863
1155
  event.dataTransfer.setData("application/x-page-document-block", type);
864
1156
  event.dataTransfer.effectAllowed = "copy";
865
1157
  setDraggingLibraryType(type);
866
1158
  }, onDragEnd: () => setDraggingLibraryType(null), children: [
867
- /* @__PURE__ */ jsxs4("span", { className: "pb-library-block-title", children: [
868
- /* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockTypeLabel(type, registry) }),
869
- /* @__PURE__ */ jsx6("span", { className: "pb-library-block-drag-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(DragHandleIcon2, {}) })
1159
+ /* @__PURE__ */ jsxs5("span", { className: "pb-library-block-title", children: [
1160
+ /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockTypeLabel(type, registry) }),
1161
+ /* @__PURE__ */ jsx7("span", { className: "pb-library-block-drag-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsx7(DragHandleIcon2, {}) })
870
1162
  ] }),
871
- /* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: type })
872
- ] }, type)) }) : editor.document.blocks.length === 0 ? /* @__PURE__ */ jsx6(Text2, { as: "p", tone: "subdued", children: i18n.t("empty") }) : /* @__PURE__ */ jsx6("div", { className: "pb-block-list", "data-testid": "outline-view", children: editor.document.blocks.map((block) => /* @__PURE__ */ jsxs4("button", { type: "button", className: `pb-document-block-row pb-document-block-row--library ${block.id === editor.selectedBlockId ? "pb-document-block-row--selected" : ""}`, "aria-pressed": block.id === editor.selectedBlockId, onClick: () => editor.requestCanvasSelection(block.id), children: [
873
- /* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockLabel(block, registry) }),
874
- /* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: block.id })
1163
+ /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", tone: "subdued", children: type })
1164
+ ] }, 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: [
1165
+ /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockLabel(block, registry) }),
1166
+ /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", tone: "subdued", children: block.id })
875
1167
  ] }, block.id)) })
876
1168
  ] }),
877
- /* @__PURE__ */ jsxs4("main", { className: "pb-canvas-area", children: [
878
- /* @__PURE__ */ jsx6("div", { className: "pb-canvas-toolbar", children: /* @__PURE__ */ jsx6(ButtonGroup2, { variant: "segmented", children: Object.keys(deviceLabels2).map((device) => /* @__PURE__ */ jsx6(Button2, { pressed: editor.device === device, onClick: () => editor.setDevice(device), children: i18n.t(deviceLabels2[device]) }, device)) }) }),
879
- /* @__PURE__ */ jsxs4("div", { className: "pb-canvas-stage", children: [
880
- /* @__PURE__ */ jsx6("div", { ref: canvasFrameRef, className: `pb-canvas-frame pb-canvas-frame--${editor.device}`, "data-device": editor.device, children: /* @__PURE__ */ jsx6(Puck2.Preview, {}) }),
881
- draggingLibraryType ? /* @__PURE__ */ jsxs4("div", { className: "pb-canvas-drop-target", "data-testid": "canvas-drop-target", role: "region", "aria-label": "\u533A\u5757\u6295\u653E\u533A", onDragOver: (event) => event.preventDefault(), onDrop: dropFromLibrary, children: [
1169
+ /* @__PURE__ */ jsxs5("main", { className: "pb-canvas-area", children: [
1170
+ /* @__PURE__ */ jsxs5("div", { className: "pb-canvas-stage", children: [
1171
+ /* @__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, children: /* @__PURE__ */ jsx7(Puck2.Preview, {}) }),
1172
+ 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: [
882
1173
  "\u677E\u5F00\u4EE5\u6DFB\u52A0 ",
883
1174
  blockTypeLabel(draggingLibraryType, registry)
884
1175
  ] }) : null,
885
- editor.selectedBlock ? /* @__PURE__ */ jsxs4("div", { className: "pb-canvas-overlay", "aria-label": `\u5DF2\u9009\u62E9 ${blockLabel(editor.selectedBlock, registry)}`, children: [
886
- /* @__PURE__ */ jsx6("span", { children: blockLabel(editor.selectedBlock, registry) }),
887
- /* @__PURE__ */ jsx6("span", { children: "Selected" })
1176
+ editor.selectedBlock ? /* @__PURE__ */ jsxs5("div", { className: "pb-canvas-overlay", "aria-label": `\u5DF2\u9009\u62E9 ${blockLabel(editor.selectedBlock, registry)}`, children: [
1177
+ /* @__PURE__ */ jsx7("span", { children: blockLabel(editor.selectedBlock, registry) }),
1178
+ /* @__PURE__ */ jsx7("span", { children: "Selected" })
888
1179
  ] }) : null
889
- ] })
1180
+ ] }),
1181
+ !leftRailOpen || !rightPanelOpen ? /* @__PURE__ */ jsxs5("div", { className: "pb-collapsed-actions", children: [
1182
+ !leftRailOpen ? /* @__PURE__ */ jsx7(Button2, { onClick: () => setLeftRailOpen(true), children: i18n.t("expandLeft") }) : null,
1183
+ !rightPanelOpen ? /* @__PURE__ */ jsx7(Button2, { onClick: () => setRightPanelOpen(true), children: i18n.t("expandRight") }) : null
1184
+ ] }) : null
890
1185
  ] }),
891
- /* @__PURE__ */ jsxs4("aside", { className: "pb-right-panel", "aria-label": "PageDocument \u5C5E\u6027", children: [
892
- /* @__PURE__ */ jsx6(Text2, { as: "h2", variant: "headingSm", children: i18n.t("properties") }),
893
- editor.selectedBlock ? /* @__PURE__ */ jsx6(DocumentInspector, { block: editor.selectedBlock, registry, disabled: !editor.actionState.canEdit, onChange: (props) => editor.updateBlockProps(editor.selectedBlock.id, props) }) : /* @__PURE__ */ jsx6(Text2, { as: "p", tone: "subdued", children: i18n.t("selectBlock") })
1186
+ /* @__PURE__ */ jsxs5("aside", { className: `pb-right-panel${rightPanelOpen ? "" : " pb-panel--closed"}`, "aria-label": "PageDocument \u5C5E\u6027", children: [
1187
+ /* @__PURE__ */ jsxs5(InlineStack3, { align: "space-between", blockAlign: "center", children: [
1188
+ /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingSm", children: i18n.t("properties") }),
1189
+ /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("collapseRight"), icon: XIcon2, variant: "tertiary", onClick: () => setRightPanelOpen(false) })
1190
+ ] }),
1191
+ editor.selectedBlock ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1192
+ /* @__PURE__ */ jsx7(DocumentInspector, { pageId: editor.document.pageId, assetPicker, block: editor.selectedBlock, registry, disabled: !editor.actionState.canEdit, appearanceControls, onChange: (props) => editor.updateBlockProps(editor.selectedBlock.id, props), onPresentationChange: (presentation) => updateBlockPresentation(editor.selectedBlock.id, presentation) }),
1193
+ /* @__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) })
1194
+ ] }) : /* @__PURE__ */ jsx7(Text3, { as: "p", tone: "subdued", children: i18n.t("selectBlock") })
894
1195
  ] })
895
- ] })
1196
+ ] }),
1197
+ editor.pendingDeleteBlock ? /* @__PURE__ */ jsx7(DeleteConfirmation, { block: editor.pendingDeleteBlock, config: deleteConfirmation, i18n, onCancel: editor.cancelDeleteBlock, onConfirm: editor.confirmDeleteBlock }) : null,
1198
+ confirmBack ? /* @__PURE__ */ jsx7(LeaveConfirmation, { i18n, onCancel: () => setConfirmBack(false), onConfirm: () => {
1199
+ setConfirmBack(false);
1200
+ onBack?.();
1201
+ } }) : null
896
1202
  ] })
897
1203
  ] }) });
898
1204
  }
899
- function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canvasMutationVersion }) {
1205
+ function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canvasMutationVersion, canvasResetVersion }) {
900
1206
  const puck = usePuck();
901
1207
  const lastSelectedId = useRef4(null);
902
1208
  const lastSyncedData = useRef4(null);
903
1209
  const lastCanvasMutationVersion = useRef4(0);
1210
+ const lastCanvasResetVersion = useRef4(0);
904
1211
  const serializedData = JSON.stringify(data);
905
1212
  useEffect3(() => {
906
- if (lastSyncedData.current === serializedData) return;
1213
+ const forceReset = canvasResetVersion > lastCanvasResetVersion.current;
1214
+ if (lastSyncedData.current === serializedData && !forceReset) return;
907
1215
  lastSyncedData.current = serializedData;
1216
+ if (forceReset) lastCanvasResetVersion.current = canvasResetVersion;
908
1217
  if (canvasMutationVersion > lastCanvasMutationVersion.current) {
909
1218
  lastCanvasMutationVersion.current = canvasMutationVersion;
910
1219
  return;
911
1220
  }
912
1221
  puck.dispatch({ type: "setData", data });
913
- }, [canvasMutationVersion, data, puck, serializedData]);
1222
+ }, [canvasMutationVersion, canvasResetVersion, data, puck, serializedData]);
914
1223
  useEffect3(() => {
915
1224
  if (!requestedBlockId) return;
916
1225
  const selector = puck.getSelectorForId(requestedBlockId);
@@ -929,39 +1238,79 @@ function EditorStatus({ state }) {
929
1238
  const i18n = createAdminI18n();
930
1239
  const tone = state === "error" ? "critical" : state === "disabled" ? "warning" : "info";
931
1240
  const message = state === "loading" ? i18n.t("loading") : state === "empty" ? i18n.t("empty") : state === "error" ? i18n.t("error") : i18n.t("disabled");
932
- return /* @__PURE__ */ jsx6("div", { className: "pb-editor-status", "data-testid": "page-document-editor-state", "data-editor-state": state, children: /* @__PURE__ */ jsx6(Banner, { tone, title: message, children: state === "disabled" ? i18n.t("disabled") : message }) });
1241
+ 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 }) });
1242
+ }
1243
+ function EditorSessionStatus({ state, message }) {
1244
+ const i18n = createAdminI18n();
1245
+ const tone = state === "locked" ? "warning" : state === "readonly" ? "info" : "critical";
1246
+ const title = state === "acquiring" ? i18n.t("acquiringLock") : state === "locked" ? i18n.t("locked") : state === "readonly" ? i18n.t("readonly") : i18n.t("lockLost");
1247
+ 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 }) });
933
1248
  }
934
1249
  function InspectorSection({ title, children, defaultOpen = true }) {
935
- return /* @__PURE__ */ jsxs4("details", { className: "pb-inspector-section", open: defaultOpen, children: [
936
- /* @__PURE__ */ jsxs4("summary", { children: [
937
- /* @__PURE__ */ jsx6("span", { children: title }),
938
- /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", children: "\u2304" })
1250
+ return /* @__PURE__ */ jsxs5("details", { className: "pb-inspector-section", open: defaultOpen, children: [
1251
+ /* @__PURE__ */ jsxs5("summary", { children: [
1252
+ /* @__PURE__ */ jsx7("span", { children: title }),
1253
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\u2304" })
939
1254
  ] }),
940
- /* @__PURE__ */ jsx6("div", { className: "pb-inspector-section__body", children })
1255
+ /* @__PURE__ */ jsx7("div", { className: "pb-inspector-section__body", children })
941
1256
  ] });
942
1257
  }
943
1258
  function InspectorTextControl({ label, value, control, disabled, onChange }) {
944
1259
  const stringValue = typeof value === "string" ? value : "";
945
- return /* @__PURE__ */ jsx6(TextField, { label, labelHidden: true, value: stringValue, onChange, autoComplete: "off", disabled, multiline: control === "textarea" ? 4 : false, type: control === "url" ? "url" : "text" });
1260
+ 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) });
1261
+ return /* @__PURE__ */ jsx7(TextField, { label, labelHidden: true, value: stringValue, onChange, autoComplete: "off", disabled, multiline: control === "textarea" ? 4 : false, type: control === "url" ? "url" : "text" });
946
1262
  }
947
1263
  function InspectorField({ name, field, value, registry, disabled, onChange }) {
948
1264
  const label = field.label ?? name;
1265
+ const issues = validateFieldValue(field, value);
949
1266
  const Field = registry?.getField(field.field)?.component;
950
- return /* @__PURE__ */ jsxs4("div", { className: "pb-inspector-field", "data-control": field.control ?? "custom", children: [
951
- /* @__PURE__ */ jsxs4("div", { className: "pb-inspector-field__heading", children: [
952
- /* @__PURE__ */ jsx6(Text2, { as: "p", variant: "bodySm", fontWeight: "semibold", children: label }),
953
- field.description ? /* @__PURE__ */ jsx6(Text2, { as: "p", variant: "bodySm", tone: "subdued", children: field.description }) : null
1267
+ return /* @__PURE__ */ jsxs5("div", { className: "pb-inspector-field", "data-control": field.control ?? "custom", children: [
1268
+ /* @__PURE__ */ jsxs5("div", { className: "pb-inspector-field__heading", children: [
1269
+ /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", fontWeight: "semibold", children: label }),
1270
+ field.description ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: field.description }) : null
954
1271
  ] }),
955
- field.control ? /* @__PURE__ */ jsx6(InspectorTextControl, { label, value, control: field.control, disabled, onChange: (next) => onChange(next) }) : Field ? /* @__PURE__ */ jsx6(Field, { value, onChange }) : null
1272
+ field.control ? /* @__PURE__ */ jsx7(InspectorTextControl, { label, value, control: field.control, disabled, onChange: (next) => onChange(next) }) : Field ? /* @__PURE__ */ jsx7(Field, { value, onChange }) : null,
1273
+ issues.map((issue) => /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: issue.message }, issue.message))
956
1274
  ] });
957
1275
  }
1276
+ function InspectorActions({ block, canDuplicate, canDelete, canMove, canMoveUp, canMoveDown, i18n, onDuplicate, onDelete, onMove }) {
1277
+ return /* @__PURE__ */ jsx7("div", { className: "pb-inspector-actions", "aria-label": `Actions for ${block.id}`, children: /* @__PURE__ */ jsxs5(ButtonGroup2, { children: [
1278
+ /* @__PURE__ */ jsx7(Button2, { disabled: !canMove || !canMoveUp, onClick: () => onMove(-1), children: i18n.t("moveUp") }),
1279
+ /* @__PURE__ */ jsx7(Button2, { disabled: !canMove || !canMoveDown, onClick: () => onMove(1), children: i18n.t("moveDown") }),
1280
+ /* @__PURE__ */ jsx7(Button2, { disabled: !canDuplicate, onClick: onDuplicate, children: i18n.t("duplicate") }),
1281
+ /* @__PURE__ */ jsx7(Button2, { disabled: !canDelete, tone: "critical", onClick: onDelete, children: i18n.t("remove") })
1282
+ ] }) });
1283
+ }
1284
+ function DeleteConfirmation({ block, config, i18n, onCancel, onConfirm }) {
1285
+ 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: [
1286
+ /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingMd", id: "pb-delete-confirmation-title", children: config?.title ?? i18n.t("confirmDeleteTitle") }),
1287
+ /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodyMd", children: config?.message?.(block) ?? i18n.t("confirmDeleteMessage") }),
1288
+ /* @__PURE__ */ jsxs5(ButtonGroup2, { children: [
1289
+ /* @__PURE__ */ jsx7(Button2, { onClick: onCancel, children: config?.cancelLabel ?? i18n.t("cancel") }),
1290
+ /* @__PURE__ */ jsx7(Button2, { tone: "critical", onClick: onConfirm, children: config?.confirmLabel ?? i18n.t("confirm") })
1291
+ ] })
1292
+ ] }) });
1293
+ }
1294
+ function LeaveConfirmation({ i18n, onCancel, onConfirm }) {
1295
+ 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: [
1296
+ /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingMd", id: "pb-leave-confirmation-title", children: i18n.t("confirmLeaveTitle") }),
1297
+ /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodyMd", children: i18n.t("leaveWarning") }),
1298
+ /* @__PURE__ */ jsxs5(ButtonGroup2, { children: [
1299
+ /* @__PURE__ */ jsx7(Button2, { onClick: onCancel, children: i18n.t("cancel") }),
1300
+ /* @__PURE__ */ jsx7(Button2, { tone: "critical", onClick: onConfirm, children: i18n.t("leave") })
1301
+ ] })
1302
+ ] }) });
1303
+ }
958
1304
  function inspectorFieldConfig(name, field) {
959
1305
  const key = name.toLowerCase();
960
1306
  const group = /(?:href|url)/.test(key) ? "Links" : /(?:default|shipment|query|hide)/.test(key) ? "Tracking settings" : /(?:id|variant|theme)/.test(key) ? "Advanced" : "Content";
961
1307
  const description = field.description ?? (field.control === "textarea" ? "\u9002\u5408\u8F83\u957F\u6216\u591A\u884C\u7684\u5C55\u793A\u6587\u6848\u3002" : field.control === "url" ? "\u4F7F\u7528\u7AD9\u5185\u76F8\u5BF9\u8DEF\u5F84\u6216 HTTPS \u5730\u5740\u3002" : key.includes("shipment") ? "\u591A\u4E2A\u5305\u88F9\u6807\u7B7E\u4F7F\u7528 | \u5206\u9694\u3002" : key.includes("default") ? "\u4EC5\u7528\u4E8E\u7F16\u8F91\u5668\u548C\u7A7A\u72B6\u6001\u9884\u89C8\u3002" : void 0);
962
1308
  return { ...field, group: field.group ?? group, description };
963
1309
  }
964
- function DocumentInspector({ block, registry, disabled, onChange }) {
1310
+ var appearanceTokens = [["color.primary", "\u4E3B\u8272"], ["color.surface", "\u8868\u9762\u8272"], ["radius", "\u5706\u89D2"], ["spacing", "\u95F4\u8DDD"]];
1311
+ function DocumentInspector({ pageId, assetPicker, block, registry, disabled, appearanceControls, onChange, onPresentationChange }) {
1312
+ const [assetError, setAssetError] = useState3(null);
1313
+ const [selectingAsset, setSelectingAsset] = useState3(false);
965
1314
  const definition = registry?.getBlock(block.type);
966
1315
  const groupedFields = definition ? Object.entries(definition.fields).reduce((groups, entry) => {
967
1316
  const [name, field] = entry;
@@ -970,21 +1319,42 @@ function DocumentInspector({ block, registry, disabled, onChange }) {
970
1319
  (groups[group] ??= []).push([name, configuredField]);
971
1320
  return groups;
972
1321
  }, {}) : {};
973
- return /* @__PURE__ */ jsxs4("div", { className: "pb-inspector", "data-testid": "document-inspector", children: [
974
- /* @__PURE__ */ jsxs4("header", { className: "pb-inspector__header", children: [
975
- /* @__PURE__ */ jsx6(Badge2, { children: block.type }),
976
- /* @__PURE__ */ jsxs4("div", { children: [
977
- /* @__PURE__ */ jsx6(Text2, { as: "p", variant: "headingSm", children: blockLabel(block, registry) }),
978
- /* @__PURE__ */ jsx6(Text2, { as: "p", variant: "bodySm", tone: "subdued", children: definition?.category ?? "Core block" })
1322
+ return /* @__PURE__ */ jsxs5("div", { className: "pb-inspector", "data-testid": "document-inspector", children: [
1323
+ /* @__PURE__ */ jsxs5("header", { className: "pb-inspector__header", children: [
1324
+ /* @__PURE__ */ jsx7(Badge3, { children: block.type }),
1325
+ /* @__PURE__ */ jsxs5("div", { children: [
1326
+ /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "headingSm", children: blockLabel(block, registry) }),
1327
+ /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: definition?.category ?? "Core block" })
979
1328
  ] })
980
1329
  ] }),
981
- block.type === "core.text" ? /* @__PURE__ */ jsx6(InspectorSection, { title: "Content", children: /* @__PURE__ */ jsx6(InspectorField, { name: "content", field: { field: "", label: "\u6587\u672C\u5185\u5BB9", control: "textarea", description: "\u652F\u6301\u8F83\u957F\u7684\u6B63\u6587\u5185\u5BB9\u3002" }, value: block.props.content, registry, disabled, onChange: (content) => onChange({ content }) }) }) : null,
982
- block.type === "core.image" ? /* @__PURE__ */ jsxs4(InspectorSection, { title: "Image", children: [
983
- /* @__PURE__ */ jsx6(InspectorField, { name: "src", field: { field: "", label: "\u56FE\u7247 URL", control: "url", description: "\u4F7F\u7528 HTTPS \u56FE\u7247\u5730\u5740\u3002" }, value: block.props.src, registry, disabled, onChange: (src) => onChange({ src }) }),
984
- /* @__PURE__ */ jsx6(InspectorField, { name: "alt", field: { field: "", label: "\u66FF\u4EE3\u6587\u672C", control: "text", description: "\u7528\u4E8E\u65E0\u969C\u788D\u9605\u8BFB\u548C\u56FE\u7247\u52A0\u8F7D\u5931\u8D25\u573A\u666F\u3002" }, value: block.props.alt, registry, disabled, onChange: (alt) => onChange({ alt }) })
1330
+ 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, onChange: (content) => onChange({ content }) }) }) : null,
1331
+ block.type === "core.image" ? /* @__PURE__ */ jsxs5(InspectorSection, { title: "Image", children: [
1332
+ /* @__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, onChange: (src) => onChange({ src }) }),
1333
+ /* @__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, onChange: (alt) => onChange({ alt }) }),
1334
+ assetPicker ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1335
+ /* @__PURE__ */ jsx7(Button2, { disabled: disabled || selectingAsset, onClick: () => void (async () => {
1336
+ setSelectingAsset(true);
1337
+ setAssetError(null);
1338
+ try {
1339
+ const asset = await assetPicker.selectAsset({ pageId, blockId: block.id, current: { id: typeof block.props.assetId === "string" ? block.props.assetId : void 0, url: typeof block.props.src === "string" ? block.props.src : void 0, alt: typeof block.props.alt === "string" ? block.props.alt : void 0 } });
1340
+ if (asset) onChange({ ...block.props, assetId: asset.id, src: asset.url, alt: asset.alt ?? (typeof block.props.alt === "string" ? block.props.alt : "") });
1341
+ } catch {
1342
+ setAssetError("\u65E0\u6CD5\u9009\u62E9\u7D20\u6750\uFF0C\u8BF7\u91CD\u8BD5\u3002");
1343
+ } finally {
1344
+ setSelectingAsset(false);
1345
+ }
1346
+ })(), children: selectingAsset ? "\u6B63\u5728\u9009\u62E9\u7D20\u6750\u2026" : "\u9009\u62E9\u7D20\u6750" }),
1347
+ assetError ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: assetError }) : null
1348
+ ] }) : null
985
1349
  ] }) : null,
986
- Object.entries(groupedFields).map(([group, fields]) => /* @__PURE__ */ jsx6(InspectorSection, { title: group, defaultOpen: group !== "Advanced", children: fields.map(([name, field]) => /* @__PURE__ */ jsx6(InspectorField, { name, field, value: block.props[name], registry, disabled, onChange: (value) => onChange({ ...block.props, [name]: value }) }, name)) }, group)),
987
- definition ? /* @__PURE__ */ jsx6("p", { className: "pb-inspector__hint", children: "\u753B\u5E03\u4E2D\u5E26\u865A\u7EBF\u8FB9\u6846\u7684\u5185\u5BB9\u53EF\u76F4\u63A5\u7F16\u8F91\u3002" }) : null
1350
+ 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: block.props[name], registry, disabled, onChange: (value) => onChange({ ...block.props, [name]: value }) }, name)) }, group)),
1351
+ 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,
1352
+ 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) => {
1353
+ const style = { ...block.style };
1354
+ if (value.trim()) style[token] = value;
1355
+ else delete style[token];
1356
+ onPresentationChange({ variant: block.variant, style });
1357
+ } }, token)) }) : null
988
1358
  ] });
989
1359
  }
990
1360
  export {
@@ -993,6 +1363,7 @@ export {
993
1363
  ExtensionRegistry,
994
1364
  ExtensionRegistryError,
995
1365
  PageDocumentEditorShell,
1366
+ PageStatusCard,
996
1367
  TemplateRegistry,
997
1368
  WebRenderer,
998
1369
  createAdminI18n,
@@ -1000,12 +1371,18 @@ export {
1000
1371
  createPageDocument,
1001
1372
  createTemplateRegistry,
1002
1373
  fromEngineData,
1374
+ maximumBlockInstances,
1375
+ mergeBlockPolicies,
1003
1376
  mergeThemeTokens,
1004
1377
  migratePageDocument,
1378
+ minimumBlockInstances,
1005
1379
  normalizeThemeTokens,
1006
1380
  systemThemeTokens,
1007
1381
  toEngineData,
1008
1382
  toThemeStyle,
1009
1383
  useEditorContext,
1010
- validatePageDocument
1384
+ validateBlockPolicy,
1385
+ validateFieldValue,
1386
+ validatePageDocument,
1387
+ validatePageDocumentWithRegistry
1011
1388
  };