@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
@@ -3,7 +3,7 @@ import { cloneElement, isValidElement } from "react";
3
3
  import { readPhiCmsRegionPageScope, resolvePhiCmsRegionPageScope } from "../../helpers/region-page-scope";
4
4
  import { isPhiCmsWidgetContentUnpublished } from "../../helpers/cms-content-binding";
5
5
  import { comparePhiCmsInstanceIds } from "../../types/cms-instance-id";
6
- import { readPhiVisualAnchorOwner } from "../../types/tours";
6
+ import { readPhiVisualAnchorOwner } from "../../types/visual-anchors";
7
7
  import { hasPhiFlag } from "../../helpers/flags";
8
8
  import { PhiCmsFlags } from "../../constants/phi-cms";
9
9
  import { PhiFlexVerticalLayout } from "../layouts/phi-flex-vertical-layout";
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Typography } from "antd";
4
4
  import { usePhiConfig } from "../root/phi-config-provider";
5
- import { PHI_VISUAL_ANCHOR_PART_ATTRIBUTE } from "../../types/tours";
5
+ import { PHI_VISUAL_ANCHOR_PART_ATTRIBUTE } from "../../types/visual-anchors";
6
6
  import { PhiDescriptionHint } from "./phi-description-tooltip-icon";
7
7
  export function PhiCollectionHeaderControl({ title, description, filters, toolbar, }) {
8
8
  const { token } = usePhiConfig();
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { PHI_VISUAL_ANCHOR_PART_ATTRIBUTE } from "../../types/visual-anchors";
3
4
  import { usePhiConfig } from "../root/phi-config-provider";
4
5
  import { PhiCollectionHeaderControl } from "./phi-collection-header-control";
5
6
  import { PhiCollectionLayoutControl } from "./phi-collection-layout-control";
@@ -8,7 +9,7 @@ import { PhiSkeletonControl } from "./phi-skeleton-control";
8
9
  export function PhiCollectionViewControl({ title, description, filters, toolbar, panel, diagnostics, body, items, mode = "grid", gap, minColumnWidth, pagination, }) {
9
10
  const { token } = usePhiConfig();
10
11
  const resolvedGap = gap ?? token.paddingSM;
11
- return (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: token.paddingSM, width: "100%", minWidth: 0 }, children: [_jsx(PhiCollectionHeaderControl, { title: title, description: description, filters: filters, toolbar: toolbar }), panel, diagnostics, body ?? (_jsx(PhiCollectionLayoutControl, { mode: mode, gap: resolvedGap, minColumnWidth: minColumnWidth ?? 102, items: [...(items ?? [])] })), pagination ? (_jsx("div", { style: { display: "flex", justifyContent: "flex-end", width: "100%", minWidth: 0 }, children: _jsx(PhiPaginationControl, { page: pagination.page, pageSize: pagination.pageSize, total: pagination.total, simple: pagination.simple, showSizeChanger: pagination.showSizeChanger, pageSizeOptions: pagination.pageSizeOptions, size: pagination.size, onChange: (value) => pagination.onChange(value.page, value.pageSize) }) })) : null] }));
12
+ return (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: token.paddingSM, width: "100%", minWidth: 0 }, children: [_jsx(PhiCollectionHeaderControl, { title: title, description: description, filters: filters, toolbar: toolbar }), panel, diagnostics, _jsx("div", { [PHI_VISUAL_ANCHOR_PART_ATTRIBUTE]: "items", style: { width: "100%", minWidth: 0 }, children: body ?? (_jsx(PhiCollectionLayoutControl, { mode: mode, gap: resolvedGap, minColumnWidth: minColumnWidth ?? 102, items: [...(items ?? [])] })) }), pagination ? (_jsx("div", { [PHI_VISUAL_ANCHOR_PART_ATTRIBUTE]: "pagination", style: { display: "flex", justifyContent: "flex-end", width: "100%", minWidth: 0 }, children: _jsx(PhiPaginationControl, { page: pagination.page, pageSize: pagination.pageSize, total: pagination.total, simple: pagination.simple, showSizeChanger: pagination.showSizeChanger, pageSizeOptions: pagination.pageSizeOptions, size: pagination.size, onChange: (value) => pagination.onChange(value.page, value.pageSize) }) })) : null] }));
12
13
  }
