@phis/ui 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/cms/phi-cms-layout-renderer.js +1 -1
- package/components/controls/phi-collection-header-control.js +1 -1
- package/components/controls/phi-collection-view-control.js +2 -1
- package/components/controls/phi-drawer-control-adapter.d.ts +1 -1
- package/components/controls/phi-drawer-control-adapter.js +2 -2
- package/components/controls/phi-image-control.d.ts +6 -1
- package/components/controls/phi-image-control.js +23 -4
- package/components/controls/phi-lightbox-image-control-adapter.d.ts +2 -0
- package/components/controls/phi-lightbox-image-control-adapter.js +11 -0
- package/components/controls/phi-lightbox-image-control.d.ts +3 -2
- package/components/controls/phi-lightbox-image-control.js +7 -3
- package/components/controls/phi-modal-control-adapter.d.ts +1 -1
- package/components/controls/phi-modal-control-adapter.js +5 -2
- package/components/controls/phi-overlay-control-contract.d.ts +5 -0
- package/components/controls/phi-pagination-control-adapter.d.ts +2 -0
- package/components/controls/phi-pagination-control-adapter.js +15 -0
- package/components/controls/phi-pagination-control.d.ts +2 -1
- package/components/controls/phi-pagination-control.js +11 -7
- package/components/controls/phi-table-control.js +1 -1
- package/components/controls/phi-timeline-control-adapter.d.ts +2 -0
- package/components/controls/phi-timeline-control-adapter.js +28 -0
- package/components/controls/phi-timeline-control.d.ts +3 -1
- package/components/controls/phi-timeline-control.js +8 -22
- package/components/controls/phi-tour-control.js +25 -7
- package/components/controls/phi-tour-viewport.d.ts +36 -0
- package/components/controls/phi-tour-viewport.js +86 -0
- package/components/controls/phi-zoom-image-overlay.d.ts +17 -0
- package/components/controls/phi-zoom-image-overlay.js +58 -0
- package/components/layouts/layout-definitions.d.ts +0 -18
- package/components/layouts/layout-definitions.js +7 -1
- package/components/layouts/plugins/carousel-layout-plugin.js +2 -1
- package/components/media/aspect-ratios.d.ts +14 -0
- package/components/media/aspect-ratios.js +13 -0
- package/components/motion/phi-sequence-run.d.ts +27 -0
- package/components/motion/phi-sequence-run.js +87 -0
- package/components/motion/phi-sequence-viewport.d.ts +16 -0
- package/components/motion/phi-sequence-viewport.js +97 -34
- package/components/overlays/phi-overlay-container-client.js +5 -2
- package/components/runtime/runtime-visual-anchors.d.ts +13 -2
- package/components/runtime/runtime-visual-anchors.js +49 -3
- package/components/widgets/config/image-variant-options.d.ts +3 -5
- package/components/widgets/config/image-variant-options.js +11 -11
- package/components/widgets/config/sequence-settings.d.ts +75 -0
- package/components/widgets/config/sequence-settings.js +173 -0
- package/components/widgets/label-sets/content-entries.d.ts +9 -0
- package/components/widgets/label-sets/content-entries.js +17 -0
- package/components/widgets/label-types/gallery.d.ts +2 -10
- package/components/widgets/label-types/sequence.d.ts +18 -0
- package/components/widgets/label-types/sequence.js +1 -0
- package/helpers/cms-layout-defaults.js +9 -2
- package/helpers/cms-region-keys.d.ts +1 -1
- package/helpers/motion.d.ts +7 -0
- package/helpers/motion.js +7 -0
- package/package.json +1 -1
- package/plugins/runtime-modules/asset/data-providers.d.ts +4 -0
- package/plugins/runtime-modules/asset/data-providers.js +1 -0
- package/plugins/runtime-modules/asset/definition.d.ts +4 -0
- package/plugins/runtime-modules/asset/presets.d.ts +53 -1
- package/plugins/runtime-modules/asset/presets.js +16 -12
- package/plugins/runtime-modules/asset/tour.d.ts +44 -0
- package/plugins/runtime-modules/asset/tour.js +53 -0
- package/plugins/runtime-modules/builder/clients/inspector-config-field.js +42 -18
- package/plugins/runtime-modules/builder/clients/inspector-image-field.js +2 -2
- package/plugins/runtime-modules/builder/clients/workspace-scope-boundary.js +10 -0
- package/plugins/runtime-modules/builder/controller/workspace-controller.js +18 -3
- package/plugins/runtime-modules/builder/plugin-meta-labels.js +8 -0
- package/plugins/runtime-modules/builder/presets.js +5 -3
- package/plugins/runtime-modules/builder/tour.d.ts +316 -0
- package/plugins/runtime-modules/builder/tour.js +337 -0
- package/plugins/runtime-modules/builder/trees/phi-builder-inspector-area-overlay-tree.server.js +8 -1
- package/plugins/runtime-modules/builder/widgets/structure-region/built-in.js +3 -2
- package/plugins/runtime-modules/core/widgets/collection-view/client.js +10 -0
- package/plugins/runtime-modules/core/widgets/description/config.d.ts +25 -1
- package/plugins/runtime-modules/core/widgets/entry-detail/config.d.ts +2 -0
- package/plugins/runtime-modules/core/widgets/entry-detail/config.js +8 -1
- package/plugins/runtime-modules/core/widgets/entry-header/config.d.ts +3 -0
- package/plugins/runtime-modules/core/widgets/entry-header/config.js +8 -1
- package/plugins/runtime-modules/core/widgets/entry-list/client.d.ts +4 -1
- package/plugins/runtime-modules/core/widgets/entry-list/client.js +3 -2
- package/plugins/runtime-modules/core/widgets/entry-list/config.d.ts +392 -93
- package/plugins/runtime-modules/core/widgets/entry-list/config.js +21 -3
- package/plugins/runtime-modules/core/widgets/entry-list/server.js +2 -0
- package/plugins/runtime-modules/core/widgets/gallery/authoring.js +62 -10
- package/plugins/runtime-modules/core/widgets/gallery/client.d.ts +6 -1
- package/plugins/runtime-modules/core/widgets/gallery/client.js +7 -79
- package/plugins/runtime-modules/core/widgets/gallery/config.d.ts +337 -144
- package/plugins/runtime-modules/core/widgets/gallery/config.js +52 -113
- package/plugins/runtime-modules/core/widgets/gallery/defaults.d.ts +15 -0
- package/plugins/runtime-modules/core/widgets/gallery/defaults.js +20 -0
- package/plugins/runtime-modules/core/widgets/image/client.js +1 -1
- package/plugins/runtime-modules/core/widgets/image/config.d.ts +29 -4
- package/plugins/runtime-modules/core/widgets/image/config.js +19 -2
- package/plugins/runtime-modules/core/widgets/image/editor.js +1 -1
- package/plugins/runtime-modules/core/widgets/result/config.d.ts +25 -1
- package/plugins/runtime-modules/core/widgets/simple-text/config.d.ts +25 -1
- package/plugins/runtime-modules/core/widgets/table/config.d.ts +79 -3
- package/plugins/runtime-modules/core/widgets/tour-button/client.js +1 -1
- package/plugins/runtime-modules/revisions/presets.d.ts +53 -2
- package/plugins/runtime-modules/revisions/presets.js +16 -12
- package/plugins/runtime-modules/revisions/tour.d.ts +43 -0
- package/plugins/runtime-modules/revisions/tour.js +51 -0
- package/plugins/runtime-modules/theme/presets.d.ts +36 -4
- package/plugins/runtime-modules/theme/tour.d.ts +45 -11
- package/plugins/runtime-modules/theme/tour.js +39 -8
- package/plugins/runtime-modules/tour/controller/address.js +2 -2
- package/plugins/runtime-modules/tour/controller/client.js +43 -2
- package/plugins/runtime-modules/tour/controller/definition.js +2 -1
- package/server-helpers/cms-request.js +1 -0
- package/server-helpers/page-tour.d.ts +10 -3
- package/server-helpers/page-tour.js +25 -12
- package/types/cms-config.d.ts +3 -1
- package/types/cms-config.js +3 -3
- package/types/cms-plugins.d.ts +31 -4
- package/types/tour-controller.d.ts +10 -0
- package/types/tour-controller.js +13 -0
- package/types/tours.d.ts +27 -25
- package/types/tours.js +9 -23
- package/types/visual-anchors.d.ts +15 -0
- package/types/visual-anchors.js +12 -0
- package/types.d.ts +2 -0
- 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/
|
|
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/
|
|
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 (
|
|
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 (
|
|
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,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
|
|
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(
|
|
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 {
|
|
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(
|
|
11
|
-
|
|
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:
|
|
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
|
-
|
|
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 {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
}
|
|
@@ -7,7 +7,7 @@ import { CSS } from "@dnd-kit/utilities";
|
|
|
7
7
|
import { flushSync } from "react-dom";
|
|
8
8
|
import { Children, cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
9
9
|
import { PHI_GREGORY_CALENDAR_ADAPTER_KEY } from "../../types/calendar-keys";
|
|
10
|
-
import { PHI_VISUAL_ANCHOR_PART_ATTRIBUTE } from "../../types/
|
|
10
|
+
import { PHI_VISUAL_ANCHOR_PART_ATTRIBUTE } from "../../types/visual-anchors";
|
|
11
11
|
import { PhiColorControl } from "./phi-color-control";
|
|
12
12
|
import { PhiCheckboxControl } from "./phi-checkbox-control";
|
|
13
13
|
import { PhiCheckboxGroupControl } from "./phi-checkbox-group-control";
|
|
@@ -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(
|
|
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 {
|
|
4
|
-
|
|
5
|
-
|
|
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(
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
/** Whether the viewer asked for less motion: the Tour then jumps rather than glides. */
|
|
20
|
+
function prefersPhiTourReducedMotion() {
|
|
21
|
+
return typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
|
|
22
|
+
}
|
|
23
|
+
export function resolvePhiTourScrollBehavior() {
|
|
24
|
+
return prefersPhiTourReducedMotion() ? "auto" : "smooth";
|
|
25
|
+
}
|
|
26
|
+
/** Air between the cover and the target, so a highlighted edge is not drawn against the Header. */
|
|
27
|
+
const PHI_TOUR_VIEWPORT_GAP = 8;
|
|
28
|
+
function isStuck(element) {
|
|
29
|
+
const { position } = getComputedStyle(element);
|
|
30
|
+
return position === "sticky" || position === "fixed";
|
|
31
|
+
}
|
|
32
|
+
/** How far down from the window's top the bands that stay put over `target` reach, in pixels. */
|
|
33
|
+
function readPhiTourTopCover(target) {
|
|
34
|
+
const viewHeight = window.innerHeight;
|
|
35
|
+
const targetRect = target.getBoundingClientRect();
|
|
36
|
+
let cover = 0;
|
|
37
|
+
for (let node = target; node && node !== document.body; node = node.parentElement) {
|
|
38
|
+
for (let sibling = node.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
|
|
39
|
+
if (!isStuck(sibling))
|
|
40
|
+
continue;
|
|
41
|
+
const rect = sibling.getBoundingClientRect();
|
|
42
|
+
const atTop = rect.height > 0 && rect.top < viewHeight / 2 && rect.height < viewHeight / 2;
|
|
43
|
+
const overTarget = rect.left < targetRect.right && rect.right > targetRect.left;
|
|
44
|
+
if (atTop && overTarget)
|
|
45
|
+
cover = Math.max(cover, rect.bottom);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return Math.max(0, Math.round(cover));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Centered where the target fits below the cover, top-aligned under it where it does not: a target
|
|
52
|
+
* taller than what is left of the window is read from its top.
|
|
53
|
+
*/
|
|
54
|
+
export function resolvePhiTourScrollBlock(target, cover = readPhiTourTopCover(target)) {
|
|
55
|
+
const free = window.innerHeight - cover - PHI_TOUR_VIEWPORT_GAP * 2;
|
|
56
|
+
return target.getBoundingClientRect().height <= free ? "center" : "start";
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Keeps the window's scroll padding at the cover over `target`, so every `scrollIntoView` -- Ant
|
|
60
|
+
* Design's included -- aligns below it. Returns the cover.
|
|
61
|
+
*/
|
|
62
|
+
export function holdPhiTourViewport(target) {
|
|
63
|
+
const cover = readPhiTourTopCover(target);
|
|
64
|
+
document.documentElement.style.scrollPaddingTop = `${cover + PHI_TOUR_VIEWPORT_GAP}px`;
|
|
65
|
+
return cover;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Brings `target` into the part of the window nothing covers. A target already wholly in that part --
|
|
69
|
+
* or, taller than it, already standing at its top -- is left where it is. Returns whether it scrolled.
|
|
70
|
+
*/
|
|
71
|
+
export function bringPhiTourTargetIntoView(target, behavior) {
|
|
72
|
+
const cover = holdPhiTourViewport(target);
|
|
73
|
+
const rect = target.getBoundingClientRect();
|
|
74
|
+
const block = resolvePhiTourScrollBlock(target, cover);
|
|
75
|
+
const inView = block === "center"
|
|
76
|
+
? rect.top >= cover && rect.bottom <= window.innerHeight
|
|
77
|
+
: Math.abs(rect.top - cover - PHI_TOUR_VIEWPORT_GAP) <= 1;
|
|
78
|
+
if (inView)
|
|
79
|
+
return false;
|
|
80
|
+
target.scrollIntoView({ block, inline: "nearest", behavior });
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
/** The window's scroll padding as it was before a Tour set it. */
|
|
84
|
+
export function releasePhiTourViewport() {
|
|
85
|
+
document.documentElement.style.scrollPaddingTop = "";
|
|
86
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type PhiZoomImageOverlayProps = {
|
|
2
|
+
open: boolean;
|
|
3
|
+
src: string;
|
|
4
|
+
alt: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* A picture scaled to the viewport over the page, and nothing else: no toolbar, no zoom steps, no
|
|
10
|
+
* rotation -- that is the lightbox's (`PhiLightboxImageControl`). A click anywhere closes it, as does
|
|
11
|
+
* Escape, and the focus goes back to where it was.
|
|
12
|
+
*
|
|
13
|
+
* On `document.body`, at the layer a Modal stands on, for the reason `PhiFloatButtonPortal` gives: a
|
|
14
|
+
* picture's slot and Region open stacking contexts of their own, and an overlay left inside them would be
|
|
15
|
+
* painted under the Header. The server draws nothing; it opens only on a click.
|
|
16
|
+
*/
|
|
17
|
+
export declare function PhiZoomImageOverlay({ open, src, alt, title, onClose }: PhiZoomImageOverlayProps): import("react").ReactPortal | null;
|