@godxjp/ui 28.7.0 → 28.9.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/dist/components/data-display/index.d.ts +2 -0
- package/dist/components/data-display/index.js +2 -0
- package/dist/components/data-display/marquee.d.ts +16 -0
- package/dist/components/data-display/marquee.js +155 -0
- package/dist/components/general/reveal.d.ts +23 -2
- package/dist/components/general/reveal.js +37 -7
- package/dist/components/general/typography.d.ts +4 -1
- package/dist/components/general/typography.js +14 -1
- package/dist/components/layout/affix.d.ts +86 -0
- package/dist/components/layout/affix.js +187 -0
- package/dist/components/layout/index.d.ts +4 -0
- package/dist/components/layout/index.js +4 -0
- package/dist/components/layout/legal-document-shell.js +4 -3
- package/dist/components/layout/masonry.d.ts +74 -0
- package/dist/components/layout/masonry.js +214 -0
- package/dist/components/layout/page-container.js +5 -20
- package/dist/components/navigation/anchor.d.ts +64 -0
- package/dist/components/navigation/anchor.js +284 -0
- package/dist/components/navigation/index.d.ts +4 -0
- package/dist/components/navigation/index.js +4 -0
- package/dist/components/navigation/mega-menu.d.ts +21 -0
- package/dist/components/navigation/mega-menu.js +526 -0
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +517 -0
- package/dist/i18n/messages/ja.json +513 -0
- package/dist/i18n/messages/vi.json +513 -0
- package/dist/lib/hooks.d.ts +68 -0
- package/dist/lib/hooks.js +52 -0
- package/dist/lib/platform.d.ts +14 -0
- package/dist/lib/platform.js +10 -1
- package/dist/lib/utils.d.ts +1 -1
- package/dist/lib/utils.js +3 -2
- package/dist/lib/variants.js +4 -1
- package/dist/props/components/data-display.prop.d.ts +95 -1
- package/dist/props/components/general.prop.d.ts +47 -3
- package/dist/props/components/layout.prop.d.ts +194 -0
- package/dist/props/components/navigation.prop.d.ts +263 -0
- package/dist/props/registry.d.ts +359 -4
- package/dist/props/registry.js +472 -3
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/props/vocabulary/interaction.prop.d.ts +39 -2
- package/dist/props/vocabulary/layout.prop.d.ts +1 -1
- package/dist/styles/base.css +47 -14
- package/dist/styles/card-layout.css +2 -2
- package/dist/styles/chart-layout.css +6 -6
- package/dist/styles/control.css +15 -10
- package/dist/styles/data-display-layout.css +22 -6
- package/dist/styles/density.css +6 -0
- package/dist/styles/dialog-layout.css +4 -1
- package/dist/styles/focus-ring.css +4 -1
- package/dist/styles/layout.css +92 -3
- package/dist/styles/motion.css +121 -1
- package/dist/styles/navigation-layout.css +397 -1
- package/dist/styles/shell-layout.css +28 -21
- package/dist/styles/text-layout.css +134 -15
- package/dist/tokens/base.css +5 -0
- package/dist/tokens/components/activity.css +13 -4
- package/dist/tokens/components/affix.css +7 -0
- package/dist/tokens/components/anchor.css +17 -0
- package/dist/tokens/components/attachments.css +1 -1
- package/dist/tokens/components/badge.css +1 -1
- package/dist/tokens/components/card.css +28 -7
- package/dist/tokens/components/chart.css +4 -1
- package/dist/tokens/components/chat-composer.css +4 -1
- package/dist/tokens/components/control.css +72 -33
- package/dist/tokens/components/conversations.css +4 -1
- package/dist/tokens/components/data-display.css +42 -15
- package/dist/tokens/components/data-entry.css +8 -2
- package/dist/tokens/components/descriptions.css +1 -1
- package/dist/tokens/components/feedback.css +8 -5
- package/dist/tokens/components/float-button.css +8 -2
- package/dist/tokens/components/form.css +1 -1
- package/dist/tokens/components/legal-document.css +12 -3
- package/dist/tokens/components/logo.css +15 -6
- package/dist/tokens/components/marquee.css +7 -0
- package/dist/tokens/components/masonry.css +6 -0
- package/dist/tokens/components/mega-menu.css +71 -0
- package/dist/tokens/components/navigation.css +37 -13
- package/dist/tokens/components/segmented.css +4 -1
- package/dist/tokens/components/separator.css +4 -1
- package/dist/tokens/components/shell.css +99 -31
- package/dist/tokens/components/table.css +11 -5
- package/dist/tokens/components/thought-chain.css +4 -1
- package/dist/tokens/components/toggle.css +4 -1
- package/dist/tokens/components/tree.css +1 -1
- package/dist/tokens/components/upload.css +21 -9
- package/dist/tokens/foundation.css +35 -30
- package/dist/tokens/semantic/layout.css +26 -5
- package/docs/COMPOSITION-VS-COMPONENT.md +19 -1
- package/docs/DESIGN-AUTHORITY.md +99 -18
- package/docs/FRAME-COVERAGE-REPORT.md +7 -2
- package/docs/TOKENS.md +16 -1
- package/docs/data-display/marquee.tsx +254 -0
- package/docs/foundation/_theme-editor-scope.ts +222 -0
- package/docs/foundation/density.tsx +12 -2
- package/docs/foundation/spacing.tsx +5 -0
- package/docs/foundation/theme-editor.tsx +645 -0
- package/docs/general/activity.tsx +65 -0
- package/docs/general/reveal.tsx +290 -22
- package/docs/general/typography.tsx +91 -1
- package/docs/layout/affix.tsx +209 -0
- package/docs/layout/masonry.tsx +291 -0
- package/docs/navigation/anchor.tsx +285 -0
- package/docs/navigation/mega-menu-panel.tsx +86 -0
- package/docs/navigation/mega-menu.tsx +254 -0
- package/docs/roadmap/website-components.md +779 -0
- package/docs/showcase/acme-website.tsx +75 -39
- package/docs/showcase/case4-login.tsx +10 -2
- package/docs/showcase/case5-shift-calendar.tsx +1 -1
- package/docs/showcase/case6-agency-handy.tsx +6 -6
- package/docs/showcase/futurelastic-web.tsx +89 -49
- package/docs/showcase/marketing-page.tsx +885 -0
- package/docs/showcase/table-expandable-rows.tsx +4 -1
- package/docs/showcase/table-footer-totals.tsx +12 -2
- package/docs/showcase/theme-customization.tsx +1259 -0
- package/package.json +6 -3
- package/scripts/brand-accent.generated.mjs +27 -0
- package/scripts/ui-audit.mjs +66 -0
- package/scripts/visual-audit-rules.mjs +46 -2
|
@@ -10,6 +10,8 @@ export { Thumbnail } from "./thumbnail.js";
|
|
|
10
10
|
export type { ThumbnailProp, ThumbnailProps, ThumbnailSizeProp } from "./thumbnail.js";
|
|
11
11
|
export { ListRow } from "./list-row.js";
|
|
12
12
|
export type { ListRowProps, ListRowDensity } from "./list-row.js";
|
|
13
|
+
export { Marquee } from "./marquee.js";
|
|
14
|
+
export type { MarqueeDirectionProp, MarqueeProp, MarqueeProps, MarqueeSpeedProp } from "./marquee.js";
|
|
13
15
|
export { CredentialReveal } from "./credential-reveal.js";
|
|
14
16
|
export type { CredentialRevealProp, CredentialRevealProps, CredentialRevealTone, } from "./credential-reveal.js";
|
|
15
17
|
export { QrCode } from "./qr-code.js";
|
|
@@ -4,6 +4,7 @@ import { Swatch } from "./swatch.js";
|
|
|
4
4
|
import { FeatureList } from "./feature-list.js";
|
|
5
5
|
import { Thumbnail } from "./thumbnail.js";
|
|
6
6
|
import { ListRow } from "./list-row.js";
|
|
7
|
+
import { Marquee } from "./marquee.js";
|
|
7
8
|
import { CredentialReveal } from "./credential-reveal.js";
|
|
8
9
|
import { QrCode } from "./qr-code.js";
|
|
9
10
|
import { Avatar, AvatarImage, AvatarFallback } from "./avatar.js";
|
|
@@ -101,6 +102,7 @@ export {
|
|
|
101
102
|
HoverCardTrigger,
|
|
102
103
|
Legend,
|
|
103
104
|
ListRow,
|
|
105
|
+
Marquee,
|
|
104
106
|
PermissionMatrix,
|
|
105
107
|
Popover,
|
|
106
108
|
PopoverAnchor,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { GapProp } from "../../props/vocabulary/index.js";
|
|
3
|
+
export type { MarqueeDirectionProp, MarqueeSpeedProp, MarqueeProp, MarqueeProp as MarqueeProps, } from "../../props/components/data-display.prop.js";
|
|
4
|
+
export declare const Marquee: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLDivElement>, "children" | "onPause" | "onPlay"> & {
|
|
5
|
+
children?: import("../../props/index.js").ChildrenProp;
|
|
6
|
+
play?: boolean;
|
|
7
|
+
defaultPlay?: boolean;
|
|
8
|
+
onPlayChange?: (play: boolean) => void;
|
|
9
|
+
direction?: import("./marquee.js").MarqueeDirectionProp;
|
|
10
|
+
speed?: import("./marquee.js").MarqueeSpeedProp;
|
|
11
|
+
gap?: GapProp;
|
|
12
|
+
pauseOnHover?: boolean;
|
|
13
|
+
fade?: boolean;
|
|
14
|
+
label?: import("../../props/index.js").LabelProp;
|
|
15
|
+
className?: import("../../props/index.js").ClassNameProp;
|
|
16
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Pause, Play } from "lucide-react";
|
|
5
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
|
+
import { useMediaQuery } from "../../lib/hooks.js";
|
|
7
|
+
import { cn } from "../../lib/utils.js";
|
|
8
|
+
import { Button } from "../general/button.js";
|
|
9
|
+
import { ScrollArea } from "./scroll-area.js";
|
|
10
|
+
function gapToken(step) {
|
|
11
|
+
if (step === "none" || step === 0) return "0px";
|
|
12
|
+
if (typeof step === "number") return `var(--space-${step})`;
|
|
13
|
+
return `var(--space-inline-${step})`;
|
|
14
|
+
}
|
|
15
|
+
const MAX_COPIES = 20;
|
|
16
|
+
const Marquee = React.forwardRef(function Marquee2({
|
|
17
|
+
children,
|
|
18
|
+
play,
|
|
19
|
+
defaultPlay = true,
|
|
20
|
+
onPlayChange,
|
|
21
|
+
direction = "start",
|
|
22
|
+
speed = "base",
|
|
23
|
+
gap,
|
|
24
|
+
pauseOnHover = false,
|
|
25
|
+
fade = false,
|
|
26
|
+
label,
|
|
27
|
+
className,
|
|
28
|
+
style,
|
|
29
|
+
...props
|
|
30
|
+
}, ref) {
|
|
31
|
+
const { t } = useTranslation();
|
|
32
|
+
const reducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
|
|
33
|
+
const trackId = React.useId();
|
|
34
|
+
const viewportRef = React.useRef(null);
|
|
35
|
+
const copyRef = React.useRef(null);
|
|
36
|
+
const [uncontrolledPlay, setUncontrolledPlay] = React.useState(defaultPlay);
|
|
37
|
+
const playing = play ?? uncontrolledPlay;
|
|
38
|
+
const [measurement, setMeasurement] = React.useState({ copies: 2, cycleScale: 1 });
|
|
39
|
+
React.useEffect(() => {
|
|
40
|
+
if (reducedMotion) return void 0;
|
|
41
|
+
const viewport = viewportRef.current;
|
|
42
|
+
const copy = copyRef.current;
|
|
43
|
+
if (!viewport || !copy || typeof ResizeObserver === "undefined") return void 0;
|
|
44
|
+
const measure = () => {
|
|
45
|
+
const copyWidth = copy.offsetWidth;
|
|
46
|
+
const viewportWidth = viewport.offsetWidth;
|
|
47
|
+
if (copyWidth <= 0 || viewportWidth <= 0) return;
|
|
48
|
+
const copies = Math.min(MAX_COPIES, Math.ceil(viewportWidth / copyWidth) + 1);
|
|
49
|
+
const cycleScale = Math.round(copyWidth / viewportWidth * 1e3) / 1e3;
|
|
50
|
+
setMeasurement(
|
|
51
|
+
(current) => current.copies === copies && current.cycleScale === cycleScale ? current : { copies, cycleScale }
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
measure();
|
|
55
|
+
const observer = new ResizeObserver(measure);
|
|
56
|
+
observer.observe(viewport);
|
|
57
|
+
observer.observe(copy);
|
|
58
|
+
return () => {
|
|
59
|
+
observer.disconnect();
|
|
60
|
+
};
|
|
61
|
+
}, [reducedMotion, children]);
|
|
62
|
+
const setPlaying = (next) => {
|
|
63
|
+
if (play === void 0) setUncontrolledPlay(next);
|
|
64
|
+
onPlayChange?.(next);
|
|
65
|
+
};
|
|
66
|
+
const rootStyle = {
|
|
67
|
+
...style,
|
|
68
|
+
...gap === void 0 ? null : { "--marquee-gap-inline": gapToken(gap) }
|
|
69
|
+
};
|
|
70
|
+
if (reducedMotion) {
|
|
71
|
+
return /* @__PURE__ */ jsx(
|
|
72
|
+
"div",
|
|
73
|
+
{
|
|
74
|
+
ref,
|
|
75
|
+
"data-slot": "marquee",
|
|
76
|
+
"data-reduced-motion": "true",
|
|
77
|
+
"data-direction": direction,
|
|
78
|
+
className: cn("ui-marquee", className),
|
|
79
|
+
style: rootStyle,
|
|
80
|
+
...props,
|
|
81
|
+
children: /* @__PURE__ */ jsx(
|
|
82
|
+
ScrollArea,
|
|
83
|
+
{
|
|
84
|
+
orientation: "horizontal",
|
|
85
|
+
className: "ui-marquee-scroller",
|
|
86
|
+
label: typeof label === "string" ? label : void 0,
|
|
87
|
+
children: /* @__PURE__ */ jsx("div", { id: trackId, "data-slot": "marquee-track", className: "ui-marquee-track", children: /* @__PURE__ */ jsx("div", { ref: copyRef, "data-slot": "marquee-copy", className: "ui-marquee-copy", children }) })
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const animatedStyle = {
|
|
94
|
+
...rootStyle,
|
|
95
|
+
"--marquee-copies": measurement.copies,
|
|
96
|
+
"--marquee-cycle-scale": measurement.cycleScale
|
|
97
|
+
};
|
|
98
|
+
const named = typeof label === "string" && label.trim() !== "";
|
|
99
|
+
const controlLabel = playing ? named ? t("dataDisplay.marquee.pauseNamed", { label }) : t("dataDisplay.marquee.pause") : named ? t("dataDisplay.marquee.playNamed", { label }) : t("dataDisplay.marquee.play");
|
|
100
|
+
return /* @__PURE__ */ jsxs(
|
|
101
|
+
"div",
|
|
102
|
+
{
|
|
103
|
+
ref,
|
|
104
|
+
"data-slot": "marquee",
|
|
105
|
+
"data-direction": direction,
|
|
106
|
+
"data-speed": speed,
|
|
107
|
+
"data-playing": playing ? "true" : "false",
|
|
108
|
+
"data-pause-on-hover": pauseOnHover ? "true" : "false",
|
|
109
|
+
"data-fade": fade ? "true" : "false",
|
|
110
|
+
className: cn("ui-marquee", className),
|
|
111
|
+
style: animatedStyle,
|
|
112
|
+
...props,
|
|
113
|
+
children: [
|
|
114
|
+
/* @__PURE__ */ jsx("div", { ref: viewportRef, "data-slot": "marquee-viewport", className: "ui-marquee-viewport", children: /* @__PURE__ */ jsxs("div", { id: trackId, "data-slot": "marquee-track", className: "ui-marquee-track", children: [
|
|
115
|
+
/* @__PURE__ */ jsx("div", { ref: copyRef, "data-slot": "marquee-copy", className: "ui-marquee-copy", children }),
|
|
116
|
+
Array.from({ length: measurement.copies - 1 }, (_, index) => (
|
|
117
|
+
// Decorative by construction: `aria-hidden` keeps the duplicate out of the
|
|
118
|
+
// accessibility tree and `inert` keeps it out of the tab order. The prior art has
|
|
119
|
+
// neither, which is why its content is announced once per screenful of width.
|
|
120
|
+
/* @__PURE__ */ jsx(
|
|
121
|
+
"div",
|
|
122
|
+
{
|
|
123
|
+
"data-slot": "marquee-clone",
|
|
124
|
+
className: "ui-marquee-copy",
|
|
125
|
+
"aria-hidden": "true",
|
|
126
|
+
inert: true,
|
|
127
|
+
children
|
|
128
|
+
},
|
|
129
|
+
index
|
|
130
|
+
)
|
|
131
|
+
))
|
|
132
|
+
] }) }),
|
|
133
|
+
/* @__PURE__ */ jsx(
|
|
134
|
+
Button,
|
|
135
|
+
{
|
|
136
|
+
type: "button",
|
|
137
|
+
variant: "ghost",
|
|
138
|
+
size: "icon-sm",
|
|
139
|
+
"data-slot": "marquee-control",
|
|
140
|
+
className: "ui-marquee-control",
|
|
141
|
+
"aria-label": controlLabel,
|
|
142
|
+
"aria-controls": trackId,
|
|
143
|
+
onClick: () => {
|
|
144
|
+
setPlaying(!playing);
|
|
145
|
+
},
|
|
146
|
+
children: playing ? /* @__PURE__ */ jsx(Pause, { className: "ui-marquee-control-glyph", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Play, { className: "ui-marquee-control-glyph", "aria-hidden": "true" })
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
export {
|
|
154
|
+
Marquee
|
|
155
|
+
};
|
|
@@ -1,8 +1,29 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from "react";
|
|
2
2
|
export type { RevealProp, RevealProp as RevealProps } from "../../props/components/general.prop.js";
|
|
3
3
|
/**
|
|
4
4
|
* Reveal — the official entrance-motion primitive (staggered fade-up). `delay` is a stagger
|
|
5
5
|
* ordinal (`0..6`) — an index into the `--reveal-stagger-step` ladder, never a raw ms — so a
|
|
6
6
|
* column of rows cascades in.
|
|
7
|
+
*
|
|
8
|
+
* `on="view"` moves the TRIGGER to the viewport (gh#829). It is one prop rather than a second
|
|
9
|
+
* component because everything else is identical: the same keyframes, the same tokens, the same
|
|
10
|
+
* reduced-motion contract.
|
|
11
|
+
*
|
|
12
|
+
* ## The observer gates the ANIMATION, never the VISIBILITY
|
|
13
|
+
*
|
|
14
|
+
* `.ui-reveal`'s resting state in `styles/motion.css` is the finished, fully visible one. The
|
|
15
|
+
* hidden state is written here, as `data-reveal-state="out"`, and ONLY once this component is
|
|
16
|
+
* mounted in a browser that has an `IntersectionObserver` and has measured the element as still
|
|
17
|
+
* outside the viewport. Everything else — the server render, jsdom, a browser without the API,
|
|
18
|
+
* and `prefers-reduced-motion: reduce`, under which no observer is attached at all — renders the
|
|
19
|
+
* content exactly as it will finally look.
|
|
7
20
|
*/
|
|
8
|
-
export declare
|
|
21
|
+
export declare const Reveal: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
|
|
22
|
+
children?: import("../../props/index.js").ChildrenProp;
|
|
23
|
+
delay?: import("../../props/index.js").RevealDelayProp;
|
|
24
|
+
on?: import("../../props/index.js").RevealTriggerProp;
|
|
25
|
+
once?: boolean;
|
|
26
|
+
amount?: import("../../props/index.js").InViewAmountProp;
|
|
27
|
+
asChild?: import("../../props/index.js").AsChildProp;
|
|
28
|
+
className?: import("../../props/index.js").ClassNameProp;
|
|
29
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -1,21 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
+
import { mergeRefs } from "@react-aria/utils";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { useInView, useMediaQuery } from "../../lib/hooks.js";
|
|
3
6
|
import { cn } from "../../lib/utils.js";
|
|
4
|
-
|
|
7
|
+
const Reveal = React.forwardRef(function Reveal2({
|
|
8
|
+
children,
|
|
9
|
+
delay = 0,
|
|
10
|
+
on = "mount",
|
|
11
|
+
once = true,
|
|
12
|
+
amount = "some",
|
|
13
|
+
asChild = false,
|
|
14
|
+
className,
|
|
15
|
+
...props
|
|
16
|
+
}, ref) {
|
|
17
|
+
const localRef = React.useRef(null);
|
|
18
|
+
const reduceMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
|
|
19
|
+
const observing = on === "view" && !reduceMotion;
|
|
20
|
+
const inView = useInView(localRef, { enabled: observing, once, amount });
|
|
5
21
|
const shared = {
|
|
6
22
|
"data-slot": "reveal",
|
|
7
|
-
"data-reveal-delay": delay > 0 ? String(delay) : void 0
|
|
23
|
+
"data-reveal-delay": delay > 0 ? String(delay) : void 0,
|
|
24
|
+
"data-reveal-on": on === "view" ? "view" : void 0,
|
|
25
|
+
"data-reveal-state": observing ? inView ? "in" : "out" : void 0
|
|
8
26
|
};
|
|
9
|
-
if (asChild && isValidElement(children)) {
|
|
27
|
+
if (asChild && React.isValidElement(children)) {
|
|
10
28
|
const child = children;
|
|
11
|
-
return cloneElement(child, {
|
|
29
|
+
return React.cloneElement(child, {
|
|
12
30
|
...shared,
|
|
13
31
|
...props,
|
|
32
|
+
// React 19 passes `ref` through `cloneElement` like any other prop, which is what `asChild`
|
|
33
|
+
// promises — the observer measures the child's own box, not a wrapper's.
|
|
34
|
+
ref: mergeRefs(ref, localRef, child.props.ref),
|
|
14
35
|
className: cn("ui-reveal", child.props.className, className)
|
|
15
36
|
});
|
|
16
37
|
}
|
|
17
|
-
return /* @__PURE__ */ jsx(
|
|
18
|
-
|
|
38
|
+
return /* @__PURE__ */ jsx(
|
|
39
|
+
"div",
|
|
40
|
+
{
|
|
41
|
+
...shared,
|
|
42
|
+
...props,
|
|
43
|
+
ref: mergeRefs(ref, localRef),
|
|
44
|
+
className: cn("ui-reveal", className),
|
|
45
|
+
children
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
});
|
|
19
49
|
export {
|
|
20
50
|
Reveal
|
|
21
51
|
};
|
|
@@ -59,13 +59,16 @@ export declare const Link: React.ForwardRefExoticComponent<Omit<TextProp, "as" |
|
|
|
59
59
|
} & React.RefAttributes<HTMLElement>>;
|
|
60
60
|
/**
|
|
61
61
|
* Heading — h1..h4 sized from the `--heading-h*` tokens. `level` sets both the size token and the
|
|
62
|
-
* semantic element; override the rendered element with `as` (e.g. a visual h2 that is a real <h1>)
|
|
62
|
+
* semantic element; override the rendered element with `as` (e.g. a visual h2 that is a real <h1>),
|
|
63
|
+
* and override the SIZE alone with `size` — the same ten-step ladder `Text` reads, whose top three
|
|
64
|
+
* steps are the display ramp a marketing hero needs (gh#826).
|
|
63
65
|
*
|
|
64
66
|
* UNCHANGED by the antd port. antd's heading is `Title`, which is the sibling below; `Heading` is
|
|
65
67
|
* this library's own and keeps its four levels, its props and its markup exactly as they shipped.
|
|
66
68
|
*/
|
|
67
69
|
export declare const Heading: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLHeadingElement>, "color"> & {
|
|
68
70
|
level?: import("../../props/index.js").HeadingLevelProp;
|
|
71
|
+
size?: import("../../props/index.js").TextSizeProp;
|
|
69
72
|
as?: "h1" | "h2" | "h3" | "h4" | "div";
|
|
70
73
|
tone?: import("../../props/index.js").TextToneProp;
|
|
71
74
|
align?: import("../../props/index.js").TextAlignProp;
|
|
@@ -527,10 +527,23 @@ const Link = React.forwardRef(
|
|
|
527
527
|
);
|
|
528
528
|
Link.displayName = "Link";
|
|
529
529
|
const Heading = React.forwardRef(
|
|
530
|
-
({
|
|
530
|
+
({
|
|
531
|
+
level = 2,
|
|
532
|
+
size,
|
|
533
|
+
as,
|
|
534
|
+
tone = "default",
|
|
535
|
+
align,
|
|
536
|
+
truncate,
|
|
537
|
+
weight = "medium",
|
|
538
|
+
className,
|
|
539
|
+
...props
|
|
540
|
+
}, ref) => React.createElement(as ?? `h${level}`, {
|
|
531
541
|
ref,
|
|
532
542
|
"data-slot": "heading",
|
|
533
543
|
"data-level": level,
|
|
544
|
+
// Emitted only when asked for. The absent attribute is what `[data-level]:not([data-size])`
|
|
545
|
+
// keys on, so a Heading with no `size` paints from its level exactly as it always has.
|
|
546
|
+
"data-size": size,
|
|
534
547
|
"data-tone": tone,
|
|
535
548
|
"data-align": align,
|
|
536
549
|
"data-weight": weight,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { AffixProp } from "../../props/components/layout.prop.js";
|
|
3
|
+
export type { AffixProp, AffixProp as AffixProps, AffixTargetProp, } from "../../props/components/layout.prop.js";
|
|
4
|
+
/**
|
|
5
|
+
* Affix — Ant Design `Affix` (6.6.5): pin an element to its scrollport once the page scrolls past
|
|
6
|
+
* it, and REPORT that it is pinned.
|
|
7
|
+
*
|
|
8
|
+
* ## Why this is not `position: sticky`
|
|
9
|
+
*
|
|
10
|
+
* `position: sticky` pins, and then says nothing. There is no state, no attribute and no callback,
|
|
11
|
+
* so a sticky header cannot shrink on pin, cannot swap a wordmark for a monogram, cannot raise its
|
|
12
|
+
* shadow, and cannot tell the page that the filters are now floating over the table. Both website
|
|
13
|
+
* showcases in `docs/showcase/` wrote `position: sticky` and got exactly nothing out of it. This
|
|
14
|
+
* component's product is the boolean: `data-affixed` for CSS, `onChange` for JavaScript.
|
|
15
|
+
*
|
|
16
|
+
* It also pins against a scroll box that need not be the nearest scrolling ancestor — antd's
|
|
17
|
+
* `target`, which `FloatButton.BackTop` already spells the same way here — where `sticky` is
|
|
18
|
+
* captive to whichever ancestor happens to scroll.
|
|
19
|
+
*
|
|
20
|
+
* ## The placeholder is as much the component as the pin is
|
|
21
|
+
*
|
|
22
|
+
* Taking an element out of flow removes its height from the page, so everything below jumps UP by
|
|
23
|
+
* exactly that height at the instant of pinning and drops back on release. Every hand-rolled
|
|
24
|
+
* sticky header has this bug and it is invisible until someone measures it. So the outer box stays
|
|
25
|
+
* in flow and, the moment its content goes `position: fixed`, is given the content's MEASURED
|
|
26
|
+
* block size. Measured, not declared: a bar re-wraps between widths, and a number written once in
|
|
27
|
+
* CSS is wrong at every other width.
|
|
28
|
+
*
|
|
29
|
+
* ## One `IntersectionObserver`, and it is not this file's
|
|
30
|
+
*
|
|
31
|
+
* The pin is a threshold crossing — the one question `IntersectionObserver` exists to answer — and
|
|
32
|
+
* this component asks it through `useInView` (`src/lib/hooks.ts`), the library's single observer
|
|
33
|
+
* wrapper, which gained `rootMargin` and `assumeInView` for exactly these two needs. Nothing here
|
|
34
|
+
* polls, and nothing here listens to `scroll` at all when the scroll box is the viewport: antd's
|
|
35
|
+
* `Affix` re-measures on seven event types on every animation frame of every scroll, and all of
|
|
36
|
+
* that is replaced by one observer entry per transition.
|
|
37
|
+
*
|
|
38
|
+
* The observed element is a zero-ish **sentinel** at the pinning edge, not the box itself. This is
|
|
39
|
+
* the published `position: sticky` sentinel technique ("An event for position: sticky",
|
|
40
|
+
* developers.google.com), and the separate node is arithmetic, not decoration: the box's own
|
|
41
|
+
* intersection flips when its TRAILING edge crosses the line, a full bar-height after the moment
|
|
42
|
+
* it should pin. A hairline node at the LEADING edge flips on the leading edge. It is
|
|
43
|
+
* `aria-hidden`, out of flow, and contributes nothing to the measured height.
|
|
44
|
+
*
|
|
45
|
+
* Both directions reduce to the same expression — `affixed = !inView` — because the sentinel sits
|
|
46
|
+
* at the block-start edge with the clipping box grown downwards, and at the block-end edge with it
|
|
47
|
+
* grown upwards.
|
|
48
|
+
*
|
|
49
|
+
* ## The offset is a TOKEN that the prop overrides, and it is never a number in JavaScript
|
|
50
|
+
*
|
|
51
|
+
* `--affix-inset-block-start` / `--affix-inset-block-end` place the pin line, and they place it
|
|
52
|
+
* ONCE: the sentinel is offset by `calc(-1 * …)`, which moves the crossing, and the pinned bar is
|
|
53
|
+
* inset by the same `var()`, which moves the paint. One declaration drives both, so they cannot
|
|
54
|
+
* drift. A service sets the resting offset once (a product whose app header is 64px tall writes
|
|
55
|
+
* `--affix-inset-block-start: 64px` in its theme and every affixed bar clears it); `offsetBlockStart`
|
|
56
|
+
* / `offsetBlockEnd` override it per instance by writing the same custom property inline. That is
|
|
57
|
+
* cardinal rules #44/#45 in their literal form.
|
|
58
|
+
*
|
|
59
|
+
* It also means the offset is never parsed back out of `getComputedStyle` — a custom property
|
|
60
|
+
* hands back its AUTHORED text, so a theme written in `rem` would have come back as `"4rem"` and
|
|
61
|
+
* `parseFloat` would have read it as 4px. CSS resolves the unit; JavaScript never sees it.
|
|
62
|
+
*
|
|
63
|
+
* ## What a pinned bar owes the keyboard (C6)
|
|
64
|
+
*
|
|
65
|
+
* A bar pinned over the block-start edge covers whatever the browser scrolls to — the next focused
|
|
66
|
+
* field, the `#section` a skip link jumps to — and the browser has no idea it is there. While
|
|
67
|
+
* pinned there, this sets `scroll-padding-block-start` on the scroll box to the bar's measured
|
|
68
|
+
* bottom edge, and puts back whatever was there on release. That is WCAG 2.4.11 (Focus Not
|
|
69
|
+
* Obscured) bought with one declaration, and it is why C6 passes rather than being asserted.
|
|
70
|
+
*
|
|
71
|
+
* ## Motion
|
|
72
|
+
*
|
|
73
|
+
* `Affix` animates nothing, so it has no transition to suppress and satisfies
|
|
74
|
+
* `prefers-reduced-motion: reduce` by construction — the pin is instantaneous in every mode. The
|
|
75
|
+
* SHRINK is a composition: read `[data-affixed]` off `data-slot="affix-content"` and transition
|
|
76
|
+
* whatever the brand condenses, on `--duration-fast` / `--ease-standard`, snapping under reduced
|
|
77
|
+
* motion. It must condense; it must never fade or disappear.
|
|
78
|
+
*
|
|
79
|
+
* ## One inherited limitation, stated rather than hidden
|
|
80
|
+
*
|
|
81
|
+
* A `transform`, `filter` or `backdrop-filter` on an ancestor makes that ancestor the containing
|
|
82
|
+
* block for `position: fixed`, so a pinned bar inside one is positioned against it instead of the
|
|
83
|
+
* viewport. `position: sticky` and antd's `Affix` are both subject to it; there is no workaround
|
|
84
|
+
* inside a component, only a call site that does not do that.
|
|
85
|
+
*/
|
|
86
|
+
export declare const Affix: React.ForwardRefExoticComponent<AffixProp & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { isDevelopment } from "../../lib/dev.js";
|
|
5
|
+
import { useInView } from "../../lib/hooks.js";
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
const OVERSHOOT_MARGIN = "1000000px";
|
|
8
|
+
const ROOT_MARGIN_BLOCK_START = `0px 0px ${OVERSHOOT_MARGIN} 0px`;
|
|
9
|
+
const ROOT_MARGIN_BLOCK_END = `${OVERSHOOT_MARGIN} 0px 0px 0px`;
|
|
10
|
+
function resolveTarget(target) {
|
|
11
|
+
if (!target) return null;
|
|
12
|
+
const node = target();
|
|
13
|
+
if (!node || typeof window === "undefined" || node === window) return null;
|
|
14
|
+
return node;
|
|
15
|
+
}
|
|
16
|
+
const Affix = React.forwardRef(function Affix2({
|
|
17
|
+
children,
|
|
18
|
+
offsetBlockStart,
|
|
19
|
+
offsetBlockEnd,
|
|
20
|
+
offsetTop,
|
|
21
|
+
offsetBottom,
|
|
22
|
+
target,
|
|
23
|
+
onChange,
|
|
24
|
+
id,
|
|
25
|
+
className,
|
|
26
|
+
...rest
|
|
27
|
+
}, ref) {
|
|
28
|
+
const rootRef = React.useRef(null);
|
|
29
|
+
const sentinelRef = React.useRef(null);
|
|
30
|
+
const contentRef = React.useRef(null);
|
|
31
|
+
if (isDevelopment()) {
|
|
32
|
+
if (offsetTop !== void 0) {
|
|
33
|
+
console.warn(
|
|
34
|
+
"[@godxjp/ui] Affix: `offsetTop` is Ant Design's name for this axis; here it is `offsetBlockStart` \u2014 the logical axis, per check:rtl and docs/DESIGN-AUTHORITY.md. The value was IGNORED."
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
if (offsetBottom !== void 0) {
|
|
38
|
+
console.warn(
|
|
39
|
+
"[@godxjp/ui] Affix: `offsetBottom` is Ant Design's name for this axis; here it is `offsetBlockEnd` \u2014 the logical axis, per check:rtl and docs/DESIGN-AUTHORITY.md. The value was IGNORED."
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const pinToEnd = offsetBlockStart === void 0 && offsetBlockEnd !== void 0;
|
|
44
|
+
const [targetElement, setTargetElement] = React.useState(null);
|
|
45
|
+
React.useEffect(() => {
|
|
46
|
+
setTargetElement(resolveTarget(target));
|
|
47
|
+
}, [target]);
|
|
48
|
+
const inView = useInView(sentinelRef, {
|
|
49
|
+
root: targetElement,
|
|
50
|
+
rootMargin: pinToEnd ? ROOT_MARGIN_BLOCK_END : ROOT_MARGIN_BLOCK_START,
|
|
51
|
+
assumeInView: true
|
|
52
|
+
});
|
|
53
|
+
const affixed = !inView;
|
|
54
|
+
const [box, setBox] = React.useState(null);
|
|
55
|
+
const [targetInset, setTargetInset] = React.useState(0);
|
|
56
|
+
const measure = React.useCallback(() => {
|
|
57
|
+
const root = rootRef.current;
|
|
58
|
+
const content = contentRef.current;
|
|
59
|
+
if (!root || !content) return;
|
|
60
|
+
const rootRect = root.getBoundingClientRect();
|
|
61
|
+
const contentRect = content.getBoundingClientRect();
|
|
62
|
+
setBox(
|
|
63
|
+
(previous) => previous && previous.inline === rootRect.width && previous.block === contentRect.height ? previous : { inline: rootRect.width, block: contentRect.height }
|
|
64
|
+
);
|
|
65
|
+
const rect = targetElement?.getBoundingClientRect();
|
|
66
|
+
const next = rect ? pinToEnd ? window.innerHeight - rect.bottom : rect.top : 0;
|
|
67
|
+
setTargetInset((previous) => previous === next ? previous : next);
|
|
68
|
+
}, [pinToEnd, targetElement]);
|
|
69
|
+
React.useLayoutEffect(() => {
|
|
70
|
+
measure();
|
|
71
|
+
}, [measure, affixed, children]);
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
const root = rootRef.current;
|
|
74
|
+
const content = contentRef.current;
|
|
75
|
+
if (!root || typeof ResizeObserver === "undefined") return void 0;
|
|
76
|
+
const observer = new ResizeObserver(() => {
|
|
77
|
+
measure();
|
|
78
|
+
});
|
|
79
|
+
observer.observe(root);
|
|
80
|
+
if (content) observer.observe(content);
|
|
81
|
+
if (targetElement) observer.observe(targetElement);
|
|
82
|
+
return () => {
|
|
83
|
+
observer.disconnect();
|
|
84
|
+
};
|
|
85
|
+
}, [measure, targetElement]);
|
|
86
|
+
React.useEffect(() => {
|
|
87
|
+
if (!affixed || !targetElement || typeof window === "undefined") return void 0;
|
|
88
|
+
let frame = null;
|
|
89
|
+
const onScroll = () => {
|
|
90
|
+
if (frame !== null) return;
|
|
91
|
+
frame = window.requestAnimationFrame(() => {
|
|
92
|
+
frame = null;
|
|
93
|
+
measure();
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
window.addEventListener("scroll", onScroll, true);
|
|
97
|
+
window.addEventListener("resize", onScroll);
|
|
98
|
+
return () => {
|
|
99
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
100
|
+
window.removeEventListener("resize", onScroll);
|
|
101
|
+
if (frame !== null) window.cancelAnimationFrame(frame);
|
|
102
|
+
};
|
|
103
|
+
}, [affixed, targetElement, measure]);
|
|
104
|
+
React.useEffect(() => {
|
|
105
|
+
const content = contentRef.current;
|
|
106
|
+
if (pinToEnd || !affixed || !content || typeof document === "undefined") return void 0;
|
|
107
|
+
const scrollBox = targetElement ?? document.documentElement;
|
|
108
|
+
const boxTop = targetElement ? targetElement.getBoundingClientRect().top : 0;
|
|
109
|
+
const padding = content.getBoundingClientRect().bottom - boxTop;
|
|
110
|
+
if (padding <= 0) return void 0;
|
|
111
|
+
const previous = scrollBox.style.scrollPaddingBlockStart;
|
|
112
|
+
scrollBox.style.scrollPaddingBlockStart = `${padding}px`;
|
|
113
|
+
return () => {
|
|
114
|
+
scrollBox.style.scrollPaddingBlockStart = previous;
|
|
115
|
+
};
|
|
116
|
+
}, [affixed, pinToEnd, targetInset, box?.block, targetElement]);
|
|
117
|
+
const onChangeRef = React.useRef(onChange);
|
|
118
|
+
React.useEffect(() => {
|
|
119
|
+
onChangeRef.current = onChange;
|
|
120
|
+
});
|
|
121
|
+
const mountedRef = React.useRef(false);
|
|
122
|
+
React.useEffect(() => {
|
|
123
|
+
if (!mountedRef.current) {
|
|
124
|
+
mountedRef.current = true;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
onChangeRef.current?.(affixed);
|
|
128
|
+
}, [affixed]);
|
|
129
|
+
const style = {
|
|
130
|
+
...offsetBlockStart === void 0 ? void 0 : { "--affix-inset-block-start": `${offsetBlockStart}px` },
|
|
131
|
+
...offsetBlockEnd === void 0 ? void 0 : { "--affix-inset-block-end": `${offsetBlockEnd}px` },
|
|
132
|
+
...targetInset === 0 ? void 0 : { "--affix-target-inset": `${targetInset}px` },
|
|
133
|
+
...affixed && box ? { "--affix-content-inline-size": `${box.inline}px` } : void 0
|
|
134
|
+
};
|
|
135
|
+
return /* @__PURE__ */ jsxs(
|
|
136
|
+
"div",
|
|
137
|
+
{
|
|
138
|
+
ref: (node) => {
|
|
139
|
+
rootRef.current = node;
|
|
140
|
+
if (typeof ref === "function") ref(node);
|
|
141
|
+
else if (ref) ref.current = node;
|
|
142
|
+
},
|
|
143
|
+
id,
|
|
144
|
+
"data-slot": "affix",
|
|
145
|
+
"data-affixed": affixed ? "" : void 0,
|
|
146
|
+
className: cn("ui-affix", className),
|
|
147
|
+
style,
|
|
148
|
+
...rest,
|
|
149
|
+
children: [
|
|
150
|
+
/* @__PURE__ */ jsx(
|
|
151
|
+
"div",
|
|
152
|
+
{
|
|
153
|
+
ref: sentinelRef,
|
|
154
|
+
"data-slot": "affix-sentinel",
|
|
155
|
+
"data-edge": pinToEnd ? "block-end" : "block-start",
|
|
156
|
+
"aria-hidden": "true",
|
|
157
|
+
className: "ui-affix-sentinel"
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
affixed && box ? /* @__PURE__ */ jsx(
|
|
161
|
+
"div",
|
|
162
|
+
{
|
|
163
|
+
"data-slot": "affix-placeholder",
|
|
164
|
+
"aria-hidden": "true",
|
|
165
|
+
className: "ui-affix-placeholder",
|
|
166
|
+
style: { blockSize: box.block }
|
|
167
|
+
}
|
|
168
|
+
) : null,
|
|
169
|
+
/* @__PURE__ */ jsx(
|
|
170
|
+
"div",
|
|
171
|
+
{
|
|
172
|
+
ref: contentRef,
|
|
173
|
+
"data-slot": "affix-content",
|
|
174
|
+
"data-edge": pinToEnd ? "block-end" : "block-start",
|
|
175
|
+
"data-affixed": affixed ? "" : void 0,
|
|
176
|
+
className: "ui-affix-content",
|
|
177
|
+
children
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
Affix.displayName = "Affix";
|
|
185
|
+
export {
|
|
186
|
+
Affix
|
|
187
|
+
};
|
|
@@ -59,3 +59,7 @@ export type { LegalDocumentSectionProp, LegalDocumentShellProp, LegalDocumentShe
|
|
|
59
59
|
export { ServiceRolePanel } from "./service-role-panel.js";
|
|
60
60
|
export type { ServiceRoleItemProp, ServiceRolePanelProp, ServiceRolePanelProps, } from "./service-role-panel.js";
|
|
61
61
|
export { useAppShellNavigationMode } from "./app-shell.js";
|
|
62
|
+
export { Masonry } from "./masonry.js";
|
|
63
|
+
export type { MasonryColumnsProp, MasonryGapProp, MasonryItemProp, MasonryLayoutEntryProp, MasonryProp, MasonryProps, } from "./masonry.js";
|
|
64
|
+
export { Affix } from "./affix.js";
|
|
65
|
+
export type { AffixProp, AffixProps, AffixTargetProp } from "./affix.js";
|
|
@@ -28,8 +28,11 @@ import { AspectRatio } from "./aspect-ratio.js";
|
|
|
28
28
|
import { LegalDocumentShell } from "./legal-document-shell.js";
|
|
29
29
|
import { ServiceRolePanel } from "./service-role-panel.js";
|
|
30
30
|
import { useAppShellNavigationMode } from "./app-shell.js";
|
|
31
|
+
import { Masonry } from "./masonry.js";
|
|
32
|
+
import { Affix } from "./affix.js";
|
|
31
33
|
export {
|
|
32
34
|
AccountChip,
|
|
35
|
+
Affix,
|
|
33
36
|
AppLauncher,
|
|
34
37
|
AppShell,
|
|
35
38
|
AspectRatio,
|
|
@@ -45,6 +48,7 @@ export {
|
|
|
45
48
|
ErrorSurface,
|
|
46
49
|
Flex,
|
|
47
50
|
LegalDocumentShell,
|
|
51
|
+
Masonry,
|
|
48
52
|
MasterDetail,
|
|
49
53
|
MobileShell,
|
|
50
54
|
NavList,
|
|
@@ -6,6 +6,7 @@ import { useControlledLatch } from "../../lib/hooks.js";
|
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
7
|
import { Heading } from "../general/typography.js";
|
|
8
8
|
const SPY_ROOT_MARGIN = "-10% 0px -75% 0px";
|
|
9
|
+
const SECTION_ID_SEPARATOR = "\0";
|
|
9
10
|
const ISO_CALENDAR_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
10
11
|
function prefersReducedMotion() {
|
|
11
12
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
@@ -75,18 +76,18 @@ const LegalDocumentShell = React.forwardRef(
|
|
|
75
76
|
activeRef.current = active;
|
|
76
77
|
emitRef.current = emit;
|
|
77
78
|
});
|
|
78
|
-
const sectionIdsKey = sections.map((section) => section.id).join(
|
|
79
|
+
const sectionIdsKey = sections.map((section) => section.id).join(SECTION_ID_SEPARATOR);
|
|
79
80
|
const hashHandledRef = React.useRef(false);
|
|
80
81
|
React.useEffect(() => {
|
|
81
82
|
if (hashHandledRef.current || typeof window === "undefined") return;
|
|
82
83
|
hashHandledRef.current = true;
|
|
83
84
|
const hash = decodeURIComponent(window.location.hash.replace(/^#/, ""));
|
|
84
|
-
if (hash && sectionIdsKey.split(
|
|
85
|
+
if (hash && sectionIdsKey.split(SECTION_ID_SEPARATOR).includes(hash)) emitRef.current(hash);
|
|
85
86
|
}, [sectionIdsKey]);
|
|
86
87
|
React.useEffect(() => {
|
|
87
88
|
const root = rootRef.current;
|
|
88
89
|
if (!root || typeof IntersectionObserver === "undefined") return;
|
|
89
|
-
const ids = sectionIdsKey ? sectionIdsKey.split(
|
|
90
|
+
const ids = sectionIdsKey ? sectionIdsKey.split(SECTION_ID_SEPARATOR) : [];
|
|
90
91
|
const intersecting = /* @__PURE__ */ new Map();
|
|
91
92
|
const observer = new IntersectionObserver(
|
|
92
93
|
(entries) => {
|