@standhigher/puck-page-builder 0.9.0 → 0.10.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";
@@ -342,7 +348,7 @@ function PropertyPanel({
342
348
 
343
349
  // src/editor/shell/PageDocumentEditorShell.tsx
344
350
  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";
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";
346
352
  import { DragHandleIcon as DragHandleIcon2, LayoutSectionIcon as LayoutSectionIcon2, MenuIcon as MenuIcon2, RedoIcon as RedoIcon2, UndoIcon as UndoIcon2 } 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
 
@@ -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,8 +790,21 @@ 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",
@@ -680,6 +820,10 @@ var messages = {
680
820
  redo: "Redo",
681
821
  duplicate: "Duplicate",
682
822
  remove: "Delete",
823
+ confirmDeleteTitle: "Delete this block?",
824
+ confirmDeleteMessage: "You can restore it with Undo after deleting.",
825
+ cancel: "Cancel",
826
+ confirm: "Delete block",
683
827
  moveUp: "Move up",
684
828
  moveDown: "Move down",
685
829
  loading: "Loading editor\u2026",
@@ -694,8 +838,21 @@ var messages = {
694
838
  publish: "Publish",
695
839
  publishing: "Publishing\u2026",
696
840
  saveFailed: "Could not save draft",
841
+ retrySave: "Retry save",
842
+ offline: "Offline \u2014 changes will save when connection returns",
697
843
  publishFailed: "Could not publish",
698
844
  published: "Published",
845
+ back: "Back",
846
+ confirmLeaveTitle: "Leave the editor?",
847
+ leave: "Leave",
848
+ zoom: "Zoom",
849
+ zoomAuto: "Auto zoom",
850
+ preview: "Preview",
851
+ addToStore: "Add to store",
852
+ acquiringLock: "Acquiring edit lock\u2026",
853
+ locked: "This page is being edited",
854
+ lockLost: "The edit lock has expired",
855
+ readonly: "This page is read-only",
699
856
  leaveWarning: "You have unsaved changes. Are you sure you want to leave?",
700
857
  selectBlock: "Select a block to edit it.",
701
858
  desktop: "Desktop",
@@ -718,7 +875,7 @@ function blockIdAtRelativeY(ids, relativeY) {
718
875
  }
719
876
 
720
877
  // src/editor/shell/PageDocumentEditorShell.tsx
721
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
878
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
722
879
  var deviceLabels2 = { desktop: "desktop", tablet: "tablet", mobile: "mobile" };
723
880
  function blockLabel(block, registry) {
724
881
  return blockTypeLabel(block.type, registry);
@@ -729,39 +886,142 @@ function blockTypeLabel(type, registry) {
729
886
  return registry?.getBlock(type)?.label ?? type;
730
887
  }
731
888
  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
- });
889
+ return registry ? validatePageDocumentWithRegistry(document, registry) : [];
890
+ }
891
+ function useEditorSession(adapter, pageId) {
892
+ const [managed, setManaged] = useState3(() => adapter ? { state: "acquiring" } : { state: "active" });
893
+ useEffect3(() => {
894
+ let cancelled = false;
895
+ let session;
896
+ let heartbeatTimer;
897
+ const stopHeartbeat = () => {
898
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
899
+ heartbeatTimer = void 0;
900
+ };
901
+ if (!adapter) return;
902
+ void adapter.acquire({ pageId }).then((result) => {
903
+ if (cancelled) return;
904
+ if (result.state !== "active") {
905
+ setManaged({ state: result.state, message: result.message ?? (result.state === "locked" && result.editorName ? `${result.editorName} \u6B63\u5728\u7F16\u8F91` : void 0) });
906
+ return;
907
+ }
908
+ session = result.session;
909
+ setManaged({ state: "active", session });
910
+ if (!adapter.heartbeat) return;
911
+ const heartbeat = () => {
912
+ if (typeof navigator !== "undefined" && !navigator.onLine) return;
913
+ void adapter.heartbeat?.({ pageId, session: result.session }).catch(() => {
914
+ if (!cancelled) {
915
+ stopHeartbeat();
916
+ 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" });
917
+ }
918
+ });
919
+ };
920
+ heartbeatTimer = setInterval(heartbeat, adapter.heartbeatIntervalMs ?? 3e4);
921
+ }).catch(() => {
922
+ if (!cancelled) setManaged({ state: "lost", message: "\u65E0\u6CD5\u83B7\u53D6\u7F16\u8F91\u9501\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002" });
923
+ });
924
+ return () => {
925
+ cancelled = true;
926
+ stopHeartbeat();
927
+ if (session) void adapter.release?.({ pageId, session });
928
+ };
929
+ }, [adapter, pageId]);
930
+ return managed;
742
931
  }
743
932
  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 }) });
