@standhigher/puck-page-builder 0.12.4 → 0.12.5

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
@@ -179,6 +179,8 @@ type EditorContextValue = {
179
179
  actionState: EditorActionState;
180
180
  canAddBlock(type: string): boolean;
181
181
  canDragBlock(id: string): boolean;
182
+ canDeleteBlock(id: string): boolean;
183
+ canDuplicateBlock(id: string): boolean;
182
184
  /** Request a canvas selection. The inspector changes only after Puck confirms it. */
183
185
  requestCanvasSelection(id: string): void;
184
186
  /** Called from the canvas/Puck selection event. */
@@ -299,6 +301,7 @@ declare const messages: {
299
301
  readonly undo: "撤销";
300
302
  readonly redo: "恢复";
301
303
  readonly duplicate: "复制";
304
+ readonly duplicateUnavailable: "此区块已达到允许数量上限,或复制操作已被禁用。";
302
305
  readonly remove: "删除";
303
306
  readonly confirmDeleteTitle: "确认删除区块?";
304
307
  readonly confirmDeleteMessage: "删除后可使用撤销恢复。";
@@ -363,6 +366,7 @@ declare const messages: {
363
366
  readonly undo: "Undo";
364
367
  readonly redo: "Redo";
365
368
  readonly duplicate: "Duplicate";
369
+ readonly duplicateUnavailable: "This block has reached its limit or duplication is disabled.";
366
370
  readonly remove: "Delete";
367
371
  readonly confirmDeleteTitle: "Delete this block?";
368
372
  readonly confirmDeleteMessage: "You can restore it with Undo after deleting.";
@@ -424,7 +428,7 @@ type AdminMessageKey = keyof typeof messages["zh-CN"];
424
428
  /** Minimal, dependency-free Admin copy catalog. Product locale remains PageDocument.settings.locale. */
425
429
  declare function createAdminI18n(locale?: string): {
426
430
  locale: AdminLocale;
427
- t: (key: AdminMessageKey) => "Desktop" | "Tablet" | "Mobile" | "未保存变更" | "Outline" | "Preview" | "缩放" | "区块" | "结构" | "撤销" | "恢复" | "收起左侧面板" | "收起属性面板" | "Delete" | "Duplicate" | "products" | "编辑锁已失效" | "属性" | "添加区块" | "复制" | "删除" | "确认删除区块?" | "删除后可使用撤销恢复。" | "取消" | "确认删除" | "上移" | "下移" | "正在加载编辑器…" | "页面还没有区块" | "编辑器无法加载" | "编辑器当前不可编辑" | "操作已完成" | "所有更改已保留在当前会话中" | "保存草稿" | "历史记录" | "正在保存…" | "发布" | "正在发布…" | "保存草稿失败" | "重试保存" | "当前离线,恢复网络后将自动保存" | "发布失败" | "已发布" | "发布成功" | "返回" | "离开编辑器?" | "离开" | "自动缩放" | "正式预览" | "添加到店铺" | "正在获取编辑锁…" | "该页面正在被编辑" | "页面为只读状态" | "你有未保存的更改。确定要离开吗?" | "选择一个区块以编辑。" | "桌面" | "平板" | "手机" | "打开区块面板" | "打开属性面板" | "从 Shopify 选择商品" | "正在选择商品…" | "无法选择商品,请重试。" | "未选择商品" | "件商品" | "选择图片" | "正在上传图片…" | "无法上传图片,请重试。" | "未设置图片" | "移除图片" | "Blocks" | "Properties" | "Add block" | "Undo" | "Redo" | "Delete this block?" | "You can restore it with Undo after deleting." | "Cancel" | "Delete block" | "Move up" | "Move down" | "Loading editor…" | "This page has no blocks" | "The editor could not load" | "The editor is currently read-only" | "Action completed" | "Unsaved changes" | "Changes are kept in this session" | "Save draft" | "History" | "Saving…" | "Publish" | "Publishing…" | "Could not save draft" | "Retry save" | "Offline — changes will save when connection returns" | "Could not publish" | "Published" | "Back" | "Leave the editor?" | "Leave" | "Zoom" | "Auto zoom" | "Add to store" | "Acquiring edit lock…" | "This page is being edited" | "The edit lock has expired" | "This page is read-only" | "You have unsaved changes. Are you sure you want to leave?" | "Select a block to edit it." | "Collapse blocks panel" | "Collapse properties panel" | "Open blocks panel" | "Open properties panel" | "Select from Shopify" | "Selecting products…" | "Could not select products. Try again." | "No products selected" | "Select image" | "Uploading image…" | "Could not upload the image. Try again." | "No image selected" | "Remove image";
431
+ t: (key: AdminMessageKey) => "Desktop" | "Tablet" | "Mobile" | "未保存变更" | "Outline" | "Preview" | "缩放" | "区块" | "结构" | "撤销" | "恢复" | "收起左侧面板" | "收起属性面板" | "Delete" | "Duplicate" | "products" | "编辑锁已失效" | "属性" | "添加区块" | "复制" | "此区块已达到允许数量上限,或复制操作已被禁用。" | "删除" | "确认删除区块?" | "删除后可使用撤销恢复。" | "取消" | "确认删除" | "上移" | "下移" | "正在加载编辑器…" | "页面还没有区块" | "编辑器无法加载" | "编辑器当前不可编辑" | "操作已完成" | "所有更改已保留在当前会话中" | "保存草稿" | "历史记录" | "正在保存…" | "发布" | "正在发布…" | "保存草稿失败" | "重试保存" | "当前离线,恢复网络后将自动保存" | "发布失败" | "已发布" | "发布成功" | "返回" | "离开编辑器?" | "离开" | "自动缩放" | "正式预览" | "添加到店铺" | "正在获取编辑锁…" | "该页面正在被编辑" | "页面为只读状态" | "你有未保存的更改。确定要离开吗?" | "选择一个区块以编辑。" | "桌面" | "平板" | "手机" | "打开区块面板" | "打开属性面板" | "从 Shopify 选择商品" | "正在选择商品…" | "无法选择商品,请重试。" | "未选择商品" | "件商品" | "选择图片" | "正在上传图片…" | "无法上传图片,请重试。" | "未设置图片" | "移除图片" | "Blocks" | "Properties" | "Add block" | "Undo" | "Redo" | "This block has reached its limit or duplication is disabled." | "Delete this block?" | "You can restore it with Undo after deleting." | "Cancel" | "Delete block" | "Move up" | "Move down" | "Loading editor…" | "This page has no blocks" | "The editor could not load" | "The editor is currently read-only" | "Action completed" | "Unsaved changes" | "Changes are kept in this session" | "Save draft" | "History" | "Saving…" | "Publish" | "Publishing…" | "Could not save draft" | "Retry save" | "Offline — changes will save when connection returns" | "Could not publish" | "Published" | "Back" | "Leave the editor?" | "Leave" | "Zoom" | "Auto zoom" | "Add to store" | "Acquiring edit lock…" | "This page is being edited" | "The edit lock has expired" | "This page is read-only" | "You have unsaved changes. Are you sure you want to leave?" | "Select a block to edit it." | "Collapse blocks panel" | "Collapse properties panel" | "Open blocks panel" | "Open properties panel" | "Select from Shopify" | "Selecting products…" | "Could not select products. Try again." | "No products selected" | "Select image" | "Uploading image…" | "Could not upload the image. Try again." | "No image selected" | "Remove image";
428
432
  };
429
433
 
430
434
  declare function toEngineData(document: PageDocument, registry?: ExtensionRegistry): Data;
package/dist/index.js CHANGED
@@ -353,7 +353,7 @@ function PropertyPanel({
353
353
  import { Puck as Puck2, usePuck } from "@puckeditor/core";
354
354
  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
355
  import { DragHandleIcon as DragHandleIcon2, LayoutSectionIcon as LayoutSectionIcon2, MenuIcon as MenuIcon2, ProductIcon, RedoIcon as RedoIcon2, UndoIcon as UndoIcon2, XIcon as XIcon2 } from "@shopify/polaris-icons";
356
- import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef4, useState as useState3 } from "react";
356
+ import { useCallback as useCallback2, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo2, useRef as useRef4, useState as useState3 } from "react";
357
357
 
358
358
  // src/adapters/puck/canvas-extension-block.tsx
359
359
  import { registerOverlayPortal } from "@puckeditor/core";
@@ -369,6 +369,20 @@ function CanvasExtensionBlock({ active, children, label, onSelect }) {
369
369
 
370
370
  // src/adapters/puck/page-document-config.tsx
371
371
  import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
372
+ function withBlockPermissions(component, type, resolvePermissions) {
373
+ if (!resolvePermissions) return component;
374
+ return {
375
+ ...component,
376
+ // Keep actions hidden until the policy resolver has produced the current
377
+ // per-block result. This prevents a one-frame clickable action that may be
378
+ // rejected by the PageDocument policy after Puck emits onChange.
379
+ permissions: { drag: false, duplicate: false, delete: false, edit: false, insert: false },
380
+ resolvePermissions: (item) => {
381
+ const id = typeof item?.props?.id === "string" ? item.props.id : "";
382
+ return resolvePermissions({ id, type });
383
+ }
384
+ };
385
+ }
372
386
  function canvasFieldFallback({ block, props, registry, onPropsChange }) {
373
387
  const fields = Object.entries(block.fields);
374
388
  if (!fields.length) return null;
@@ -380,11 +394,11 @@ function canvasFieldFallback({ block, props, registry, onPropsChange }) {
380
394
  ] }, name) : null;
381
395
  }) });
