@standhigher/puck-page-builder 0.12.0 → 0.12.1

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.
@@ -72,7 +72,7 @@ function validateFieldValue(config, value) {
72
72
  if (control === "text" && /[\r\n]/.test(value)) issues.push({ path: "", message: "\u5FC5\u987B\u4E3A\u5355\u884C\u6587\u672C\u3002" });
73
73
  if (config.validation?.minLength !== void 0 && stringLength(value) < config.validation.minLength) issues.push({ path: "", message: `\u81F3\u5C11\u9700\u8981 ${config.validation.minLength} \u4E2A\u5B57\u7B26\u3002` });
74
74
  if (config.validation?.maxLength !== void 0 && stringLength(value) > config.validation.maxLength) issues.push({ path: "", message: `\u6700\u591A\u5141\u8BB8 ${config.validation.maxLength} \u4E2A\u5B57\u7B26\u3002` });
75
- if (value && control === "url" && !isSafeUrl(value, config)) issues.push({ path: "", message: "\u5FC5\u987B\u662F\u7AD9\u5185\u76F8\u5BF9\u8DEF\u5F84\u6216\u5141\u8BB8\u7684 HTTP(S) URL\u3002" });
75
+ if (value && (control === "url" || control === "asset") && !isSafeUrl(value, config)) issues.push({ path: "", message: "\u5FC5\u987B\u662F\u7AD9\u5185\u76F8\u5BF9\u8DEF\u5F84\u6216\u5141\u8BB8\u7684 HTTP(S) URL\u3002" });
76
76
  if (value && control === "color" && !hexColor.test(value)) issues.push({ path: "", message: "\u5FC5\u987B\u662F\u5341\u516D\u8FDB\u5236\u989C\u8272\uFF0C\u4F8B\u5982 #005BD3\u3002" });
77
77
  return issues;
78
78
  }