933
+ const { sessionAdapter, initialDocument, onSessionStateChange } = props;
934
+ const managedSession = useEditorSession(sessionAdapter, initialDocument.pageId);
935
+ useEffect3(() => {
936
+ onSessionStateChange?.(managedSession.state);
937
+ }, [managedSession.state, onSessionStateChange]);
938
+ 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
939
  }
746
- function PageDocumentEditor({ iframe = true, registry, adminLocale, onSave, onPublish }) {
940
+ function PageDocumentEditor({ iframe = true, registry, adminLocale, onSave, onPublish, draftPersistence, draftRevision: initialDraftRevision, autoSave = true, autoSaveDelayMs = 800, publishAction, assetPicker, pageStatus, onBack, onPreview, onAddToStore, deleteConfirmation, availableBlockTypes, appearanceControls = false }) {
747
941
  const editor = useEditorContext();
748
942
  const [blockView, setBlockView] = useState3("blocks");
749
943
  const [draggingLibraryType, setDraggingLibraryType] = useState3(null);
750
944
  const canvasFrameRef = useRef4(null);
751
945
  const [canvasMutationVersion, setCanvasMutationVersion] = useState3(0);
946
+ const [canvasResetVersion, setCanvasResetVersion] = useState3(0);
752
947
  const [request, setRequest] = useState3("idle");
753
948
  const [notice, setNotice] = useState3(null);
949
+ const [draftRevision, setDraftRevision] = useState3(initialDraftRevision);
950
+ const [isOnline, setIsOnline] = useState3(() => typeof navigator === "undefined" || navigator.onLine);
951
+ const [saveState, setSaveState] = useState3("saved");
952
+ const [zoom, setZoom] = useState3("auto");
953
+ const [confirmBack, setConfirmBack] = useState3(false);
754
954
  const i18n = createAdminI18n(adminLocale);
755
955
  const engineData = useMemo2(() => toEngineData(editor.document, registry), [editor.document, registry]);
756
956
  const validationIssues = useMemo2(() => validateDocumentBlocks(editor.document, registry), [editor.document, registry]);
757
- const { confirmCanvasSelection, selectedBlockId, updateBlockProps } = editor;
957
+ const { confirmCanvasSelection, selectedBlockId, updateBlockProps, updateBlockPresentation } = editor;
758
958
  const updateFromCanvasInput = useCallback2((id, props, preserveCanvasValue = false) => {
759
959
  if (preserveCanvasValue) setCanvasMutationVersion((version) => version + 1);
760
960
  updateBlockProps(id, props);
761
961
  }, [updateBlockProps]);
762
962
  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) ?? []];
