@phis/ui 0.3.0 → 0.4.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.
Files changed (164) hide show
  1. package/components/cms/phi-cms-layout-renderer.js +1 -1
  2. package/components/controls/phi-collection-header-control.js +1 -1
  3. package/components/controls/phi-collection-view-control.js +2 -1
  4. package/components/controls/phi-drawer-control-adapter.d.ts +1 -1
  5. package/components/controls/phi-drawer-control-adapter.js +2 -2
  6. package/components/controls/phi-image-control.d.ts +6 -1
  7. package/components/controls/phi-image-control.js +23 -4
  8. package/components/controls/phi-lightbox-image-control-adapter.d.ts +2 -0
  9. package/components/controls/phi-lightbox-image-control-adapter.js +11 -0
  10. package/components/controls/phi-lightbox-image-control.d.ts +3 -2
  11. package/components/controls/phi-lightbox-image-control.js +7 -3
  12. package/components/controls/phi-modal-control-adapter.d.ts +1 -1
  13. package/components/controls/phi-modal-control-adapter.js +5 -2
  14. package/components/controls/phi-overlay-control-contract.d.ts +5 -0
  15. package/components/controls/phi-pagination-control-adapter.d.ts +2 -0
  16. package/components/controls/phi-pagination-control-adapter.js +15 -0
  17. package/components/controls/phi-pagination-control.d.ts +2 -1
  18. package/components/controls/phi-pagination-control.js +11 -7
  19. package/components/controls/phi-table-control.d.ts +6 -1
  20. package/components/controls/phi-table-control.js +17 -5
  21. package/components/controls/phi-timeline-control-adapter.d.ts +2 -0
  22. package/components/controls/phi-timeline-control-adapter.js +28 -0
  23. package/components/controls/phi-timeline-control.d.ts +3 -1
  24. package/components/controls/phi-timeline-control.js +8 -22
  25. package/components/controls/phi-tour-control.js +25 -7
  26. package/components/controls/phi-tour-viewport.d.ts +36 -0
  27. package/components/controls/phi-tour-viewport.js +86 -0
  28. package/components/controls/phi-zoom-image-overlay.d.ts +17 -0
  29. package/components/controls/phi-zoom-image-overlay.js +58 -0
  30. package/components/layouts/layout-definitions.d.ts +2 -20
  31. package/components/layouts/layout-definitions.js +13 -2
  32. package/components/layouts/plugins/carousel-layout-plugin.js +2 -1
  33. package/components/media/aspect-ratios.d.ts +14 -0
  34. package/components/media/aspect-ratios.js +13 -0
  35. package/components/motion/phi-sequence-run.d.ts +27 -0
  36. package/components/motion/phi-sequence-run.js +87 -0
  37. package/components/motion/phi-sequence-viewport.d.ts +16 -0
  38. package/components/motion/phi-sequence-viewport.js +97 -34
  39. package/components/overlays/phi-overlay-container-client.js +5 -2
  40. package/components/runtime/runtime-visual-anchors.d.ts +13 -2
  41. package/components/runtime/runtime-visual-anchors.js +49 -3
  42. package/components/shell/use-phi-shell-sticky-top.d.ts +5 -0
  43. package/components/shell/use-phi-shell-sticky-top.js +78 -0
  44. package/components/widgets/config/image-variant-options.d.ts +3 -5
  45. package/components/widgets/config/image-variant-options.js +11 -11
  46. package/components/widgets/config/sequence-settings.d.ts +75 -0
  47. package/components/widgets/config/sequence-settings.js +173 -0
  48. package/components/widgets/helpers/semantic-html-translation.d.ts +3 -1
  49. package/components/widgets/helpers/semantic-html-translation.js +8 -3
  50. package/components/widgets/label-sets/content-entries.d.ts +9 -0
  51. package/components/widgets/label-sets/content-entries.js +17 -0
  52. package/components/widgets/label-types/gallery.d.ts +2 -10
  53. package/components/widgets/label-types/sequence.d.ts +18 -0
  54. package/components/widgets/label-types/sequence.js +1 -0
  55. package/components/widgets/navigation-descriptor-resolver.server.js +6 -1
  56. package/components/widgets/shared/markdown-render.js +15 -5
  57. package/helpers/cms-layout-defaults.js +9 -2
  58. package/helpers/cms-region-keys.d.ts +1 -1
  59. package/helpers/motion.d.ts +7 -0
  60. package/helpers/motion.js +7 -0
  61. package/helpers/text-source.d.ts +19 -0
  62. package/helpers/text-source.js +20 -0
  63. package/next/config.mjs +1 -1
  64. package/package.json +1 -1
  65. package/plugins/runtime-modules/asset/data-providers.d.ts +4 -0
  66. package/plugins/runtime-modules/asset/data-providers.js +1 -0
  67. package/plugins/runtime-modules/asset/definition.d.ts +4 -0
  68. package/plugins/runtime-modules/asset/presets.d.ts +53 -1
  69. package/plugins/runtime-modules/asset/presets.js +16 -12
  70. package/plugins/runtime-modules/asset/tour.d.ts +44 -0
  71. package/plugins/runtime-modules/asset/tour.js +53 -0
  72. package/plugins/runtime-modules/auth/trees/phi-default-pub-confirm-page-tree.js +3 -0
  73. package/plugins/runtime-modules/auth/trees/phi-default-pub-login-page-tree.js +2 -0
  74. package/plugins/runtime-modules/auth/trees/phi-default-pub-registration-page-tree.js +3 -0
  75. package/plugins/runtime-modules/auth/trees/phi-default-pub-reset-password-page-tree.js +3 -0
  76. package/plugins/runtime-modules/builder/clients/inspector-config-field.js +42 -18
  77. package/plugins/runtime-modules/builder/clients/inspector-image-field.js +2 -2
  78. package/plugins/runtime-modules/builder/clients/widget-inspector.js +9 -0
  79. package/plugins/runtime-modules/builder/clients/workspace-scope-boundary.js +10 -0
  80. package/plugins/runtime-modules/builder/controller/workspace-controller.js +18 -3
  81. package/plugins/runtime-modules/builder/plugin-meta-labels.js +8 -0
  82. package/plugins/runtime-modules/builder/presets.js +5 -3
  83. package/plugins/runtime-modules/builder/tour.d.ts +316 -0
  84. package/plugins/runtime-modules/builder/tour.js +337 -0
  85. package/plugins/runtime-modules/builder/trees/phi-builder-inspector-area-overlay-tree.server.js +8 -1
  86. package/plugins/runtime-modules/builder/trees/phi-default-builder-area-preset-tree.js +4 -0
  87. package/plugins/runtime-modules/builder/widgets/structure-region/built-in.js +3 -2
  88. package/plugins/runtime-modules/core/widgets/card/server.js +4 -2
  89. package/plugins/runtime-modules/core/widgets/collection-view/client.js +10 -0
  90. package/plugins/runtime-modules/core/widgets/description/config.d.ts +28 -1
  91. package/plugins/runtime-modules/core/widgets/description/config.js +2 -0
  92. package/plugins/runtime-modules/core/widgets/description/server.js +10 -12
  93. package/plugins/runtime-modules/core/widgets/entry-detail/config.d.ts +2 -0
  94. package/plugins/runtime-modules/core/widgets/entry-detail/config.js +8 -1
  95. package/plugins/runtime-modules/core/widgets/entry-header/config.d.ts +3 -0
  96. package/plugins/runtime-modules/core/widgets/entry-header/config.js +8 -1
  97. package/plugins/runtime-modules/core/widgets/entry-list/client.d.ts +4 -1
  98. package/plugins/runtime-modules/core/widgets/entry-list/client.js +3 -2
  99. package/plugins/runtime-modules/core/widgets/entry-list/config.d.ts +392 -93
  100. package/plugins/runtime-modules/core/widgets/entry-list/config.js +21 -3
  101. package/plugins/runtime-modules/core/widgets/entry-list/server.js +4 -2
  102. package/plugins/runtime-modules/core/widgets/gallery/authoring.js +62 -10
  103. package/plugins/runtime-modules/core/widgets/gallery/client.d.ts +6 -1
  104. package/plugins/runtime-modules/core/widgets/gallery/client.js +7 -79
  105. package/plugins/runtime-modules/core/widgets/gallery/config.d.ts +337 -144
  106. package/plugins/runtime-modules/core/widgets/gallery/config.js +52 -113
  107. package/plugins/runtime-modules/core/widgets/gallery/defaults.d.ts +15 -0
  108. package/plugins/runtime-modules/core/widgets/gallery/defaults.js +20 -0
  109. package/plugins/runtime-modules/core/widgets/html/server.js +1 -1
  110. package/plugins/runtime-modules/core/widgets/image/client.js +1 -1
  111. package/plugins/runtime-modules/core/widgets/image/config.d.ts +29 -4
  112. package/plugins/runtime-modules/core/widgets/image/config.js +19 -2
  113. package/plugins/runtime-modules/core/widgets/image/editor.js +1 -1
  114. package/plugins/runtime-modules/core/widgets/markdown-toc/server.js +7 -2
  115. package/plugins/runtime-modules/core/widgets/quick-links/config.d.ts +3 -0
  116. package/plugins/runtime-modules/core/widgets/quick-links/config.js +2 -0
  117. package/plugins/runtime-modules/core/widgets/quick-links/plugin.js +4 -2
  118. package/plugins/runtime-modules/core/widgets/quick-links/server.d.ts +5 -1
  119. package/plugins/runtime-modules/core/widgets/quick-links/server.js +4 -2
  120. package/plugins/runtime-modules/core/widgets/result/config.d.ts +25 -1
  121. package/plugins/runtime-modules/core/widgets/simple-text/config.d.ts +28 -1
  122. package/plugins/runtime-modules/core/widgets/simple-text/config.js +2 -0
  123. package/plugins/runtime-modules/core/widgets/simple-text/plugin.js +1 -1
  124. package/plugins/runtime-modules/core/widgets/simple-text/server.d.ts +4 -1
  125. package/plugins/runtime-modules/core/widgets/simple-text/server.js +3 -3
  126. package/plugins/runtime-modules/core/widgets/table/client.js +1 -1
  127. package/plugins/runtime-modules/core/widgets/table/config.d.ts +80 -3
  128. package/plugins/runtime-modules/core/widgets/table/config.js +3 -0
  129. package/plugins/runtime-modules/core/widgets/tour-button/client.js +1 -1
  130. package/plugins/runtime-modules/phi-default-site-area-preset-tree.js +12 -4
  131. package/plugins/runtime-modules/public/trees/phi-default-pub-contact-page-tree.js +2 -0
  132. package/plugins/runtime-modules/public/trees/phi-default-pub-unsubscribe-page-tree.js +3 -0
  133. package/plugins/runtime-modules/public/trees/phi-default-pub-welcome-page-tree.js +2 -1
  134. package/plugins/runtime-modules/revisions/presets.d.ts +53 -2
  135. package/plugins/runtime-modules/revisions/presets.js +16 -12
  136. package/plugins/runtime-modules/revisions/tour.d.ts +43 -0
  137. package/plugins/runtime-modules/revisions/tour.js +51 -0
  138. package/plugins/runtime-modules/revisions/trees/phi-default-builder-revisions-page-tree.js +4 -0
  139. package/plugins/runtime-modules/theme/presets.d.ts +36 -4
  140. package/plugins/runtime-modules/theme/tour.d.ts +45 -11
  141. package/plugins/runtime-modules/theme/tour.js +39 -8
  142. package/plugins/runtime-modules/tour/controller/address.js +2 -2
  143. package/plugins/runtime-modules/tour/controller/client.js +43 -2
  144. package/plugins/runtime-modules/tour/controller/definition.js +2 -1
  145. package/server-helpers/cms-request.js +1 -0
  146. package/server-helpers/page-tour.d.ts +10 -3
  147. package/server-helpers/page-tour.js +25 -12
  148. package/server-helpers/translate.d.ts +9 -0
  149. package/server-helpers/translate.js +14 -0
  150. package/styles/controls.css +15 -1
  151. package/styles/root.css +20 -19
  152. package/types/cms-config.d.ts +3 -1
  153. package/types/cms-config.js +3 -3
  154. package/types/cms-plugins.d.ts +31 -4
  155. package/types/core-widget-placements.d.ts +5 -0
  156. package/types/table-widget.d.ts +2 -0
  157. package/types/tour-controller.d.ts +10 -0
  158. package/types/tour-controller.js +13 -0
  159. package/types/tours.d.ts +27 -25
  160. package/types/tours.js +9 -23
  161. package/types/visual-anchors.d.ts +15 -0
  162. package/types/visual-anchors.js +12 -0
  163. package/types.d.ts +2 -0
  164. package/types.js +4 -1
