@standhigher/puck-page-builder 0.12.6 → 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.d.ts CHANGED
@@ -195,7 +195,7 @@ type EditorContextValue = {
195
195
  confirmDeleteBlock(): void;
196
196
  deleteBlock(id: string): void;
197
197
  moveBlock(id: string, direction: -1 | 1): void;
198
- reorderBlock(id: string, beforeId: string): void;
198
+ reorderBlock(id: string, beforeId?: string): void;
199
199
  updateBlockProps(id: string, props: Record<string, JsonValue>): void;
200
200
  updateBlockPresentation(id: string, presentation: Pick<BlockNode, "variant" | "style">): void;
201
201
  undo(): void;
package/dist/index.js CHANGED
@@ -759,11 +759,17 @@ function EditorProvider({ initialDocument, registry, policy, loadState = "ready"
759
759
  reorderBlock: (id, beforeId) => {
760
760
  if (!editable || id === beforeId) return;
761
761
  const source = history.document.blocks.find((block) => block.id === id);
762
+ if (!source || !canDragBlock(source, registry?.getBlock(source.type), policy)) return;
762
763
  const withoutSource = history.document.blocks.filter((block) => block.id !== id);
763
- const targetIndex = withoutSource.findIndex((block) => block.id === beforeId);
764
- if (!source || targetIndex < 0 || !canDragBlock(source, registry?.getBlock(source.type), policy)) return;
765
764
  const blocks = [...withoutSource];
766
- 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;
767
773
  replace({ ...history.document, blocks }, id);
768
774
  },
769
775
  updateBlockProps,
@@ -966,6 +972,29 @@ import { useEffect as useEffect3, useRef as useRef4, useState as useState3 } fro
966
972
  function nearestBlockIdAtY(blocks, y) {
967
973
  return blocks.find((block) => y < block.top + block.height / 2)?.id;
968
974
  }
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
+ }
969
998
 
970
999
  // src/editor/shell/CanvasLibraryDropTarget.tsx
971
1000
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
@@ -1221,6 +1250,9 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1221
1250
  const [leftRailOpen, setLeftRailOpen] = useState4(true);
1222
1251
  const [rightPanelOpen, setRightPanelOpen] = useState4(true);
1223
1252
  const [draggingLibraryType, setDraggingLibraryType] = useState4(null);
1253
+ const [draggingBlockId, setDraggingBlockId] = useState4(null);
1254
+ const draggingBlockIdRef = useRef5(null);
1255
+ const [dropTarget, setDropTarget] = useState4(null);
1224
1256
  const canvasFrameRef = useRef5(null);
1225
1257
  const [canvasMutationVersion, setCanvasMutationVersion] = useState4(0);
1226
1258
  const [canvasResetVersion, setCanvasResetVersion] = useState4(0);
@@ -1313,14 +1345,27 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1313
1345
  if (editor.sessionState !== "active") return /* @__PURE__ */ jsx9(EditorSessionStatus, { state: editor.sessionState, message: editor.sessionMessage });
1314
1346
  const allBlockTypes = ["core.text", "core.image", ...registry?.blocks.map((block) => block.type) ?? []];
1315
1347
  const blockTypes = availableBlockTypes ? allBlockTypes.filter((type) => availableBlockTypes.includes(type)) : allBlockTypes;
1348
+ const orderedBlockTypes = libraryTypesInDocumentOrder(blockTypes, editor.document.blocks);
1316
1349
  const addFromLibrary = (type, beforeId) => {
1317
1350
  const id = editor.addBlock(type, beforeId);
1318
1351
  if (id) editor.requestCanvasSelection(id);
1319
1352
  };
1353
+ const clearLibraryDrag = () => {
1354
+ draggingBlockIdRef.current = null;
1355
+ setDraggingLibraryType(null);
1356
+ setDraggingBlockId(null);
1357
+ setDropTarget(null);
1358
+ };
1320
1359
  const cancelLibraryDrop = (event) => {
1321
1360
  event.preventDefault();
1322
1361
  event.stopPropagation();
1323
- 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);
1324
1369
  };
1325
1370
  const saveBadgeTone = resolvedSaveState === "failed" ? "critical" : resolvedSaveState === "offline" || resolvedSaveState === "dirty" ? "attention" : "success";
1326
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");
@@ -1381,26 +1426,57 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1381
1426
  /* @__PURE__ */ jsx9(Text3, { as: "h2", variant: "headingSm", children: blockView === "blocks" ? i18n.t("blocks") : i18n.t("outline") }),
1382
1427
  /* @__PURE__ */ jsx9(Button2, { accessibilityLabel: i18n.t("collapseLeft"), icon: XIcon2, variant: "tertiary", onClick: () => setLeftRailOpen(false) })
1383
1428
  ] }),