382
396
  }
383
- function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId, registry) {
397
+ function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId, registry, resolveBlockPermissions) {
384
398
  const extensionComponents = Object.fromEntries((registry?.blocks ?? []).flatMap((block) => {
385
399
  const BlockRenderer = block.render.web;
386
400
  if (!BlockRenderer) return [];
387
- return [[block.type, {
401
+ return [[block.type, withBlockPermissions({
388
402
  render: (props) => {
389
403
  const id = typeof props.id === "string" ? props.id : `unknown-${block.type}`;
390
404
  const EditorRenderer = block.render.editor;
@@ -394,11 +408,11 @@ function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId,
394
408
  active ? canvasFieldFallback({ block, props, registry, onPropsChange: (next) => onPropsChange(id, next) }) : null
395
409
  ] }) });
396
410
  }
397
- }]];
411
+ }, block.type, resolveBlockPermissions)]];
398
412
  }));
399
413
  return {
400
414
  components: {
401
- Text: {
415
+ Text: withBlockPermissions({
402
416
  render: (props) => {
403
417
  const id = typeof props.id === "string" ? props.id : "unknown-text";
404
418
  const content = typeof props.content === "string" ? props.content : "";
@@ -409,8 +423,8 @@ function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId,
409
423
  if (editable) onPropsChange(id, { content: event.currentTarget.textContent ?? "" }, true);
410
424
  }, children: content }) });
411
425
  }