@@ -327,11 +327,12 @@ function validateBlock(document, index, registry) {
327
327
  if (block.version !== definition.version) issues.push({ path: `${path}.version`, message: `\u533A\u5757\u7248\u672C\u5FC5\u987B\u4E3A ${definition.version}\u3002` });
328
328
  if (!supportsTarget(definition, document.target)) issues.push({ path, message: `\u533A\u5757\u4E0D\u652F\u6301 ${document.target} target\u3002` });
329
329
  if (definition.variants?.length && !definition.variants.some((variant) => variant.id === block.variant)) issues.push({ path: `${path}.variant`, message: `\u4E0D\u652F\u6301\u7684 variant\uFF1A${block.variant}\u3002` });
330
- const allowedProps = /* @__PURE__ */ new Set([...Object.keys(definition.defaultProps), ...Object.keys(definition.fields)]);
330
+ const allowedProps = /* @__PURE__ */ new Set([...Object.keys(definition.defaultProps), ...Object.entries(definition.fields).filter(([, field]) => field.persist !== false).map(([name]) => name)]);
331
331
  for (const key of Object.keys(block.props)) {
332
332
  if (!allowedProps.has(key)) issues.push({ path: `${path}.props.${key}`, message: "\u4E0D\u5141\u8BB8\u4FDD\u5B58\u672A\u77E5\u5B57\u6BB5\u3002" });
333
333
  }
334
334
  for (const [name, field] of Object.entries(definition.fields)) {
335
+ if (field.persist === false) continue;
335
336
  for (const issue of validateFieldValue(field, block.props[name])) {
336
337
  issues.push({ ...issue, path: issue.path ? `${path}.props.${name}.${issue.path}` : `${path}.props.${name}` });
337
338
  }
@@ -1,5 +1,5 @@
1
- import { h as ExtensionRegistry, V as ValidationIssue, c as BlockPolicy, F as FieldConfig } from './registry-CXJdOya3.js';
2
- export { B as BlockDefinition, a as BlockEditorProps, b as BlockOperation, d as BlockVariantDefinition, D as DataSourceDefinition, E as EditorAction, e as EditorActionPosition, f as EditorOperationPolicy, g as ExtensionActionContext, i as ExtensionRegistryError, j as ExtensionRegistryErrorCode, k as ExtensionRegistryOptions, l as ExtensionTarget, m as FieldDefinition, n as FieldProps, o as FieldValidation, L as LifecycleHooks, P as PageBuilderExtension, R as RendererDefinition, T as TemplateDefinition, p as TemplateRegistry, q as TemplateSource, U as UISlotContribution, r as UISlotName, s as createExtensionRegistry, t as createTemplateRegistry } from './registry-CXJdOya3.js';
1
+ import { h as ExtensionRegistry, V as ValidationIssue, c as BlockPolicy, F as FieldConfig } from './registry-BkqtMP-A.js';
2
+ export { B as BlockDefinition, a as BlockEditorProps, b as BlockOperation, d as BlockVariantDefinition, D as DataSourceDefinition, E as EditorAction, e as EditorActionPosition, f as EditorOperationPolicy, g as ExtensionActionContext, i as ExtensionRegistryError, j as ExtensionRegistryErrorCode, k as ExtensionRegistryOptions, l as ExtensionTarget, m as FieldDefinition, n as FieldProps, o as FieldValidation, L as LifecycleHooks, P as PageBuilderExtension, R as RendererDefinition, T as TemplateDefinition, p as TemplateRegistry, q as TemplateSource, U as UISlotContribution, r as UISlotName, s as createExtensionRegistry, t as createTemplateRegistry } from './registry-BkqtMP-A.js';
3
3
  import { PageDocument, JsonValue } from './schema.js';
4
4
  import 'react';
5
5
  import './theme.js';
@@ -13,7 +13,7 @@ import {
13
13
  validateBlockPolicy,
14
14
  validateFieldValue,
15
15
  validatePageDocumentWithRegistry
16
- } from "./chunk-YXBTNYPR.js";
16
+ } from "./chunk-RLKWR5JM.js";
17
17
  export {
18
18
  ExtensionRegistry,
19
19
  ExtensionRegistryError,
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { f as EditorOperationPolicy, c as BlockPolicy, V as ValidationIssue, h as ExtensionRegistry } from './registry-CXJdOya3.js';
4
- export { B as BlockDefinition, a as BlockEditorProps, b as BlockOperation, d as BlockVariantDefinition, D as DataSourceDefinition, E as EditorAction, e as EditorActionPosition, g as ExtensionActionContext, i as ExtensionRegistryError, j as ExtensionRegistryErrorCode, k as ExtensionRegistryOptions, l as ExtensionTarget, F as FieldConfig, m as FieldDefinition, n as FieldProps, o as FieldValidation, L as LifecycleHooks, P as PageBuilderExtension, R as RendererDefinition, T as TemplateDefinition, p as TemplateRegistry, q as TemplateSource, U as UISlotContribution, r as UISlotName, s as createExtensionRegistry, t as createTemplateRegistry } from './registry-CXJdOya3.js';
3
+ import { f as EditorOperationPolicy, c as BlockPolicy, V as ValidationIssue, h as ExtensionRegistry } from './registry-BkqtMP-A.js';
4
+ export { B as BlockDefinition, a as BlockEditorProps, b as BlockOperation, d as BlockVariantDefinition, D as DataSourceDefinition, E as EditorAction, e as EditorActionPosition, g as ExtensionActionContext, i as ExtensionRegistryError, j as ExtensionRegistryErrorCode, k as ExtensionRegistryOptions, l as ExtensionTarget, F as FieldConfig, m as FieldDefinition, n as FieldProps, o as FieldValidation, L as LifecycleHooks, P as PageBuilderExtension, R as RendererDefinition, T as TemplateDefinition, p as TemplateRegistry, q as TemplateSource, U as UISlotContribution, r as UISlotName, s as createExtensionRegistry, t as createTemplateRegistry } from './registry-BkqtMP-A.js';
5
5
  import { PageDocument, BlockNode, JsonValue } from './schema.js';
6
6
  export { BlockStyleOverrides, DataBinding, PageDocumentIssue, PageDocumentMigration, PageDocumentSchemaVersion, PageSettings, RenderTarget, createPageDocument, migratePageDocument, validatePageDocument } from './schema.js';
7
7
  import { ProductReference } from './extensions.js';
@@ -247,6 +247,13 @@ type PageDocumentEditorShellProps = {
247
247
  publishAction?: PublishAction;
248
248
  /** Host-owned picker/uploader for shop-scoped assets. */
249
249
  assetPicker?: AssetPickerAdapter;
250
+ /** External settings, keyed by block id (or block type for shared settings), outside document history and persistence. */
251
+ inspectorSettings?: Record<string, {
252
+ values: Record<string, JsonValue>;
253
+ disabled?: boolean;
254
+ onChange: (fieldName: string, value: JsonValue) => void;
255
+ footer?: ReactNode;
256
+ }>;
250
257
  /** Host-owned product picker. The shell only shows the selected snapshot and a button. */
251
258
  productPicker?: ProductPickerAdapter;
252
259
  /** Optional reusable page lifecycle summary. */
@@ -341,6 +348,11 @@ declare const messages: {
341
348
  readonly selectProductsFailed: "无法选择商品,请重试。";
342
349
  readonly emptyProducts: "未选择商品";
343
350
  readonly productCountSuffix: "件商品";
351
+ readonly selectAsset: "选择图片";
352
+ readonly selectingAsset: "正在上传图片…";
353
+ readonly selectAssetFailed: "无法上传图片,请重试。";
354
+ readonly emptyAsset: "未设置图片";
355
+ readonly removeAsset: "移除图片";
344
356
  };
345
357
  readonly en: {
346
358
  readonly blocks: "Blocks";
@@ -399,13 +411,18 @@ declare const messages: {
399
411
  readonly selectProductsFailed: "Could not select products. Try again.";
400
412
  readonly emptyProducts: "No products selected";
401
413
  readonly productCountSuffix: "products";
414
+ readonly selectAsset: "Select image";
415
+ readonly selectingAsset: "Uploading image…";
416
+ readonly selectAssetFailed: "Could not upload the image. Try again.";
417
+ readonly emptyAsset: "No image selected";
418
+ readonly removeAsset: "Remove image";
402
419
  };
403
420
  };
404
421
  type AdminMessageKey = keyof typeof messages["zh-CN"];
405
422
  /** Minimal, dependency-free Admin copy catalog. Product locale remains PageDocument.settings.locale. */
406
423
  declare function createAdminI18n(locale?: string): {
407
424
  locale: AdminLocale;
408
- 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";
425
+ 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";
409
426
  };
410
427
 
411
428
  declare function toEngineData(document: PageDocument, registry?: ExtensionRegistry): Data;
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  validateBlockPolicy,
14
14
  validateFieldValue,
15
15
  validatePageDocumentWithRegistry
16
- } from "./chunk-YXBTNYPR.js";
16
+ } from "./chunk-RLKWR5JM.js";
17
17
  import {
18
18
  WebRenderer
19
19
  } from "./chunk-KYXIXIWK.js";
@@ -822,7 +822,12 @@ var messages = {
822
822
  selectingProducts: "\u6B63\u5728\u9009\u62E9\u5546\u54C1\u2026",
823
823
  selectProductsFailed: "\u65E0\u6CD5\u9009\u62E9\u5546\u54C1\uFF0C\u8BF7\u91CD\u8BD5\u3002",
824
824
  emptyProducts: "\u672A\u9009\u62E9\u5546\u54C1",
825
- productCountSuffix: "\u4EF6\u5546\u54C1"
825
+ productCountSuffix: "\u4EF6\u5546\u54C1",
826
+ selectAsset: "\u9009\u62E9\u56FE\u7247",
827
+ selectingAsset: "\u6B63\u5728\u4E0A\u4F20\u56FE\u7247\u2026",
828
+ selectAssetFailed: "\u65E0\u6CD5\u4E0A\u4F20\u56FE\u7247\uFF0C\u8BF7\u91CD\u8BD5\u3002",
829
+ emptyAsset: "\u672A\u8BBE\u7F6E\u56FE\u7247",
830
+ removeAsset: "\u79FB\u9664\u56FE\u7247"
826
831
  },
827
832
  en: {
828
833
  blocks: "Blocks",
@@ -880,7 +885,12 @@ var messages = {
880
885
  selectingProducts: "Selecting products\u2026",
881
886
  selectProductsFailed: "Could not select products. Try again.",
882
887
  emptyProducts: "No products selected",
883
- productCountSuffix: "products"
888
+ productCountSuffix: "products",
889
+ selectAsset: "Select image",
890
+ selectingAsset: "Uploading image\u2026",
891
+ selectAssetFailed: "Could not upload the image. Try again.",
892
+ emptyAsset: "No image selected",
893
+ removeAsset: "Remove image"
884
894
  }
885
895
  };
886
896
  function createAdminI18n(locale) {
@@ -960,7 +970,7 @@ function PageDocumentEditorShell(props) {
960
970
  }, [managedSession.state, onSessionStateChange]);
961
971
  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 }) });