@@ -314,6 +314,21 @@ function resolveInspectorChoiceOptions(field, widgetReferenceOptions, resolvedOp
314
314
  }
315
315
  return resolvedOptions;
316
316
  }
317
+ /**
318
+ * A closed list of numbers. The select holds the numbers themselves, so what is picked is what is
319
+ * stored; only the empty line is a word, and it stands for `emptyValue`.
320
+ */
321
+ function renderPhiInspectorNumberChoiceField({ field, value, defaultValue, disabled, onChange, }) {
322
+ const stored = value ?? defaultValue;
323
+ const emptyOptionValue = field.emptyOption?.value;
324
+ const selectedValue = typeof stored === "number" ? stored : emptyOptionValue;
325
+ const options = field.emptyOption
326
+ ? [field.emptyOption, ...field.options]
327
+ : field.options;
328
+ return renderPhiInspectorConfigFieldControl(field, _jsx(PhiSelectControl, { options: options, placeholder: field.placeholder, value: selectedValue, disabled: disabled, style: { width: "100%" }, onChange: (next) => onChange?.({
329
+ [field.key]: emptyOptionValue != null && next === emptyOptionValue ? field.emptyValue : next,
330
+ }) }));
331
+ }
317
332
  function PhiInspectorChoiceFieldControl({ field, config, value, defaultValue, disabled, widgetReferenceOptions, labels, onChange, }) {
318
333
  const staticOptions = useMemo(() => field.filter?.widgetType
319
334
  ? widgetReferenceOptions
@@ -524,7 +539,7 @@ function renderPhiInspectorConfigFieldBody({ field, value, defaultValue, config,
524
539
  * other kinds are a Provider or nothing.
525
540
  */
526
541
  const supportsStaticOptions = !field.providerKind || field.providerKind === "options" || field.providerKind === "timeline";
527
- const selectedProvider = dataProviderDescriptors.find((descriptor) => descriptor.key === providerConfig?.providerKey) ?? null;
542
+ const selectedProvider = dataProviderDescriptors.find((descriptor) => descriptor.key === (field.providerKey ?? providerConfig?.providerKey)) ?? null;
528
543
  const options = [
529
544
  ...(supportsStaticOptions
530
545
  ? [{
@@ -540,7 +555,7 @@ function renderPhiInspectorConfigFieldBody({ field, value, defaultValue, config,
540
555
  description: descriptor.description,
541
556
  })),
542
557
  ];
543
- return renderPhiInspectorConfigFieldControl(field, _jsxs(PhiFlexControl, { vertical: true, gap: 8, style: { width: "100%" }, children: [_jsx(PhiSelectControl, { options: options, placeholder: fieldLabels.selectDataProvider, value: providerConfig?.providerKey ?? (supportsStaticOptions ? PHI_STATIC_OPTIONS_PROVIDER_VALUE : undefined), disabled: disabled || !onChange, style: { width: "100%" }, onChange: (providerKey) => onChange?.({
558
+ const bindingRow = renderPhiInspectorConfigFieldControl(field, _jsxs(PhiFlexControl, { vertical: true, gap: 8, style: { width: "100%" }, children: [_jsx(PhiSelectControl, { options: options, placeholder: fieldLabels.selectDataProvider, value: providerConfig?.providerKey ?? (supportsStaticOptions ? PHI_STATIC_OPTIONS_PROVIDER_VALUE : undefined), disabled: disabled || !onChange, style: { width: "100%" }, onChange: (providerKey) => onChange?.({
544
559
  [field.key]: providerKey === PHI_STATIC_OPTIONS_PROVIDER_VALUE
545
560
  ? null
546
561
  : field.providerKind === "table" || field.providerKind === "tree" || field.providerKind === "collection"
@@ -575,22 +590,28 @@ function renderPhiInspectorConfigFieldBody({ field, value, defaultValue, config,
575
590
  resourceKey,
576
591
  params: isPhiRecord(rawProviderConfig.params) ? rawProviderConfig.params : undefined,
577
592
  },
578
- }) })) : null, field.providerKind === "record" && selectedProvider
579
- ? (selectedProvider.settingsFields ?? []).map((paramField) => {
580
- const params = isPhiRecord(rawProviderConfig.params) ? rawProviderConfig.params : {};
581
- return _jsx(Fragment, { children: renderPhiInspectorConfigField({
582
- field: paramField,
583
- value: params[paramField.key],
584
- defaultValue: undefined,
585
- config: params,
586
- disabled,
587
- fieldLabels,
588
- onChange: (patch) => onChange?.({
589
- [field.key]: { providerKey: selectedProvider.key, params: { ...params, ...patch } },
590
- }),
591
- }) }, paramField.key);
592
- })
593
- : null] }));
593
+ }) })) : null] }));
594
+ if (field.providerKind !== "record" || !selectedProvider)
595
+ return bindingRow;
596
+ /*
597
+ * A record is one thing, named by its parameters -- an entry by kind and address -- and the
598
+ * Provider says which it takes in its `settingsFields`. They are rows of their own under the
599
+ * binding, words on the left like every other field; inside the binding's control column they
600
+ * would draw a second pair of columns squeezed into the first one's right half. A binding fixed
601
+ * to one Provider shows only them.
602
+ */
603
+ const params = isPhiRecord(rawProviderConfig.params) ? rawProviderConfig.params : {};
604
+ return (_jsxs(Fragment, { children: [field.providerKey ? null : bindingRow, (selectedProvider.settingsFields ?? []).map((paramField) => (_jsx(Fragment, { children: renderPhiInspectorConfigField({
605
+ field: paramField,
606
+ value: params[paramField.key],
607
+ defaultValue: undefined,
608
+ config: params,
609
+ disabled,
610
+ fieldLabels,
611
+ onChange: (patch) => onChange?.({
612
+ [field.key]: { providerKey: selectedProvider.key, params: { ...params, ...patch } },
613
+ }),
614
+ }) }, paramField.key)))] }, field.key));
594
615
  }
595
616
  if (field.type === "calendar-adapter") {
596
617
  return renderPhiInspectorConfigFieldControl(field, _jsx(PhiSelectControl, { options: calendarAdapterDescriptors.map((descriptor) => ({
@@ -692,6 +713,9 @@ function renderPhiInspectorConfigFieldBody({ field, value, defaultValue, config,
692
713
  ...(onChange ? { onChange: (checked) => onChange({ [field.key]: checked }) } : {}),
693
714
  });
694
715
  }
716
+ if (field.type === "choice" && field.valueType === "number") {
717
+ return renderPhiInspectorNumberChoiceField({ field, value, defaultValue, disabled: disabled || !onChange, onChange });
718
+ }
695
719
  if (field.type === "choice") {
696
720
  return (_jsx(PhiInspectorChoiceFieldControl, { field: field, config: config, value: value, defaultValue: defaultValue, disabled: disabled || !onChange, widgetReferenceOptions: widgetReferenceOptions ?? [], labels: fieldLabels, onChange: onChange }, field.key));
697
721
  }
@@ -26,7 +26,7 @@ export function PhiInspectorImageFieldControl({ config, blockId, disabled, onCha
26
26
  const assetId = config.sourceKind === "asset" && typeof config.assetId === "number" ? config.assetId : null;
27
27
  const asset = usePhiAuthoringAssetDetails(assetId);
28
28
  const sourceUrl = typeof config.sourceUrl === "string" ? config.sourceUrl : "";
29
- const variantKey = typeof config.variantKey === "number" ? String(config.variantKey) : PHI_IMAGE_FIELD_ORIGINAL_VARIANT;
29
+ const variantKey = typeof config.variantKey === "number" ? config.variantKey : PHI_IMAGE_FIELD_ORIGINAL_VARIANT;
30
30
  const editable = !disabled && onChange != null;
31
31
  return (_jsxs(PhiFlexControl, { vertical: true, gap: 8, style: { width: "100%", minWidth: 0 }, children: [_jsx(PhiMediaPickerBinding, { config: {
32
32
  mediaType: PhiMediaKind.Image,
@@ -52,7 +52,7 @@ export function PhiInspectorImageFieldControl({ config, blockId, disabled, onCha
52
52
  sourceUrl: undefined,
53
53
  })
54
54
  : undefined }), assetId != null ? (_jsx(PhiInspectorFieldRow, { label: "Variant", children: _jsx(PhiSelectControl, { value: variantKey, options: [{ value: PHI_IMAGE_FIELD_ORIGINAL_VARIANT, label: "Original", separator: "after" }, ...PHI_IMAGE_VARIANT_OPTIONS], disabled: !editable, style: { width: "100%" }, onChange: (next) => onChange?.({
55
- variantKey: next === PHI_IMAGE_FIELD_ORIGINAL_VARIANT ? null : Number(next),
55
+ variantKey: next === PHI_IMAGE_FIELD_ORIGINAL_VARIANT ? null : next,
56
56
  }) }) })) : (_jsx(PhiInspectorFieldRow, { label: "URL", children: _jsx(PhiTextControl, { value: sourceUrl, inputType: "url", placeholder: "https://", disabled: !editable, style: { width: "100%" }, onChange: (next) => onChange?.({ sourceKind: "url", sourceUrl: next?.trim() || undefined }) }) })), _jsx(PhiInspectorFieldRow, { label: "Alt text", children: _jsx(PhiTextControl, { value: typeof config.alt === "string" ? config.alt : "",
57
57
  /* An Asset brings its own; this replaces it for this placement only. */
58
58
  placeholder: asset?.altText ?? undefined, disabled: !editable, style: { width: "100%" }, onChange: (next) => onChange?.({ alt: next || undefined }) }) })] }));
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { isPhiAuthoredTextField, readPhiTextSource } from "../../../../helpers/text-source";
3
4
  import { useEffect } from "react";
4
5
  import { PhiGeometryControl } from "../../../../components/controls/phi-geometry-control";
5
6
  import { PhiSurfaceControl } from "../../../../components/controls/phi-surface-control";
@@ -81,6 +82,14 @@ export function PhiDeveloperBuilderWidgetInspectorWidgetClient({ section, builde
81
82
  videoProviderDescriptors,
82
83
  onChange: (next) => {
83
84
  const patch = buildPhiInspectorConfigPathPatch(currentWidgetSettingsConfigRecord, next);
85
+ /*
86
+ * An author writing into a Preset's text makes the Widget's words the Site's: from here on the
87
+ * Site's store translates them, not the global one (`helpers/text-source.ts`).
88
+ */
89
+ if (readPhiTextSource(currentWidgetSettingsConfigRecord.textSource) != null
90
+ && isPhiAuthoredTextField(field)) {
91
+ patch.textSource = undefined;
92
+ }
84
93
  onConfigChange?.(patch);
85
94
  },
86
95
  });
@@ -4,10 +4,20 @@ import { PhiBuilderWorkspacePreviewSkeleton } from "../../../../components/widge
4
4
  import { usePhiDeveloperBuilderStateValue, } from "../developer-workspace-store";
5
5
  import { PhiSignalRuntimePartitionProvider } from "../../../../components/runtime/runtime-signal-partition";
6
6
  import { PhiBuilderCanvasRegionOverrides } from "./builder-region-overrides";
7
+ import { usePhiSignalIdentity } from "../../../../components/runtime/runtime-signal-identity";
8
+ import { usePhiVisualAnchorParts } from "../../../../components/runtime/runtime-visual-anchors";
9
+ import { PHI_BUILDER_PAGE_REGION_KEYS, PHI_BUILDER_SHELL_REGION_KEYS } from "../region-keys";
7
10
  export function PhiBuilderWorkspaceScopeBoundary({ kind, targetArea, targetPageKey, children, }) {
8
11
  const activeArea = usePhiDeveloperBuilderStateValue("public", (state) => state.area);
9
12
  const activePageKey = usePhiDeveloperBuilderStateValue("public", (state) => state.pageKey);
10
13
  const scopeMatches = activeArea === targetArea &&
11
14
  (targetPageKey == null || activePageKey === targetPageKey);
15
+ /*
16
+ * Each Region on the canvas, offered to a Tour as `cms:<workspace>:<regionKey>`. Registered here, on
17
+ * this side of the canvas partition: the Tour looks in the Page's partition, and the canvas below runs
18
+ * in an isolated one of its own.
19
+ */
20
+ const signalIdentity = usePhiSignalIdentity();
21
+ usePhiVisualAnchorParts(signalIdentity.receiver?.startsWith("cms:") ? signalIdentity.receiver : null, kind === "shells" ? PHI_BUILDER_SHELL_REGION_KEYS : PHI_BUILDER_PAGE_REGION_KEYS);
12
22
  return scopeMatches ? (_jsx(PhiSignalRuntimePartitionProvider, { id: `canvas:${kind}:${targetArea}:${targetPageKey ?? "area"}`, kind: "canvas", context: { area: targetArea, pageKey: targetPageKey ?? null }, isolated: true, children: _jsx(PhiBuilderCanvasRegionOverrides, { children: children }) })) : _jsx(PhiBuilderWorkspacePreviewSkeleton, { kind: kind });
13
23
  }
@@ -22,7 +22,7 @@ import { isPhiBuilderAreaKey, resolvePhiBuilderAreaAsCmsArea, } from "../../../.
22
22
  import { resolvePhiRuntimeAreaDefinition } from "../../../../plugins/runtime-modules/area-definitions";
23
23
  import { resolvePhiBuilderNavigationWidgetNavKey } from "../navigation-widget-runtime";
24
24
  import { parsePhiBuilderNavigationScopeKey } from "../../../../helpers/cms-navigation-catalog";
25
- import { builderWorkspaceStore, usePhiDeveloperBuilderWorkspaceState, completePhiDeveloperBuilderEffectsEditor, normalizePhiDeveloperBuilderArea as normalizeBuilderArea, previewPhiDeveloperBuilderEffects, resetPhiBuilderSelectionToPage, selectPhiDeveloperBuilderNode, usePhiDeveloperRegionDraft, getPhiDeveloperBuilderStateSnapshot, readPhiBuilderEffectiveAreaRootRoute, setPhiDeveloperBuilderAreaRootRoutes, setPhiDeveloperBuilderAreaMetaBaseline, setPhiDeveloperBuilderAreaControllerSettings, readPhiBuilderEffectiveAreaMeta, closePhiBuilderModuleDeactivationRequest, closePhiBuilderPublicRouteCollisionRequest, } from "../developer-workspace-store";
25
+ import { builderWorkspaceStore, usePhiDeveloperBuilderWorkspaceState, completePhiDeveloperBuilderEffectsEditor, normalizePhiDeveloperBuilderArea as normalizeBuilderArea, previewPhiDeveloperBuilderEffects, resetPhiBuilderSelectionToPage, selectPhiDeveloperBuilderNode, usePhiDeveloperRegionDraft, getPhiDeveloperBuilderStateSnapshot, getPhiDeveloperRegionDraftsSnapshot, readPhiBuilderEffectiveAreaRootRoute, setPhiDeveloperBuilderAreaRootRoutes, setPhiDeveloperBuilderAreaMetaBaseline, setPhiDeveloperBuilderAreaControllerSettings, readPhiBuilderEffectiveAreaMeta, closePhiBuilderModuleDeactivationRequest, closePhiBuilderPublicRouteCollisionRequest, } from "../developer-workspace-store";
26
26
  import {} from "../options-providers";
27
27
  import { PHI_AREA_META_PUBLIC_DEFAULTS } from "../../../../helpers/cms-area-config";
28
28
  import { getPhiBuilderRegionDraftKey } from "../region-keys";
@@ -1072,13 +1072,28 @@ function usePhiDeveloperBuilderWorkspaceController(defaultArea, options) {
1072
1072
  if (!next || typeof next.nodeKey !== "string" || typeof next.nodeKind !== "string") {
1073
1073
  return;
1074
1074
  }
1075
- const nextNodeKey = next.nodeKey;
1075
+ let nextNodeKey = next.nodeKey;
1076
+ let nextNodeId = readPhiCmsInstanceId(next.nodeId);
1076
1077
  const nextPageKey = typeof next.pageKey === "string" ? next.pageKey : undefined;
1078
+ /*
1079
+ * A Layout named only by its Region is that Region's root Layout, read from its draft. A sender that
1080
+ * cannot know a Site's node ids -- a Tour step showing the Layout Inspector -- names it this way. A
1081
+ * Region without a root Layout selects nothing.
1082
+ */
1083
+ if (next.nodeKind === "layout" && nextNodeId == null && typeof next.regionKey === "string") {
1084
+ const current = getPhiDeveloperBuilderStateSnapshot(defaultArea);
1085
+ const rootNode = getPhiDeveloperRegionDraftsSnapshot()[getPhiBuilderRegionDraftKey(typeof next.area === "string" ? next.area : current.area, next.regionKey, nextPageKey ?? current.pageKey)]?.rootNode;
1086
+ if (!rootNode) {
1087
+ return;
1088
+ }
1089
+ nextNodeId = rootNode.id;
1090
+ nextNodeKey = rootNode.widgetType;
1091
+ }
1077
1092
  selectPhiDeveloperBuilderNode(defaultArea, {
1078
1093
  ...(typeof next.area === "string" ? { area: next.area } : {}),
1079
1094
  ...(nextPageKey ? { pageKey: nextPageKey } : {}),
1080
1095
  nodeKey: nextNodeKey,
1081
- nodeId: readPhiCmsInstanceId(next.nodeId),
1096
+ nodeId: nextNodeId,
1082
1097
  nodeKind: next.nodeKind,
1083
1098
  regionType: typeof next.regionType === "number" ? next.regionType : null,
1084
1099
  regionKey: typeof next.regionKey === "string" ? next.regionKey : null,
@@ -38,6 +38,14 @@ function mapPhiConfigFieldText(field, mapText) {
38
38
  ...(field.description == null ? {} : { description: mapOptionalText(field.description, mapText) }),
39
39
  ...(field.heading == null ? {} : { heading: mapOptionalText(field.heading, mapText) }),
40
40
  };
41
+ if (mapped.type === "choice" && mapped.valueType === "number") {
42
+ return {
43
+ ...mapped,
44
+ options: mapped.options.map((option) => mapPhiControlOptionText(option, mapText)),
45
+ ...(mapped.emptyOption == null ? {} : { emptyOption: mapPhiControlOptionText(mapped.emptyOption, mapText) }),
46
+ ...(mapped.placeholder == null ? {} : { placeholder: mapOptionalText(mapped.placeholder, mapText) }),
47
+ };
48
+ }
41
49
  if (mapped.type === "choice") {
42
50
  return {
43
51
  ...mapped,
@@ -2,6 +2,7 @@ import { PHI_BASE_PAGE_LAYOUT_VERSION } from "../../../components/regions/preset
2
2
  import { buildPhiAreaRootRoutePresetDescriptor } from "../area-root-route";
3
3
  import { PHI_BUILDER_RUNTIME_MODULE_ID } from "./ids";
4
4
  import { PHI_BUILDER_PAGE_PRESET_VERSION } from "../../../components/regions/presets/phi-builder-page-preset-version";
5
+ import { PHI_BUILDER_NAVIGATION_TOUR, PHI_BUILDER_PAGES_TOUR, PHI_BUILDER_SHELLS_TOUR } from "./tour";
5
6
  export const PHI_BUILDER_RUNTIME_MODULE_AREA_SHELLS = [{
6
7
  ownerModuleId: PHI_BUILDER_RUNTIME_MODULE_ID,
7
8
  presetKey: "builder-area-preset",
@@ -11,9 +12,9 @@ export const PHI_BUILDER_RUNTIME_MODULE_AREA_SHELLS = [{
11
12
  .then((module) => module.buildPhiDefaultBuilderAreaPresetTree({ page, runtime })),
12
13
  }];
13
14
  const BUILDER_ROUTE_PRESETS = [
14
- { presetKey: "builder-shells-page", title: "Shells", path: "/shells" },
15
- { presetKey: "builder-pages-page", title: "Pages", path: "/pages" },
16
- { presetKey: "builder-navigation-page", title: "Navigation", path: "/navigation" },
15
+ { presetKey: "builder-shells-page", title: "Shells", path: "/shells", tour: PHI_BUILDER_SHELLS_TOUR },
16
+ { presetKey: "builder-pages-page", title: "Pages", path: "/pages", tour: PHI_BUILDER_PAGES_TOUR },
17
+ { presetKey: "builder-navigation-page", title: "Navigation", path: "/navigation", tour: PHI_BUILDER_NAVIGATION_TOUR },
17
18
  { presetKey: "builder-modules-page", title: "Modules", path: "/modules" },
18
19
  ];
19
20
  const BUILDER_WORKSPACE_ROUTES = BUILDER_ROUTE_PRESETS.map((route) => ({
@@ -23,6 +24,7 @@ const BUILDER_WORKSPACE_ROUTES = BUILDER_ROUTE_PRESETS.map((route) => ({
23
24
  area: "builder",
24
25
  title: route.title,
25
26
  path: route.path,
27
+ ...("tour" in route ? { tour: route.tour } : {}),
26
28
  loadTree: ({ page, runtime }) => import("./trees/phi-default-builder-area-preset-tree")
27
29
  .then((module) => module.buildPhiDefaultBuilderPagePresetTree({
28
30
  page,
@@ -0,0 +1,316 @@
1
+ import { type PhiCmsInstanceId } from "../../../types/cms-instance-id";
2
+ /**
3
+ * The Shells workspace's Tour. What the workspace does not say is what a Shell is: the frame every Page
4
+ * of the Area stands in, so a change here reaches all of them at once. The rest is the order a person
5
+ * meets the workspace in, ending where the work goes live. The canvas steps point at single Regions
6
+ * (`cms:<canvas>:<regionKey>`): the canvas is taller than the window, and pointing at all of it scrolled
7
+ * it under the Builder's header and lit the header up with it.
8
+ */
9
+ export declare const PHI_BUILDER_SHELLS_TOUR: {
10
+ readonly id: "@phis/ui/modules/builder/shells";
11
+ readonly version: 1;
12
+ readonly steps: readonly [{
13
+ readonly key: "area";
14
+ readonly target: `cms:${PhiCmsInstanceId}`;
15
+ readonly title: "Which Area you are building";
16
+ readonly description: "Every Area has its own Shell, and everything in this workspace applies to the Area chosen here. Usually only Public and App are worked on; the other Areas come as their Modules build them.";
17
+ readonly placement: "bottom";
18
+ readonly ifMissing: "skip";
19
+ }, {
20
+ readonly key: "shell";
21
+ readonly target: `cms:${PhiCmsInstanceId}:header_main`;
22
+ readonly title: "The frame of every Page";
23
+ readonly description: "The Shell is what every Page of the Area shares: the rows of the header, the left sider and the footer. What you change here appears on all of them.";
24
+ readonly placement: "bottom";
25
+ }, {
26
+ readonly key: "regions";
27
+ readonly target: `cms:${PhiCmsInstanceId}:sider_left`;
28
+ readonly title: "Regions and what stands in them";
29
+ readonly description: "A region holds only Layouts, and a Layout holds further Layouts or Widgets, so the first thing placed in an empty region is always a Layout.";
30
+ readonly placement: "right";
31
+ }, {
32
+ readonly key: "select";
33
+ readonly target: `cms:${PhiCmsInstanceId}:header_top` | `cms:${PhiCmsInstanceId}:header_bottom`;
34
+ readonly title: "Setting something up";
35
+ readonly description: "A click on a region, a Layout or a Widget opens its Inspector, where everything about it is set up.";
36
+ readonly placement: "bottom";
37
+ }, {
38
+ readonly key: "inspector";
39
+ readonly target: `cms:${PhiCmsInstanceId}`;
40
+ readonly title: "The Inspector";
41
+ readonly description: "For a region: its size and whether it stays put while the Page scrolls, the screens and the Pages it shows on, its spacing and its surface. A Layout and a Widget open an Inspector of their own, with their settings and the signals they send and hear. A change shows on the canvas at once and is part of the draft.";
42
+ readonly placement: "left";
43
+ readonly prepare: readonly [{
44
+ readonly receiver: `controller:${string}/${string}:${string}`;
45
+ readonly channel: "selection";
46
+ readonly action: "change";
47
+ readonly valueType: "json";
48
+ readonly valueSchema: `${string}/signals/${string}`;
49
+ readonly value: {
50
+ readonly nodeKind: "region";
51
+ readonly nodeKey: "region:header_top" | "region:header_bottom";
52
+ readonly regionKey: "header_top" | "header_bottom";
53
+ readonly regionType: number;
54
+ };
55
+ }];
56
+ readonly release: readonly [{
57
+ readonly receiver: `controller:${string}/${string}:${string}`;
58
+ readonly channel: "inspectorVisibility";
59
+ readonly action: "change";
60
+ readonly valueType: "boolean";
61
+ readonly value: false;
62
+ }];
63
+ }, ...{
64
+ readonly key: string;
65
+ readonly target: `cms:${PhiCmsInstanceId}`;
66
+ readonly title: string;
67
+ readonly description: string;
68
+ readonly placement: "left";
69
+ readonly ifMissing: "skip";
70
+ readonly prepare: readonly [{
71
+ readonly receiver: `controller:${string}/${string}:${string}`;
72
+ readonly channel: "selection";
73
+ readonly action: "change";
74
+ readonly valueType: "json";
75
+ readonly valueSchema: `${string}/signals/${string}`;
76
+ readonly value: {
77
+ readonly nodeKind: "layout";
78
+ readonly nodeKey: "layout";
79
+ readonly regionKey: "content" | "header_top";
80
+ };
81
+ }, {
82
+ readonly receiver: `cms:${PhiCmsInstanceId}`;
83
+ readonly channel: "slot";
84
+ readonly action: "open";
85
+ readonly valueType: "string";
86
+ readonly value: string;
87
+ }];
88
+ readonly release: readonly [{
89
+ readonly receiver: `controller:${string}/${string}:${string}`;
90
+ readonly channel: "inspectorVisibility";
91
+ readonly action: "change";
92
+ readonly valueType: "boolean";
93
+ readonly value: false;
94
+ }, {
95
+ readonly receiver: `cms:${PhiCmsInstanceId}`;
96
+ readonly channel: "slot";
97
+ readonly action: "open";
98
+ readonly valueType: "string";
99
+ readonly value: string;
100
+ }];
101
+ }[], {
102
+ readonly key: "mode";
103
+ readonly target: `cms:${PhiCmsInstanceId}`;
104
+ readonly title: "Editor and Preview";
105
+ readonly description: "Editor shows the regions to work in. Preview gives a first impression of your changes without saving them -- a sketch, not exactly what a visitor will see.";
106
+ readonly placement: "bottom";
107
+ }, {
108
+ readonly key: "sider";
109
+ readonly target: `cms:${PhiCmsInstanceId}`;
110
+ readonly title: "How tall the sider is";
111
+ readonly description: "Whether the left sider runs the full height of the Shell, or stands beside the content between header and footer.";
112
+ readonly placement: "bottom";
113
+ readonly ifMissing: "skip";
114
+ }, {
115
+ readonly key: "area-settings";
116
+ readonly target: `cms:${PhiCmsInstanceId}`;
117
+ readonly title: "What the Area says about itself";
118
+ readonly description: "What the Area's own address shows, how Page titles are built, and for the public Area whether search engines may find it.";
119
+ readonly placement: "bottom";
120
+ }, {
121
+ readonly key: "commands";
122
+ readonly target: `cms:${PhiCmsInstanceId}`;
123
+ readonly title: "Saving and publishing";
124
+ readonly description: "Save keeps your changes as a draft, and Live preview opens the Area with it. Publish puts the Shell live on every Page; publishing takes the Publisher role. Undo and Redo step through your changes, and Reset discards the draft without touching what is live.";
125
+ readonly placement: "bottom";
126
+ }, {
127
+ readonly key: "draft-status";
128
+ readonly target: `cms:${PhiCmsInstanceId}`;
129
+ readonly title: "Where your changes stand";
130
+ readonly description: "What you change here is a draft. Visitors see the published Shell until the draft is published.";
131
+ readonly placement: "bottom";
132
+ }];
133
+ };
134
+ /**
135
+ * The Pages workspace's Tour. Its first point is the split with Shells: a Page owns its own regions, and
136
+ * the frame around them is not edited here. A new or deleted Page is a draft like any other change.
137
+ */
138
+ export declare const PHI_BUILDER_PAGES_TOUR: {
139
+ readonly id: "@phis/ui/modules/builder/pages";
140
+ readonly version: 1;
141
+ readonly steps: readonly [{
142
+ readonly key: "area";
143
+ readonly target: `cms:${PhiCmsInstanceId}`;
144
+ readonly title: "Which Area you are building";
145
+ readonly description: "Every Area has its own Pages, and the Page list below holds those of the Area chosen here. Usually only Public and App are worked on; the other Areas come as their Modules build them.";
146
+ readonly placement: "bottom";
147
+ readonly ifMissing: "skip";
148
+ }, {
149
+ readonly key: "page-choice";
150
+ readonly target: `cms:${PhiCmsInstanceId}`;
151
+ readonly title: "Which Page you are building";
152
+ readonly description: "Every Page of the Area, the ones created here and the ones a Module brings.";
153
+ readonly placement: "bottom";
154
+ }, {
155
+ readonly key: "page";
156
+ readonly target: `cms:${PhiCmsInstanceId}:content`;
157
+ readonly title: "What belongs to this Page";
158
+ readonly description: "The content, the hero, the row under the header, the right sider, the row above the footer and the drawer are this Page's alone. The frame around them is the Shell, edited under Shells.";
159
+ readonly placement: "top";
160
+ }, {
161
+ readonly key: "regions";
162
+ readonly target: `cms:${PhiCmsInstanceId}:hero`;
163
+ readonly title: "Regions and what stands in them";
164
+ readonly description: "A region holds only Layouts, and a Layout holds further Layouts or Widgets, so the first thing placed in an empty region is always a Layout.";
165
+ readonly placement: "bottom";
166
+ }, {
167
+ readonly key: "select";
168
+ readonly target: `cms:${PhiCmsInstanceId}:header_top` | `cms:${PhiCmsInstanceId}:header_bottom`;
169
+ readonly title: "Setting something up";
170
+ readonly description: "A click on a region, a Layout or a Widget opens its Inspector, where everything about it is set up.";
171
+ readonly placement: "bottom";
172
+ }, {
173
+ readonly key: "inspector";
174
+ readonly target: `cms:${PhiCmsInstanceId}`;
175
+ readonly title: "The Inspector";
176
+ readonly description: "For a region: its size and whether it stays put while the Page scrolls, the screens and the Pages it shows on, its spacing and its surface. A Layout and a Widget open an Inspector of their own, with their settings and the signals they send and hear. A change shows on the canvas at once and is part of the draft.";
177
+ readonly placement: "left";
178
+ readonly prepare: readonly [{
179
+ readonly receiver: `controller:${string}/${string}:${string}`;
180
+ readonly channel: "selection";
181
+ readonly action: "change";
182
+ readonly valueType: "json";
183
+ readonly valueSchema: `${string}/signals/${string}`;
184
+ readonly value: {
185
+ readonly nodeKind: "region";
186
+ readonly nodeKey: "region:header_top" | "region:header_bottom";
187
+ readonly regionKey: "header_top" | "header_bottom";
188
+ readonly regionType: number;
189
+ };
190
+ }];
191
+ readonly release: readonly [{
192
+ readonly receiver: `controller:${string}/${string}:${string}`;
193
+ readonly channel: "inspectorVisibility";
194
+ readonly action: "change";
195
+ readonly valueType: "boolean";
196
+ readonly value: false;
197
+ }];
198
+ }, ...{
199
+ readonly key: string;
200
+ readonly target: `cms:${PhiCmsInstanceId}`;
201
+ readonly title: string;
202
+ readonly description: string;
203
+ readonly placement: "left";
204
+ readonly ifMissing: "skip";
205
+ readonly prepare: readonly [{
206
+ readonly receiver: `controller:${string}/${string}:${string}`;
207
+ readonly channel: "selection";
208
+ readonly action: "change";
209
+ readonly valueType: "json";
210
+ readonly valueSchema: `${string}/signals/${string}`;
211
+ readonly value: {
212
+ readonly nodeKind: "layout";
213
+ readonly nodeKey: "layout";
214
+ readonly regionKey: "content" | "header_top";
215
+ };
216
+ }, {
217
+ readonly receiver: `cms:${PhiCmsInstanceId}`;
218
+ readonly channel: "slot";
219
+ readonly action: "open";
220
+ readonly valueType: "string";
221
+ readonly value: string;
222
+ }];
223
+ readonly release: readonly [{
224
+ readonly receiver: `controller:${string}/${string}:${string}`;
225
+ readonly channel: "inspectorVisibility";
226
+ readonly action: "change";
227
+ readonly valueType: "boolean";
228
+ readonly value: false;
229
+ }, {
230
+ readonly receiver: `cms:${PhiCmsInstanceId}`;
231
+ readonly channel: "slot";
232
+ readonly action: "open";
233
+ readonly valueType: "string";
234
+ readonly value: string;
235
+ }];
236
+ }[], {
237
+ readonly key: "page-commands";
238
+ readonly target: `cms:${PhiCmsInstanceId}`;
239
+ readonly title: "New Pages and what a Page says";
240
+ readonly description: "New page creates one in this Area. Page meta sets its title, its description and whether search engines may find it. Delete takes the Page away once the change is published.";
241
+ readonly placement: "bottom";
242
+ }, {
243
+ readonly key: "mode";
244
+ readonly target: `cms:${PhiCmsInstanceId}`;
245
+ readonly title: "Editor and Preview";
246
+ readonly description: "Editor shows the regions to work in. Preview gives a first impression of your changes without saving them -- a sketch, not exactly what a visitor will see.";
247
+ readonly placement: "bottom";
248
+ }, {
249
+ readonly key: "commands";
250
+ readonly target: `cms:${PhiCmsInstanceId}`;
251
+ readonly title: "Saving and publishing";
252
+ readonly description: "Save keeps your changes as a draft, and Live preview opens the Page with it. Publish puts it live, and a new Page exists for visitors only from then on; publishing takes the Publisher role. Undo and Redo step through your changes, and Reset discards the draft without touching what is live.";
253
+ readonly placement: "bottom";
254
+ }, {
255
+ readonly key: "draft-status";
256
+ readonly target: `cms:${PhiCmsInstanceId}`;
257
+ readonly title: "Where your changes stand";
258
+ readonly description: "What you change here is a draft. Visitors see the published Page until the draft is published.";
259
+ readonly placement: "bottom";
260
+ }];
261
+ };
262
+ /**
263
+ * The Navigation workspace's Tour. What the workspace does not say is that a Site has several
264
+ * navigations, that a Page is linked by dragging it across rather than by typing its path, and that the
265
+ * items a Module brings stand in the same list as the Site's own.
266
+ */
267
+ export declare const PHI_BUILDER_NAVIGATION_TOUR: {
268
+ readonly id: "@phis/ui/modules/builder/navigation";
269
+ readonly version: 1;
270
+ readonly steps: readonly [{
271
+ readonly key: "choice";
272
+ readonly target: `cms:${PhiCmsInstanceId}:filters`;
273
+ readonly title: "Which navigation";
274
+ readonly description: "A Site has more than one navigation -- the main menu, the one in the footer, a sider's. Everything below belongs to the one chosen here, and the plus beside it adds a new one under a key of its own.";
275
+ readonly placement: "bottom";
276
+ readonly ifMissing: "skip";
277
+ }, {
278
+ readonly key: "source";
279
+ readonly target: `cms:${PhiCmsInstanceId}`;
280
+ readonly title: "The Pages to link";
281
+ readonly description: "Every Page of the Area chosen above the list. Drag one into the navigation to link it, onto a container to put it inside; the link follows the Page wherever its path goes.";
282
+ readonly placement: "right";
283
+ }, {
284
+ readonly key: "items";
285
+ readonly target: `cms:${PhiCmsInstanceId}:header`;
286
+ readonly title: "What the navigation holds";
287
+ readonly description: "A row per item, in the order it is shown. Drag a row to move it, into a container to nest it. A click on a label or an icon changes it in place. Origin says whether the item is the Site's own or came with a Module.";
288
+ readonly placement: "bottom";
289
+ }, {
290
+ readonly key: "add";
291
+ readonly target: `cms:${PhiCmsInstanceId}:toolbar`;
292
+ readonly title: "Links, containers, separators";
293
+ readonly description: "Add link adds a link to an address outside the Site, written in its Path; a Page of the Site is linked by dragging it in. A container holds further items and opens as a group; a separator draws a line between them.";
294
+ readonly placement: "bottom";
295
+ readonly ifMissing: "skip";
296
+ }, {
297
+ readonly key: "row-actions";
298
+ readonly target: `cms:${PhiCmsInstanceId}:row-actions`;
299
+ readonly title: "Hiding and deleting";
300
+ readonly description: "A hidden item stays in the list and keeps its place, but visitors do not see it. Delete takes it out; Undo brings it back.";
301
+ readonly placement: "left";
302
+ readonly ifMissing: "skip";
303
+ }, {
304
+ readonly key: "commands";
305
+ readonly target: `cms:${PhiCmsInstanceId}`;
306
+ readonly title: "Saving and publishing";
307
+ readonly description: "Save keeps your changes as a draft, and Live preview shows the Site with it. Publish puts the navigation live; publishing takes the Publisher role. Undo and Redo step through your changes, and Reset discards the draft without touching what is live.";
308
+ readonly placement: "bottom";
309
+ }, {
310
+ readonly key: "draft-status";
311
+ readonly target: `cms:${PhiCmsInstanceId}`;
312
+ readonly title: "Where your changes stand";
313
+ readonly description: "What you change here is a draft. Visitors see the published navigation until the draft is published.";
314
+ readonly placement: "bottom";
315
+ }];
316
+ };