13
14
  export function PhiCollectionViewSkeletonControl({ mode = "grid", gap, minColumnWidth, active = true, count = 8, }) {
14
15
  const { token } = usePhiConfig();
@@ -1,2 +1,2 @@
1
1
  import type { PhiDrawerControlProps } from "./phi-drawer-control";
2
- export declare function PhiDrawerControlAdapter({ open, title, header, body, footer, closable, keyboard, mask, mountPolicy, placement, size, maxSize, resizable, push, zIndex, containerStyle, onDismiss, afterOpenChange, }: PhiDrawerControlProps): import("react").JSX.Element;
2
+ export declare function PhiDrawerControlAdapter({ open, title, header, body, footer, closable, keyboard, mask, mountPolicy, placement, size, maxSize, resizable, push, zIndex, containerStyle, onDismiss, afterOpenChange, panelRef, }: PhiDrawerControlProps): import("react").JSX.Element;
@@ -12,7 +12,7 @@ const subscribeHydration = () => () => undefined;
12
12
  const PHI_DRAWER_HEADER_MIN_BLOCK_SIZE = `calc(var(--ant-control-height) + ${PHI_SPACE.xs} + ${PHI_SPACE.xs})`;
13
13
  /** Where a title starts when the close button stands before it: past the button, not beneath it. */
14
14
  const PHI_DRAWER_TITLE_AFTER_CLOSE = `calc(${PHI_SPACE.sm} + var(--ant-control-height) + ${PHI_SPACE.xs})`;
15
- export function PhiDrawerControlAdapter({ open, title, header, body, footer, closable = true, keyboard = true, mask = PHI_OVERLAY_DEFAULT_MASK, mountPolicy = "remount", placement = "right", size, maxSize, resizable = false, push = false, zIndex, containerStyle, onDismiss, afterOpenChange, }) {
15
+ export function PhiDrawerControlAdapter({ open, title, header, body, footer, closable = true, keyboard = true, mask = PHI_OVERLAY_DEFAULT_MASK, mountPolicy = "remount", placement = "right", size, maxSize, resizable = false, push = false, zIndex, containerStyle, onDismiss, afterOpenChange, panelRef, }) {
16
16
  const hydrated = useSyncExternalStore(subscribeHydration, () => true, () => false);
17
17
  const resolvedMask = resolvePhiOverlayMaskPresentation(mask);
18
18
  const closeAtInlineStart = placement === "right";
@@ -71,5 +71,5 @@ export function PhiDrawerControlAdapter({ open, title, header, body, footer, clo
71
71
  background: "transparent",
72
72
  },
73
73
  footer: { flex: "0 0 auto", padding: 0, background: "transparent" },
74
- }, onClose: (event) => onDismiss?.(resolvePhiOverlayDismissSource(event)), afterOpenChange: afterOpenChange, drawerRender: (node) => (_jsx("div", { style: { display: "flex", flexDirection: "column", blockSize: "100%", minBlockSize: 0 }, onMouseDown: (event) => event.stopPropagation(), onClick: (event) => event.stopPropagation(), children: node })), children: _jsx("div", { "data-phi-drawer-body-content": "true", style: { display: "contents" }, children: body }) }));
74
+ }, onClose: (event) => onDismiss?.(resolvePhiOverlayDismissSource(event)), afterOpenChange: afterOpenChange, drawerRender: (node) => (_jsx("div", { ref: panelRef, style: { display: "flex", flexDirection: "column", blockSize: "100%", minBlockSize: 0 }, onMouseDown: (event) => event.stopPropagation(), onClick: (event) => event.stopPropagation(), children: node })), children: _jsx("div", { "data-phi-drawer-body-content": "true", style: { display: "contents" }, children: body }) }));
75
75
  }