963
+ useEffect3(() => {
964
+ const online = () => setIsOnline(true);
965
+ const offline = () => setIsOnline(false);
966
+ window.addEventListener("online", online);
967
+ window.addEventListener("offline", offline);
968
+ return () => {
969
+ window.removeEventListener("online", online);
970
+ window.removeEventListener("offline", offline);
971
+ };
972
+ }, []);
973
+ const resolvedSaveState = !isOnline && editor.isDirty ? "offline" : saveState === "offline" && editor.isDirty ? "dirty" : saveState === "saved" && editor.isDirty ? "dirty" : saveState;
974
+ const canPersistDraft = Boolean(draftPersistence || onSave);
975
+ const save = useCallback2(async () => {
976
+ if (!canPersistDraft || request !== "idle" || validationIssues.length > 0) return;
977
+ if (!isOnline) {
978
+ setSaveState("offline");
979
+ return;
980
+ }
981
+ const document = editor.document;
982
+ setRequest("saving");
983
+ setSaveState("saving");
984
+ setNotice(null);
985
+ try {
986
+ const result = draftPersistence ? await draftPersistence.saveDraft({ document, expectedRevision: draftRevision, session: editor.session }) : await onSave?.(document);
987
+ if (result?.revision !== void 0) setDraftRevision(result.revision);
988
+ editor.markSaved(document);
989
+ setSaveState("saved");
990
+ } catch {
991
+ setNotice("saveFailed");
992
+ setSaveState(typeof navigator !== "undefined" && !navigator.onLine ? "offline" : "failed");
993
+ } finally {
994
+ setRequest("idle");
995
+ }
996
+ }, [canPersistDraft, draftPersistence, draftRevision, editor, isOnline, onSave, request, validationIssues.length]);
997
+ useEffect3(() => {
998
+ if (!autoSave || !canPersistDraft || !editor.isDirty || resolvedSaveState !== "dirty" || !isOnline || request !== "idle" || validationIssues.length > 0 || editor.sessionState !== "active") return;
999
+ const timer = window.setTimeout(() => {
1000
+ void save();
1001
+ }, autoSaveDelayMs);
1002
+ return () => window.clearTimeout(timer);
1003
+ }, [autoSave, autoSaveDelayMs, canPersistDraft, editor.isDirty, editor.sessionState, isOnline, request, resolvedSaveState, save, validationIssues.length]);
1004
+ const publish = async () => {
1005
+ if (!publishAction && !onPublish || request !== "idle" || validationIssues.length > 0 || !isOnline) return;
1006
+ const document = editor.document;
1007
+ setRequest("publishing");
1008
+ setNotice(null);
1009
+ try {
1010
+ if (publishAction) await publishAction.publish({ document, expectedRevision: draftRevision, session: editor.session, validationIssues });
1011
+ else await onPublish?.(document);
1012
+ editor.markSaved(document);
1013
+ setSaveState("saved");
1014
+ setNotice("published");
1015
+ } catch {
1016
+ setNotice("publishFailed");
1017
+ } finally {
1018
+ setRequest("idle");
1019
+ }
1020
+ };
1021
+ if (editor.loadState !== "ready" && editor.loadState !== "success") return /* @__PURE__ */ jsx7(EditorStatus, { state: editor.loadState });
1022
+ if (editor.sessionState !== "active") return /* @__PURE__ */ jsx7(EditorSessionStatus, { state: editor.sessionState, message: editor.sessionMessage });
1023
+ const allBlockTypes = ["core.text", "core.image", ...registry?.blocks.map((block) => block.type) ?? []];
1024
+ const blockTypes = availableBlockTypes ? allBlockTypes.filter((type) => availableBlockTypes.includes(type)) : allBlockTypes;
765
1025
  const addFromLibrary = (type, beforeId) => {
766
1026
  const id = editor.addBlock(type, beforeId);
767
1027
  if (id) editor.requestCanvasSelection(id);
@@ -797,120 +1057,125 @@ function PageDocumentEditor({ iframe = true, registry, adminLocale, onSave, onPu
797
1057
  event.stopPropagation();
798
1058
  setDraggingLibraryType(null);
799
1059
  };
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
- }
1060
+ const saveBadgeTone = resolvedSaveState === "failed" ? "critical" : resolvedSaveState === "offline" || resolvedSaveState === "dirty" ? "attention" : "success";
1061
+ 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");
1062
+ const saveButtonLabel = request === "saving" ? i18n.t("saving") : resolvedSaveState === "failed" ? i18n.t("retrySave") : i18n.t("save");
1063
+ const requestBack = () => {
1064
+ if (!onBack) return;
1065
+ if (editor.isDirty || request === "saving") setConfirmBack(true);
1066
+ else onBack();
812
1067
  };
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
1068
+ return /* @__PURE__ */ jsx7(Puck2, { config, data: engineData, iframe: { enabled: iframe }, onChange: (data) => {
1069
+ if (!editor.updateFromCanvas(fromEngineData(data, editor.document, registry))) setCanvasResetVersion((version) => version + 1);
1070
+ }, children: /* @__PURE__ */ jsxs5(Puck2.Layout, { children: [
1071
+ /* @__PURE__ */ jsx7(CanvasSelectionBridge, { data: engineData, requestedBlockId: editor.canvasSelectionRequest, onCanvasSelected: confirmCanvasSelection, canvasMutationVersion, canvasResetVersion }),
1072
+ /* @__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, children: [
1073
+ /* @__PURE__ */ jsx7("header", { className: "pb-header", children: /* @__PURE__ */ jsxs5("div", { className: "pb-header-content", children: [
1074
+ /* @__PURE__ */ jsxs5("div", { className: "pb-header-title-group", children: [
1075
+ onBack ? /* @__PURE__ */ jsx7(Button2, { variant: "tertiary", onClick: requestBack, children: i18n.t("back") }) : null,
1076
+ /* @__PURE__ */ jsxs5("div", { className: "pb-page-title", children: [
1077
+ /* @__PURE__ */ jsx7(Text3, { as: "h1", variant: "headingSm", children: editor.document.settings.seoTitle ?? editor.document.pageId }),
1078
+ /* @__PURE__ */ jsxs5(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: [
1079
+ "PageDocument V",
1080
+ editor.document.schemaVersion,
1081
+ " \xB7 ",
1082
+ editor.document.target
1083
+ ] }),
1084
+ pageStatus ? /* @__PURE__ */ jsx7(PageStatusCard, { status: pageStatus, sessionState: editor.sessionState }) : null
838
1085
  ] })
839
1086
  ] }),
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
- ] })
1087
+ /* @__PURE__ */ jsxs5("div", { className: "pb-header-device-toolbar", children: [
1088
+ /* @__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)) }),
1089
+ /* @__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) }) })
1090
+ ] }),
1091
+ /* @__PURE__ */ jsx7("div", { className: "pb-header-actions", children: /* @__PURE__ */ jsxs5(InlineStack3, { gap: "150", blockAlign: "center", wrap: false, children: [
1092
+ /* @__PURE__ */ jsx7(Badge3, { tone: saveBadgeTone, children: saveBadgeLabel }),
1093
+ /* @__PURE__ */ jsx7(Button2, { disabled: !canPersistDraft || !editor.isDirty || request !== "idle" || validationIssues.length > 0 || !isOnline, onClick: () => void save(), children: saveButtonLabel }),
1094
+ onPreview ? /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle" || !isOnline, onClick: () => void onPreview({ document: editor.document, draftRevision, session: editor.session }), children: i18n.t("preview") }) : null,
1095
+ onAddToStore ? /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle" || pageStatus?.publicationStatus === "unpublished", onClick: () => void onAddToStore({ document: editor.document, session: editor.session }), children: i18n.t("addToStore") }) : null,
1096
+ /* @__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") }),
1097
+ /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("undo"), icon: UndoIcon2, variant: "tertiary", disabled: !editor.actionState.canUndo, onClick: editor.undo }),
1098
+ /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("redo"), icon: RedoIcon2, variant: "tertiary", disabled: !editor.actionState.canRedo, onClick: editor.redo })
1099
+ ] }) })
847
1100
  ] }) }),
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: [
1101
+ editor.loadState === "success" ? /* @__PURE__ */ jsx7(Banner, { tone: "success", children: i18n.t("success") }) : null,
1102
+ notice ? /* @__PURE__ */ jsx7(Banner, { tone: notice === "published" ? "success" : "critical", children: i18n.t(notice) }) : null,
1103
+ 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
1104
  issue.path,
852
1105
  ": ",
853
1106
  issue.message
854
1107
  ] }, `${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") })
1108
+ /* @__PURE__ */ jsxs5("div", { className: "pb-workspace pb-workspace--document", children: [
1109
+ /* @__PURE__ */ jsxs5("nav", { className: "pb-tool-rail", "aria-label": "\u7F16\u8F91\u5668\u5DE5\u5177", children: [
1110
+ /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("blocks"), icon: LayoutSectionIcon2, pressed: blockView === "blocks", variant: "tertiary", onClick: () => setBlockView("blocks") }),
1111
+ /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("outline"), icon: MenuIcon2, pressed: blockView === "outline", variant: "tertiary", onClick: () => setBlockView("outline") })
859
1112
  ] }),
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) => {
1113
+ /* @__PURE__ */ jsxs5("aside", { className: "pb-left-panel", "aria-label": "PageDocument \u533A\u5757", children: [
1114
+ /* @__PURE__ */ jsx7(InlineStack3, { align: "space-between", blockAlign: "center", children: /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingSm", children: blockView === "blocks" ? i18n.t("blocks") : i18n.t("outline") }) }),
1115
+ 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) => {
1116
+ if (!editor.canAddBlock(type)) {
1117
+ event.preventDefault();
1118
+ return;
1119
+ }
863
1120
  event.dataTransfer.setData("application/x-page-document-block", type);
864
1121
  event.dataTransfer.effectAllowed = "copy";
865
1122
  setDraggingLibraryType(type);
866
1123
  }, 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, {}) })
1124
+ /* @__PURE__ */ jsxs5("span", { className: "pb-library-block-title", children: [
1125
+ /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockTypeLabel(type, registry) }),
1126
+ /* @__PURE__ */ jsx7("span", { className: "pb-library-block-drag-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsx7(DragHandleIcon2, {}) })
870
1127
  ] }),
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 })
1128
+ /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", tone: "subdued", children: type })
1129
+ ] }, 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: [
1130
+ /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockLabel(block, registry) }),
1131
+ /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", tone: "subdued", children: block.id })
875
1132
  ] }, block.id)) })
876
1133
  ] }),
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: [
882
- "\u677E\u5F00\u4EE5\u6DFB\u52A0 ",
883
- blockTypeLabel(draggingLibraryType, registry)
884
- ] }) : 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" })
888
- ] }) : null
889
- ] })
890
- ] }),
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") })
1134
+ /* @__PURE__ */ jsx7("main", { className: "pb-canvas-area", children: /* @__PURE__ */ jsxs5("div", { className: "pb-canvas-stage", children: [
1135
+ /* @__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, {}) }),
1136
+ 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: [
1137
+ "\u677E\u5F00\u4EE5\u6DFB\u52A0 ",
1138
+ blockTypeLabel(draggingLibraryType, registry)
1139
+ ] }) : null,
1140
+ editor.selectedBlock ? /* @__PURE__ */ jsxs5("div", { className: "pb-canvas-overlay", "aria-label": `\u5DF2\u9009\u62E9 ${blockLabel(editor.selectedBlock, registry)}`, children: [
1141
+ /* @__PURE__ */ jsx7("span", { children: blockLabel(editor.selectedBlock, registry) }),
1142
+ /* @__PURE__ */ jsx7("span", { children: "Selected" })
1143
+ ] }) : null
1144
+ ] }) }),
1145
+ /* @__PURE__ */ jsxs5("aside", { className: "pb-right-panel", "aria-label": "PageDocument \u5C5E\u6027", children: [
1146
+ /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingSm", children: i18n.t("properties") }),
1147
+ editor.selectedBlock ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1148
+ /* @__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) }),
1149
+ /* @__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) })
1150
+ ] }) : /* @__PURE__ */ jsx7(Text3, { as: "p", tone: "subdued", children: i18n.t("selectBlock") })
894
1151
  ] })
895
- ] })
1152
+ ] }),
1153
+ editor.pendingDeleteBlock ? /* @__PURE__ */ jsx7(DeleteConfirmation, { block: editor.pendingDeleteBlock, config: deleteConfirmation, i18n, onCancel: editor.cancelDeleteBlock, onConfirm: editor.confirmDeleteBlock }) : null,
1154
+ confirmBack ? /* @__PURE__ */ jsx7(LeaveConfirmation, { i18n, onCancel: () => setConfirmBack(false), onConfirm: () => {
1155
+ setConfirmBack(false);
1156
+ onBack?.();
1157
+ } }) : null
896
1158
  ] })
897
1159
  ] }) });
898
1160
  }
899
- function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canvasMutationVersion }) {
1161
+ function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canvasMutationVersion, canvasResetVersion }) {
900
1162
  const puck = usePuck();
901
1163
  const lastSelectedId = useRef4(null);
902
1164
  const lastSyncedData = useRef4(null);
903
1165
  const lastCanvasMutationVersion = useRef4(0);
1166
+ const lastCanvasResetVersion = useRef4(0);
904
1167
  const serializedData = JSON.stringify(data);
905
1168
  useEffect3(() => {
906
- if (lastSyncedData.current === serializedData) return;
1169
+ const forceReset = canvasResetVersion > lastCanvasResetVersion.current;
1170
+ if (lastSyncedData.current === serializedData && !forceReset) return;
907
1171
  lastSyncedData.current = serializedData;
1172
+ if (forceReset) lastCanvasResetVersion.current = canvasResetVersion;
908
1173
  if (canvasMutationVersion > lastCanvasMutationVersion.current) {
909
1174
  lastCanvasMutationVersion.current = canvasMutationVersion;
910
1175
  return;
911
1176
  }
912
1177
  puck.dispatch({ type: "setData", data });
913
- }, [canvasMutationVersion, data, puck, serializedData]);
1178
+ }, [canvasMutationVersion, canvasResetVersion, data, puck, serializedData]);
914
1179
  useEffect3(() => {
915
1180
  if (!requestedBlockId) return;
916
1181
  const selector = puck.getSelectorForId(requestedBlockId);
@@ -929,39 +1194,79 @@ function EditorStatus({ state }) {
929
1194
  const i18n = createAdminI18n();
930
1195
  const tone = state === "error" ? "critical" : state === "disabled" ? "warning" : "info";
931
1196
  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 }) });
1197
+ 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 }) });
1198
+ }
1199
+ function EditorSessionStatus({ state, message }) {
1200
+ const i18n = createAdminI18n();
1201
+ const tone = state === "locked" ? "warning" : state === "readonly" ? "info" : "critical";
1202
+ const title = state === "acquiring" ? i18n.t("acquiringLock") : state === "locked" ? i18n.t("locked") : state === "readonly" ? i18n.t("readonly") : i18n.t("lockLost");
1203
+ 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
1204
  }
934
1205
  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" })
1206
+ return /* @__PURE__ */ jsxs5("details", { className: "pb-inspector-section", open: defaultOpen, children: [
1207
+ /* @__PURE__ */ jsxs5("summary", { children: [
1208
+ /* @__PURE__ */ jsx7("span", { children: title }),
1209
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\u2304" })
939
1210
  ] }),
940
- /* @__PURE__ */ jsx6("div", { className: "pb-inspector-section__body", children })
1211
+ /* @__PURE__ */ jsx7("div", { className: "pb-inspector-section__body", children })
941
1212
  ] });
942
1213
  }
943
1214
  function InspectorTextControl({ label, value, control, disabled, onChange }) {
944
1215
  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" });
1216
+ 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) });
1217
+ return /* @__PURE__ */ jsx7(TextField, { label, labelHidden: true, value: stringValue, onChange, autoComplete: "off", disabled, multiline: control === "textarea" ? 4 : false, type: control === "url" ? "url" : "text" });
946
1218
  }
947
1219
  function InspectorField({ name, field, value, registry, disabled, onChange }) {
948
1220
  const label = field.label ?? name;
1221
+ const issues = validateFieldValue(field, value);
949
1222
  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
1223
+ return /* @__PURE__ */ jsxs5("div", { className: "pb-inspector-field", "data-control": field.control ?? "custom", children: [
1224
+ /* @__PURE__ */ jsxs5("div", { className: "pb-inspector-field__heading", children: [
1225
+ /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", fontWeight: "semibold", children: label }),
1226
+ field.description ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: field.description }) : null
954
1227
  ] }),
955
- field.control ? /* @__PURE__ */ jsx6(InspectorTextControl, { label, value, control: field.control, disabled, onChange: (next) => onChange(next) }) : Field ? /* @__PURE__ */ jsx6(Field, { value, onChange }) : null
1228
+ field.control ? /* @__PURE__ */ jsx7(InspectorTextControl, { label, value, control: field.control, disabled, onChange: (next) => onChange(next) }) : Field ? /* @__PURE__ */ jsx7(Field, { value, onChange }) : null,
1229
+ issues.map((issue) => /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: issue.message }, issue.message))
956
1230
  ] });
957
1231
  }
1232
+ function InspectorActions({ block, canDuplicate, canDelete, canMove, canMoveUp, canMoveDown, i18n, onDuplicate, onDelete, onMove }) {
1233
+ return /* @__PURE__ */ jsx7("div", { className: "pb-inspector-actions", "aria-label": `Actions for ${block.id}`, children: /* @__PURE__ */ jsxs5(ButtonGroup2, { children: [
1234
+ /* @__PURE__ */ jsx7(Button2, { disabled: !canMove || !canMoveUp, onClick: () => onMove(-1), children: i18n.t("moveUp") }),
1235
+ /* @__PURE__ */ jsx7(Button2, { disabled: !canMove || !canMoveDown, onClick: () => onMove(1), children: i18n.t("moveDown") }),
1236
+ /* @__PURE__ */ jsx7(Button2, { disabled: !canDuplicate, onClick: onDuplicate, children: i18n.t("duplicate") }),
1237
+ /* @__PURE__ */ jsx7(Button2, { disabled: !canDelete, tone: "critical", onClick: onDelete, children: i18n.t("remove") })
1238
+ ] }) });
1239
+ }
1240
+ function DeleteConfirmation({ block, config, i18n, onCancel, onConfirm }) {
1241
+ 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: [
1242
+ /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingMd", id: "pb-delete-confirmation-title", children: config?.title ?? i18n.t("confirmDeleteTitle") }),
1243
+ /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodyMd", children: config?.message?.(block) ?? i18n.t("confirmDeleteMessage") }),
1244
+ /* @__PURE__ */ jsxs5(ButtonGroup2, { children: [
1245
+ /* @__PURE__ */ jsx7(Button2, { onClick: onCancel, children: config?.cancelLabel ?? i18n.t("cancel") }),
1246
+ /* @__PURE__ */ jsx7(Button2, { tone: "critical", onClick: onConfirm, children: config?.confirmLabel ?? i18n.t("confirm") })
1247
+ ] })
1248
+ ] }) });
1249
+ }
1250
+ function LeaveConfirmation({ i18n, onCancel, onConfirm }) {
1251
+ 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: [
1252
+ /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingMd", id: "pb-leave-confirmation-title", children: i18n.t("confirmLeaveTitle") }),
1253
+ /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodyMd", children: i18n.t("leaveWarning") }),
1254
+ /* @__PURE__ */ jsxs5(ButtonGroup2, { children: [
1255
+ /* @__PURE__ */ jsx7(Button2, { onClick: onCancel, children: i18n.t("cancel") }),
1256
+ /* @__PURE__ */ jsx7(Button2, { tone: "critical", onClick: onConfirm, children: i18n.t("leave") })
1257
+ ] })
1258
+ ] }) });
1259
+ }
958
1260
  function inspectorFieldConfig(name, field) {
959
1261
  const key = name.toLowerCase();
960
1262
  const group = /(?:href|url)/.test(key) ? "Links" : /(?:default|shipment|query|hide)/.test(key) ? "Tracking settings" : /(?:id|variant|theme)/.test(key) ? "Advanced" : "Content";
961
1263
  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
1264
  return { ...field, group: field.group ?? group, description };
963
1265
  }
964
- function DocumentInspector({ block, registry, disabled, onChange }) {
1266
+ var appearanceTokens = [["color.primary", "\u4E3B\u8272"], ["color.surface", "\u8868\u9762\u8272"], ["radius", "\u5706\u89D2"], ["spacing", "\u95F4\u8DDD"]];
1267
+ function DocumentInspector({ pageId, assetPicker, block, registry, disabled, appearanceControls, onChange, onPresentationChange }) {
1268
+ const [assetError, setAssetError] = useState3(null);
1269
+ const [selectingAsset, setSelectingAsset] = useState3(false);
965
1270
  const definition = registry?.getBlock(block.type);
966
1271
  const groupedFields = definition ? Object.entries(definition.fields).reduce((groups, entry) => {
967
1272
  const [name, field] = entry;
@@ -970,21 +1275,43 @@ function DocumentInspector({ block, registry, disabled, onChange }) {
970
1275
  (groups[group] ??= []).push([name, configuredField]);
971
1276
  return groups;
972
1277
  }, {}) : {};
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" })
1278
+ return /* @__PURE__ */ jsxs5("div", { className: "pb-inspector", "data-testid": "document-inspector", children: [
1279
+ /* @__PURE__ */ jsxs5("header", { className: "pb-inspector__header", children: [
1280
+ /* @__PURE__ */ jsx7(Badge3, { children: block.type }),
1281
+ /* @__PURE__ */ jsxs5("div", { children: [
1282
+ /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "headingSm", children: blockLabel(block, registry) }),
1283
+ /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: definition?.category ?? "Core block" })
979
1284
  ] })
980
1285
  ] }),
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 }) })
1286
+ 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,
1287
+ block.type === "core.image" ? /* @__PURE__ */ jsxs5(InspectorSection, { title: "Image", children: [
1288
+ /* @__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 }) }),
1289
+ /* @__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 }) }),
1290
+ assetPicker ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1291
+ /* @__PURE__ */ jsx7(Button2, { disabled: disabled || selectingAsset, onClick: () => void (async () => {
1292
+ setSelectingAsset(true);
1293
+ setAssetError(null);
1294
+ try {
1295
+ 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 } });
1296
+ if (asset) onChange({ ...block.props, assetId: asset.id, src: asset.url, alt: asset.alt ?? (typeof block.props.alt === "string" ? block.props.alt : "") });
1297
+ } catch {
1298
+ setAssetError("\u65E0\u6CD5\u9009\u62E9\u7D20\u6750\uFF0C\u8BF7\u91CD\u8BD5\u3002");
1299
+ } finally {
1300
+ setSelectingAsset(false);
1301
+ }
1302
+ })(), children: selectingAsset ? "\u6B63\u5728\u9009\u62E9\u7D20\u6750\u2026" : "\u9009\u62E9\u7D20\u6750" }),
1303
+ assetError ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: assetError }) : null
1304
+ ] }) : null
985
1305
  ] }) : 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
1306
+ 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)),
1307
+ 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,
1308
+ 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) => {
1309
+ const style = { ...block.style };
1310
+ if (value.trim()) style[token] = value;
1311
+ else delete style[token];
1312
+ onPresentationChange({ variant: block.variant, style });
1313
+ } }, token)) }) : null,
1314
+ definition ? /* @__PURE__ */ jsx7("p", { className: "pb-inspector__hint", children: "\u753B\u5E03\u4E2D\u5E26\u865A\u7EBF\u8FB9\u6846\u7684\u5185\u5BB9\u53EF\u76F4\u63A5\u7F16\u8F91\u3002" }) : null
988
1315
  ] });
989
1316
  }
990
1317
  export {
@@ -993,6 +1320,7 @@ export {
993
1320
  ExtensionRegistry,
994
1321
  ExtensionRegistryError,
995
1322
  PageDocumentEditorShell,
1323
+ PageStatusCard,
996
1324
  TemplateRegistry,
997
1325
  WebRenderer,
998
1326
  createAdminI18n,
@@ -1000,12 +1328,18 @@ export {
1000
1328
  createPageDocument,
1001
1329
  createTemplateRegistry,
1002
1330
  fromEngineData,
1331
+ maximumBlockInstances,
1332
+ mergeBlockPolicies,
1003
1333
  mergeThemeTokens,
1004
1334
  migratePageDocument,
1335
+ minimumBlockInstances,
1005
1336
  normalizeThemeTokens,
1006
1337
  systemThemeTokens,
1007
1338
  toEngineData,
1008
1339
  toThemeStyle,
1009
1340
  useEditorContext,
1010
- validatePageDocument
1341
+ validateBlockPolicy,
1342
+ validateFieldValue,
1343
+ validatePageDocument,
1344
+ validatePageDocumentWithRegistry
1011
1345
  };