962
972
  }
963
- function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onPublish, draftPersistence, draftRevision: initialDraftRevision, autoSave = true, autoSaveDelayMs = 800, publishAction, assetPicker, productPicker, pageStatus, onBack, onHistory, onPreview, onAddToStore, deleteConfirmation, availableBlockTypes, appearanceControls = false }) {
973
+ 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 }) {
964
974
  const editor = useEditorContext();
965
975
  const [blockView, setBlockView] = useState3("blocks");
966
976
  const [leftRailOpen, setLeftRailOpen] = useState3(true);
@@ -1109,20 +1119,22 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1109
1119
  pageStatus ? /* @__PURE__ */ jsx7(PageStatusCard, { status: pageStatus, sessionState: editor.sessionState }) : null
1110
1120
  ] })
1111
1121
  ] }),
1112
- /* @__PURE__ */ jsx7("div", { className: "pb-header-controls", children: /* @__PURE__ */ jsxs5("div", { className: "pb-header-device-toolbar", children: [
1113
- /* @__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)) }),
1114
- /* @__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) }) })
1115
- ] }) }),
1116
- /* @__PURE__ */ jsx7("div", { className: "pb-header-actions", children: /* @__PURE__ */ jsxs5(InlineStack3, { gap: "150", blockAlign: "center", wrap: true, children: [
1117
- /* @__PURE__ */ jsx7(Badge3, { tone: saveBadgeTone, children: saveBadgeLabel }),
1118
- 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,
1119
- /* @__PURE__ */ jsx7(Button2, { disabled: !canPersistDraft || !editor.isDirty || request !== "idle" || validationIssues.length > 0 || !isOnline, onClick: () => void save(), children: saveButtonLabel }),
1120
- onPreview ? /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle" || !isOnline, onClick: () => void onPreview({ document: editor.document, draftRevision, session: editor.session }), children: i18n.t("preview") }) : null,
1121
- onAddToStore ? /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle" || pageStatus?.publicationStatus === "unpublished", onClick: () => void onAddToStore({ document: editor.document, session: editor.session }), children: i18n.t("addToStore") }) : null,
1122
- /* @__PURE__ */ jsx7(Button2, { variant: "primary", disabled: !publishAction && !onPublish || request !== "idle" || validationIssues.length > 0 || !isOnline, onClick: () => void publish(), children: request === "publishing" ? i18n.t("publishing") : i18n.t("publish") }),
1123
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("undo"), icon: UndoIcon2, variant: "tertiary", disabled: !editor.actionState.canUndo, onClick: editor.undo }),
1124
- /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("redo"), icon: RedoIcon2, variant: "tertiary", disabled: !editor.actionState.canRedo, onClick: editor.redo })
1125
- ] }) })
1122
+ /* @__PURE__ */ jsxs5("div", { className: "pb-header-controls", children: [
1123
+ /* @__PURE__ */ jsxs5("div", { className: "pb-header-device-toolbar", children: [
1124
+ /* @__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)) }),
1125
+ /* @__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) }) })
1126
+ ] }),
1127
+ /* @__PURE__ */ jsx7("div", { className: "pb-header-actions", children: /* @__PURE__ */ jsxs5(InlineStack3, { gap: "150", blockAlign: "center", wrap: true, children: [
1128
+ /* @__PURE__ */ jsx7(Badge3, { tone: saveBadgeTone, children: saveBadgeLabel }),
1129
+ 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,
1130
+ /* @__PURE__ */ jsx7(Button2, { disabled: !canPersistDraft || !editor.isDirty || request !== "idle" || validationIssues.length > 0 || !isOnline, onClick: () => void save(), children: saveButtonLabel }),
1131
+ onPreview ? /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle" || !isOnline, onClick: () => void onPreview({ document: editor.document, draftRevision, session: editor.session }), children: i18n.t("preview") }) : null,
1132
+ onAddToStore ? /* @__PURE__ */ jsx7(Button2, { disabled: request !== "idle" || pageStatus?.publicationStatus === "unpublished", onClick: () => void onAddToStore({ document: editor.document, session: editor.session }), children: i18n.t("addToStore") }) : null,
1133
+ /* @__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") }),
1134
+ /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("undo"), icon: UndoIcon2, variant: "tertiary", disabled: !editor.actionState.canUndo, onClick: editor.undo }),
1135
+ /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("redo"), icon: RedoIcon2, variant: "tertiary", disabled: !editor.actionState.canRedo, onClick: editor.redo })
1136
+ ] }) })
1137
+ ] })
1126
1138
  ] }) }),
1127
1139
  editor.loadState === "success" ? /* @__PURE__ */ jsx7(Banner, { tone: "success", children: i18n.t("success") }) : null,
1128
1140
  notice ? /* @__PURE__ */ jsx7(Banner, { tone: notice === "published" ? "success" : "critical", children: i18n.t(notice) }) : null,
@@ -1189,7 +1201,7 @@ function PageDocumentEditor({ iframe = false, registry, adminLocale, onSave, onP
1189
1201
  /* @__PURE__ */ jsx7(Button2, { accessibilityLabel: i18n.t("collapseRight"), icon: XIcon2, variant: "tertiary", onClick: () => setRightPanelOpen(false) })
1190
1202
  ] }),
1191
1203
  editor.selectedBlock ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1192
- /* @__PURE__ */ jsx7(DocumentInspector, { 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) }),
1204
+ /* @__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),
1193
1205
  /* @__PURE__ */ jsx7(InspectorActions, { block: editor.selectedBlock, canDuplicate: editor.actionState.canDuplicate, canDelete: editor.actionState.canDelete, canMove: editor.actionState.canReorder, canMoveUp: editor.document.blocks[0]?.id !== editor.selectedBlock.id, canMoveDown: editor.document.blocks.at(-1)?.id !== editor.selectedBlock.id, i18n, onDuplicate: () => editor.duplicateBlock(editor.selectedBlock.id), onDelete: () => editor.requestDeleteBlock(editor.selectedBlock.id), onMove: (direction) => editor.moveBlock(editor.selectedBlock.id, direction) })
1194
1206
  ] }) : /* @__PURE__ */ jsx7(Text3, { as: "p", tone: "subdued", children: i18n.t("selectBlock") })
1195
1207
  ] })
@@ -1260,6 +1272,39 @@ function InspectorTextControl({ label, value, control, disabled, onChange }) {
1260
1272
  if (control === "color") return /* @__PURE__ */ jsx7("input", { "aria-label": label, type: "color", value: /^#[\da-f]{6}$/i.test(stringValue) ? stringValue : "#000000", disabled, onChange: (event) => onChange(event.currentTarget.value) });
1261
1273
  return /* @__PURE__ */ jsx7(TextField, { label, labelHidden: true, value: stringValue, onChange, autoComplete: "off", disabled, multiline: control === "textarea" ? 4 : false, type: control === "url" ? "url" : "text" });
1262
1274
  }
1275
+ function InspectorAssetControl({ value, disabled, picker, pageId, blockId, i18n, onChange }) {
1276
+ const [busy, setBusy] = useState3(false);
1277
+ const [error, setError] = useState3(null);
1278
+ const active = useRef4(true);
1279
+ useEffect3(() => {
1280
+ active.current = !disabled;
1281
+ return () => {
1282
+ active.current = false;
1283
+ };
1284
+ }, [disabled]);
1285
+ const url = typeof value === "string" && validateFieldValue({ field: "", control: "asset" }, value).length === 0 ? value : "";
1286
+ return /* @__PURE__ */ jsxs5("div", { className: "pb-asset-picker", "data-testid": "asset-picker", children: [
1287
+ 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") }),
1288
+ url ? /* @__PURE__ */ jsx7(Button2, { fullWidth: true, variant: "tertiary", disabled: disabled || busy, onClick: () => onChange(""), children: i18n.t("removeAsset") }) : null,
1289
+ /* @__PURE__ */ jsx7(Button2, { fullWidth: true, disabled: disabled || busy || !picker, onClick: () => void (async () => {
1290
+ if (!picker) return;
1291
+ setBusy(true);
1292
+ setError(null);
1293
+ try {
1294
+ const asset = await picker.selectAsset({ pageId, blockId, current: { url } });
1295
+ if (asset && active.current) {
1296
+ if (validateFieldValue({ field: "", control: "asset", required: true }, asset.url).length) throw new Error("Invalid image URL");
1297
+ onChange(asset.url);
1298
+ }
1299
+ } catch {
1300
+ setError(i18n.t("selectAssetFailed"));
1301
+ } finally {
1302
+ setBusy(false);
1303
+ }
1304
+ })(), children: busy ? i18n.t("selectingAsset") : i18n.t("selectAsset") }),
1305
+ error ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: error }) : null
1306
+ ] });
1307
+ }
1263
1308
  function InspectorProductsControl({ value, disabled, picker, pageId, blockId, i18n, onChange }) {
1264
1309
  const products = parseProductReferences(value);
1265
1310
  const [busy, setBusy] = useState3(false);
@@ -1286,7 +1331,7 @@ function InspectorProductsControl({ value, disabled, picker, pageId, blockId, i1
1286
1331
  error ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: error }) : null
1287
1332
  ] });
1288
1333
  }
1289
- function InspectorField({ name, field, value, registry, disabled, productPicker, pageId, blockId, i18n, onChange }) {
1334
+ function InspectorField({ name, field, value, registry, disabled, assetPicker, productPicker, pageId, blockId, i18n, onChange }) {
1290
1335
  const label = field.label ?? name;
1291
1336
  const issues = validateFieldValue(field, value);
1292
1337
  const Field = registry?.getField(field.field)?.component;
@@ -1295,7 +1340,7 @@ function InspectorField({ name, field, value, registry, disabled, productPicker,
1295
1340
  /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", fontWeight: "semibold", children: label }),
1296
1341
  field.description ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "subdued", children: field.description }) : null
1297
1342
  ] }),