@@ -32,6 +32,11 @@ export type PhiImageControlProps = {
32
32
  loading?: "eager" | "lazy";
33
33
  /** Opens the picture larger over the page when clicked. */
34
34
  preview?: boolean;
35
+ /**
36
+ * Scales the picture to the viewport over the page when clicked, with nothing around it; another click
37
+ * closes it. The plain counterpart of `preview`, which brings the lightbox's tools.
38
+ */
39
+ zoom?: boolean;
35
40
  /** The box around the picture: size, corners, mask. */
36
41
  style?: CSSProperties;
37
42
  /** Added to the picture's own style after the framing. */
@@ -49,4 +54,4 @@ export type PhiImageControlProps = {
49
54
  *
50
55
  * `preview` hands the picture to `PhiLightboxImageControl`, the one place the lightbox lives.
51
56
  */
52
- export declare function PhiImageControl({ presentation, source, alt, title, width, height, blurDataUrl, optimizable, sizes, preload, loading, preview, style, imageStyle, }: PhiImageControlProps): import("react").JSX.Element | null;
57
+ export declare function PhiImageControl({ presentation, source, alt, title, width, height, blurDataUrl, optimizable, sizes, preload, loading, preview, zoom, style, imageStyle, }: PhiImageControlProps): import("react").JSX.Element | null;
@@ -1,8 +1,9 @@
1
1
  "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import NextImage from "next/image";
4
4
  import { useState } from "react";
5
5
  import { PhiLightboxImageControl } from "./phi-lightbox-image-control";
6
+ import { PhiZoomImageOverlay } from "./phi-zoom-image-overlay";
6
7
  function toCssSize(value) {
7
8
  if (typeof value === "number" && Number.isFinite(value))
8
9
  return `${value}px`;
@@ -22,8 +23,9 @@ function toCssSize(value) {
22
23
  *
23
24
  * `preview` hands the picture to `PhiLightboxImageControl`, the one place the lightbox lives.
24
25
  */
25
- export function PhiImageControl({ presentation, source, alt, title, width, height, blurDataUrl, optimizable = false, sizes, preload = false, loading, preview = false, style, imageStyle, }) {
26
+ export function PhiImageControl({ presentation, source, alt, title, width, height, blurDataUrl, optimizable = false, sizes, preload = false, loading, preview = false, zoom = false, style, imageStyle, }) {
26
27
  const [loadedUrl, setLoadedUrl] = useState(null);
28
+ const [zoomed, setZoomed] = useState(false);
27
29
  const url = presentation.url;
28
30
  if (!url) {
29
31
  return null;
@@ -40,8 +42,25 @@ export function PhiImageControl({ presentation, source, alt, title, width, heigh
40
42
  lineHeight: 0,
41
43
  overflow: presentation.simulatedCropStyle ? "hidden" : undefined,
42
44
  ...(loaded || blur ? {} : { backgroundColor: "var(--ant-color-fill-quaternary)" }),
45
+ ...(zoom ? { cursor: "zoom-in" } : {}),
43
46
  ...style,
44
47
  };
48
+ // The box is what is clicked: the picture itself may be `next/image`, which hands out no handler of ours.
49
+ const zoomTrigger = zoom
50
+ ? {
51
+ role: "button",
52
+ tabIndex: 0,
53
+ "aria-label": alt || title,
54
+ onClick: () => setZoomed(true),
55
+ onKeyDown: (event) => {
56
+ if (event.key !== "Enter" && event.key !== " ")
57
+ return;
58
+ event.preventDefault();
59
+ setZoomed(true);
60
+ },
61
+ }
62
+ : {};
63
+ const zoomOverlay = zoom ? (_jsx(PhiZoomImageOverlay, { open: zoomed, src: url, alt: alt, title: title, onClose: () => setZoomed(false) })) : null;
45
64
  const pictureStyle = {
46
65
  objectFit: presentation.fit,
47
66
  objectPosition: presentation.objectPosition,
@@ -63,7 +82,7 @@ export function PhiImageControl({ presentation, source, alt, title, width, heigh
63
82
  return (_jsx("div", { style: boxStyle, children: _jsx(PhiLightboxImageControl, { alt: alt, src: url, title: title, width: numericWidth, height: numericHeight, style: pictureStyle }) }));
64
83
  }
65
84
  if (source !== "url" && numericWidth != null && numericHeight != null) {
66
- return (_jsx("div", { style: boxStyle, children: _jsx(NextImage, { alt: alt, src: url, title: title, width: numericWidth, height: numericHeight, sizes: sizes?.trim() || undefined, unoptimized: !optimizable, placeholder: blur ? "blur" : undefined, blurDataURL: blur ?? undefined, preload: preload, loading: loading, onLoad: markLoaded, style: pictureStyle }) }));
85
+ return (_jsxs("div", { style: boxStyle, ...zoomTrigger, children: [_jsx(NextImage, { alt: alt, src: url, title: title, width: numericWidth, height: numericHeight, sizes: sizes?.trim() || undefined, unoptimized: !optimizable, placeholder: blur ? "blur" : undefined, blurDataURL: blur ?? undefined, preload: preload, loading: loading, onLoad: markLoaded, style: pictureStyle }), zoomOverlay] }));
67
86
  }
68
- return (_jsx("div", { style: boxStyle, children: _jsx("img", { alt: alt, src: url, title: title, width: numericWidth, height: numericHeight, loading: loading ?? (preload ? "eager" : "lazy"), ref: noticeLoaded, onLoad: markLoaded, style: pictureStyle }) }));
87
+ return (_jsxs("div", { style: boxStyle, ...zoomTrigger, children: [_jsx("img", { alt: alt, src: url, title: title, width: numericWidth, height: numericHeight, loading: loading ?? (preload ? "eager" : "lazy"), ref: noticeLoaded, onLoad: markLoaded, style: pictureStyle }), zoomOverlay] }));
69
88
  }
@@ -0,0 +1,2 @@
1
+ import type { PhiLightboxImageControlProps } from "./phi-lightbox-image-control";
2
+ export declare function PhiLightboxImageControlAdapter({ src, alt, title, width, height, style }: PhiLightboxImageControlProps): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /*
4
+ * Ant Design's Image, behind `PhiLightboxImageControl`, which loads this file where a picture is drawn
5
+ * with its lightbox. The primitive brings its preview -- the overlay, the toolbar, zoom and rotation --
6
+ * whether or not a page shows one, so every page that draws a card or a picture would carry it.
7
+ */
8
+ import { Image } from "antd";
9
+ export function PhiLightboxImageControlAdapter({ src, alt, title, width, height, style }) {
10
+ return _jsx(Image, { alt: alt, src: src, title: title, width: width, height: height, style: style, preview: true });
11
+ }
@@ -1,4 +1,4 @@
1
- import type { CSSProperties } from "react";
1
+ import { type CSSProperties } from "react";
2
2
  export type PhiLightboxImageControlProps = {
3
3
  src: string;
4
4
  alt: string;
@@ -12,5 +12,6 @@ export type PhiLightboxImageControlProps = {
12
12
  *
13
13
  * Only for that: an image that is just shown is an `<img>` or `next/image` in the Widget that places it.
14
14
  * The lightbox is what needs a primitive -- the overlay, the zoom, the close -- and so it is a Control.
15
+ * Until the primitive has loaded, the picture stands there plainly, at the same size.
15
16
  */
16
- export declare function PhiLightboxImageControl({ src, alt, title, width, height, style }: PhiLightboxImageControlProps): import("react").JSX.Element;
17
+ export declare function PhiLightboxImageControl(props: PhiLightboxImageControlProps): import("react").JSX.Element;
@@ -1,12 +1,16 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { Image } from "antd";
3
+ import { lazy, Suspense } from "react";
4
+ const PhiLightboxImageControlAdapter = lazy(() => import("./phi-lightbox-image-control-adapter")
5
+ .then((module) => ({ default: module.PhiLightboxImageControlAdapter })));
4
6
  /**
5
7
  * An image that opens larger over the page when clicked.
6
8
  *
7
9
  * Only for that: an image that is just shown is an `<img>` or `next/image` in the Widget that places it.
8
10
  * The lightbox is what needs a primitive -- the overlay, the zoom, the close -- and so it is a Control.
11
+ * Until the primitive has loaded, the picture stands there plainly, at the same size.
9
12
  */
10
- export function PhiLightboxImageControl({ src, alt, title, width, height, style }) {
11
- return _jsx(Image, { alt: alt, src: src, title: title, width: width, height: height, style: style, preview: true });
13
+ export function PhiLightboxImageControl(props) {
14
+ const { src, alt, title, width, height, style } = props;
15
+ return (_jsx(Suspense, { fallback: _jsx("img", { alt: alt, src: src, title: title, width: width, height: height, style: style }), children: _jsx(PhiLightboxImageControlAdapter, { ...props }) }));
12
16
  }
@@ -1,2 +1,2 @@
1
1
  import type { PhiModalControlProps } from "./phi-modal-control";
2
- export declare function PhiModalControlAdapter({ open, title, header, body, footer, closable, keyboard, mask, mountPolicy, centered, controlSize, size, width, rootClassName, containerStyle, onDismiss, afterOpenChange, }: PhiModalControlProps): import("react").JSX.Element;
2
+ export declare function PhiModalControlAdapter({ open, title, header, body, footer, closable, keyboard, mask, mountPolicy, centered, controlSize, size, width, rootClassName, containerStyle, onDismiss, afterOpenChange, panelRef, }: PhiModalControlProps): import("react").JSX.Element;
@@ -56,7 +56,7 @@ const PHI_MODAL_TAB_STOP_SELECTOR = [
56
56
  /** About a second: a Form that loads its descriptor late still gets the focus, a slower one does not. */
57
57
  const PHI_MODAL_FIRST_FIELD_MAX_FRAMES = 60;
58
58
  const PHI_MODAL_HEADER_MIN_BLOCK_SIZE = `calc(${PHI_CONTROL.md} + ${PHI_SPACE.xs} + ${PHI_SPACE.xs})`;
59
- export function PhiModalControlAdapter({ open, title, header, body, footer, closable = true, keyboard = true, mask = PHI_OVERLAY_DEFAULT_MASK, mountPolicy = "remount", centered = false, controlSize, size, width, rootClassName, containerStyle, onDismiss, afterOpenChange, }) {
59
+ export function PhiModalControlAdapter({ open, title, header, body, footer, closable = true, keyboard = true, mask = PHI_OVERLAY_DEFAULT_MASK, mountPolicy = "remount", centered = false, controlSize, size, width, rootClassName, containerStyle, onDismiss, afterOpenChange, panelRef, }) {
60
60
  const { token } = usePhiConfig();
61
61
  const hydrated = useSyncExternalStore(subscribeHydration, () => true, () => false);
62
62
  const mode = useSyncExternalStore(subscribeModalMode, readModalMode, readServerModalMode);
@@ -205,7 +205,10 @@ export function PhiModalControlAdapter({ open, title, header, body, footer, clos
205
205
  padding: 0,
206
206
  background: "transparent",
207
207
  },
208
- }, onCancel: (event) => onDismiss?.(resolvePhiOverlayDismissSource(event)), afterOpenChange: afterOpenChange, modalRender: (node) => (_jsxs("div", { ref: dialogRef, onMouseDown: (event) => event.stopPropagation(), onClick: (event) => event.stopPropagation(), children: [node, _jsx("span", { tabIndex: 0, "data-phi-modal-focus-wrap": "true", style: { position: "fixed", inlineSize: 0, blockSize: 0, overflow: "hidden" }, onFocus: (event) => {
208
+ }, onCancel: (event) => onDismiss?.(resolvePhiOverlayDismissSource(event)), afterOpenChange: afterOpenChange, modalRender: (node) => (_jsxs("div", { ref: (element) => {
209
+ dialogRef.current = element;
210
+ panelRef?.(element);
211
+ }, onMouseDown: (event) => event.stopPropagation(), onClick: (event) => event.stopPropagation(), children: [node, _jsx("span", { tabIndex: 0, "data-phi-modal-focus-wrap": "true", style: { position: "fixed", inlineSize: 0, blockSize: 0, overflow: "hidden" }, onFocus: (event) => {
209
212
  const dialog = dialogRef.current;
210
213
  if (dialog == null)
211
214
  return;
@@ -19,6 +19,11 @@ export type PhiOverlayControlCommonProps = {
19
19
  containerStyle?: CSSProperties;
20
20
  onDismiss?: (source: PhiOverlayCloseSource) => void;
21
21
  afterOpenChange?: (open: boolean) => void;
22
+ /**
23
+ * The element the Overlay is drawn as, handed out while it is mounted -- what a Tour points at when it
24
+ * names the Overlay (design/TOURS.md, Visual-anchor registration). Never an Ant Design ref.
25
+ */
26
+ panelRef?: (element: HTMLElement | null) => void;
22
27
  };
23
28
  export declare const PHI_OVERLAY_DEFAULT_MASK: {
24
29
  readonly appearance: "normal";
@@ -0,0 +1,2 @@
1
+ import type { PhiPaginationControlProps } from "./phi-pagination-control";
2
+ export declare function PhiPaginationControlAdapter({ page, pageSize, total, disabled, readOnly, simple, showSizeChanger, pageSizeOptions, size, getPopupContainer, popupRootClassName, onPopupOpenChange, onChange, }: PhiPaginationControlProps): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /*
4
+ * Ant Design's Pagination, behind `PhiPaginationControl`, which loads this file where a list is drawn
5
+ * page by page. A Collection that shows everything at once -- a Dashboard's cards, a short Entry list --
6
+ * would otherwise carry the pager, its size changer and its Select all the same.
7
+ */
8
+ import { Pagination } from "antd";
9
+ export function PhiPaginationControlAdapter({ page, pageSize, total, disabled = false, readOnly = false, simple, showSizeChanger, pageSizeOptions, size, getPopupContainer, popupRootClassName, onPopupOpenChange, onChange, }) {
10
+ return (_jsx(Pagination, { current: page, pageSize: pageSize, total: total, disabled: disabled || readOnly, simple: simple, size: size, pageSizeOptions: pageSizeOptions ? [...pageSizeOptions] : undefined, showSizeChanger: showSizeChanger ? {
11
+ getPopupContainer,
12
+ classNames: popupRootClassName ? { popup: { root: popupRootClassName } } : undefined,
13
+ onOpenChange: onPopupOpenChange,
14
+ } : false, onChange: (nextPage, nextPageSize) => onChange?.({ page: nextPage, pageSize: nextPageSize, total }) }));
15
+ }
@@ -22,4 +22,5 @@ export type PhiPaginationControlProps = PhiPaginationControlValue & {
22
22
  onPopupOpenChange?: (open: boolean) => void;
23
23
  onChange?: (value: PhiPaginationControlValue) => void;
24
24
  };
25
- export declare function PhiPaginationControl({ page, pageSize, total, disabled, readOnly, simple, showSizeChanger, pageSizeOptions, size, getPopupContainer, popupRootClassName, onPopupOpenChange, onChange, }: PhiPaginationControlProps): import("react").JSX.Element;
25
+ /** The pager. Until the primitive has loaded, its row is held at the height it will take. */
26
+ export declare function PhiPaginationControl(props: PhiPaginationControlProps): import("react").JSX.Element;
@@ -1,10 +1,14 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { Pagination } from "antd";
4
- export function PhiPaginationControl({ page, pageSize, total, disabled = false, readOnly = false, simple, showSizeChanger, pageSizeOptions, size, getPopupContainer, popupRootClassName, onPopupOpenChange, onChange, }) {
5
- return (_jsx(Pagination, { current: page, pageSize: pageSize, total: total, disabled: disabled || readOnly, simple: simple, size: size, pageSizeOptions: pageSizeOptions ? [...pageSizeOptions] : undefined, showSizeChanger: showSizeChanger ? {
6
- getPopupContainer,
7
- classNames: popupRootClassName ? { popup: { root: popupRootClassName } } : undefined,
8
- onOpenChange: onPopupOpenChange,
9
- } : false, onChange: (nextPage, nextPageSize) => onChange?.({ page: nextPage, pageSize: nextPageSize, total }) }));
3
+ import { lazy, Suspense } from "react";
4
+ import { usePhiConfig } from "../root/phi-config-provider";
5
+ const PhiPaginationControlAdapter = lazy(() => import("./phi-pagination-control-adapter")
6
+ .then((module) => ({ default: module.PhiPaginationControlAdapter })));
7
+ /** The pager. Until the primitive has loaded, its row is held at the height it will take. */
8
+ export function PhiPaginationControl(props) {
9
+ const { token } = usePhiConfig();
10
+ const height = props.size === "small"
11
+ ? token.controlHeightSM
12
+ : props.size === "large" ? token.controlHeightLG : token.controlHeight;
13
+ return (_jsx(Suspense, { fallback: _jsx("div", { style: { minHeight: height } }), children: _jsx(PhiPaginationControlAdapter, { ...props }) }));
10
14
  }
@@ -122,6 +122,11 @@ export type PhiTableControlProps<TRow extends Record<string, unknown>> = {
122
122
  bordered?: boolean;
123
123
  striped?: boolean;
124
124
  showHeader?: boolean;
125
+ /**
126
+ * The header row stays at the top while the rows scroll under it: under the Shell's Header on a page,
127
+ * at the top edge of a Modal's or Drawer's body. `styles/controls.css` places it.
128
+ */
129
+ stickyHeader?: boolean;
125
130
  footer?: PhiTableControlFooter;
126
131
  summary?: PhiTableControlSummary;
127
132
  size?: PhiControlSize;
@@ -141,4 +146,4 @@ export type PhiTableControlProps<TRow extends Record<string, unknown>> = {
141
146
  onExternalDrop?: (event: DragEvent<HTMLDivElement>) => void;
142
147
  };
143
148
  export declare function readPhiTableControlValue(row: Record<string, unknown>, path: string): unknown;
144
- export declare function PhiTableControl<TRow extends Record<string, unknown>>({ rows, rowIdentityPath, columns, columnOrder, sortingMode, sorts, onSortsChange, columnReordering, onColumnOrderChange, rowSelection, pagination, tree, rowReordering, editing, loading, bordered, striped, showHeader, footer, summary, size, emptyText, layout, rowStyle, onRowActivate, onExternalRowDragOver, onExternalRowDrop, onExternalDragOver, onExternalDrop, }: PhiTableControlProps<TRow>): import("react").JSX.Element;
149
+ export declare function PhiTableControl<TRow extends Record<string, unknown>>({ rows, rowIdentityPath, columns, columnOrder, sortingMode, sorts, onSortsChange, columnReordering, onColumnOrderChange, rowSelection, pagination, tree, rowReordering, editing, loading, bordered, striped, stickyHeader, showHeader, footer, summary, size, emptyText, layout, rowStyle, onRowActivate, onExternalRowDragOver, onExternalRowDrop, onExternalDragOver, onExternalDrop, }: PhiTableControlProps<TRow>): import("react").JSX.Element;
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { usePhiShellStickyTop } from "../shell/use-phi-shell-sticky-top";
3
4
  import { Button, Flex, Table } from "antd";
4
5
  import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors, } from "@dnd-kit/core";
5
6
  import { SortableContext, sortableKeyboardCoordinates, useSortable, } from "@dnd-kit/sortable";
@@ -7,7 +8,7 @@ import { CSS } from "@dnd-kit/utilities";
7
8
  import { flushSync } from "react-dom";
8
9
  import { Children, cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
9
10
  import { PHI_GREGORY_CALENDAR_ADAPTER_KEY } from "../../types/calendar-keys";
10
- import { PHI_VISUAL_ANCHOR_PART_ATTRIBUTE } from "../../types/tours";
11
+ import { PHI_VISUAL_ANCHOR_PART_ATTRIBUTE } from "../../types/visual-anchors";
11
12
  import { PhiColorControl } from "./phi-color-control";
12
13
  import { PhiCheckboxControl } from "./phi-checkbox-control";
13
14
  import { PhiCheckboxGroupControl } from "./phi-checkbox-group-control";
@@ -354,7 +355,7 @@ function buildRowMove(rows, rowIdentityPath, parentRowIdentityPath, movedRowIden
354
355
  afterRowIdentity: placement === "after" ? targetRowIdentity : null,
355
356
  };
356
357
  }
357
- export function PhiTableControl({ rows, rowIdentityPath, columns, columnOrder, sortingMode, sorts, onSortsChange, columnReordering = false, onColumnOrderChange, rowSelection, pagination = false, tree, rowReordering, editing, loading = false, bordered, striped = false, showHeader, footer, summary, size, emptyText, layout, rowStyle, onRowActivate, onExternalRowDragOver, onExternalRowDrop, onExternalDragOver, onExternalDrop, }) {
358
+ export function PhiTableControl({ rows, rowIdentityPath, columns, columnOrder, sortingMode, sorts, onSortsChange, columnReordering = false, onColumnOrderChange, rowSelection, pagination = false, tree, rowReordering, editing, loading = false, bordered, striped = false, stickyHeader = false, showHeader, footer, summary, size, emptyText, layout, rowStyle, onRowActivate, onExternalRowDragOver, onExternalRowDrop, onExternalDragOver, onExternalDrop, }) {
358
359
  const { token } = usePhiConfig();
359
360
  const [editingRowIdentityState, setEditingRowIdentity] = useState(null);
360
361
  const [rowEditPatch, setRowEditPatch] = useState({});
@@ -988,6 +989,8 @@ export function PhiTableControl({ rows, rowIdentityPath, columns, columnOrder, s
988
989
  const hasClosingBlock = !loading &&
989
990
  (Boolean(footer) || (summary != null && summary.placement !== "sticky-top"));
990
991
  const frameRef = useRef(null);
992
+ // Where the Shell's Header ends, which is where a sticky header row stops (`styles/controls.css`).
993
+ usePhiShellStickyTop({ active: stickyHeader && showHeader !== false, elementRef: frameRef });
991
994
  const [headerBox, setHeaderBox] = useState(null);
992
995
  useEffect(() => {
993
996
  const frame = frameRef.current;
@@ -1007,11 +1010,15 @@ export function PhiTableControl({ rows, rowIdentityPath, columns, columnOrder, s
1007
1010
  observer.observe(frame);
1008
1011
  return () => observer.disconnect();
1009
1012
  }, [showHeader, loading]);
1010
- const table = (_jsx(Table, { className: ["phi-table-control", hasClosingBlock ? "phi-table-control--closing-block" : null]
1013
+ const table = (_jsx(Table, { className: [
1014
+ "phi-table-control",
1015
+ hasClosingBlock ? "phi-table-control--closing-block" : null,
1016
+ stickyHeader && showHeader !== false ? "phi-table-control--sticky-header" : null,
1017
+ ]
1011
1018
  .filter(Boolean)
1012
1019
  .join(" "), style: { "--phi-table-striped-row-background": token.colorFillAlter }, rowKey: (row) => loading
1013
1020
  ? `__phi_table_loading_${String(row[PHI_TABLE_LOADING_ROW])}`
1014
- : String(readIdentity(row, rowIdentityPath)), columns: renderedColumns, dataSource: loading ? loadingRows : dataSource, bordered: bordered, showHeader: showHeader, tableLayout: layout.mode, size: size, rowClassName: (_row, index) => striped && index % 2 === 1 ? "phi-table-control__row--striped" : "", rowSelection: loading ? undefined : antdRowSelection, components: rowReordering?.enabled && !loading ? { body: { row: PhiTableSortableRow } } : undefined, scroll: layout.overflowX === "auto" ? { x: true } : undefined, pagination: pagination === false ? false : {
1021
+ : String(readIdentity(row, rowIdentityPath)), columns: renderedColumns, dataSource: loading ? loadingRows : dataSource, bordered: bordered, showHeader: showHeader, tableLayout: layout.mode, size: size, rowClassName: (_row, index) => striped && index % 2 === 1 ? "phi-table-control__row--striped" : "", rowSelection: loading ? undefined : antdRowSelection, components: rowReordering?.enabled && !loading ? { body: { row: PhiTableSortableRow } } : undefined, scroll: layout.overflowX === "auto" ? { x: true } : undefined, sticky: stickyHeader && showHeader !== false ? true : undefined, pagination: pagination === false ? false : {
1015
1022
  ...{ [PHI_VISUAL_ANCHOR_PART_ATTRIBUTE]: "pagination" },
1016
1023
  current: pagination.page,
1017
1024
  pageSize: pagination.pageSize,
@@ -1097,7 +1104,12 @@ export function PhiTableControl({ rows, rowIdentityPath, columns, columnOrder, s
1097
1104
  insertionGapIndex,
1098
1105
  insertionGapHeight: activeDragRowHeight,
1099
1106
  }, children: table }) }) })) : table;
1100
- const constrainedTable = (_jsxs("div", { ref: frameRef, style: { position: "relative", width: "100%", maxWidth: "100%", minWidth: 0, overflow: "hidden" }, children: [renderedTable, headerBox ? (_jsx("div", { "aria-hidden": true, [PHI_VISUAL_ANCHOR_PART_ATTRIBUTE]: "header", style: { position: "absolute", left: 0, right: 0, top: headerBox.top, height: headerBox.height, pointerEvents: "none" } })) : null] }));
1107
+ /*
1108
+ * Clipped, not hidden: `hidden` makes the frame a scroll container, and a sticky header row then sticks
1109
+ * to the frame's own top -- pushed down inside the Table by the Shell's Header height -- instead of to
1110
+ * the page. `clip` cuts off the same overflow and leaves the page the one that scrolls.
1111
+ */
1112
+ const constrainedTable = (_jsxs("div", { ref: frameRef, style: { position: "relative", width: "100%", maxWidth: "100%", minWidth: 0, overflow: "clip" }, children: [renderedTable, headerBox ? (_jsx("div", { "aria-hidden": true, [PHI_VISUAL_ANCHOR_PART_ATTRIBUTE]: "header", style: { position: "absolute", left: 0, right: 0, top: headerBox.top, height: headerBox.height, pointerEvents: "none" } })) : null] }));
1101
1113
  return onExternalDragOver || onExternalDrop ? (_jsx("div", { onDragOver: (event) => {
1102
1114
  if (event.defaultPrevented) {
1103
1115
  setExternalAppendActive(false);
@@ -0,0 +1,2 @@
1
+ import type { PhiTimelineControlProps } from "./phi-timeline-control";
2
+ export declare function PhiTimelineControlAdapter({ items, placement, ariaLabel, }: PhiTimelineControlProps): import("react").JSX.Element;
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /*
4
+ * Ant Design's Timeline, behind `PhiTimelineControl`, which loads this file where a Timeline is drawn.
5
+ */
6
+ import { Timeline } from "antd";
7
+ import { PHI_COLOR } from "../../theme/antd-css-var-contract";
8
+ import { PhiIcon } from "../shell/phi-icon";
9
+ /*
10
+ * Ant Design colours four marks by name and takes anything else as a CSS colour. The Theme's own tokens
11
+ * go in as that colour, so a tone follows the Theme the way a text tone does.
12
+ */
13
+ const PHI_TIMELINE_TONE_COLOR = {
14
+ secondary: PHI_COLOR.textTertiary,
15
+ success: PHI_COLOR.success,
16
+ warning: PHI_COLOR.warning,
17
+ danger: PHI_COLOR.error,
18
+ };
19
+ export function PhiTimelineControlAdapter({ items, placement = "start", ariaLabel, }) {
20
+ return (_jsx(Timeline, { "aria-label": ariaLabel, mode: placement, items: items.map((item) => ({
21
+ key: item.key,
22
+ title: item.title && item.time ? `${item.time} · ${item.title}` : item.title ?? item.time,
23
+ content: item.content,
24
+ icon: item.icon ? _jsx(PhiIcon, { name: item.icon, size: "inherit" }) : undefined,
25
+ color: item.tone ? PHI_TIMELINE_TONE_COLOR[item.tone] : undefined,
26
+ loading: item.pending,
27
+ })) }));
28
+ }
@@ -28,5 +28,7 @@ export type PhiTimelineControlProps = {
28
28
  * a Timeline here is a record a Widget stores or a Module reads, so its entries are words and tones.
29
29
  * The time stands in Ant Design's title column when the entry has no title of its own, and in front of
30
30
  * the title otherwise -- one column, so that `alternate` still has a side to put it on.
31
+ *
32
+ * Until the primitive has loaded, the entries stand there as a plain list, in the same words.
31
33
  */
32
- export declare function PhiTimelineControl({ items, placement, ariaLabel, }: PhiTimelineControlProps): import("react").JSX.Element;
34
+ export declare function PhiTimelineControl(props: PhiTimelineControlProps): import("react").JSX.Element;
@@ -1,18 +1,8 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { Timeline } from "antd";
4
- import { PHI_COLOR } from "../../theme/antd-css-var-contract";
5
- import { PhiIcon } from "../shell/phi-icon";
6
- /*
7
- * Ant Design colours four marks by name and takes anything else as a CSS colour. The Theme's own tokens
8
- * go in as that colour, so a tone follows the Theme the way a text tone does.
9
- */
10
- const PHI_TIMELINE_TONE_COLOR = {
11
- secondary: PHI_COLOR.textTertiary,
12
- success: PHI_COLOR.success,
13
- warning: PHI_COLOR.warning,
14
- danger: PHI_COLOR.error,
15
- };
3
+ import { lazy, Suspense } from "react";
4
+ const PhiTimelineControlAdapter = lazy(() => import("./phi-timeline-control-adapter")
5
+ .then((module) => ({ default: module.PhiTimelineControlAdapter })));
16
6
  /**
17
7
  * What happened, in order: a ticket's history, the revisions of a Page, the stages of an order.
18
8
  *
@@ -20,14 +10,10 @@ const PHI_TIMELINE_TONE_COLOR = {
20
10
  * a Timeline here is a record a Widget stores or a Module reads, so its entries are words and tones.
21
11
  * The time stands in Ant Design's title column when the entry has no title of its own, and in front of
22
12
  * the title otherwise -- one column, so that `alternate` still has a side to put it on.
13
+ *
14
+ * Until the primitive has loaded, the entries stand there as a plain list, in the same words.
23
15
  */
24
- export function PhiTimelineControl({ items, placement = "start", ariaLabel, }) {
25
- return (_jsx(Timeline, { "aria-label": ariaLabel, mode: placement, items: items.map((item) => ({
26
- key: item.key,
27
- title: item.title && item.time ? `${item.time} · ${item.title}` : item.title ?? item.time,
28
- content: item.content,
29
- icon: item.icon ? _jsx(PhiIcon, { name: item.icon, size: "inherit" }) : undefined,
30
- color: item.tone ? PHI_TIMELINE_TONE_COLOR[item.tone] : undefined,
31
- loading: item.pending,
32
- })) }));
16
+ export function PhiTimelineControl(props) {
17
+ const fallback = (_jsx("ol", { "aria-label": props.ariaLabel, style: { listStyle: "none", margin: 0, padding: 0 }, children: props.items.map((item) => (_jsx("li", { children: [item.time, item.title, item.content].filter(Boolean).join(" · ") }, item.key))) }));
18
+ return (_jsx(Suspense, { fallback: fallback, children: _jsx(PhiTimelineControlAdapter, { ...props }) }));
33
19
  }
@@ -1,10 +1,9 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useMemo } from "react";
3
+ import { useEffect, useMemo } from "react";
4
4
  import { Tour } from "antd";
5
- function prefersReducedMotion() {
6
- return typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
7
- }
5
+ import { PhiCompactGroupControl } from "./phi-compact-group-control";
6
+ import { bringPhiTourTargetIntoView, resolvePhiTourScrollBehavior, resolvePhiTourScrollBlock, } from "./phi-tour-viewport";
8
7
  /**
9
8
  * A guided Tour's presentation, and the only place Ant Design `Tour` is used.
10
9
  *
@@ -22,8 +21,27 @@ export function PhiTourControl({ open, current, steps, labels, mask = true, disa
22
21
  // Ant Design types the getter as one that always finds its element; a target can leave mid-step.
23
22
  target: (step.target ?? null),
24
23
  placement: step.target ? step.placement : undefined,
25
- prevButtonProps: { children: labels.previous },
26
- nextButtonProps: { children: index === steps.length - 1 ? labels.finish : labels.next },
24
+ // Ant Design spaces the two buttons apart; joined, the seam between them is drawn once instead.
25
+ prevButtonProps: { children: labels.previous, style: { marginInlineStart: 0 } },
26
+ nextButtonProps: { children: index === steps.length - 1 ? labels.finish : labels.next, style: { marginInlineStart: 0 } },
27
27
  })), [labels.finish, labels.next, labels.previous, steps]);
28
- return (_jsx(Tour, { open: open && steps.length > 0, current: current, steps: tourSteps, mask: mask, disabledInteraction: disabledInteraction, keyboard: true, scrollIntoViewOptions: { block: "center", behavior: prefersReducedMotion() ? "auto" : "smooth" }, onChange: onStepChange, onClose: onClose, onFinish: onFinish }));
28
+ /*
29
+ * The shown step scrolls the way its Controller already brought it in (`phi-tour-viewport.ts`): Ant
30
+ * Design scrolls again whenever the target is not wholly inside the window, which a tall one never is,
31
+ * and the same alignment under the same scroll padding lands where the target already stands.
32
+ */
33
+ const shownTarget = open ? steps[current]?.target?.() ?? null : null;
34
+ const shownSteps = shownTarget
35
+ ? tourSteps.map((step, index) => index === current
36
+ ? { ...step, scrollIntoViewOptions: { block: resolvePhiTourScrollBlock(shownTarget), inline: "nearest", behavior: resolvePhiTourScrollBehavior() } }
37
+ : step)
38
+ : tourSteps;
39
+ // A target under a sticky band is inside the window, so Ant Design leaves it there; this brings it out.
40
+ useEffect(() => {
41
+ if (shownTarget)
42
+ bringPhiTourTargetIntoView(shownTarget, resolvePhiTourScrollBehavior());
43
+ }, [current, shownTarget]);
44
+ return (_jsx(Tour, { open: open && steps.length > 0, current: current, steps: shownSteps, mask: mask, disabledInteraction: disabledInteraction, keyboard: true,
45
+ // Previous and Next as one bar: two steps of one way through, not two separate commands.
46
+ actionsRender: (actions) => _jsx(PhiCompactGroupControl, { size: "small", children: actions }), scrollIntoViewOptions: { block: "center", behavior: resolvePhiTourScrollBehavior() }, onChange: onStepChange, onClose: onClose, onFinish: onFinish }));
29
47
  }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Where a Tour step's target is brought into view: below whatever stays stuck at the top of the window.
3
+ *
4
+ * Ant Design's Tour knows neither. It scrolls only when the target is not wholly inside the window, so a
5
+ * target taller than the window is scrolled on every step and every scroll event -- to the middle, its
6
+ * top under the Header -- and a target under a sticky Header counts as seen and is not scrolled at all.
7
+ * So the owning Controller sets the window's scroll padding to the cover before the step is presented
8
+ * (design/TOURS.md, Target readiness), `PhiTourControl` hands Ant Design the alignment that fits the
9
+ * target, and once the step stands it brings in a target Ant Design took for seen. Every scroll then
10
+ * aims at the same place, and the one Ant Design repeats on every scroll event moves nothing.
11
+ *
12
+ * Not while the previous step is still shown: Ant Design scrolls back to a shown target that leaves the
13
+ * window, so a scroll begun before the new step is presented is undone by the old one.
14
+ *
15
+ * The cover is found from the target outwards, not by searching the document: the Shell's Header bands
16
+ * and a Page's sticky band are siblings of something the target stands in, so the bands are found among
17
+ * the earlier siblings of the target's ancestors that are stuck, sit at the top, and stand over the target.
18
+ */
19
+ export declare function resolvePhiTourScrollBehavior(): ScrollBehavior;
20
+ /**
21
+ * Centered where the target fits below the cover, top-aligned under it where it does not: a target
22
+ * taller than what is left of the window is read from its top.
23
+ */
24
+ export declare function resolvePhiTourScrollBlock(target: HTMLElement, cover?: number): Extract<ScrollLogicalPosition, "center" | "start">;
25
+ /**
26
+ * Keeps the window's scroll padding at the cover over `target`, so every `scrollIntoView` -- Ant
27
+ * Design's included -- aligns below it. Returns the cover.
28
+ */
29
+ export declare function holdPhiTourViewport(target: HTMLElement): number;
30
+ /**
31
+ * Brings `target` into the part of the window nothing covers. A target already wholly in that part --
32
+ * or, taller than it, already standing at its top -- is left where it is. Returns whether it scrolled.
33
+ */
34
+ export declare function bringPhiTourTargetIntoView(target: HTMLElement, behavior: ScrollBehavior): boolean;
35
+ /** The window's scroll padding as it was before a Tour set it. */
36
+ export declare function releasePhiTourViewport(): void;