412
- },
413
- Image: {
426
+ }, "core.text", resolveBlockPermissions),
427
+ Image: withBlockPermissions({
414
428
  render: (props) => {
415
429
  const id = typeof props.id === "string" ? props.id : "unknown-image";
416
430
  const src = typeof props.src === "string" ? props.src : "";
@@ -423,7 +437,7 @@ function createPageDocumentPuckConfig(onSelect, onPropsChange, selectedBlockId,
423
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
424
438
  ] });
425
439
  }
426
- },
440
+ }, "core.image", resolveBlockPermissions),
427
441
  ...extensionComponents
428
442
  }
429
443
  };
@@ -658,6 +672,14 @@ function EditorProvider({ initialDocument, registry, policy, loadState = "ready"
658
672
  const block = history.document.blocks.find((item) => item.id === id);
659
673
  return editable && canDragBlock(block, registry?.getBlock(block?.type ?? ""), policy);
660
674
  },
675
+ canDeleteBlock: (id) => {
676
+ const block = history.document.blocks.find((item) => item.id === id);
677
+ return editable && canDeleteBlock(block, history.document.blocks, registry?.getBlock(block?.type ?? ""), policy);
678
+ },
679
+ canDuplicateBlock: (id) => {
680
+ const block = history.document.blocks.find((item) => item.id === id);
681
+ return editable && canDuplicateBlock(block, history.document.blocks, registry?.getBlock(block?.type ?? ""), policy);
682
+ },
661
683
  requestCanvasSelection,
662
684
  confirmCanvasSelection,
663
685
  updateFromCanvas,
@@ -774,6 +796,7 @@ var messages = {
774
796
  undo: "\u64A4\u9500",
775
797
  redo: "\u6062\u590D",
776
798
  duplicate: "\u590D\u5236",
799
+ duplicateUnavailable: "\u6B64\u533A\u5757\u5DF2\u8FBE\u5230\u5141\u8BB8\u6570\u91CF\u4E0A\u9650\uFF0C\u6216\u590D\u5236\u64CD\u4F5C\u5DF2\u88AB\u7981\u7528\u3002",
777
800
  remove: "\u5220\u9664",
778
801
  confirmDeleteTitle: "\u786E\u8BA4\u5220\u9664\u533A\u5757\uFF1F",
779
802
  confirmDeleteMessage: "\u5220\u9664\u540E\u53EF\u4F7F\u7528\u64A4\u9500\u6062\u590D\u3002",
@@ -838,6 +861,7 @@ var messages = {
838
861
  undo: "Undo",
839
862
  redo: "Redo",
840
863
  duplicate: "Duplicate",
864
+ duplicateUnavailable: "This block has reached its limit or duplication is disabled.",
841
865
  remove: "Delete",
842
866
  confirmDeleteTitle: "Delete this block?",
843
867
  confirmDeleteMessage: "You can restore it with Undo after deleting.",
@@ -974,6 +998,10 @@ function PageDocumentEditorShell(props) {
974
998
  }
975
999
  function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onPublish, draftPersistence, draftRevision: initialDraftRevision, autoSave = true, autoSaveDelayMs = 800, publishAction, assetPicker, inspectorSettings, productPicker, pageStatus, onBack, onHistory, onPreview, onAddToStore, deleteConfirmation, availableBlockTypes, appearanceControls = false }) {
976
1000
  const editor = useEditorContext();
1001
+ const editorRef = useRef4(editor);
1002
+ useLayoutEffect(() => {
1003
+ editorRef.current = editor;
1004
+ }, [editor]);
977
1005
  const [blockView, setBlockView] = useState3("blocks");
978
1006
  const [leftRailOpen, setLeftRailOpen] = useState3(true);
979
1007
  const [rightPanelOpen, setRightPanelOpen] = useState3(true);
@@ -1000,7 +1028,14 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1000
1028
  if (preserveCanvasValue) setCanvasMutationVersion((version) => version + 1);
1001
1029
  updateBlockProps(id, props);
1002
1030
  }, [updateBlockProps]);