1384
- blockView === "blocks" ? /* @__PURE__ */ jsx9("div", { className: "pb-block-list", "data-testid": "blocks-view", "aria-label": "\u533A\u5757\u7C7B\u578B\u5E93", role: "list", onDrop: cancelLibraryDrop, children: blockTypes.map((type) => /* @__PURE__ */ jsxs7("div", { className: `pb-document-block-row pb-document-block-row--library ${editor.selectedBlock?.type === type ? "pb-document-block-row--selected" : ""}`, "data-block-type": type, "data-selected": editor.selectedBlock?.type === type, role: "listitem", draggable: editor.canAddBlock(type), "aria-disabled": !editor.canAddBlock(type), "aria-label": `${blockTypeLabel(type, registry)}\uFF0C\u62D6\u62FD\u81F3\u753B\u5E03\u4EE5\u6DFB\u52A0${editor.selectedBlock?.type === type ? "\uFF0C\u5F53\u524D\u9009\u4E2D\u7C7B\u578B" : ""}`, onClick: () => {
1385
- const block = editor.document.blocks.find((item) => item.type === type);
1386
- if (!block) return;
1387
- editor.requestCanvasSelection(block.id);
1388
- scrollCanvasBlockIntoView(block.id, canvasFrameRef.current);
1389
- }, onDragStart: (event) => {
1390
- 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;
1391
1467
  event.preventDefault();
1392
- return;
1393
- }
1394
- event.dataTransfer.setData("application/x-page-document-block", type);
1395
- event.dataTransfer.effectAllowed = "copy";
1396
- setDraggingLibraryType(type);
1397
- }, onDragEnd: () => setDraggingLibraryType(null), children: [
1398
- /* @__PURE__ */ jsxs7("span", { className: "pb-library-block-title", children: [
1399
- /* @__PURE__ */ jsx9(Text3, { as: "span", variant: "bodySm", fontWeight: "semibold", children: blockTypeLabel(type, registry) }),
1400
- /* @__PURE__ */ jsx9("span", { className: "pb-library-block-drag-hint", "aria-hidden": "true", children: /* @__PURE__ */ jsx9(DragHandleIcon2, {}) })
1401
- ] }),
1402
- /* @__PURE__ */ jsx9(Text3, { as: "span", variant: "bodySm", tone: "subdued", children: type })
1403
- ] }, type)) }) : editor.document.blocks.length === 0 ? /* @__PURE__ */ jsx9(Text3, { as: "p", tone: "subdued", children: i18n.t("empty") }) : /* @__PURE__ */ jsx9("div", { className: "pb-block-list", "data-testid": "outline-view", children: editor.document.blocks.map((block) => /* @__PURE__ */ jsxs7("button", { type: "button", className: `pb-document-block-row pb-document-block-row--library ${block.id === editor.selectedBlockId ? "pb-document-block-row--selected" : ""}`, "aria-pressed": block.id === editor.selectedBlockId, onClick: () => {
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: () => {
1404
1480
  editor.requestCanvasSelection(block.id);
1405
1481
  scrollCanvasBlockIntoView(block.id, canvasFrameRef.current);
1406
1482
  }, children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@standhigher/puck-page-builder",
3
- "version": "0.12.6",
3
+ "version": "0.12.7",
4
4
  "description": "React PageDocument editor, extension registry, and web renderer for BestTrack page experiences.",
5
5
  "type": "module",
6
6
  "repository": {
package/src/styles.css CHANGED
@@ -127,6 +127,9 @@ button:focus-visible, [role="button"]:focus-visible { outline: 2px solid var(--b
127
127
  .pb-document-block-row--library { width: 100%; flex-direction: column; align-items: flex-start; text-align: left; color: var(--builder-text); font: inherit; cursor: grab; }
128
128
  .pb-document-block-row--library:active { cursor: grabbing; }
129
129
  .pb-document-block-row--library[aria-disabled="true"] { cursor: default; }
130
+ .pb-document-block-row--dragging { opacity: 0.55; }
131
+ .pb-document-block-row--drop-before { box-shadow: inset 0 3px 0 0 var(--builder-accent); }
132
+ .pb-document-block-row--drop-after { box-shadow: inset 0 -3px 0 0 var(--builder-accent); }
130
133
  .pb-document-block-row--library[type="button"] { cursor: pointer; }
131
134
  .pb-library-block-title { display: flex; align-items: center; justify-content: space-between; width: 100%; gap: var(--builder-space-2); }
132
135
  .pb-library-block-drag-hint { display: inline-flex; color: var(--builder-text-muted); pointer-events: none; }