@standhigher/puck-page-builder 0.12.5 → 0.12.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -260,13 +260,7 @@ function EditorShell({ initialState, iframe = true }) {
260
260
  ] }),
261
261
  /* @__PURE__ */ jsxs2("main", { className: "pb-canvas-area", children: [
262
262
  /* @__PURE__ */ jsx2(CanvasToolbar, { device: state.device, zoom: state.zoom, onDevice: (device) => update({ device }), onZoom: (zoom) => update({ zoom }) }),
263
- /* @__PURE__ */ jsxs2("div", { className: "pb-canvas-stage", children: [
264
- /* @__PURE__ */ jsx2("div", { className: `pb-canvas-frame pb-canvas-frame--${state.device} pb-canvas-frame--zoom-${state.zoom}`, "data-device": state.device, "data-zoom": state.zoom, children: /* @__PURE__ */ jsx2(Puck.Preview, {}) }),
265
- selectedBlock ? /* @__PURE__ */ jsxs2("div", { className: "pb-canvas-overlay", "aria-label": `\u5DF2\u9009\u62E9 ${selectedBlock.label}`, children: [
266
- /* @__PURE__ */ jsx2("span", { children: selectedBlock.label }),
267
- /* @__PURE__ */ jsx2("span", { children: "Selected" })
268
- ] }) : null
269
- ] }),
263
+ /* @__PURE__ */ jsx2("div", { className: "pb-canvas-stage", children: /* @__PURE__ */ jsx2("div", { className: `pb-canvas-frame pb-canvas-frame--${state.device} pb-canvas-frame--zoom-${state.zoom}`, "data-device": state.device, "data-zoom": state.zoom, children: /* @__PURE__ */ jsx2(Puck.Preview, {}) }) }),
270
264
  !state.isLeftRailOpen || !state.isRightPanelOpen ? /* @__PURE__ */ jsxs2("div", { className: "pb-collapsed-actions", children: [
271
265
  !state.isLeftRailOpen ? /* @__PURE__ */ jsx2(Button, { onClick: () => update({ isLeftRailOpen: true }), children: "\u6253\u5F00\u533A\u5757\u9762\u677F" }) : null,
272
266
  !state.isRightPanelOpen ? /* @__PURE__ */ jsx2(Button, { onClick: () => update({ isRightPanelOpen: true }), children: "\u6253\u5F00\u5C5E\u6027\u9762\u677F" }) : null
@@ -353,22 +347,72 @@ function PropertyPanel({
353
347
  import { Puck as Puck2, usePuck } from "@puckeditor/core";
354
348
  import { Badge as Badge3, Banner, Button as Button2, ButtonGroup as ButtonGroup2, Frame, InlineStack as InlineStack3, Select as Select2, Text as Text3, TextField, Toast } from "@shopify/polaris";
355
349
  import { DragHandleIcon as DragHandleIcon2, LayoutSectionIcon as LayoutSectionIcon2, MenuIcon as MenuIcon2, ProductIcon, RedoIcon as RedoIcon2, UndoIcon as UndoIcon2, XIcon as XIcon2 } from "@shopify/polaris-icons";
356
- import { useCallback as useCallback2, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo2, useRef as useRef4, useState as useState3 } from "react";
350
+ import { useCallback as useCallback2, useEffect as useEffect4, useLayoutEffect, useMemo as useMemo2, useRef as useRef5, useState as useState4 } from "react";
357
351
 
358
- // src/adapters/puck/canvas-extension-block.tsx
352
+ // src/adapters/puck/use-canvas-editing.ts
359
353
  import { registerOverlayPortal } from "@puckeditor/core";
360
354
  import { useEffect, useRef as useRef2 } from "react";
355
+ var editingControlSelector = "input, textarea, select, button, a[href], [contenteditable]:not([contenteditable=false]), [role=textbox]";
356
+ function useCanvasEditing(active) {
357
+ const ref = useRef2(null);
358
+ useEffect(() => {
359
+ const root = ref.current;
360
+ if (!active || !root) return;
361
+ const cleanupPortal = registerOverlayPortal(root, { disableDragOnFocus: false });
362
+ const protectEditingControl = (event) => {
363
+ const target = event.target;
364
+ const control = typeof target?.closest === "function" ? target.closest(editingControlSelector) : null;
365
+ if (control && root.contains(control)) event.stopPropagation();
366
+ };
367
+ root.addEventListener("pointerdown", protectEditingControl, { capture: true });
368
+ return () => {
369
+ root.removeEventListener("pointerdown", protectEditingControl, { capture: true });
370
+ cleanupPortal?.();
371
+ };
372
+ }, [active]);
373
+ return ref;
374
+ }
375
+
376
+ // src/adapters/puck/canvas-extension-block.tsx
361
377
  import { jsx as jsx3 } from "react/jsx-runtime";
362
- function CanvasExtensionBlock({ active, children, label, onSelect }) {
363
- const editorRef = useRef2(null);
364
- useEffect(() => active ? registerOverlayPortal(editorRef.current, { disableDrag: true }) : void 0, [active]);
365
- return /* @__PURE__ */ jsx3("div", { ref: editorRef, className: "pb-document-canvas__extension", "aria-label": "Select " + label + " in canvas", role: "group", tabIndex: 0, onClick: onSelect, onKeyDown: (event) => {
366
- if (event.key === "Enter" || event.key === " ") onSelect();
378
+ function CanvasExtensionBlock({ active, blockId, children, label, onSelect }) {
379
+ const editorRef = useCanvasEditing(active);
380
+ return /* @__PURE__ */ jsx3("div", { ref: editorRef, className: "pb-document-canvas__extension", "data-page-document-block-id": blockId, "aria-label": "Select " + label + " in canvas", role: "group", tabIndex: 0, onClick: onSelect, onKeyDown: (event) => {
381
+ if (event.target === event.currentTarget && (event.key === "Enter" || event.key === " ")) {
382
+ event.preventDefault();
383
+ onSelect();
384
+ }
367
385
  }, children });
368
386
  }
369
387
 
388
+ // src/adapters/puck/canvas-core-blocks.tsx
389
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
390
+ function CanvasTextBlock({ id, content, editable, onSelect, onPropsChange }) {
391
+ const ref = useCanvasEditing(editable);
392
+ return /* @__PURE__ */ jsx4("section", { ref, className: "pb-document-canvas__text", "data-page-document-block-id": id, "aria-label": "Select Text in canvas", role: "button", tabIndex: 0, onClick: () => onSelect(id), onKeyDown: (event) => {
393
+ if (event.target === event.currentTarget && (event.key === "Enter" || event.key === " ")) {
394
+ event.preventDefault();
395
+ onSelect(id);
396
+ }
397
+ }, children: /* @__PURE__ */ jsx4("p", { contentEditable: editable, suppressContentEditableWarning: true, onInput: (event) => {
398
+ if (editable) onPropsChange(id, { content: event.currentTarget.textContent ?? "" }, true);
399
+ }, children: content }) });
400
+ }
401
+ function CanvasImageBlock({ id, src, alt, editable, onSelect, onPropsChange }) {
402
+ const ref = useCanvasEditing(editable);
403
+ return /* @__PURE__ */ jsxs3("figure", { ref, className: "pb-document-canvas__image", "data-page-document-block-id": id, "aria-label": "Select Image in canvas", role: "button", tabIndex: 0, onClick: () => onSelect(id), onKeyDown: (event) => {
404
+ if (event.target === event.currentTarget && (event.key === "Enter" || event.key === " ")) {
405
+ event.preventDefault();
406
+ onSelect(id);
407
+ }
408
+ }, children: [
409
+ /* @__PURE__ */ jsx4("img", { src, alt }),
410
+ editable ? /* @__PURE__ */ jsx4("input", { "aria-label": "\u753B\u5E03\u56FE\u7247 URL", value: src, onChange: (event) => onPropsChange(id, { src: event.currentTarget.value }, true), onClick: (event) => event.stopPropagation() }) : null
411
+ ] });
412
+ }
413
+
370
414
  // src/adapters/puck/page-document-config.tsx
371
- import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
415
+ import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
372
416
  function withBlockPermissions(component, type, resolvePermissions) {
373
417
  if (!resolvePermissions) return component;
374
418
  return {
@@ -386,11 +430,11 @@ function withBlockPermissions(component, type, resolvePermissions) {
386
430
  function canvasFieldFallback({ block, props, registry, onPropsChange }) {
387
431
  const fields = Object.entries(block.fields);
388
432
  if (!fields.length) return null;
389
- return /* @__PURE__ */ jsx4("div", { "aria-label": "Edit " + block.label + " values in canvas", onClick: (event) => event.stopPropagation(), style: { display: "grid", gap: 8, marginTop: 12, padding: 12, border: "1px dashed #94a3b8", borderRadius: 6, background: "#f8fafc" }, children: fields.map(([name, field]) => {
433
+ return /* @__PURE__ */ jsx5("div", { "aria-label": "Edit " + block.label + " values in canvas", onClick: (event) => event.stopPropagation(), style: { display: "grid", gap: 8, marginTop: 12, padding: 12, border: "1px dashed #94a3b8", borderRadius: 6, background: "#f8fafc" }, children: fields.map(([name, field]) => {
390
434
  const Field = registry?.getField(field.field)?.component;
391
- return Field ? /* @__PURE__ */ jsxs3("label", { style: { display: "grid", gap: 4, fontSize: 12, fontWeight: 600 }, children: [
435
+ return Field ? /* @__PURE__ */ jsxs4("label", { style: { display: "grid", gap: 4, fontSize: 12, fontWeight: 600 }, children: [
392
436
  field.label ?? name,
393
- /* @__PURE__ */ jsx4(Field, { value: props[name], onChange: (value) => onPropsChange({ [name]: value }) })
437
+ /* @__PURE__ */ jsx5(Field, { value: props[name], onChange: (value) => onPropsChange({ [name]: value }) })
394
438
  ] }, name) : null;
395
439
  }) });
396
440
  }
@@ -403,8 +447,8 @@ function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId,
403
447
  const id = typeof props.id === "string" ? props.id : `unknown-${block.type}`;
404
448
  const EditorRenderer = block.render.editor;
405
449
  const active = id === selectedBlockId;
406
- return /* @__PURE__ */ jsx4(CanvasExtensionBlock, { active, label: block.label, onSelect: () => onSelect(id), children: EditorRenderer ? /* @__PURE__ */ jsx4(EditorRenderer, { ...props, blockId: id, selected: active, onPropsChange: (next) => onPropsChange(id, next) }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
407
- /* @__PURE__ */ jsx4(BlockRenderer, { ...props }),
450
+ return /* @__PURE__ */ jsx5(CanvasExtensionBlock, { active, blockId: id, label: block.label, onSelect: () => onSelect(id), children: EditorRenderer ? /* @__PURE__ */ jsx5(EditorRenderer, { ...props, blockId: id, selected: active, onPropsChange: (next) => onPropsChange(id, next) }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
451
+ /* @__PURE__ */ jsx5(BlockRenderer, { ...props }),
408
452
  active ? canvasFieldFallback({ block, props, registry, onPropsChange: (next) => onPropsChange(id, next) }) : null
409
453
  ] }) });
410
454
  }
@@ -417,11 +461,7 @@ function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId,
417
461
  const id = typeof props.id === "string" ? props.id : "unknown-text";
418
462
  const content = typeof props.content === "string" ? props.content : "";
419
463
  const editable = id === selectedBlockId;
420
- return /* @__PURE__ */ jsx4("section", { className: "pb-document-canvas__text", "data-page-document-block-id": id, "aria-label": "Select Text in canvas", role: "button", tabIndex: 0, onClick: () => onSelect(id), onKeyDown: (event) => {
421
- if (event.key === "Enter" || event.key === " ") onSelect(id);
422
- }, children: /* @__PURE__ */ jsx4("p", { contentEditable: editable, suppressContentEditableWarning: true, onInput: (event) => {
423
- if (editable) onPropsChange(id, { content: event.currentTarget.textContent ?? "" }, true);
424
- }, children: content }) });
464
+ return /* @__PURE__ */ jsx5(CanvasTextBlock, { id, content, editable, onSelect, onPropsChange });
425
465
  }
426
466
  }, "core.text", resolveBlockPermissions),
427
467
  Image: withBlockPermissions({
@@ -430,12 +470,7 @@ function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId,
430
470
  const src = typeof props.src === "string" ? props.src : "";
431
471
  const alt = typeof props.alt === "string" ? props.alt : "";
432
472
  const editable = id === selectedBlockId;
433
- return /* @__PURE__ */ jsxs3("figure", { className: "pb-document-canvas__image", "data-page-document-block-id": id, "aria-label": "Select Image in canvas", role: "button", tabIndex: 0, onClick: () => onSelect(id), onKeyDown: (event) => {
434
- if (event.key === "Enter" || event.key === " ") onSelect(id);
435
- }, children: [
436
- /* @__PURE__ */ jsx4("img", { src, alt }),
437
- editable ? /* @__PURE__ */ jsx4("input", { "aria-label": "\u753B\u5E03\u56FE\u7247 URL", value: src, onChange: (event) => onPropsChange(id, { src: event.currentTarget.value }, true), onClick: (event) => event.stopPropagation() }) : null
438
- ] });
473
+ return /* @__PURE__ */ jsx5(CanvasImageBlock, { id, src, alt, editable, onSelect, onPropsChange });
439
474
  }
440
475
  }, "core.image", resolveBlockPermissions),
441
476
  ...extensionComponents
@@ -454,10 +489,10 @@ function toEngineBlock(block, registry) {
454
489
  if (!type) throw new Error(`V0.2 \u4E0D\u652F\u6301\u8F6C\u6362\u533A\u5757\u7C7B\u578B\uFF1A${block.type}`);
455
490
  return { type, props: { id: block.id, ...block.props } };
456
491
  }
457
- function toEngineData(document, registry) {
492
+ function toEngineData(document2, registry) {
458
493
  return {
459
- root: { props: { ...document.root } },
460
- content: document.blocks.map((block) => toEngineBlock(block, registry))
494
+ root: { props: { ...document2.root } },
495
+ content: document2.blocks.map((block) => toEngineBlock(block, registry))
461
496
  };
462
497
  }
463
498
  function fromEngineData(data, base, registry) {
@@ -544,7 +579,7 @@ function canApplyCanvasDocument(previous, next, getDefinition, policy) {
544
579
  }
545
580
 
546
581
  // src/editor/context/EditorContext.tsx
547
- import { jsx as jsx5 } from "react/jsx-runtime";
582
+ import { jsx as jsx6 } from "react/jsx-runtime";
548
583
  function historyReducer(state, action) {
549
584
  if (action.type === "select") return { ...state, selectedBlockId: action.id };
550
585
  if (action.type === "device") return { ...state, device: action.device };
@@ -577,7 +612,7 @@ function defaultBlock(type, blocks, registry) {
577
612
  return { id: uniqueBlockId(type, blocks), type, version: definition.version, props: definition.defaultProps, variant: definition.defaultVariant ?? "default", style: {} };
578
613
  }
579
614
  function EditorProvider({ initialDocument, registry, policy, loadState = "ready", sessionState = "active", session, sessionMessage, leaveWarning = "You have unsaved changes.", onDocumentChange, children }) {
580
- const [history, dispatch] = useReducer(historyReducer, initialDocument, (document) => ({ document, selectedBlockId: document.blocks[0]?.id ?? null, past: [], future: [], device: "desktop", savedDocument: document }));
615
+ const [history, dispatch] = useReducer(historyReducer, initialDocument, (document2) => ({ document: document2, selectedBlockId: document2.blocks[0]?.id ?? null, past: [], future: [], device: "desktop", savedDocument: document2 }));
581
616
  const historyRef = useRef3(history);
582
617
  useEffect2(() => {
583
618
  historyRef.current = history;
@@ -587,7 +622,7 @@ function EditorProvider({ initialDocument, registry, policy, loadState = "ready"
587
622
  const editable = (loadState === "ready" || loadState === "success") && sessionState === "active";
588
623
  const selectedBlock = history.document.blocks.find((block) => block.id === history.selectedBlockId) ?? null;
589
624
  const pendingDeleteBlock = history.document.blocks.find((block) => block.id === pendingDeleteBlockId) ?? null;
590
- const replace = useCallback((document, selectedBlockId) => dispatch({ type: "replace", document, selectedBlockId }), []);
625
+ const replace = useCallback((document2, selectedBlockId) => dispatch({ type: "replace", document: document2, selectedBlockId }), []);
591
626
  const requestCanvasSelection = useCallback((id) => {
592
627
  if (editable) setCanvasSelectionRequest(id);
593
628
  }, [editable]);
@@ -595,11 +630,11 @@ function EditorProvider({ initialDocument, registry, policy, loadState = "ready"
595
630
  setCanvasSelectionRequest(null);
596
631
  dispatch({ type: "select", id });
597
632
  }, []);
598
- const updateFromCanvas = useCallback((document) => {
633
+ const updateFromCanvas = useCallback((document2) => {
599
634
  const current = historyRef.current;
600
- if (!editable || JSON.stringify(document) === JSON.stringify(current.document)) return false;
601
- if (!canApplyCanvasDocument(current.document, document, (type) => registry?.getBlock(type), policy)) return false;
602
- replace(document, current.selectedBlockId);
635
+ if (!editable || JSON.stringify(document2) === JSON.stringify(current.document)) return false;
636
+ if (!canApplyCanvasDocument(current.document, document2, (type) => registry?.getBlock(type), policy)) return false;
637
+ replace(document2, current.selectedBlockId);
603
638
  return true;
604
639
  }, [editable, policy, registry, replace]);
605
640
  const updateBlockProps = useCallback((id, props) => {
@@ -724,11 +759,17 @@ function EditorProvider({ initialDocument, registry, policy, loadState = "ready"
724
759
  reorderBlock: (id, beforeId) => {
725
760
  if (!editable || id === beforeId) return;
726
761
  const source = history.document.blocks.find((block) => block.id === id);
762
+ if (!source || !canDragBlock(source, registry?.getBlock(source.type), policy)) return;
727
763
  const withoutSource = history.document.blocks.filter((block) => block.id !== id);
728
- const targetIndex = withoutSource.findIndex((block) => block.id === beforeId);
729
- if (!source || targetIndex < 0 || !canDragBlock(source, registry?.getBlock(source.type), policy)) return;
730
764
  const blocks = [...withoutSource];
731
- blocks.splice(targetIndex, 0, source);
765
+ if (beforeId) {
766
+ const targetIndex = withoutSource.findIndex((block) => block.id === beforeId);
767
+ if (targetIndex < 0) return;
768
+ blocks.splice(targetIndex, 0, source);
769
+ } else {
770
+ blocks.push(source);
771
+ }
772
+ if (blocks.every((block, index) => block.id === history.document.blocks[index]?.id)) return;
732
773
  replace({ ...history.document, blocks }, id);
733
774
  },
734
775
  updateBlockProps,
@@ -755,10 +796,10 @@ function EditorProvider({ initialDocument, registry, policy, loadState = "ready"
755
796
  redo: () => {
756
797
  if (editable) dispatch({ type: "redo" });
757
798
  },
758
- markSaved: (document = historyRef.current.document) => dispatch({ type: "saved", document })
799
+ markSaved: (document2 = historyRef.current.document) => dispatch({ type: "saved", document: document2 })
759
800
  };
760
801
  }, [canvasSelectionRequest, confirmCanvasSelection, editable, history, isDirty, loadState, pendingDeleteBlock, pendingDeleteBlockId, policy, registry, replace, requestCanvasSelection, selectedBlock, session, sessionMessage, sessionState, updateBlockPresentation, updateBlockProps, updateFromCanvas]);
761
- return /* @__PURE__ */ jsx5(EditorContext.Provider, { value, children });
802
+ return /* @__PURE__ */ jsx6(EditorContext.Provider, { value, children });
762
803
  }
763
804
  function useEditorContext() {
764
805
  const context = useContext(EditorContext);
@@ -768,18 +809,18 @@ function useEditorContext() {
768
809
 
769
810
  // src/editor/components/PageStatusCard.tsx
770
811
  import { Badge as Badge2, InlineStack as InlineStack2, Text as Text2 } from "@shopify/polaris";
771
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
812
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
772
813
  function PageStatusCard({ status, sessionState }) {
773
814
  const publicationTone = status.publicationStatus === "published" ? "success" : "attention";
774
815
  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;
775
- return /* @__PURE__ */ jsxs4("div", { className: "pb-page-status-card", "data-testid": "page-status-card", "data-publication-status": status.publicationStatus, "data-session-state": sessionState, children: [
776
- /* @__PURE__ */ jsxs4(InlineStack2, { gap: "150", blockAlign: "center", wrap: true, children: [
777
- /* @__PURE__ */ jsx6(Badge2, { tone: publicationTone, children: status.publicationStatus === "published" ? "\u7EBF\u4E0A" : "\u672A\u53D1\u5E03" }),
778
- status.draftLabel ? /* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: status.draftLabel }) : null,
779
- status.publishedVersionLabel ? /* @__PURE__ */ jsx6(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: status.publishedVersionLabel }) : null,
780
- sessionLabel ? /* @__PURE__ */ jsx6(Badge2, { tone: sessionState === "lost" ? "critical" : "attention", children: sessionLabel }) : null
816
+ return /* @__PURE__ */ jsxs5("div", { className: "pb-page-status-card", "data-testid": "page-status-card", "data-publication-status": status.publicationStatus, "data-session-state": sessionState, children: [
817
+ /* @__PURE__ */ jsxs5(InlineStack2, { gap: "150", blockAlign: "center", wrap: true, children: [
818
+ /* @__PURE__ */ jsx7(Badge2, { tone: publicationTone, children: status.publicationStatus === "published" ? "\u7EBF\u4E0A" : "\u672A\u53D1\u5E03" }),
819
+ status.draftLabel ? /* @__PURE__ */ jsx7(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: status.draftLabel }) : null,
820
+ status.publishedVersionLabel ? /* @__PURE__ */ jsx7(Text2, { as: "span", variant: "bodySm", tone: "subdued", children: status.publishedVersionLabel }) : null,
821
+ sessionLabel ? /* @__PURE__ */ jsx7(Badge2, { tone: sessionState === "lost" ? "critical" : "attention", children: sessionLabel }) : null
781
822
  ] }),
782
- status.lastSavedAt ? /* @__PURE__ */ jsxs4(Text2, { as: "p", variant: "bodySm", tone: "subdued", children: [
823
+ status.lastSavedAt ? /* @__PURE__ */ jsxs5(Text2, { as: "p", variant: "bodySm", tone: "subdued", children: [
783
824
  "\u6700\u540E\u4FDD\u5B58\uFF1A",
784
825
  status.lastSavedAt
785
826
  ] }) : null
@@ -924,17 +965,220 @@ function createAdminI18n(locale) {
924
965
  return { locale: resolvedLocale, t: (key) => messages[resolvedLocale][key] };
925
966
  }
926
967
 
968
+ // src/editor/shell/CanvasLibraryDropTarget.tsx
969
+ import { useEffect as useEffect3, useRef as useRef4, useState as useState3 } from "react";
970
+
927
971
  // src/editor/shell/drop-position.ts
928
972
  function nearestBlockIdAtY(blocks, y) {
929
973
  return blocks.find((block) => y < block.top + block.height / 2)?.id;
930
974
  }
931
- function blockIdAtRelativeY(ids, relativeY) {
932
- const index = Math.min(ids.length, Math.max(0, Math.round(relativeY * ids.length)));
933
- return ids[index];
975
+ function libraryTypesInDocumentOrder(blockTypes, blocks) {
976
+ const allowed = new Set(blockTypes);
977
+ const seen = /* @__PURE__ */ new Set();
978
+ const ordered = [];
979
+ for (const block of blocks) {
980
+ if (!allowed.has(block.type) || seen.has(block.type)) continue;
981
+ ordered.push(block.type);
982
+ seen.add(block.type);
983
+ }
984
+ for (const type of blockTypes) {
985
+ if (!seen.has(type)) ordered.push(type);
986
+ }
987
+ return ordered;
988
+ }
989
+ function dropPlacement(clientY, top, height) {
990
+ if (height <= 0) return "before";
991
+ return clientY < top + height / 2 ? "before" : "after";
992
+ }
993
+ function beforeIdAfterPlacement(blockIds, targetId, placement) {
994
+ if (placement === "before") return targetId;
995
+ const index = blockIds.indexOf(targetId);
996
+ return index < 0 ? targetId : blockIds[index + 1];
997
+ }
998
+
999
+ // src/editor/shell/CanvasLibraryDropTarget.tsx
1000
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1001
+ function uniqueBlocks(blocks) {
1002
+ const seen = /* @__PURE__ */ new Set();
1003
+ return blocks.filter((block) => !seen.has(block.id) && seen.add(block.id));
1004
+ }
1005
+ function scrollableAncestors(start) {
1006
+ const result = [];
1007
+ let node = start;
1008
+ while (node) {
1009
+ const style = window.getComputedStyle(node);
1010
+ if (["auto", "scroll", "overlay"].includes(style.overflowY) && node.scrollHeight > node.clientHeight) result.push(node);
1011
+ node = node.parentElement;
1012
+ }
1013
+ return result;
1014
+ }
1015
+ function scrollDelta(pointerY, rect, elapsedMs) {
1016
+ const top = Math.max(0, rect.top);
1017
+ const bottom = Math.min(window.innerHeight, rect.bottom);
1018
+ const height = bottom - top;
1019
+ if (height <= 0 || pointerY < top || pointerY > bottom) return 0;
1020
+ const edge = Math.min(96, Math.max(36, height * 0.24));
1021
+ const speed = 900;
1022
+ if (pointerY < top + edge) return -speed * Math.min(1, (top + edge - pointerY) / edge) * elapsedMs / 1e3;
1023
+ if (pointerY > bottom - edge) return speed * Math.min(1, (pointerY - (bottom - edge)) / edge) * elapsedMs / 1e3;
1024
+ return 0;
1025
+ }
1026
+ function scrollElement(element, delta) {
1027
+ const canScroll = delta < 0 ? element.scrollTop > 0 : element.scrollTop < element.scrollHeight - element.clientHeight;
1028
+ if (!delta || !canScroll) return false;
1029
+ if (typeof element.scrollBy === "function") element.scrollBy({ top: delta, behavior: "auto" });
1030
+ else element.scrollTop += delta;
1031
+ return true;
1032
+ }
1033
+ function CanvasLibraryDropTarget({ frameRef, blocks, label, onDrop, onCancel, adminLocale }) {
1034
+ const targetRef = useRef4(null);
1035
+ const latestPreview = useRef4(null);
1036
+ const dragging = useRef4(false);
1037
+ const completed = useRef4(false);
1038
+ const pointerY = useRef4(0);
1039
+ const animationFrame = useRef4(null);
1040
+ const lastFrameTime = useRef4(null);
1041
+ const callbacks = useRef4({ onDrop, onCancel });
1042
+ useEffect3(() => {
1043
+ callbacks.current = { onDrop, onCancel };
1044
+ }, [onDrop, onCancel]);
1045
+ const orderedBlocks = uniqueBlocks(blocks);
1046
+ const [preview, setPreview] = useState3(null);
1047
+ const english = adminLocale?.toLowerCase().startsWith("en") ?? false;
1048
+ const stopAutoScroll = () => {
1049
+ if (animationFrame.current !== null) window.cancelAnimationFrame(animationFrame.current);
1050
+ animationFrame.current = null;
1051
+ lastFrameTime.current = null;
1052
+ };
1053
+ const clear = (cancel) => {
1054
+ stopAutoScroll();
1055
+ dragging.current = false;
1056
+ latestPreview.current = null;
1057
+ setPreview(null);
1058
+ if (cancel) {
1059
+ completed.current = true;
1060
+ callbacks.current.onCancel();
1061
+ }
1062
+ };
1063
+ const calculatePreview = (clientY) => {
1064
+ const target = targetRef.current;
1065
+ const frame = frameRef.current;
1066
+ if (!target || !frame) return null;
1067
+ const targetRect = target.getBoundingClientRect();
1068
+ const frameRect = frame.getBoundingClientRect();
1069
+ const iframe = frame.querySelector("iframe");
1070
+ const iframeDocument = iframe?.contentDocument;
1071
+ const root = iframeDocument ?? frame;
1072
+ const allowed = new Set(orderedBlocks.map((block) => block.id));
1073
+ const byId = /* @__PURE__ */ new Map();
1074
+ const iframeRect = iframeDocument ? iframe?.getBoundingClientRect() : void 0;
1075
+ const scaleY = iframe && iframeRect && iframeRect.height > 0 && iframe.clientHeight > 0 ? iframeRect.height / iframe.clientHeight : 1;
1076
+ const elements = Array.from(root.querySelectorAll("[data-page-document-block-id], [data-puck-dnd]"));
1077
+ for (const element of elements) {
1078
+ const id = element.dataset.pageDocumentBlockId ?? element.dataset.puckDnd;
1079
+ if (!id || !allowed.has(id) || byId.has(id) || element.closest('[aria-hidden="true"], [class*="DragOverlay"]')) continue;
1080
+ const rect = element.getBoundingClientRect();
1081
+ byId.set(id, { id, top: iframeRect ? iframeRect.top + rect.top * scaleY : rect.top, height: iframeRect ? rect.height * scaleY : rect.height });
1082
+ }
1083
+ const candidates = orderedBlocks.flatMap((block) => byId.get(block.id) ?? []);
1084
+ const beforeId = nearestBlockIdAtY(candidates, clientY);
1085
+ const markerCandidate = beforeId ? candidates.find((candidate) => candidate.id === beforeId) : candidates.at(-1);
1086
+ const markerViewportTop = markerCandidate ? beforeId ? markerCandidate.top : markerCandidate.top + markerCandidate.height : Math.min(frameRect.bottom, Math.max(frameRect.top, clientY));
1087
+ return { beforeId, markerTop: Math.max(0, markerViewportTop - targetRect.top), markerLeft: Math.max(0, frameRect.left - targetRect.left), markerWidth: frameRect.width };
1088
+ };
1089
+ const updatePreview = (clientY) => {
1090
+ pointerY.current = clientY;
1091
+ const next = calculatePreview(clientY);
1092
+ const previous = latestPreview.current;
1093
+ latestPreview.current = next;
1094
+ if (previous?.beforeId !== next?.beforeId || previous?.markerTop !== next?.markerTop || previous?.markerLeft !== next?.markerLeft || previous?.markerWidth !== next?.markerWidth) setPreview(next);
1095
+ };
1096
+ const autoScroll = (timestamp) => {
1097
+ if (!dragging.current) return;
1098
+ const elapsedMs = Math.min(64, Math.max(0, timestamp - (lastFrameTime.current ?? timestamp)));
1099
+ lastFrameTime.current = timestamp;
1100
+ const stage = targetRef.current?.parentElement;
1101
+ if (stage) {
1102
+ let scrolled = false;
1103
+ for (const ancestor of scrollableAncestors(stage)) {
1104
+ const delta = scrollDelta(pointerY.current, ancestor.getBoundingClientRect(), elapsedMs);
1105
+ if (scrollElement(ancestor, delta)) {
1106
+ scrolled = true;
1107
+ break;
1108
+ }
1109
+ }
1110
+ const documentScroller = document.scrollingElement;
1111
+ if (!scrolled && documentScroller) {
1112
+ const viewport = new DOMRect(0, 0, window.innerWidth, window.innerHeight);
1113
+ scrollElement(documentScroller, scrollDelta(pointerY.current, viewport, elapsedMs));
1114
+ }
1115
+ updatePreview(pointerY.current);
1116
+ }
1117
+ animationFrame.current = window.requestAnimationFrame(autoScroll);
1118
+ };
1119
+ const startAutoScroll = () => {
1120
+ if (animationFrame.current === null) animationFrame.current = window.requestAnimationFrame(autoScroll);
1121
+ };
1122
+ useEffect3(() => {
1123
+ const cancelOutside = (event) => {
1124
+ if (event.type === "drop" && targetRef.current?.contains(event.target)) return;
1125
+ if (!completed.current) clear(true);
1126
+ };
1127
+ const onKeyDown = (event) => {
1128
+ if (event.key === "Escape" && !completed.current) clear(true);
1129
+ };
1130
+ document.addEventListener("keydown", onKeyDown);
1131
+ document.addEventListener("dragend", cancelOutside, true);
1132
+ document.addEventListener("drop", cancelOutside, true);
1133
+ return () => {
1134
+ document.removeEventListener("keydown", onKeyDown);
1135
+ document.removeEventListener("dragend", cancelOutside, true);
1136
+ document.removeEventListener("drop", cancelOutside, true);
1137
+ stopAutoScroll();
1138
+ };
1139
+ }, []);
1140
+ const handleDragOver = (event) => {
1141
+ event.preventDefault();
1142
+ event.stopPropagation();
1143
+ event.dataTransfer.dropEffect = "copy";
1144
+ completed.current = false;
1145
+ dragging.current = true;
1146
+ updatePreview(event.clientY);
1147
+ startAutoScroll();
1148
+ };
1149
+ const handleDragLeave = (event) => {
1150
+ if (!event.relatedTarget || !event.currentTarget.contains(event.relatedTarget)) clear(false);
1151
+ };
1152
+ const handleDrop = (event) => {
1153
+ event.preventDefault();
1154
+ event.stopPropagation();
1155
+ const next = latestPreview.current ?? calculatePreview(event.clientY);
1156
+ completed.current = true;
1157
+ clear(false);
1158
+ callbacks.current.onDrop(next?.beforeId);
1159
+ };
1160
+ const position = preview?.beforeId ? orderedBlocks.findIndex((block) => block.id === preview.beforeId) + 1 : orderedBlocks.length + 1;
1161
+ const hint = english ? `Release to add ${label}${preview ? ` \xB7 position ${position}` : ""}` : `\u677E\u5F00\u4EE5\u6DFB\u52A0 ${label}${preview ? ` \xB7 \u7B2C ${position} \u4E2A\u4F4D\u7F6E` : ""}`;
1162
+ const style = { position: "absolute", inset: 0, zIndex: 2 };
1163
+ const markerLabel = english ? `Position ${position}` : `\u7B2C ${position} \u4E2A\u4F4D\u7F6E`;
1164
+ return /* @__PURE__ */ jsxs6("div", { ref: targetRef, className: "pb-canvas-drop-target", "data-testid": "canvas-drop-target", role: "region", "aria-label": english ? "Block drop area" : "\u533A\u5757\u6295\u653E\u533A", style, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [
1165
+ /* @__PURE__ */ jsx8("span", { className: "pb-canvas-drop-target__hint", "aria-live": "polite", children: hint }),
1166
+ preview ? /* @__PURE__ */ jsx8("span", { className: "pb-canvas-drop-marker", "aria-hidden": "true", style: { top: preview.markerTop, left: preview.markerLeft, width: preview.markerWidth }, children: /* @__PURE__ */ jsx8("span", { className: "pb-canvas-drop-marker__label", children: markerLabel }) }) : null
1167
+ ] });
934
1168
  }
935
1169
 
936
1170
  // src/editor/shell/PageDocumentEditorShell.tsx
937
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1171
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1172
+ function scrollCanvasBlockIntoView(blockId, frame) {
1173
+ const selector = `[data-page-document-block-id="${CSS.escape(blockId)}"]`;
1174
+ const scroll = () => {
1175
+ const iframe = frame?.querySelector("iframe");
1176
+ const element = iframe?.contentDocument?.querySelector(selector) ?? document.querySelector(selector);
1177
+ element?.scrollIntoView({ block: "start", inline: "nearest" });
1178
+ };
1179
+ if (typeof requestAnimationFrame === "function") requestAnimationFrame(scroll);
1180
+ else scroll();
1181
+ }
938
1182
  var deviceLabels2 = { desktop: "desktop", tablet: "tablet", mobile: "mobile" };
939
1183
  function blockLabel(block, registry) {
940
1184
  return blockTypeLabel(block.type, registry);
@@ -944,12 +1188,12 @@ function blockTypeLabel(type, registry) {
944
1188
  if (type === "core.image") return "\u56FE\u7247";
945
1189
  return registry?.getBlock(type)?.label ?? type;
946
1190
  }
947
- function validateDocumentBlocks(document, registry) {
948
- return registry ? validatePageDocumentWithRegistry(document, registry) : [];
1191
+ function validateDocumentBlocks(document2, registry) {
1192
+ return registry ? validatePageDocumentWithRegistry(document2, registry) : [];
949
1193
  }
950
1194
  function useEditorSession(adapter, pageId) {
951
- const [managed, setManaged] = useState3(() => adapter ? { state: "acquiring" } : { state: "active" });
952
- useEffect3(() => {
1195
+ const [managed, setManaged] = useState4(() => adapter ? { state: "acquiring" } : { state: "active" });
1196
+ useEffect4(() => {
953
1197
  let cancelled = false;
954
1198
  let session;
955
1199
  let heartbeatTimer;
@@ -991,31 +1235,34 @@ function useEditorSession(adapter, pageId) {
991
1235
  function PageDocumentEditorShell(props) {
992
1236
  const { sessionAdapter, initialDocument, onSessionStateChange } = props;
993
1237
  const managedSession = useEditorSession(sessionAdapter, initialDocument.pageId);
994
- useEffect3(() => {
1238
+ useEffect4(() => {
995
1239
  onSessionStateChange?.(managedSession.state);
996
1240
  }, [managedSession.state, onSessionStateChange]);
997
- 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 }) });
1241
+ return /* @__PURE__ */ jsx9(EditorProvider, { initialDocument: props.initialDocument, registry: props.registry, policy: props.policy, loadState: props.loadState, sessionState: managedSession.state, session: managedSession.session, sessionMessage: managedSession.message, leaveWarning: createAdminI18n(props.adminLocale).t("leaveWarning"), onDocumentChange: props.onDocumentChange, children: /* @__PURE__ */ jsx9(PageDocumentEditor, { ...props }) });
998
1242
  }
999
1243
  function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onPublish, draftPersistence, draftRevision: initialDraftRevision, autoSave = true, autoSaveDelayMs = 800, publishAction, assetPicker, inspectorSettings, productPicker, pageStatus, onBack, onHistory, onPreview, onAddToStore, deleteConfirmation, availableBlockTypes, appearanceControls = false }) {
1000
1244
  const editor = useEditorContext();
1001
- const editorRef = useRef4(editor);
1245
+ const editorRef = useRef5(editor);
1002
1246
  useLayoutEffect(() => {
1003
1247
  editorRef.current = editor;
1004
1248
  }, [editor]);
1005
- const [blockView, setBlockView] = useState3("blocks");
1006
- const [leftRailOpen, setLeftRailOpen] = useState3(true);
1007
- const [rightPanelOpen, setRightPanelOpen] = useState3(true);
1008
- const [draggingLibraryType, setDraggingLibraryType] = useState3(null);
1009
- const canvasFrameRef = useRef4(null);
1010
- const [canvasMutationVersion, setCanvasMutationVersion] = useState3(0);
1011
- const [canvasResetVersion, setCanvasResetVersion] = useState3(0);
1012
- const [request, setRequest] = useState3("idle");
1013
- const [notice, setNotice] = useState3(null);
1014
- const [draftRevision, setDraftRevision] = useState3(initialDraftRevision);
1015
- const [isOnline, setIsOnline] = useState3(() => typeof navigator === "undefined" || navigator.onLine);
1016
- const [saveState, setSaveState] = useState3("saved");
1017
- const [zoom, setZoom] = useState3("auto");
1018
- const [confirmBack, setConfirmBack] = useState3(false);
1249
+ const [blockView, setBlockView] = useState4("blocks");
1250
+ const [leftRailOpen, setLeftRailOpen] = useState4(true);
1251
+ const [rightPanelOpen, setRightPanelOpen] = useState4(true);
1252
+ const [draggingLibraryType, setDraggingLibraryType] = useState4(null);
1253
+ const [draggingBlockId, setDraggingBlockId] = useState4(null);
1254
+ const draggingBlockIdRef = useRef5(null);
1255
+ const [dropTarget, setDropTarget] = useState4(null);
1256
+ const canvasFrameRef = useRef5(null);
1257
+ const [canvasMutationVersion, setCanvasMutationVersion] = useState4(0);
1258
+ const [canvasResetVersion, setCanvasResetVersion] = useState4(0);
1259
+ const [request, setRequest] = useState4("idle");
1260
+ const [notice, setNotice] = useState4(null);
1261
+ const [draftRevision, setDraftRevision] = useState4(initialDraftRevision);
1262
+ const [isOnline, setIsOnline] = useState4(() => typeof navigator === "undefined" || navigator.onLine);
1263
+ const [saveState, setSaveState] = useState4("saved");
1264
+ const [zoom, setZoom] = useState4("auto");
1265
+ const [confirmBack, setConfirmBack] = useState4(false);
1019
1266
  const i18n = createAdminI18n(adminLocale);
1020
1267
  const engineData = useMemo2(() => toEngineData(editor.document, registry), [editor.document, registry]);
1021
1268
  const canvasThemeStyle = useMemo2(() => {
@@ -1036,7 +1283,7 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1036
1283
  insert: editorRef.current.canAddBlock(type)
1037
1284
  }), []);
1038
1285
  const config = useMemo2(() => createPageDocumentPuckConfig(confirmCanvasSelection, updateFromCanvasInput, selectedBlockId, registry, resolveBlockPermissions), [confirmCanvasSelection, registry, resolveBlockPermissions, selectedBlockId, updateFromCanvasInput]);
1039
- useEffect3(() => {
1286
+ useEffect4(() => {
1040
1287
  const online = () => setIsOnline(true);
1041
1288
  const offline = () => setIsOnline(false);
1042
1289
  window.addEventListener("online", online);
@@ -1054,14 +1301,14 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1054
1301
  setSaveState("offline");
1055
1302
  return;
1056
1303
  }
1057
- const document = editor.document;
1304
+ const document2 = editor.document;
1058
1305
  setRequest("saving");
1059
1306
  setSaveState("saving");
1060
1307
  setNotice(null);
1061
1308
  try {
1062
- const result = draftPersistence ? await draftPersistence.saveDraft({ document, expectedRevision: draftRevision, session: editor.session }) : await onSave?.(document);
1309
+ const result = draftPersistence ? await draftPersistence.saveDraft({ document: document2, expectedRevision: draftRevision, session: editor.session }) : await onSave?.(document2);
1063
1310
  if (result?.revision !== void 0) setDraftRevision(result.revision);
1064
- editor.markSaved(document);
1311
+ editor.markSaved(document2);
1065
1312
  setSaveState("saved");
1066
1313
  } catch {
1067
1314
  setNotice("saveFailed");
@@ -1070,7 +1317,7 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1070
1317
  setRequest("idle");
1071
1318
  }
1072
1319
  }, [canPersistDraft, draftPersistence, draftRevision, editor, isOnline, onSave, request, validationIssues.length]);
1073
- useEffect3(() => {
1320
+ useEffect4(() => {
1074
1321
  if (!autoSave || !canPersistDraft || !editor.isDirty || resolvedSaveState !== "dirty" && resolvedSaveState !== "failed" || !isOnline || request !== "idle" || validationIssues.length > 0 || editor.sessionState !== "active") return;
1075
1322
  const timer = window.setTimeout(() => {
1076
1323
  void save();
@@ -1079,13 +1326,13 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1079
1326
  }, [autoSave, autoSaveDelayMs, canPersistDraft, editor.isDirty, editor.sessionState, isOnline, request, resolvedSaveState, save, validationIssues.length]);
1080
1327
  const publish = async () => {
1081
1328
  if (!publishAction && !onPublish || request !== "idle" || validationIssues.length > 0 || !isOnline) return;
1082
- const document = editor.document;
1329
+ const document2 = editor.document;
1083
1330
  setRequest("publishing");
1084
1331
  setNotice(null);
1085
1332
  try {
1086
- if (publishAction) await publishAction.publish({ document, expectedRevision: draftRevision, session: editor.session, validationIssues });
1087
- else await onPublish?.(document);
1088
- editor.markSaved(document);
1333
+ if (publishAction) await publishAction.publish({ document: document2, expectedRevision: draftRevision, session: editor.session, validationIssues });
1334
+ else await onPublish?.(document2);
1335
+ editor.markSaved(document2);
1089
1336
  setSaveState("saved");
1090
1337
  setNotice("published");
1091
1338
  } catch {
@@ -1094,44 +1341,31 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1094
1341
  setRequest("idle");
1095
1342
  }
1096
1343
  };
1097
- if (editor.loadState !== "ready" && editor.loadState !== "success") return /* @__PURE__ */ jsx7(EditorStatus, { state: editor.loadState });
1098
- if (editor.sessionState !== "active") return /* @__PURE__ */ jsx7(EditorSessionStatus, { state: editor.sessionState, message: editor.sessionMessage });
1344
+ if (editor.loadState !== "ready" && editor.loadState !== "success") return /* @__PURE__ */ jsx9(EditorStatus, { state: editor.loadState });
1345
+ if (editor.sessionState !== "active") return /* @__PURE__ */ jsx9(EditorSessionStatus, { state: editor.sessionState, message: editor.sessionMessage });
1099
1346
  const allBlockTypes = ["core.text", "core.image", ...registry?.blocks.map((block) => block.type) ?? []];
1100
1347
  const blockTypes = availableBlockTypes ? allBlockTypes.filter((type) => availableBlockTypes.includes(type)) : allBlockTypes;
1348
+ const orderedBlockTypes = libraryTypesInDocumentOrder(blockTypes, editor.document.blocks);
1101
1349
  const addFromLibrary = (type, beforeId) => {
1102
1350
  const id = editor.addBlock(type, beforeId);
1103
1351
  if (id) editor.requestCanvasSelection(id);
1104
1352
  };
1105
- const getDropBeforeId = (clientY) => {
1106
- const frame = canvasFrameRef.current;
1107
- if (!frame) return void 0;
1108
- const iframe2 = frame.querySelector("iframe");
1109
- const root = iframe2?.contentDocument ?? frame;
1110
- const offsetTop = iframe2?.getBoundingClientRect().top ?? 0;
1111
- const elements = Array.from(root.querySelectorAll("[data-page-document-block-id]"));
1112
- const puckElements = elements.length > 0 ? elements : Array.from(root.querySelectorAll("[data-puck-dnd]"));
1113
- const blockElements = puckElements.length > 0 ? puckElements : Array.from(window.document.querySelectorAll("[data-page-document-block-id], [data-puck-dnd]"));
1114
- const candidates = blockElements.map((element) => ({
1115
- id: element.dataset.pageDocumentBlockId ?? element.dataset.puckDnd,
1116
- top: element.getBoundingClientRect().top,
1117
- height: element.getBoundingClientRect().height
1118
- })).filter((item) => typeof item.id === "string");
1119
- const nearestId = nearestBlockIdAtY(candidates, clientY - offsetTop);
1120
- if (nearestId || candidates.length > 0) return nearestId;
1121
- const rect = frame.getBoundingClientRect();
1122
- if (rect.height <= 0) return void 0;
1123
- return blockIdAtRelativeY(editor.document.blocks.map((block) => block.id), (clientY - rect.top) / rect.height);
1124
- };
1125
- const dropFromLibrary = (event) => {
1126
- event.preventDefault();
1127
- const type = event.dataTransfer.getData("application/x-page-document-block") || draggingLibraryType;
1128
- if (type && blockTypes.includes(type)) addFromLibrary(type, getDropBeforeId(event.clientY));
1353
+ const clearLibraryDrag = () => {
1354
+ draggingBlockIdRef.current = null;
1129
1355
  setDraggingLibraryType(null);
1356
+ setDraggingBlockId(null);
1357
+ setDropTarget(null);
1130
1358
  };
1131
1359
  const cancelLibraryDrop = (event) => {
1132
1360
  event.preventDefault();
1133
1361
  event.stopPropagation();
1134
- setDraggingLibraryType(null);
1362
+ clearLibraryDrag();
1363
+ };
1364
+ const applyListReorder = (sourceId, targetId, edge) => {
1365
+ const beforeId = beforeIdAfterPlacement(editorRef.current.document.blocks.map((block) => block.id), targetId, edge);
1366
+ editorRef.current.reorderBlock(sourceId, beforeId);
1367
+ editorRef.current.requestCanvasSelection(sourceId);
1368
+ scrollCanvasBlockIntoView(sourceId, canvasFrameRef.current);
1135
1369
  };
1136
1370
  const saveBadgeTone = resolvedSaveState === "failed" ? "critical" : resolvedSaveState === "offline" || resolvedSaveState === "dirty" ? "attention" : "success";
1137
1371
  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");
@@ -1141,123 +1375,164 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1141
1375
  else onBack();
1142
1376
  };
1143
1377
  const dismissPublishedNotice = () => setNotice((current) => current === "published" ? null : current);
1144
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1145
- /* @__PURE__ */ jsx7(Puck2, { config, data: engineData, iframe: { enabled: iframe }, onChange: (data) => {
1378
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
1379
+ /* @__PURE__ */ jsx9(Puck2, { config, data: engineData, iframe: { enabled: iframe }, dnd: { behavior: "static" }, overrides: { actionBar: CanvasSelectionActionBar }, onChange: (data) => {
1146
1380
  if (!editor.updateFromCanvas(fromEngineData(data, editor.document, registry))) setCanvasResetVersion((version) => version + 1);
1147
- }, children: /* @__PURE__ */ jsxs5(Puck2.Layout, { children: [
1148
- /* @__PURE__ */ jsx7(CanvasSelectionBridge, { data: engineData, requestedBlockId: editor.canvasSelectionRequest, onCanvasSelected: confirmCanvasSelection, canvasMutationVersion, canvasResetVersion, permissionsVersion: `${editor.isEditable}:${editor.document.blocks.map((block) => `${block.id}:${block.type}`).join("|")}` }),
1149
- /* @__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: [
1150
- /* @__PURE__ */ jsx7("header", { className: "pb-header", children: /* @__PURE__ */ jsxs5("div", { className: "pb-header-content", children: [
1151
- /* @__PURE__ */ jsxs5("div", { className: "pb-header-title-group", children: [
1152
- onBack ? /* @__PURE__ */ jsx7(Button2, { variant: "tertiary", onClick: requestBack, children: i18n.t("back") }) : null,
1153
- /* @__PURE__ */ jsxs5("div", { className: "pb-page-title", children: [
1154
- /* @__PURE__ */ jsx7(Text3, { as: "h1", variant: "headingSm", children: editor.document.settings.seoTitle ?? editor.document.pageId }),
1155
- pageStatus ? /* @__PURE__ */ jsx7(PageStatusCard, { status: pageStatus, sessionState: editor.sessionState }) : null
1381
+ }, children: /* @__PURE__ */ jsxs7(Puck2.Layout, { children: [
1382
+ /* @__PURE__ */ jsx9(CanvasSelectionBridge, { data: engineData, requestedBlockId: editor.canvasSelectionRequest, onCanvasSelected: confirmCanvasSelection, canvasMutationVersion, canvasResetVersion, permissionsVersion: `${editor.isEditable}:${editor.document.blocks.map((block) => `${block.id}:${block.type}`).join("|")}` }),
1383
+ /* @__PURE__ */ jsxs7("div", { className: "pb-shell pb-shell--v04", "data-testid": "page-document-editor", "data-page-id": editor.document.pageId, "data-dirty": editor.isDirty, "data-editor-state": editor.loadState, "data-editor-session-state": editor.sessionState, "data-save-state": resolvedSaveState, "data-left-panel": leftRailOpen ? "open" : "closed", "data-right-panel": rightPanelOpen ? "open" : "closed", children: [
1384
+ /* @__PURE__ */ jsx9("header", { className: "pb-header", children: /* @__PURE__ */ jsxs7("div", { className: "pb-header-content", children: [
1385
+ /* @__PURE__ */ jsxs7("div", { className: "pb-header-title-group", children: [
1386
+ onBack ? /* @__PURE__ */ jsx9(Button2, { variant: "tertiary", onClick: requestBack, children: i18n.t("back") }) : null,
1387
+ /* @__PURE__ */ jsxs7("div", { className: "pb-page-title", children: [
1388
+ /* @__PURE__ */ jsx9(Text3, { as: "h1", variant: "headingSm", children: editor.document.settings.seoTitle ?? editor.document.pageId }),
1389
+ pageStatus ? /* @__PURE__ */ jsx9(PageStatusCard, { status: pageStatus, sessionState: editor.sessionState }) : null
1156
1390
  ] })
1157
1391
  ] }),
1158
- /* @__PURE__ */ jsx7("div", { className: "pb-header-controls", children: /* @__PURE__ */ jsxs5("div", { className: "pb-header-device-toolbar", children: [
1159
- /* @__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)) }),
1160
- /* @__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) }) })
1392
+ /* @__PURE__ */ jsx9("div", { className: "pb-header-controls", children: /* @__PURE__ */ jsxs7("div", { className: "pb-header-device-toolbar", children: [
1393
+ /* @__PURE__ */ jsx9(ButtonGroup2, { variant: "segmented", children: Object.keys(deviceLabels2).map((device) => /* @__PURE__ */ jsx9(Button2, { pressed: editor.device === device, onClick: () => editor.setDevice(device), children: i18n.t(deviceLabels2[device]) }, device)) }),
1394
+ /* @__PURE__ */ jsx9("div", { className: "pb-zoom-control", children: /* @__PURE__ */ jsx9(Select2, { label: i18n.t("zoom"), labelHidden: true, options: [{ label: i18n.t("zoomAuto"), value: "auto" }, { label: "50%", value: "50" }, { label: "70%", value: "70" }, { label: "100%", value: "100" }], value: zoom, onChange: (value) => setZoom(value) }) })
1161
1395
  ] }) }),
1162
- /* @__PURE__ */ jsx7("div", { className: "pb-header-actions", children: /* @__PURE__ */ jsxs5(InlineStack3, { gap: "150", blockAlign: "center", wrap: true, children: [
1163
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("undo"), icon: UndoIcon2, variant: "tertiary", disabled: !editor.actionState.canUndo, onClick: editor.undo }),
1164
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("redo"), icon: RedoIcon2, variant: "tertiary", disabled: !editor.actionState.canRedo, onClick: editor.redo }),
1165
- resolvedSaveState === "saved" ? null : /* @__PURE__ */ jsx7(Badge3, { tone: saveBadgeTone, children: saveBadgeLabel }),
1166
- onHistory ? /* @__PURE__ */ jsx7("span", { "data-editor-history": "", children: /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle", onClick: () => onHistory({ document: editor.document, draftRevision, session: editor.session }), children: i18n.t("history") }) }) : null,
1167
- onPreview ? /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle" || !isOnline, onClick: () => void onPreview({ document: editor.document, draftRevision, session: editor.session }), children: i18n.t("preview") }) : null,
1168
- onAddToStore ? /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle" || pageStatus?.publicationStatus === "unpublished", onClick: () => void onAddToStore({ document: editor.document, session: editor.session }), children: i18n.t("addToStore") }) : null,
1169
- /* @__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") })
1396
+ /* @__PURE__ */ jsx9("div", { className: "pb-header-actions", children: /* @__PURE__ */ jsxs7(InlineStack3, { gap: "150", blockAlign: "center", wrap: true, children: [
1397
+ /* @__PURE__ */ jsx9(Button2, { accessibilityLabel: i18n.t("undo"), icon: UndoIcon2, variant: "tertiary", disabled: !editor.actionState.canUndo, onClick: editor.undo }),
1398
+ /* @__PURE__ */ jsx9(Button2, { accessibilityLabel: i18n.t("redo"), icon: RedoIcon2, variant: "tertiary", disabled: !editor.actionState.canRedo, onClick: editor.redo }),
1399
+ resolvedSaveState === "saved" ? null : /* @__PURE__ */ jsx9(Badge3, { tone: saveBadgeTone, children: saveBadgeLabel }),
1400
+ onHistory ? /* @__PURE__ */ jsx9("span", { "data-editor-history": "", children: /* @__PURE__ */ jsx9(Button2, { disabled: request !== "idle", onClick: () => onHistory({ document: editor.document, draftRevision, session: editor.session }), children: i18n.t("history") }) }) : null,
1401
+ onPreview ? /* @__PURE__ */ jsx9(Button2, { disabled: request !== "idle" || !isOnline, onClick: () => void onPreview({ document: editor.document, draftRevision, session: editor.session }), children: i18n.t("preview") }) : null,
1402
+ onAddToStore ? /* @__PURE__ */ jsx9(Button2, { disabled: request !== "idle" || pageStatus?.publicationStatus === "unpublished", onClick: () => void onAddToStore({ document: editor.document, session: editor.session }), children: i18n.t("addToStore") }) : null,
1403
+ /* @__PURE__ */ jsx9(Button2, { variant: "primary", disabled: !publishAction && !onPublish || request !== "idle" || validationIssues.length > 0 || !isOnline, onClick: () => void publish(), children: request === "publishing" ? i18n.t("publishing") : i18n.t("publish") })
1170
1404
  ] }) })
1171
1405
  ] }) }),
1172
- editor.loadState === "success" ? /* @__PURE__ */ jsx7(Banner, { tone: "success", children: i18n.t("success") }) : null,
1173
- notice && notice !== "published" ? /* @__PURE__ */ jsx7("div", { className: `pb-editor-notice pb-editor-notice--${notice}`, "data-testid": "editor-notice", "data-notice": notice, children: /* @__PURE__ */ jsx7(Banner, { tone: "critical", children: i18n.t(notice) }) }) : null,
1174
- 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: [
1406
+ editor.loadState === "success" ? /* @__PURE__ */ jsx9(Banner, { tone: "success", children: i18n.t("success") }) : null,
1407
+ notice && notice !== "published" ? /* @__PURE__ */ jsx9("div", { className: `pb-editor-notice pb-editor-notice--${notice}`, "data-testid": "editor-notice", "data-notice": notice, children: /* @__PURE__ */ jsx9(Banner, { tone: "critical", children: i18n.t(notice) }) }) : null,
1408
+ validationIssues.length > 0 ? /* @__PURE__ */ jsx9(Banner, { tone: "critical", title: "\u533A\u5757\u5C5E\u6027\u672A\u901A\u8FC7\u6821\u9A8C", children: /* @__PURE__ */ jsx9("ul", { children: validationIssues.map((issue) => /* @__PURE__ */ jsxs7("li", { children: [
1175
1409
  issue.path,
1176
1410
  ": ",
1177
1411
  issue.message
1178
1412
  ] }, `${issue.path}-${issue.message}`)) }) }) : null,
1179
- /* @__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: [
1180
- /* @__PURE__ */ jsxs5("nav", { className: "pb-tool-rail", "aria-label": "\u7F16\u8F91\u5668\u5DE5\u5177", children: [
1181
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("blocks"), icon: LayoutSectionIcon2, pressed: blockView === "blocks" && leftRailOpen, variant: "tertiary", onClick: () => {
1413
+ /* @__PURE__ */ jsxs7("div", { className: `pb-workspace pb-workspace--document${leftRailOpen ? "" : " pb-workspace--left-closed"}${rightPanelOpen ? "" : " pb-workspace--right-closed"}`, "data-left-panel": leftRailOpen ? "open" : "closed", "data-right-panel": rightPanelOpen ? "open" : "closed", children: [
1414
+ /* @__PURE__ */ jsxs7("nav", { className: "pb-tool-rail", "aria-label": "\u7F16\u8F91\u5668\u5DE5\u5177", children: [
1415
+ /* @__PURE__ */ jsx9(Button2, { accessibilityLabel: i18n.t("blocks"), icon: LayoutSectionIcon2, pressed: blockView === "blocks" && leftRailOpen, variant: "tertiary", onClick: () => {
1182
1416
  setBlockView("blocks");
1183
1417
  setLeftRailOpen(true);
1184
1418
  } }),
1185
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("outline"), icon: MenuIcon2, pressed: blockView === "outline" && leftRailOpen, variant: "tertiary", onClick: () => {
1419
+ /* @__PURE__ */ jsx9(Button2, { accessibilityLabel: i18n.t("outline"), icon: MenuIcon2, pressed: blockView === "outline" && leftRailOpen, variant: "tertiary", onClick: () => {
1186
1420
  setBlockView("outline");
1187
1421
  setLeftRailOpen(true);
1188
1422
  } })
1189
1423
  ] }),
1190
- /* @__PURE__ */ jsxs5("aside", { className: `pb-left-panel${leftRailOpen ? "" : " pb-panel--closed"}`, "aria-label": "PageDocument \u533A\u5757", children: [
1191
- /* @__PURE__ */ jsxs5(InlineStack3, { align: "space-between", blockAlign: "center", children: [
1192
- /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingSm", children: blockView === "blocks" ? i18n.t("blocks") : i18n.t("outline") }),
1193
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("collapseLeft"), icon: XIcon2, variant: "tertiary", onClick: () => setLeftRailOpen(false) })
1424
+ /* @__PURE__ */ jsxs7("aside", { className: `pb-left-panel${leftRailOpen ? "" : " pb-panel--closed"}`, "aria-label": "PageDocument \u533A\u5757", children: [
1425
+ /* @__PURE__ */ jsxs7(InlineStack3, { align: "space-between", blockAlign: "center", children: [
1426
+ /* @__PURE__ */ jsx9(Text3, { as: "h2", variant: "headingSm", children: blockView === "blocks" ? i18n.t("blocks") : i18n.t("outline") }),
1427
+ /* @__PURE__ */ jsx9(Button2, { accessibilityLabel: i18n.t("collapseLeft"), icon: XIcon2, variant: "tertiary", onClick: () => setLeftRailOpen(false) })
1194
1428
  ] }),
1195
- 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) => {
1196
- if (!editor.canAddBlock(type)) {
1429
+ blockView === "blocks" ? /* @__PURE__ */ jsx9("div", { className: "pb-block-list", "data-testid": "blocks-view", "aria-label": "\u533A\u5757\u7C7B\u578B\u5E93", role: "list", onDrop: cancelLibraryDrop, children: orderedBlockTypes.map((type) => {
1430
+ const instance = editor.document.blocks.find((item) => item.type === type);
1431
+ const canAdd = editor.canAddBlock(type);
1432
+ const canReorder = Boolean(instance && editor.canDragBlock(instance.id));
1433
+ const draggable = canAdd || canReorder;
1434
+ const selected = editor.selectedBlock?.type === type;
1435
+ const dropEdge = draggingBlockId && instance && dropTarget?.id === instance.id ? dropTarget.edge : void 0;
1436
+ const actionHint = canReorder && canAdd ? "\uFF0C\u62D6\u62FD\u6392\u5E8F\uFF0C\u6216\u62D6\u62FD\u81F3\u753B\u5E03\u4EE5\u6DFB\u52A0" : canReorder ? "\uFF0C\u62D6\u62FD\u6392\u5E8F" : canAdd ? "\uFF0C\u62D6\u62FD\u81F3\u753B\u5E03\u4EE5\u6DFB\u52A0" : "";
1437
+ return /* @__PURE__ */ jsxs7("div", { className: `pb-document-block-row pb-document-block-row--library${selected ? " pb-document-block-row--selected" : ""}${draggingBlockId === instance?.id ? " pb-document-block-row--dragging" : ""}${dropEdge === "before" ? " pb-document-block-row--drop-before" : ""}${dropEdge === "after" ? " pb-document-block-row--drop-after" : ""}`, "data-block-type": type, "data-selected": selected, "data-drop-edge": dropEdge, role: "listitem", draggable, "aria-disabled": !draggable, "aria-label": `${blockTypeLabel(type, registry)}${actionHint}${selected ? "\uFF0C\u5F53\u524D\u9009\u4E2D\u7C7B\u578B" : ""}`, onClick: () => {
1438
+ if (!instance) return;
1439
+ editor.requestCanvasSelection(instance.id);
1440
+ scrollCanvasBlockIntoView(instance.id, canvasFrameRef.current);
1441
+ }, onDragStart: (event) => {
1442
+ if (!draggable) {
1443
+ event.preventDefault();
1444
+ return;
1445
+ }
1446
+ event.dataTransfer.setData("application/x-page-document-block", type);
1447
+ if (instance && canReorder) {
1448
+ event.dataTransfer.setData("application/x-page-document-block-id", instance.id);
1449
+ event.dataTransfer.effectAllowed = canAdd ? "copyMove" : "move";
1450
+ draggingBlockIdRef.current = instance.id;
1451
+ setDraggingBlockId(instance.id);
1452
+ } else event.dataTransfer.effectAllowed = "copy";
1453
+ if (canAdd) setDraggingLibraryType(type);
1454
+ }, onDragOver: (event) => {
1455
+ const sourceId = draggingBlockIdRef.current;
1456
+ if (!sourceId || !instance || sourceId === instance.id) return;
1457
+ event.preventDefault();
1458
+ event.stopPropagation();
1459
+ event.dataTransfer.dropEffect = "move";
1460
+ const rect = event.currentTarget.getBoundingClientRect();
1461
+ setDropTarget({ id: instance.id, edge: dropPlacement(event.clientY, rect.top, rect.height) });
1462
+ }, onDragLeave: (event) => {
1463
+ if (instance && !event.currentTarget.contains(event.relatedTarget)) setDropTarget((current) => current?.id === instance.id ? null : current);
1464
+ }, onDrop: (event) => {
1465
+ const sourceId = draggingBlockIdRef.current;
1466
+ if (!sourceId || !instance || sourceId === instance.id) return;
1197
1467
  event.preventDefault();
1198
- return;
1199
- }
1200
- event.dataTransfer.setData("application/x-page-document-block", type);
1201
- event.dataTransfer.effectAllowed = "copy";
1202
- setDraggingLibraryType(type);
1203
- }, onDragEnd: () => setDraggingLibraryType(null), children: [
1204
- /* @__PURE__ */ jsxs5("span", { className: "pb-library-block-title", children: [
1205
- /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockTypeLabel(type, registry) }),
1206
- /* @__PURE__ */ jsx7("span", { className: "pb-library-block-drag-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsx7(DragHandleIcon2, {}) })
1207
- ] }),
1208
- /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", tone: "subdued", children: type })
1209
- ] }, 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: [
1210
- /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockLabel(block, registry) }),
1211
- /* @__PURE__ */ jsx7(Text3, { as: "span", variant: "bodySm", tone: "subdued", children: block.id })
1468
+ event.stopPropagation();
1469
+ const rect = event.currentTarget.getBoundingClientRect();
1470
+ applyListReorder(sourceId, instance.id, dropPlacement(event.clientY, rect.top, rect.height));
1471
+ clearLibraryDrag();
1472
+ }, onDragEnd: clearLibraryDrag, children: [
1473
+ /* @__PURE__ */ jsxs7("span", { className: "pb-library-block-title", children: [
1474
+ /* @__PURE__ */ jsx9(Text3, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockTypeLabel(type, registry) }),
1475
+ /* @__PURE__ */ jsx9("span", { className: "pb-library-block-drag-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsx9(DragHandleIcon2, {}) })
1476
+ ] }),
1477
+ /* @__PURE__ */ jsx9(Text3, { as: "span", variant: "bodySm", tone: "subdued", children: type })
1478
+ ] }, type);
1479
+ }) }) : editor.document.blocks.length === 0 ? /* @__PURE__ */ jsx9(Text3, { as: "p", tone: "subdued", children: i18n.t("empty") }) : /* @__PURE__ */ jsx9("div", { className: "pb-block-list", "data-testid": "outline-view", children: editor.document.blocks.map((block) => /* @__PURE__ */ jsxs7("button", { type: "button", className: `pb-document-block-row pb-document-block-row--library ${block.id === editor.selectedBlockId ? "pb-document-block-row--selected" : ""}`, "aria-pressed": block.id === editor.selectedBlockId, onClick: () => {
1480
+ editor.requestCanvasSelection(block.id);
1481
+ scrollCanvasBlockIntoView(block.id, canvasFrameRef.current);
1482
+ }, children: [
1483
+ /* @__PURE__ */ jsx9(Text3, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockLabel(block, registry) }),
1484
+ /* @__PURE__ */ jsx9(Text3, { as: "span", variant: "bodySm", tone: "subdued", children: block.id })
1212
1485
  ] }, block.id)) })
1213
1486
  ] }),
1214
- /* @__PURE__ */ jsxs5("main", { className: "pb-canvas-area", children: [
1215
- /* @__PURE__ */ jsxs5("div", { className: "pb-canvas-stage", children: [
1216
- /* @__PURE__ */ jsx7("div", { ref: canvasFrameRef, className: `pb-canvas-frame pb-canvas-frame--${editor.device}${zoom === "auto" ? "" : ` pb-canvas-frame--zoom-${zoom}`}`, "data-device": editor.device, "data-zoom": zoom, style: canvasThemeStyle, children: /* @__PURE__ */ jsx7(Puck2.Preview, {}) }),
1217
- 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: [
1218
- "\u677E\u5F00\u4EE5\u6DFB\u52A0 ",
1219
- blockTypeLabel(draggingLibraryType, registry)
1220
- ] }) : null,
1221
- editor.selectedBlock ? /* @__PURE__ */ jsxs5("div", { className: "pb-canvas-overlay", "aria-label": `\u5DF2\u9009\u62E9 ${blockLabel(editor.selectedBlock, registry)}`, children: [
1222
- /* @__PURE__ */ jsx7("span", { children: blockLabel(editor.selectedBlock, registry) }),
1223
- /* @__PURE__ */ jsx7("span", { children: "Selected" })
1224
- ] }) : null
1487
+ /* @__PURE__ */ jsxs7("main", { className: "pb-canvas-area", children: [
1488
+ /* @__PURE__ */ jsxs7("div", { className: "pb-canvas-stage", children: [
1489
+ /* @__PURE__ */ jsx9("div", { ref: canvasFrameRef, className: `pb-canvas-frame pb-canvas-frame--${editor.device}${zoom === "auto" ? "" : ` pb-canvas-frame--zoom-${zoom}`}`, "data-device": editor.device, "data-zoom": zoom, style: canvasThemeStyle, children: /* @__PURE__ */ jsx9(Puck2.Preview, {}) }),
1490
+ draggingLibraryType ? /* @__PURE__ */ jsx9(CanvasLibraryDropTarget, { frameRef: canvasFrameRef, blocks: editor.document.blocks.map((block) => ({ id: block.id, label: blockLabel(block, registry) })), label: blockTypeLabel(draggingLibraryType, registry), adminLocale, onDrop: (beforeId) => {
1491
+ addFromLibrary(draggingLibraryType, beforeId);
1492
+ setDraggingLibraryType(null);
1493
+ }, onCancel: () => setDraggingLibraryType(null) }) : null
1225
1494
  ] }),
1226
- !leftRailOpen || !rightPanelOpen ? /* @__PURE__ */ jsxs5("div", { className: "pb-collapsed-actions", children: [
1227
- !leftRailOpen ? /* @__PURE__ */ jsx7(Button2, { onClick: () => setLeftRailOpen(true), children: i18n.t("expandLeft") }) : null,
1228
- !rightPanelOpen ? /* @__PURE__ */ jsx7(Button2, { onClick: () => setRightPanelOpen(true), children: i18n.t("expandRight") }) : null
1495
+ !leftRailOpen || !rightPanelOpen ? /* @__PURE__ */ jsxs7("div", { className: "pb-collapsed-actions", children: [
1496
+ !leftRailOpen ? /* @__PURE__ */ jsx9(Button2, { onClick: () => setLeftRailOpen(true), children: i18n.t("expandLeft") }) : null,
1497
+ !rightPanelOpen ? /* @__PURE__ */ jsx9(Button2, { onClick: () => setRightPanelOpen(true), children: i18n.t("expandRight") }) : null
1229
1498
  ] }) : null
1230
1499
  ] }),
1231
- /* @__PURE__ */ jsxs5("aside", { className: `pb-right-panel${rightPanelOpen ? "" : " pb-panel--closed"}`, "aria-label": "PageDocument \u5C5E\u6027", children: [
1232
- /* @__PURE__ */ jsxs5(InlineStack3, { align: "space-between", blockAlign: "center", children: [
1233
- /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingSm", children: i18n.t("properties") }),
1234
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("collapseRight"), icon: XIcon2, variant: "tertiary", onClick: () => setRightPanelOpen(false) })
1500
+ /* @__PURE__ */ jsxs7("aside", { className: `pb-right-panel${rightPanelOpen ? "" : " pb-panel--closed"}`, "aria-label": "PageDocument \u5C5E\u6027", children: [
1501
+ /* @__PURE__ */ jsxs7(InlineStack3, { align: "space-between", blockAlign: "center", children: [
1502
+ /* @__PURE__ */ jsx9(Text3, { as: "h2", variant: "headingSm", children: i18n.t("properties") }),
1503
+ /* @__PURE__ */ jsx9(Button2, { accessibilityLabel: i18n.t("collapseRight"), icon: XIcon2, variant: "tertiary", onClick: () => setRightPanelOpen(false) })
1235
1504
  ] }),
1236
- editor.selectedBlock ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1237
- /* @__PURE__ */ jsx7(DocumentInspector, { settings: inspectorSettings?.[editor.selectedBlock.id] ?? inspectorSettings?.[editor.selectedBlock.type], pageId: editor.document.pageId, assetPicker, productPicker, i18n, block: editor.selectedBlock, registry, disabled: !editor.actionState.canEdit, appearanceControls, onChange: (props) => editor.updateBlockProps(editor.selectedBlock.id, props), onPresentationChange: (presentation) => updateBlockPresentation(editor.selectedBlock.id, presentation) }, editor.selectedBlock.id),
1238
- /* @__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) })
1239
- ] }) : /* @__PURE__ */ jsx7(Text3, { as: "p", tone: "subdued", children: i18n.t("selectBlock") })
1505
+ editor.selectedBlock ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
1506
+ /* @__PURE__ */ jsx9(DocumentInspector, { settings: inspectorSettings?.[editor.selectedBlock.id] ?? inspectorSettings?.[editor.selectedBlock.type], pageId: editor.document.pageId, assetPicker, productPicker, i18n, block: editor.selectedBlock, registry, disabled: !editor.actionState.canEdit, appearanceControls, onChange: (props) => editor.updateBlockProps(editor.selectedBlock.id, props), onPresentationChange: (presentation) => updateBlockPresentation(editor.selectedBlock.id, presentation) }, editor.selectedBlock.id),
1507
+ /* @__PURE__ */ jsx9(InspectorActions, { block: editor.selectedBlock, canDuplicate: editor.actionState.canDuplicate, canDelete: editor.actionState.canDelete, canMove: editor.actionState.canReorder, canMoveUp: editor.document.blocks[0]?.id !== editor.selectedBlock.id, canMoveDown: editor.document.blocks.at(-1)?.id !== editor.selectedBlock.id, i18n, onDuplicate: () => editor.duplicateBlock(editor.selectedBlock.id), onDelete: () => editor.requestDeleteBlock(editor.selectedBlock.id), onMove: (direction) => editor.moveBlock(editor.selectedBlock.id, direction) })
1508
+ ] }) : /* @__PURE__ */ jsx9(Text3, { as: "p", tone: "subdued", children: i18n.t("selectBlock") })
1240
1509
  ] })
1241
1510
  ] }),
1242
- editor.pendingDeleteBlock ? /* @__PURE__ */ jsx7(DeleteConfirmation, { block: editor.pendingDeleteBlock, config: deleteConfirmation, i18n, onCancel: editor.cancelDeleteBlock, onConfirm: editor.confirmDeleteBlock }) : null,
1243
- confirmBack ? /* @__PURE__ */ jsx7(LeaveConfirmation, { i18n, onCancel: () => setConfirmBack(false), onConfirm: () => {
1511
+ editor.pendingDeleteBlock ? /* @__PURE__ */ jsx9(DeleteConfirmation, { block: editor.pendingDeleteBlock, config: deleteConfirmation, i18n, onCancel: editor.cancelDeleteBlock, onConfirm: editor.confirmDeleteBlock }) : null,
1512
+ confirmBack ? /* @__PURE__ */ jsx9(LeaveConfirmation, { i18n, onCancel: () => setConfirmBack(false), onConfirm: () => {
1244
1513
  setConfirmBack(false);
1245
1514
  onBack?.();
1246
1515
  } }) : null
1247
1516
  ] })
1248
1517
  ] }) }),
1249
- /* @__PURE__ */ jsx7("div", { className: "pb-toast-host", ...notice === "published" ? { "data-testid": "editor-notice", "data-notice": "published" } : {}, children: /* @__PURE__ */ jsx7(Frame, { children: notice === "published" ? /* @__PURE__ */ jsx7(Toast, { content: i18n.t("publishSuccessTitle"), onDismiss: dismissPublishedNotice }) : null }) })
1518
+ /* @__PURE__ */ jsx9("div", { className: "pb-toast-host", ...notice === "published" ? { "data-testid": "editor-notice", "data-notice": "published" } : {}, children: /* @__PURE__ */ jsx9(Frame, { children: notice === "published" ? /* @__PURE__ */ jsx9(Toast, { content: i18n.t("publishSuccessTitle"), onDismiss: dismissPublishedNotice }) : null }) })
1250
1519
  ] });
1251
1520
  }
1521
+ function CanvasSelectionActionBar({ label, children, parentAction }) {
1522
+ void label;
1523
+ void children;
1524
+ void parentAction;
1525
+ return /* @__PURE__ */ jsx9(Fragment2, {});
1526
+ }
1252
1527
  function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canvasMutationVersion, canvasResetVersion, permissionsVersion }) {
1253
1528
  const puck = usePuck();
1254
- const lastSelectedId = useRef4(null);
1255
- const lastSyncedData = useRef4(null);
1256
- const lastCanvasMutationVersion = useRef4(0);
1257
- const lastCanvasResetVersion = useRef4(0);
1529
+ const lastSelectedId = useRef5(null);
1530
+ const lastSyncedData = useRef5(null);
1531
+ const lastCanvasMutationVersion = useRef5(0);
1532
+ const lastCanvasResetVersion = useRef5(0);
1258
1533
  const serializedData = JSON.stringify(data);
1259
1534
  const refreshPermissions = puck.refreshPermissions;
1260
- useEffect3(() => {
1535
+ useEffect4(() => {
1261
1536
  const forceReset = canvasResetVersion > lastCanvasResetVersion.current;
1262
1537
  if (lastSyncedData.current === serializedData && !forceReset) return;
1263
1538
  lastSyncedData.current = serializedData;
@@ -1268,16 +1543,17 @@ function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canva
1268
1543
  }
1269
1544
  puck.dispatch({ type: "setData", data });
1270
1545
  }, [canvasMutationVersion, canvasResetVersion, data, puck, serializedData]);
1271
- useEffect3(() => {
1546
+ useEffect4(() => {
1272
1547
  refreshPermissions?.({}, true);
1273
1548
  }, [permissionsVersion, refreshPermissions]);
1274
- useEffect3(() => {
1549
+ useEffect4(() => {
1275
1550
  if (!requestedBlockId) return;
1276
1551
  const selector = puck.getSelectorForId(requestedBlockId);
1277
1552
  if (selector) puck.dispatch({ type: "setUi", ui: { itemSelector: selector } });
1553
+ scrollCanvasBlockIntoView(requestedBlockId);
1278
1554
  }, [puck, requestedBlockId]);
1279
1555
  const selectedId = typeof puck.selectedItem?.props.id === "string" ? puck.selectedItem.props.id : null;
1280
- useEffect3(() => {
1556
+ useEffect4(() => {
1281
1557
  if (selectedId && lastSelectedId.current !== selectedId) {
1282
1558
  lastSelectedId.current = selectedId;
1283
1559
  onCanvasSelected(selectedId);
@@ -1289,32 +1565,32 @@ function EditorStatus({ state }) {
1289
1565
  const i18n = createAdminI18n();
1290
1566
  const tone = state === "error" ? "critical" : state === "disabled" ? "warning" : "info";
1291
1567
  const message = state === "loading" ? i18n.t("loading") : state === "empty" ? i18n.t("empty") : state === "error" ? i18n.t("error") : i18n.t("disabled");
1292
- 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 }) });
1568
+ return /* @__PURE__ */ jsx9("div", { className: "pb-editor-status", "data-testid": "page-document-editor-state", "data-editor-state": state, children: /* @__PURE__ */ jsx9(Banner, { tone, title: message, children: state === "disabled" ? i18n.t("disabled") : message }) });
1293
1569
  }
1294
1570
  function EditorSessionStatus({ state, message }) {
1295
1571
  const i18n = createAdminI18n();
1296
1572
  const tone = state === "locked" ? "warning" : state === "readonly" ? "info" : "critical";
1297
1573
  const title = state === "acquiring" ? i18n.t("acquiringLock") : state === "locked" ? i18n.t("locked") : state === "readonly" ? i18n.t("readonly") : i18n.t("lockLost");
1298
- 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 }) });
1574
+ return /* @__PURE__ */ jsx9("div", { className: "pb-editor-status", "data-testid": "page-document-editor-session-state", "data-editor-session-state": state, children: /* @__PURE__ */ jsx9(Banner, { tone, title, children: message ?? title }) });
1299
1575
  }
1300
1576
  function InspectorSection({ title, children, defaultOpen = true }) {
1301
- return /* @__PURE__ */ jsxs5("details", { className: "pb-inspector-section", open: defaultOpen, children: [
1302
- /* @__PURE__ */ jsxs5("summary", { children: [
1303
- /* @__PURE__ */ jsx7("span", { children: title }),
1304
- /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\u2304" })
1577
+ return /* @__PURE__ */ jsxs7("details", { className: "pb-inspector-section", open: defaultOpen, children: [
1578
+ /* @__PURE__ */ jsxs7("summary", { children: [
1579
+ /* @__PURE__ */ jsx9("span", { children: title }),
1580
+ /* @__PURE__ */ jsx9("span", { "aria-hidden": "true", children: "\u2304" })
1305
1581
  ] }),
1306
- /* @__PURE__ */ jsx7("div", { className: "pb-inspector-section__body", children })
1582
+ /* @__PURE__ */ jsx9("div", { className: "pb-inspector-section__body", children })
1307
1583
  ] });
1308
1584
  }
1309
1585
  function InspectorTextControl({ label, value, control, maxLength, disabled, onChange }) {
1310
1586
  const stringValue = typeof value === "string" ? value : "";
1311
- 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) });
1587
+ if (control === "color") return /* @__PURE__ */ jsx9("input", { "aria-label": label, type: "color", value: /^#[\da-f]{6}$/i.test(stringValue) ? stringValue : "#000000", disabled, onChange: (event) => onChange(event.currentTarget.value) });
1312
1588
  const length = Array.from(stringValue).length;
1313
- const field = /* @__PURE__ */ jsx7(TextField, { label, labelHidden: true, value: stringValue, onChange: (next) => onChange(maxLength === void 0 ? next : Array.from(next).slice(0, maxLength).join("")), autoComplete: "off", disabled, multiline: control === "textarea" ? 4 : false, type: control === "url" ? "url" : "text", maxLength });
1589
+ const field = /* @__PURE__ */ jsx9(TextField, { label, labelHidden: true, value: stringValue, onChange: (next) => onChange(maxLength === void 0 ? next : Array.from(next).slice(0, maxLength).join("")), autoComplete: "off", disabled, multiline: control === "textarea" ? 4 : false, type: control === "url" ? "url" : "text", maxLength });
1314
1590
  if (maxLength === void 0) return field;
1315
- return /* @__PURE__ */ jsxs5("div", { className: "pb-inspector-field__limited", children: [
1591
+ return /* @__PURE__ */ jsxs7("div", { className: "pb-inspector-field__limited", children: [
1316
1592
  field,
1317
- /* @__PURE__ */ jsxs5("span", { className: "pb-inspector-field__limit", "aria-label": `Limit ${maxLength}`, children: [
1593
+ /* @__PURE__ */ jsxs7("span", { className: "pb-inspector-field__limit", "aria-label": `Limit ${maxLength}`, children: [
1318
1594
  length,
1319
1595
  "/",
1320
1596
  maxLength
@@ -1322,20 +1598,20 @@ function InspectorTextControl({ label, value, control, maxLength, disabled, onCh
1322
1598
  ] });
1323
1599
  }
1324
1600
  function InspectorAssetControl({ value, disabled, picker, pageId, blockId, i18n, onChange }) {
1325
- const [busy, setBusy] = useState3(false);
1326
- const [error, setError] = useState3(null);
1327
- const active = useRef4(true);
1328
- useEffect3(() => {
1601
+ const [busy, setBusy] = useState4(false);
1602
+ const [error, setError] = useState4(null);
1603
+ const active = useRef5(true);
1604
+ useEffect4(() => {
1329
1605
  active.current = !disabled;
1330
1606
  return () => {
1331
1607
  active.current = false;
1332
1608
  };
1333
1609
  }, [disabled]);
1334
1610
  const url = typeof value === "string" && validateFieldValue({ field: "", control: "asset" }, value).length === 0 ? value : "";
1335
- return /* @__PURE__ */ jsxs5("div", { className: "pb-asset-picker", "data-testid": "asset-picker", children: [
1336
- url ? /* @__PURE__ */ jsx7("img", { src: url, alt: "", className: "pb-asset-picker__preview" }) : /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: i18n.t("emptyAsset") }),
1337
- url ? /* @__PURE__ */ jsx7(Button2, { fullWidth: true, variant: "tertiary", disabled: disabled || busy, onClick: () => onChange(""), children: i18n.t("removeAsset") }) : null,
1338
- /* @__PURE__ */ jsx7(Button2, { fullWidth: true, disabled: disabled || busy || !picker, onClick: () => void (async () => {
1611
+ return /* @__PURE__ */ jsxs7("div", { className: "pb-asset-picker", "data-testid": "asset-picker", children: [
1612
+ url ? /* @__PURE__ */ jsx9("img", { src: url, alt: "", className: "pb-asset-picker__preview" }) : /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: i18n.t("emptyAsset") }),
1613
+ url ? /* @__PURE__ */ jsx9(Button2, { fullWidth: true, variant: "tertiary", disabled: disabled || busy, onClick: () => onChange(""), children: i18n.t("removeAsset") }) : null,
1614
+ /* @__PURE__ */ jsx9(Button2, { fullWidth: true, disabled: disabled || busy || !picker, onClick: () => void (async () => {
1339
1615
  if (!picker) return;
1340
1616
  setBusy(true);
1341
1617
  setError(null);
@@ -1351,20 +1627,20 @@ function InspectorAssetControl({ value, disabled, picker, pageId, blockId, i18n,
1351
1627
  setBusy(false);
1352
1628
  }
1353
1629
  })(), children: busy ? i18n.t("selectingAsset") : i18n.t("selectAsset") }),
1354
- error ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: error }) : null
1630
+ error ? /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "critical", children: error }) : null
1355
1631
  ] });
1356
1632
  }
1357
1633
  function InspectorProductsControl({ value, disabled, picker, pageId, blockId, i18n, onChange }) {
1358
1634
  const products = parseProductReferences(value);
1359
- const [busy, setBusy] = useState3(false);
1360
- const [error, setError] = useState3(null);
1635
+ const [busy, setBusy] = useState4(false);
1636
+ const [error, setError] = useState4(null);
1361
1637
  const preview = products.slice(0, 4);
1362
- return /* @__PURE__ */ jsxs5("div", { className: "pb-product-picker", "data-testid": "product-picker", children: [
1363
- /* @__PURE__ */ jsxs5("div", { className: "pb-product-picker__summary", children: [
1364
- preview.length ? /* @__PURE__ */ jsx7("span", { className: "pb-product-picker__thumbs", "aria-hidden": "true", children: preview.map((product, index) => /* @__PURE__ */ jsx7("span", { className: "pb-product-picker__thumb", style: { zIndex: preview.length - index }, children: product.imageUrl ? /* @__PURE__ */ jsx7("img", { src: product.imageUrl, alt: "" }) : /* @__PURE__ */ jsx7("span", { className: "pb-product-picker__thumb-fallback" }) }, product.id)) }) : null,
1365
- /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", children: products.length ? `${products.length} ${i18n.t("productCountSuffix")}` : i18n.t("emptyProducts") })
1638
+ return /* @__PURE__ */ jsxs7("div", { className: "pb-product-picker", "data-testid": "product-picker", children: [
1639
+ /* @__PURE__ */ jsxs7("div", { className: "pb-product-picker__summary", children: [
1640
+ preview.length ? /* @__PURE__ */ jsx9("span", { className: "pb-product-picker__thumbs", "aria-hidden": "true", children: preview.map((product, index) => /* @__PURE__ */ jsx9("span", { className: "pb-product-picker__thumb", style: { zIndex: preview.length - index }, children: product.imageUrl ? /* @__PURE__ */ jsx9("img", { src: product.imageUrl, alt: "" }) : /* @__PURE__ */ jsx9("span", { className: "pb-product-picker__thumb-fallback" }) }, product.id)) }) : null,
1641
+ /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", children: products.length ? `${products.length} ${i18n.t("productCountSuffix")}` : i18n.t("emptyProducts") })
1366
1642
  ] }),
1367
- /* @__PURE__ */ jsx7(Button2, { fullWidth: true, icon: ProductIcon, variant: "secondary", disabled: disabled || busy || !picker, onClick: () => void (async () => {
1643
+ /* @__PURE__ */ jsx9(Button2, { fullWidth: true, icon: ProductIcon, variant: "secondary", disabled: disabled || busy || !picker, onClick: () => void (async () => {
1368
1644
  if (!picker) return;
1369
1645
  setBusy(true);
1370
1646
  setError(null);
@@ -1377,50 +1653,50 @@ function InspectorProductsControl({ value, disabled, picker, pageId, blockId, i1
1377
1653
  setBusy(false);
1378
1654
  }
1379
1655
  })(), children: busy ? i18n.t("selectingProducts") : i18n.t("selectProducts") }),
1380
- error ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: error }) : null
1656
+ error ? /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "critical", children: error }) : null
1381
1657
  ] });
1382
1658
  }
1383
1659
  function InspectorField({ name, field, value, registry, disabled, assetPicker, productPicker, pageId, blockId, i18n, onChange }) {
1384
1660
  const label = field.label ?? name;
1385
1661
  const issues = validateFieldValue(field, value);
1386
1662
  const Field = registry?.getField(field.field)?.component;
1387
- return /* @__PURE__ */ jsxs5("div", { className: "pb-inspector-field", "data-control": field.control ?? "custom", children: [
1388
- /* @__PURE__ */ jsxs5("div", { className: "pb-inspector-field__heading", children: [
1389
- /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", fontWeight: "semibold", children: label }),
1390
- field.description ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: field.description }) : null
1663
+ return /* @__PURE__ */ jsxs7("div", { className: "pb-inspector-field", "data-control": field.control ?? "custom", children: [
1664
+ /* @__PURE__ */ jsxs7("div", { className: "pb-inspector-field__heading", children: [
1665
+ /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", fontWeight: "semibold", children: label }),
1666
+ field.description ? /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: field.description }) : null
1391
1667
  ] }),
1392
- field.control === "products" ? /* @__PURE__ */ jsx7(InspectorProductsControl, { value, disabled, picker: productPicker, pageId, blockId, i18n, onChange }) : field.control === "asset" ? /* @__PURE__ */ jsx7(InspectorAssetControl, { value, disabled, picker: assetPicker, pageId, blockId, i18n, onChange }) : field.control ? /* @__PURE__ */ jsx7(InspectorTextControl, { label, value, control: field.control, maxLength: field.validation?.maxLength, disabled, onChange: (next) => onChange(next) }) : Field ? /* @__PURE__ */ jsx7(Field, { value, onChange }) : null,
1393
- issues.map((issue) => /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: issue.message }, issue.message))
1668
+ field.control === "products" ? /* @__PURE__ */ jsx9(InspectorProductsControl, { value, disabled, picker: productPicker, pageId, blockId, i18n, onChange }) : field.control === "asset" ? /* @__PURE__ */ jsx9(InspectorAssetControl, { value, disabled, picker: assetPicker, pageId, blockId, i18n, onChange }) : field.control ? /* @__PURE__ */ jsx9(InspectorTextControl, { label, value, control: field.control, maxLength: field.validation?.maxLength, disabled, onChange: (next) => onChange(next) }) : Field ? /* @__PURE__ */ jsx9(Field, { value, onChange }) : null,
1669
+ issues.map((issue) => /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "critical", children: issue.message }, issue.message))
1394
1670
  ] });
1395
1671
  }
1396
1672
  function InspectorActions({ block, canDuplicate, canDelete, canMove, canMoveUp, canMoveDown, i18n, onDuplicate, onDelete, onMove }) {
1397
- return /* @__PURE__ */ jsxs5("div", { className: "pb-inspector-actions", "aria-label": `Actions for ${block.id}`, children: [
1398
- /* @__PURE__ */ jsxs5(ButtonGroup2, { children: [
1399
- /* @__PURE__ */ jsx7(Button2, { disabled: !canMove || !canMoveUp, onClick: () => onMove(-1), children: i18n.t("moveUp") }),
1400
- /* @__PURE__ */ jsx7(Button2, { disabled: !canMove || !canMoveDown, onClick: () => onMove(1), children: i18n.t("moveDown") }),
1401
- /* @__PURE__ */ jsx7(Button2, { disabled: !canDuplicate, accessibilityLabel: canDuplicate ? i18n.t("duplicate") : i18n.t("duplicateUnavailable"), onClick: onDuplicate, children: i18n.t("duplicate") }),
1402
- /* @__PURE__ */ jsx7(Button2, { disabled: !canDelete, tone: "critical", onClick: onDelete, children: i18n.t("remove") })
1673
+ return /* @__PURE__ */ jsxs7("div", { className: "pb-inspector-actions", "aria-label": `Actions for ${block.id}`, children: [
1674
+ /* @__PURE__ */ jsxs7(ButtonGroup2, { children: [
1675
+ /* @__PURE__ */ jsx9(Button2, { disabled: !canMove || !canMoveUp, onClick: () => onMove(-1), children: i18n.t("moveUp") }),
1676
+ /* @__PURE__ */ jsx9(Button2, { disabled: !canMove || !canMoveDown, onClick: () => onMove(1), children: i18n.t("moveDown") }),
1677
+ /* @__PURE__ */ jsx9(Button2, { disabled: !canDuplicate, accessibilityLabel: canDuplicate ? i18n.t("duplicate") : i18n.t("duplicateUnavailable"), onClick: onDuplicate, children: i18n.t("duplicate") }),
1678
+ /* @__PURE__ */ jsx9(Button2, { disabled: !canDelete, tone: "critical", onClick: onDelete, children: i18n.t("remove") })
1403
1679
  ] }),
1404
- !canDuplicate ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: i18n.t("duplicateUnavailable") }) : null
1680
+ !canDuplicate ? /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: i18n.t("duplicateUnavailable") }) : null
1405
1681
  ] });
1406
1682
  }
1407
1683
  function DeleteConfirmation({ block, config, i18n, onCancel, onConfirm }) {
1408
- 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: [
1409
- /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingMd", id: "pb-delete-confirmation-title", children: config?.title ?? i18n.t("confirmDeleteTitle") }),
1410
- /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodyMd", children: config?.message?.(block) ?? i18n.t("confirmDeleteMessage") }),
1411
- /* @__PURE__ */ jsxs5(ButtonGroup2, { children: [
1412
- /* @__PURE__ */ jsx7(Button2, { onClick: onCancel, children: config?.cancelLabel ?? i18n.t("cancel") }),
1413
- /* @__PURE__ */ jsx7(Button2, { tone: "critical", onClick: onConfirm, children: config?.confirmLabel ?? i18n.t("confirm") })
1684
+ return /* @__PURE__ */ jsx9("div", { className: "pb-delete-confirmation-backdrop", role: "presentation", children: /* @__PURE__ */ jsxs7("section", { className: "pb-delete-confirmation", role: "dialog", "aria-modal": "true", "aria-labelledby": "pb-delete-confirmation-title", children: [
1685
+ /* @__PURE__ */ jsx9(Text3, { as: "h2", variant: "headingMd", id: "pb-delete-confirmation-title", children: config?.title ?? i18n.t("confirmDeleteTitle") }),
1686
+ /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodyMd", children: config?.message?.(block) ?? i18n.t("confirmDeleteMessage") }),
1687
+ /* @__PURE__ */ jsxs7(ButtonGroup2, { children: [
1688
+ /* @__PURE__ */ jsx9(Button2, { onClick: onCancel, children: config?.cancelLabel ?? i18n.t("cancel") }),
1689
+ /* @__PURE__ */ jsx9(Button2, { tone: "critical", onClick: onConfirm, children: config?.confirmLabel ?? i18n.t("confirm") })
1414
1690
  ] })
1415
1691
  ] }) });
1416
1692
  }
1417
1693
  function LeaveConfirmation({ i18n, onCancel, onConfirm }) {
1418
- 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: [
1419
- /* @__PURE__ */ jsx7(Text3, { as: "h2", variant: "headingMd", id: "pb-leave-confirmation-title", children: i18n.t("confirmLeaveTitle") }),
1420
- /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodyMd", children: i18n.t("leaveWarning") }),
1421
- /* @__PURE__ */ jsxs5(ButtonGroup2, { children: [
1422
- /* @__PURE__ */ jsx7(Button2, { onClick: onCancel, children: i18n.t("cancel") }),
1423
- /* @__PURE__ */ jsx7(Button2, { tone: "critical", onClick: onConfirm, children: i18n.t("leave") })
1694
+ return /* @__PURE__ */ jsx9("div", { className: "pb-delete-confirmation-backdrop", role: "presentation", children: /* @__PURE__ */ jsxs7("section", { className: "pb-delete-confirmation", role: "dialog", "aria-modal": "true", "aria-labelledby": "pb-leave-confirmation-title", children: [
1695
+ /* @__PURE__ */ jsx9(Text3, { as: "h2", variant: "headingMd", id: "pb-leave-confirmation-title", children: i18n.t("confirmLeaveTitle") }),
1696
+ /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodyMd", children: i18n.t("leaveWarning") }),
1697
+ /* @__PURE__ */ jsxs7(ButtonGroup2, { children: [
1698
+ /* @__PURE__ */ jsx9(Button2, { onClick: onCancel, children: i18n.t("cancel") }),
1699
+ /* @__PURE__ */ jsx9(Button2, { tone: "critical", onClick: onConfirm, children: i18n.t("leave") })
1424
1700
  ] })
1425
1701
  ] }) });
1426
1702
  }
@@ -1432,8 +1708,8 @@ function inspectorFieldConfig(name, field) {
1432
1708
  }
1433
1709
  var appearanceTokens = [["color.primary", "\u4E3B\u8272"], ["color.surface", "\u8868\u9762\u8272"], ["radius", "\u5706\u89D2"], ["spacing", "\u95F4\u8DDD"]];
1434
1710
  function DocumentInspector({ settings, pageId, assetPicker, productPicker, i18n, block, registry, disabled, appearanceControls, onChange, onPresentationChange }) {
1435
- const [assetError, setAssetError] = useState3(null);
1436
- const [selectingAsset, setSelectingAsset] = useState3(false);
1711
+ const [assetError, setAssetError] = useState4(null);
1712
+ const [selectingAsset, setSelectingAsset] = useState4(false);
1437
1713
  const definition = registry?.getBlock(block.type);
1438
1714
  const groupedFields = definition ? Object.entries(definition.fields).reduce((groups, entry) => {
1439
1715
  const [name, field] = entry;
@@ -1442,20 +1718,20 @@ function DocumentInspector({ settings, pageId, assetPicker, productPicker, i18n,
1442
1718
  (groups[group] ??= []).push([name, configuredField]);
1443
1719
  return groups;
1444
1720
  }, {}) : {};
1445
- return /* @__PURE__ */ jsxs5("div", { className: "pb-inspector", "data-testid": "document-inspector", children: [
1446
- /* @__PURE__ */ jsxs5("header", { className: "pb-inspector__header", children: [
1447
- /* @__PURE__ */ jsx7(Badge3, { children: block.type }),
1448
- /* @__PURE__ */ jsxs5("div", { children: [
1449
- /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "headingSm", children: blockLabel(block, registry) }),
1450
- /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: definition?.category ?? "Core block" })
1721
+ return /* @__PURE__ */ jsxs7("div", { className: "pb-inspector", "data-testid": "document-inspector", children: [
1722
+ /* @__PURE__ */ jsxs7("header", { className: "pb-inspector__header", children: [
1723
+ /* @__PURE__ */ jsx9(Badge3, { children: block.type }),
1724
+ /* @__PURE__ */ jsxs7("div", { children: [
1725
+ /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "headingSm", children: blockLabel(block, registry) }),
1726
+ /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: definition?.category ?? "Core block" })
1451
1727
  ] })
1452
1728
  ] }),
1453
- block.type === "core.text" ? /* @__PURE__ */ jsx7(InspectorSection, { title: "Content", children: /* @__PURE__ */ jsx7(InspectorField, { name: "content", field: { field: "", label: "\u6587\u672C\u5185\u5BB9", control: "textarea", description: "\u652F\u6301\u8F83\u957F\u7684\u6B63\u6587\u5185\u5BB9\u3002" }, value: block.props.content, registry, disabled, productPicker, pageId, blockId: block.id, i18n, onChange: (content) => onChange({ content }) }) }) : null,
1454
- block.type === "core.image" ? /* @__PURE__ */ jsxs5(InspectorSection, { title: "Image", children: [
1455
- /* @__PURE__ */ jsx7(InspectorField, { name: "src", field: { field: "", label: "\u56FE\u7247 URL", control: "url", description: "\u4F7F\u7528 HTTPS \u56FE\u7247\u5730\u5740\u3002" }, value: block.props.src, registry, disabled, productPicker, pageId, blockId: block.id, i18n, onChange: (src) => onChange({ src }) }),
1456
- /* @__PURE__ */ jsx7(InspectorField, { name: "alt", field: { field: "", label: "\u66FF\u4EE3\u6587\u672C", control: "text", description: "\u7528\u4E8E\u65E0\u969C\u788D\u9605\u8BFB\u548C\u56FE\u7247\u52A0\u8F7D\u5931\u8D25\u573A\u666F\u3002" }, value: block.props.alt, registry, disabled, productPicker, pageId, blockId: block.id, i18n, onChange: (alt) => onChange({ alt }) }),
1457
- assetPicker ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1458
- /* @__PURE__ */ jsx7(Button2, { disabled: disabled || selectingAsset, onClick: () => void (async () => {
1729
+ block.type === "core.text" ? /* @__PURE__ */ jsx9(InspectorSection, { title: "Content", children: /* @__PURE__ */ jsx9(InspectorField, { name: "content", field: { field: "", label: "\u6587\u672C\u5185\u5BB9", control: "textarea", description: "\u652F\u6301\u8F83\u957F\u7684\u6B63\u6587\u5185\u5BB9\u3002" }, value: block.props.content, registry, disabled, productPicker, pageId, blockId: block.id, i18n, onChange: (content) => onChange({ content }) }) }) : null,
1730
+ block.type === "core.image" ? /* @__PURE__ */ jsxs7(InspectorSection, { title: "Image", children: [
1731
+ /* @__PURE__ */ jsx9(InspectorField, { name: "src", field: { field: "", label: "\u56FE\u7247 URL", control: "url", description: "\u4F7F\u7528 HTTPS \u56FE\u7247\u5730\u5740\u3002" }, value: block.props.src, registry, disabled, productPicker, pageId, blockId: block.id, i18n, onChange: (src) => onChange({ src }) }),
1732
+ /* @__PURE__ */ jsx9(InspectorField, { name: "alt", field: { field: "", label: "\u66FF\u4EE3\u6587\u672C", control: "text", description: "\u7528\u4E8E\u65E0\u969C\u788D\u9605\u8BFB\u548C\u56FE\u7247\u52A0\u8F7D\u5931\u8D25\u573A\u666F\u3002" }, value: block.props.alt, registry, disabled, productPicker, pageId, blockId: block.id, i18n, onChange: (alt) => onChange({ alt }) }),
1733
+ assetPicker ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
1734
+ /* @__PURE__ */ jsx9(Button2, { disabled: disabled || selectingAsset, onClick: () => void (async () => {
1459
1735
  setSelectingAsset(true);
1460
1736
  setAssetError(null);
1461
1737
  try {
@@ -1467,13 +1743,13 @@ function DocumentInspector({ settings, pageId, assetPicker, productPicker, i18n,
1467
1743
  setSelectingAsset(false);
1468
1744
  }
1469
1745
  })(), children: selectingAsset ? "\u6B63\u5728\u9009\u62E9\u7D20\u6750\u2026" : "\u9009\u62E9\u7D20\u6750" }),
1470
- assetError ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: assetError }) : null
1746
+ assetError ? /* @__PURE__ */ jsx9(Text3, { as: "p", variant: "bodySm", tone: "critical", children: assetError }) : null
1471
1747
  ] }) : null
1472
1748
  ] }) : null,
1473
- Object.entries(groupedFields).map(([group, fields]) => /* @__PURE__ */ jsx7(InspectorSection, { title: group, defaultOpen: group !== "Advanced", children: fields.map(([name, field]) => /* @__PURE__ */ jsx7(InspectorField, { name, field, value: field.persist === false ? settings?.values[name] : block.props[name], registry, disabled: disabled || field.persist === false && (!settings || Boolean(settings.disabled)), assetPicker, productPicker, pageId, blockId: block.id, i18n, onChange: (value) => field.persist === false ? settings?.onChange(name, value) : onChange({ [name]: value }) }, name)) }, group)),
1749
+ Object.entries(groupedFields).map(([group, fields]) => /* @__PURE__ */ jsx9(InspectorSection, { title: group, defaultOpen: group !== "Advanced", children: fields.map(([name, field]) => /* @__PURE__ */ jsx9(InspectorField, { name, field, value: field.persist === false ? settings?.values[name] : block.props[name], registry, disabled: disabled || field.persist === false && (!settings || Boolean(settings.disabled)), assetPicker, productPicker, pageId, blockId: block.id, i18n, onChange: (value) => field.persist === false ? settings?.onChange(name, value) : onChange({ [name]: value }) }, name)) }, group)),
1474
1750
  Object.values(definition?.fields ?? {}).some((field) => field.persist === false) ? settings?.footer : null,
1475
- 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,
1476
- 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) => {
1751
+ appearanceControls && definition?.variants?.length ? /* @__PURE__ */ jsx9(InspectorSection, { title: "\u5916\u89C2", children: /* @__PURE__ */ jsx9(Select2, { label: "\u6837\u5F0F\u53D8\u4F53", options: definition.variants.map((variant) => ({ label: variant.label, value: variant.id })), value: block.variant, disabled, onChange: (variant) => onPresentationChange({ variant, style: block.style }) }) }) : null,
1752
+ appearanceControls && definition ? /* @__PURE__ */ jsx9(InspectorSection, { title: "\u6837\u5F0F\u8986\u76D6", defaultOpen: false, children: appearanceTokens.map(([token, label]) => /* @__PURE__ */ jsx9(TextField, { label, value: block.style[token] ?? "", placeholder: "\u7EE7\u627F\u9875\u9762\u6216\u6A21\u677F\u8BBE\u7F6E", autoComplete: "off", disabled, onChange: (value) => {
1477
1753
  const style = { ...block.style };
1478
1754
  if (value.trim()) style[token] = value;
1479
1755
  else delete style[token];