1298
- field.control === "products" ? /* @__PURE__ */ jsx7(InspectorProductsControl, { value, disabled, picker: productPicker, 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,
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,
1299
1344
  issues.map((issue) => /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: issue.message }, issue.message))
1300
1345
  ] });
1301
1346
  }
@@ -1334,7 +1379,7 @@ function inspectorFieldConfig(name, field) {
1334
1379
  return { ...field, group: field.group ?? group, description };
1335
1380
  }
1336
1381
  var appearanceTokens = [["color.primary", "\u4E3B\u8272"], ["color.surface", "\u8868\u9762\u8272"], ["radius", "\u5706\u89D2"], ["spacing", "\u95F4\u8DDD"]];
1337
- function DocumentInspector({ pageId, assetPicker, productPicker, i18n, block, registry, disabled, appearanceControls, onChange, onPresentationChange }) {
1382
+ function DocumentInspector({ settings, pageId, assetPicker, productPicker, i18n, block, registry, disabled, appearanceControls, onChange, onPresentationChange }) {
1338
1383
  const [assetError, setAssetError] = useState3(null);
1339
1384
  const [selectingAsset, setSelectingAsset] = useState3(false);
1340
1385
  const definition = registry?.getBlock(block.type);
@@ -1373,7 +1418,8 @@ function DocumentInspector({ pageId, assetPicker, productPicker, i18n, block, re
1373
1418
  assetError ? /* @__PURE__ */ jsx7(Text3, { as: "p", variant: "bodySm", tone: "critical", children: assetError }) : null
1374
1419
  ] }) : null
1375
1420
  ] }) : null,