1003
- const config = useMemo2(() => createPageDocumentPuckConfig(confirmCanvasSelection, updateFromCanvasInput, selectedBlockId, registry), [confirmCanvasSelection, registry, selectedBlockId, updateFromCanvasInput]);
1031
+ const resolveBlockPermissions = useCallback2(({ id, type }) => ({
1032
+ duplicate: editorRef.current.canDuplicateBlock(id),
1033
+ delete: editorRef.current.canDeleteBlock(id),
1034
+ drag: editorRef.current.canDragBlock(id),
1035
+ edit: editorRef.current.isEditable,
1036
+ insert: editorRef.current.canAddBlock(type)
1037
+ }), []);
1038
+ const config = useMemo2(() => createPageDocumentPuckConfig(confirmCanvasSelection, updateFromCanvasInput, selectedBlockId, registry, resolveBlockPermissions), [confirmCanvasSelection, registry, resolveBlockPermissions, selectedBlockId, updateFromCanvasInput]);
1004
1039
  useEffect3(() => {
1005
1040
  const online = () => setIsOnline(true);
1006
1041
  const offline = () => setIsOnline(false);
@@ -1110,7 +1145,7 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1110
1145
  /* @__PURE__ */ jsx7(Puck2, { config, data: engineData, iframe: { enabled: iframe }, onChange: (data) => {
1111
1146
  if (!editor.updateFromCanvas(fromEngineData(data, editor.document, registry))) setCanvasResetVersion((version) => version + 1);
1112
1147
  }, children: /* @__PURE__ */ jsxs5(Puck2.Layout, { children: [
1113
- /* @__PURE__ */ jsx7(CanvasSelectionBridge, { data: engineData, requestedBlockId: editor.canvasSelectionRequest, onCanvasSelected: confirmCanvasSelection, canvasMutationVersion, canvasResetVersion }),
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("|")}` }),
1114
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: [
1115
1150
  /* @__PURE__ */ jsx7("header", { className: "pb-header", children: /* @__PURE__ */ jsxs5("div", { className: "pb-header-content", children: [
1116
1151
  /* @__PURE__ */ jsxs5("div", { className: "pb-header-title-group", children: [
@@ -1214,13 +1249,14 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1214
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 }) })
1215
1250
  ] });
1216
1251
  }
1217
- function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canvasMutationVersion, canvasResetVersion }) {
1252
+ function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canvasMutationVersion, canvasResetVersion, permissionsVersion }) {
1218
1253
  const puck = usePuck();
1219
1254
  const lastSelectedId = useRef4(null);
1220
1255
  const lastSyncedData = useRef4(null);
1221
1256
  const lastCanvasMutationVersion = useRef4(0);
1222
1257
  const lastCanvasResetVersion = useRef4(0);
1223
1258
  const serializedData = JSON.stringify(data);
1259
+ const refreshPermissions = puck.refreshPermissions;
1224
1260
  useEffect3(() => {
1225
1261
  const forceReset = canvasResetVersion > lastCanvasResetVersion.current;
1226
1262
  if (lastSyncedData.current === serializedData && !forceReset) return;
@@ -1232,6 +1268,9 @@ function CanvasSelectionBridge({ data, requestedBlockId, onCanvasSelected, canva
1232
1268
  }
1233
1269
  puck.dispatch({ type: "setData", data });
1234
1270
  }, [canvasMutationVersion, canvasResetVersion, data, puck, serializedData]);
1271
+ useEffect3(() => {
1272
+ refreshPermissions?.({}, true);
1273
+ }, [permissionsVersion, refreshPermissions]);
1235
1274
  useEffect3(() => {
1236
1275
  if (!requestedBlockId) return;
1237
1276
  const selector = puck.getSelectorForId(requestedBlockId);
@@ -1267,10 +1306,20 @@ function InspectorSection({ title, children, defaultOpen = true }) {
1267
1306
  /* @__PURE__ */ jsx7("div", { className: "pb-inspector-section__body", children })
1268
1307
  ] });
1269
1308
  }
1270
- function InspectorTextControl({ label, value, control, disabled, onChange }) {
1309
+ function InspectorTextControl({ label, value, control, maxLength, disabled, onChange }) {
1271
1310
  const stringValue = typeof value === "string" ? value : "";
1272
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) });
1273
- return /* @__PURE__ */ jsx7(TextField, { label, labelHidden: true, value: stringValue, onChange, autoComplete: "off", disabled, multiline: control === "textarea" ? 4 : false, type: control === "url" ? "url" : "text" });
1312
+ 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 });
1314
+ if (maxLength === void 0) return field;
1315
+ return /* @__PURE__ */ jsxs5("div", { className: "pb-inspector-field__limited", children: [
1316
+ field,
1317
+ /* @__PURE__ */ jsxs5("span", { className: "pb-inspector-field__limit", "aria-label": `Limit ${maxLength}`, children: [
1318
+ length,
1319
+ "/",
1320
+ maxLength
1321
+ ] })
1322
+ ] });
1274
1323
  }
1275
1324
  function InspectorAssetControl({ value, disabled, picker, pageId, blockId, i18n, onChange }) {
1276
1325
  const [busy, setBusy] = useState3(false);
@@ -1340,17 +1389,20 @@ function InspectorField({ name, field, value, registry, disabled, assetPicker, p
1340
1389
  /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", fontWeight: "semibold", children: label }),
1341
1390
  field.description ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: field.description }) : null
1342
1391
  ] }),
1343
- field.control === "products" ? /* @__PURE__ */ jsx7(InspectorProductsControl, { value, disabled, picker: productPicker, pageId, blockId, i18n, onChange }) : field.control === "asset" ? /* @__PURE__ */ jsx7(InspectorAssetControl, { value, disabled, picker: assetPicker, pageId, blockId, i18n, onChange }) : field.control ? /* @__PURE__ */ jsx7(InspectorTextControl, { label, value, control: field.control, disabled, onChange: (next) => onChange(next) }) : Field ? /* @__PURE__ */ jsx7(Field, { value, onChange }) : null,
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,
1344
1393
  issues.map((issue) => /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: issue.message }, issue.message))
1345
1394
  ] });
1346
1395
  }
1347
1396
  function InspectorActions({ block, canDuplicate, canDelete, canMove, canMoveUp, canMoveDown, i18n, onDuplicate, onDelete, onMove }) {
1348
- return /* @__PURE__ */ jsx7("div", { className: "pb-inspector-actions", "aria-label": `Actions for ${block.id}`, children: /* @__PURE__ */ jsxs5(ButtonGroup2, { children: [
1349
- /* @__PURE__ */ jsx7(Button2, { disabled: !canMove || !canMoveUp, onClick: () => onMove(-1), children: i18n.t("moveUp") }),
1350
- /* @__PURE__ */ jsx7(Button2, { disabled: !canMove || !canMoveDown, onClick: () => onMove(1), children: i18n.t("moveDown") }),
1351
- /* @__PURE__ */ jsx7(Button2, { disabled: !canDuplicate, onClick: onDuplicate, children: i18n.t("duplicate") }),
1352
- /* @__PURE__ */ jsx7(Button2, { disabled: !canDelete, tone: "critical", onClick: onDelete, children: i18n.t("remove") })
1353
- ] }) });
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") })
1403
+ ] }),
1404
+ !canDuplicate ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: i18n.t("duplicateUnavailable") }) : null
1405
+ ] });
1354
1406
  }
1355
1407
  function DeleteConfirmation({ block, config, i18n, onCancel, onConfirm }) {
1356
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: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@standhigher/puck-page-builder",
3
- "version": "0.12.4",
3
+ "version": "0.12.5",
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
@@ -48,6 +48,9 @@ button:focus-visible, [role="button"]:focus-visible { outline: 2px solid var(--b
48
48
  .pb-inspector-section:not([open]) > summary > span:last-child { transform: rotate(-90deg); }
49
49
  .pb-inspector-section__body { display: grid; gap: var(--builder-space-3); padding: 0 var(--builder-space-3) var(--builder-space-3); border-top: 1px solid #f1f5f9; }
50
50
  .pb-inspector-field { display: grid; gap: 6px; }
51
+ .pb-inspector-field__limited { display: flex; align-items: center; gap: 8px; }
52
+ .pb-inspector-field__limited > :first-child { flex: 1; min-width: 0; }
53
+ .pb-inspector-field__limit { flex: 0 0 auto; color: #6d7175; font-size: 12px; font-variant-numeric: tabular-nums; }
51
54
  .pb-inspector-field__heading { display: grid; gap: 2px; }
52
55
  .pb-inspector-field__heading p { margin: 0; }
53
56
  .pb-inspector-field__heading p:first-child { color: #334155; font-size: 13px; }
@@ -68,7 +71,7 @@ button:focus-visible, [role="button"]:focus-visible { outline: 2px solid var(--b
68
71
  .pb-product-picker__thumb img, .pb-product-picker__thumb-fallback { display: block; width: 100%; height: 100%; object-fit: cover; }
69
72
  .pb-product-picker__thumb-fallback { background: #cbd5e1; }
70
73
  .pb-asset-picker { display: grid; gap: 8px; }
71
- .pb-asset-picker__preview { display: block; width: 100%; max-height: 180px; object-fit: cover; border: 1px solid #e2e8f0; border-radius: 10px; background: #f8fafc; }
74
+ .pb-asset-picker__preview { display: block; width: 100%; aspect-ratio: 20 / 9; object-fit: fill; object-position: center; border: 1px solid #e2e8f0; border-radius: 8px; background: #f8fafc; }
72
75
  .pb-inspector__hint { margin: 0; padding: 0 var(--builder-space-2); color: #64748b; font-size: 12px; line-height: 1.5; }
73
76
  .pb-inspector-actions { padding-top: var(--builder-space-2); border-top: 1px solid #e2e8f0; }
74
77
  .pb-delete-confirmation-backdrop { position: fixed; z-index: 1000; inset: 0; display: grid; place-items: center; padding: var(--builder-space-4); background: rgb(15 23 42 / 42%); }