1376
- Object.entries(groupedFields).map(([group, fields]) => /* @__PURE__ */ jsx7(InspectorSection, { title: group, defaultOpen: group !== "Advanced", children: fields.map(([name, field]) => /* @__PURE__ */ jsx7(InspectorField, { name, field, value: block.props[name], registry, disabled, productPicker, pageId, blockId: block.id, i18n, onChange: (value) => onChange({ ...block.props, [name]: value }) }, name)) }, group)),
1421
+ 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)),
1422
+ Object.values(definition?.fields ?? {}).some((field) => field.persist === false) ? settings?.footer : null,
1377
1423
  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,
1378
1424
  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) => {
1379
1425
  const style = { ...block.style };
@@ -42,9 +42,11 @@ type FieldConfig = {
42
42
  field: string;
43
43
  label?: string;
44
44
  /** Product-facing editor metadata. Custom field components remain supported. */
45
- control?: "text" | "textarea" | "url" | "color" | "products";
45
+ control?: "text" | "textarea" | "url" | "color" | "products" | "asset";
46
46
  description?: string;
47
47
  group?: string;
48
+ /** Host-owned setting; never written to PageDocument props. */
49
+ persist?: boolean;
48
50
  /** Kept for compatibility; it participates in the built-in validation. */
49
51
  required?: boolean;
50
52
  validation?: FieldValidation;
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { h as ExtensionRegistry } from './registry-CXJdOya3.js';
2
+ import { h as ExtensionRegistry } from './registry-BkqtMP-A.js';
3
3
  import { PageDocument } from './schema.js';
4
4
  import './theme.js';
5
5
 
package/dist/runtime.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { WebRenderer, WebRendererProps } from './renderer.js';
2
- export { B as BlockDefinition, a as BlockEditorProps, b as BlockOperation, c as BlockPolicy, d as BlockVariantDefinition, D as DataSourceDefinition, E as EditorAction, e as EditorActionPosition, f as EditorOperationPolicy, g as ExtensionActionContext, h as ExtensionRegistry, i as ExtensionRegistryError, j as ExtensionRegistryErrorCode, k as ExtensionRegistryOptions, l as ExtensionTarget, F as FieldConfig, m as FieldDefinition, n as FieldProps, o as FieldValidation, L as LifecycleHooks, P as PageBuilderExtension, R as RendererDefinition, T as TemplateDefinition, p as TemplateRegistry, q as TemplateSource, U as UISlotContribution, r as UISlotName, V as ValidationIssue, s as createExtensionRegistry, t as createTemplateRegistry } from './registry-CXJdOya3.js';
2
+ export { B as BlockDefinition, a as BlockEditorProps, b as BlockOperation, c as BlockPolicy, d as BlockVariantDefinition, D as DataSourceDefinition, E as EditorAction, e as EditorActionPosition, f as EditorOperationPolicy, g as ExtensionActionContext, h as ExtensionRegistry, i as ExtensionRegistryError, j as ExtensionRegistryErrorCode, k as ExtensionRegistryOptions, l as ExtensionTarget, F as FieldConfig, m as FieldDefinition, n as FieldProps, o as FieldValidation, L as LifecycleHooks, P as PageBuilderExtension, R as RendererDefinition, T as TemplateDefinition, p as TemplateRegistry, q as TemplateSource, U as UISlotContribution, r as UISlotName, V as ValidationIssue, s as createExtensionRegistry, t as createTemplateRegistry } from './registry-BkqtMP-A.js';
3
3
  export { ProductReference, isProductReference, maximumBlockInstances, mergeBlockPolicies, minimumBlockInstances, parseProductReferences, toProductReferenceJson, validateBlockPolicy, validateFieldValue, validatePageDocumentWithRegistry } from './extensions.js';
4
4
  export { BlockNode, BlockStyleOverrides, DataBinding, JsonPrimitive, JsonValue, PageDocument, PageDocumentIssue, PageDocumentMigration, PageDocumentSchemaVersion, PageDocumentValidation, PageSettings, RenderTarget, createPageDocument, migratePageDocument, validatePageDocument } from './schema.js';
5
5
  export { ThemeTokenName, ThemeTokenValidation, ThemeTokens, mergeThemeTokens, normalizeThemeTokens, systemThemeTokens, toThemeStyle } from './theme.js';
package/dist/runtime.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  validateBlockPolicy,
14
14
  validateFieldValue,
15
15
  validatePageDocumentWithRegistry
16
- } from "./chunk-YXBTNYPR.js";
16
+ } from "./chunk-RLKWR5JM.js";
17
17
  import {
18
18
  WebRenderer
19
19
  } from "./chunk-KYXIXIWK.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@standhigher/puck-page-builder",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
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
@@ -8,14 +8,14 @@ button:focus-visible, [role="button"]:focus-visible { outline: 2px solid var(--b
8
8
  .pb-shell [class*="PuckLayout-inner"] { height: auto !important; min-height: 100%; }
9
9
  .pb-header { position: sticky; top: 0; z-index: 20; min-height: 56px; padding: var(--builder-space-2) var(--builder-space-4); border-bottom: 1px solid var(--builder-border); background: var(--builder-surface); }
10
10
  .pb-header-content { display: flex; flex-wrap: wrap; align-items: center; gap: var(--builder-space-2) var(--builder-space-3); }
11
- .pb-header-title-group { display: flex; flex: 1 1 0; align-items: center; gap: var(--builder-space-2); min-width: 0; }
11
+ .pb-header-title-group { display: flex; flex: 1 1 12rem; align-items: center; gap: var(--builder-space-2); min-width: 0; }
12
12
  .pb-header-sidebar-toggles, [class*="PuckHeader-toggle"] { display: none !important; }
13
13
  .pb-page-title { display: grid; gap: 0; min-width: 0; }
14
14
  .pb-page-title h1, .pb-page-title p { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
15
15
  .pb-page-title p { margin: 0; }
16
- .pb-header-controls { display: flex; flex: 0 0 auto; align-items: center; justify-content: center; }
17
- .pb-header-device-toolbar { display: flex; align-items: center; gap: var(--builder-space-2); }
18
- .pb-header-actions { display: flex; flex: 1 1 0; justify-content: flex-end; min-width: 0; }
16
+ .pb-header-controls { display: flex; flex: 2 1 28rem; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--builder-space-2) var(--builder-space-3); min-width: 0; }
17
+ .pb-header-device-toolbar { display: flex; flex: 0 1 auto; align-items: center; gap: var(--builder-space-2); min-width: 0; }
18
+ .pb-header-actions { display: flex; flex: 1 1 16rem; justify-content: flex-end; min-width: 0; }
19
19
  .pb-header-actions .Polaris-InlineStack { flex-wrap: wrap; justify-content: flex-end; }
20
20
  .pb-header-actions .Polaris-Badge { max-width: 14rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
21
21
  .pb-zoom-control { width: 112px; flex: 0 0 auto; }
@@ -60,6 +60,8 @@ button:focus-visible, [role="button"]:focus-visible { outline: 2px solid var(--b
60
60
  .pb-product-picker__thumb:first-child { margin-left: 0; }
61
61
  .pb-product-picker__thumb img, .pb-product-picker__thumb-fallback { display: block; width: 100%; height: 100%; object-fit: cover; }
62
62
  .pb-product-picker__thumb-fallback { background: #cbd5e1; }
63
+ .pb-asset-picker { display: grid; gap: 8px; }
64
+ .pb-asset-picker__preview { display: block; width: 100%; max-height: 180px; object-fit: cover; border: 1px solid #e2e8f0; border-radius: 10px; background: #f8fafc; }
63
65
  .pb-inspector__hint { margin: 0; padding: 0 var(--builder-space-2); color: #64748b; font-size: 12px; line-height: 1.5; }
64
66
  .pb-inspector-actions { padding-top: var(--builder-space-2); border-top: 1px solid #e2e8f0; }
65
67
  .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%); }
@@ -143,4 +145,4 @@ button:focus-visible, [role="button"]:focus-visible { outline: 2px solid var(--b
143
145
  .bt-storefront__form button { padding: 13px 18px; border: 0; border-radius: 8px; color: white; background: #203e54; font: inherit; font-weight: 600; }
144
146
  @media (max-width: 1180px) { .pb-workspace { grid-template-columns: 64px minmax(260px, 300px) minmax(0, 1fr); } .pb-workspace--left-closed { grid-template-columns: 64px minmax(0, 1fr); } .pb-right-panel { grid-column: 2 / -1; border-top: 1px solid var(--builder-border); border-left: 0; } }
145
147
  @media (max-width: 1023px) { .pb-header { position: static; } .pb-workspace { grid-template-columns: 64px minmax(0, 1fr); } .pb-left-panel, .pb-right-panel { grid-column: 2; border-left: 0; border-right: 0; border-bottom: 1px solid var(--builder-border); } .pb-canvas-area { grid-column: 2; } }
146
- @media (max-width: 640px) { .pb-header, .pb-canvas-area { padding: var(--builder-space-2); } .pb-header-title-group, .pb-header-controls, .pb-header-actions { flex: 1 1 100%; } .pb-header-controls, .pb-header-actions { justify-content: flex-start; } .pb-header-actions .Polaris-InlineStack { justify-content: flex-start; } .pb-canvas-stage { padding: 0; } .pb-canvas-frame--tablet, .pb-canvas-frame--mobile { width: 100%; } .bt-storefront { padding: 28px 20px; } .bt-storefront__form { flex-direction: column; } }
148
+ @media (max-width: 640px) { .pb-header, .pb-canvas-area { padding: var(--builder-space-2); } .pb-header-title-group, .pb-header-controls, .pb-header-actions { flex-basis: 100%; } .pb-header-controls, .pb-header-actions { justify-content: flex-start; } .pb-header-actions .Polaris-InlineStack { justify-content: flex-start; } .pb-canvas-stage { padding: 0; } .pb-canvas-frame--tablet, .pb-canvas-frame--mobile { width: 100%; } .bt-storefront { padding: 28px 20px; } .bt-storefront__form { flex-direction: column; } }