fancy-ui-react 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/README.md +52 -19
- package/dist/components/animated-beam/AnimatedBeam.d.ts +47 -11
- package/dist/components/animated-beam/AnimatedBeam.js +293 -118
- package/dist/components/animated-tooltip/AnimatedTooltip.d.ts +10 -1
- package/dist/components/animated-tooltip/AnimatedTooltip.js +170 -101
- package/dist/components/bento-grid/BentoFrame.d.ts +16 -0
- package/dist/components/bento-grid/BentoFrame.js +28 -0
- package/dist/components/bento-grid/BentoGrid.d.ts +5 -1
- package/dist/components/bento-grid/BentoGrid.js +71 -2
- package/dist/components/bento-grid/BentoGridCard.js +34 -26
- package/dist/components/bento-grid/BentoGridItem.js +10 -10
- package/dist/components/border-beam/BorderBeam.d.ts +55 -10
- package/dist/components/border-beam/BorderBeam.js +93 -16
- package/dist/components/compare/Compare.d.ts +32 -8
- package/dist/components/compare/Compare.js +213 -95
- package/dist/components/container-scroll/ContainerScroll.d.ts +26 -4
- package/dist/components/container-scroll/ContainerScroll.js +88 -49
- package/dist/components/dock/Dock.d.ts +10 -7
- package/dist/components/dock/Dock.js +76 -31
- package/dist/components/dock/DockIcon.d.ts +14 -3
- package/dist/components/dock/DockIcon.js +23 -19
- package/dist/components/dock/DockSeparator.d.ts +4 -1
- package/dist/components/dock/DockSeparator.js +5 -2
- package/dist/components/dock/types.d.ts +13 -6
- package/dist/components/dropdown-menu/menu-shared.d.ts +11 -8
- package/dist/components/editorial-engine/engine.js +1 -14
- package/dist/components/fireworks-hdr/webgl2-renderer.js +0 -19
- package/dist/components/fireworks-hdr/webgpu-renderer.js +3 -22
- package/dist/components/flip-card/FlipCard.d.ts +42 -1
- package/dist/components/flip-card/FlipCard.js +178 -25
- package/dist/components/flip-card/index.d.ts +1 -1
- package/dist/components/fluid-cursor/FluidCursor.js +5 -4
- package/dist/components/fluid-cursor/dev.d.ts +1 -0
- package/dist/components/fluid-cursor/dev.js +8 -0
- package/dist/components/fluid-cursor/fluid-shared.d.ts +0 -2
- package/dist/components/fluid-cursor/fluid-shared.js +0 -2
- package/dist/components/fluid-cursor/webgpu-engine.js +10 -24
- package/dist/components/glow-border/GlowBorder.d.ts +41 -1
- package/dist/components/glow-border/GlowBorder.js +96 -16
- package/dist/components/glow-border/index.d.ts +2 -2
- package/dist/components/gradient-button/GradientButton.d.ts +10 -5
- package/dist/components/gradient-button/GradientButton.js +25 -18
- package/dist/components/image-trail-cursor/ImageTrailCursor.js +1 -0
- package/dist/components/image-trail-cursor/trail-variants.d.ts +37 -1
- package/dist/components/image-trail-cursor/trail-variants.js +331 -8
- package/dist/components/interactive-hover-button/InteractiveHoverButton.js +30 -35
- package/dist/components/marquee/Marquee.d.ts +7 -3
- package/dist/components/marquee/Marquee.js +134 -2
- package/dist/components/marquee/ReviewCard.d.ts +2 -1
- package/dist/components/marquee/ReviewCard.js +20 -9
- package/dist/components/meteors/Meteors.d.ts +32 -10
- package/dist/components/meteors/Meteors.js +53 -20
- package/dist/components/mosaic-glow/mosaic-glow-core.d.ts +98 -10
- package/dist/components/neon-border/NeonBorder.d.ts +24 -5
- package/dist/components/neon-border/NeonBorder.js +37 -7
- package/dist/components/pagination/Pagination.js +220 -82
- package/dist/components/rainbow-button/RainbowButton.js +13 -5
- package/dist/components/ripple-button/RippleButton.d.ts +20 -1
- package/dist/components/ripple-button/RippleButton.js +44 -21
- package/dist/components/shimmer-button/ShimmerButton.d.ts +5 -5
- package/dist/components/shimmer-button/ShimmerButton.js +17 -19
- package/dist/components/smooth-cursor/SmoothCursor.d.ts +13 -9
- package/dist/components/smooth-cursor/SmoothCursor.js +114 -163
- package/dist/components/smooth-cursor/smooth-cursor-core.d.ts +85 -0
- package/dist/components/smooth-cursor/smooth-cursor-core.js +327 -0
- package/dist/components/stepper/Step.js +32 -19
- package/dist/components/stepper/Stepper.js +18 -2
- package/dist/components/stepper/types.d.ts +13 -0
- package/dist/components/timeline/Timeline.d.ts +8 -1
- package/dist/components/timeline/Timeline.js +165 -76
- package/dist/index.js +2 -1
- package/dist/internals/markdown.d.ts +5 -0
- package/dist/internals/motion/haptics.d.ts +0 -4
- package/dist/internals/motion/presence-core.d.ts +59 -0
- package/dist/internals/motion/presence-core.js +162 -0
- package/dist/internals/motion/presence.d.ts +2 -4
- package/dist/internals/motion/presence.js +1 -156
- package/dist/internals/motion/raf.d.ts +1 -1
- package/dist/internals/motion/stagger.d.ts +3 -4
- package/dist/internals/motion/tokens.js +4 -0
- package/dist/internals/motion/transitions.d.ts +17 -17
- package/dist/internals/motion/types.d.ts +6 -7
- package/dist/internals/relative-time.d.ts +1 -1
- package/dist/styles.css +3470 -203
- package/package.json +18 -2
- package/dist/components/fireworks-hdr/webgpu-types.d.ts +0 -159
|
@@ -1,95 +1,184 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useRef, useState } from "react";
|
|
2
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useState, useCallback, memo } from "react";
|
|
4
4
|
import { cn } from "../../utils.js";
|
|
5
5
|
import { useIsomorphicLayoutEffect } from "../../internals/dom/ssr.js";
|
|
6
6
|
import { rafThrottle } from "../../internals/motion/raf.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
import { useReducedMotion } from "../../internals/motion/media-query.js";
|
|
8
|
+
/* empty css */
|
|
9
|
+
const DOT_HALF = 20;
|
|
10
|
+
const END_PAD = 56;
|
|
11
|
+
const STICKY_FALLBACK = 160;
|
|
12
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
13
|
+
const TimelineContent = memo(function TimelineContent2({
|
|
14
|
+
content,
|
|
15
|
+
item
|
|
16
|
+
}) {
|
|
17
|
+
return /* @__PURE__ */ jsx(Fragment, { children: content ? content(item) : null });
|
|
18
|
+
});
|
|
19
|
+
function Timeline({
|
|
20
|
+
items = [],
|
|
21
|
+
title,
|
|
22
|
+
description,
|
|
23
|
+
className,
|
|
24
|
+
content,
|
|
25
|
+
accent
|
|
26
|
+
}) {
|
|
27
|
+
const reduced = useReducedMotion();
|
|
28
|
+
const rootRef = useRef(null);
|
|
13
29
|
const timelineRef = useRef(null);
|
|
14
|
-
const
|
|
30
|
+
const rowEls = useRef([]);
|
|
31
|
+
const dotOffsets = useRef([]);
|
|
32
|
+
const readingLine = useRef(STICKY_FALLBACK + DOT_HALF);
|
|
33
|
+
const head = useRef(0);
|
|
15
34
|
const heightRef = useRef(0);
|
|
16
|
-
const progressValueRef = useRef(0);
|
|
17
35
|
const [timelineHeight, setTimelineHeight] = useState(0);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
progressValueRef.current = 0;
|
|
35
|
-
} else {
|
|
36
|
-
const progress = -start / total;
|
|
37
|
-
progressValueRef.current = Math.max(0, Math.min(1, progress));
|
|
38
|
-
}
|
|
39
|
-
writeProgressLine(progressRef.current, progressValueRef.current, heightRef.current);
|
|
36
|
+
const [ready, setReady] = useState(false);
|
|
37
|
+
const [activeIndex, setActiveIndex] = useState(-1);
|
|
38
|
+
const place = useCallback(() => {
|
|
39
|
+
const track = timelineRef.current;
|
|
40
|
+
if (!track) return;
|
|
41
|
+
const rect = track.getBoundingClientRect();
|
|
42
|
+
const offsets = dotOffsets.current;
|
|
43
|
+
const start = offsets[0] ?? 0;
|
|
44
|
+
const end = Math.max(start, heightRef.current - END_PAD);
|
|
45
|
+
const raw = readingLine.current - rect.top;
|
|
46
|
+
const next = Math.min(end, Math.max(start, raw));
|
|
47
|
+
head.current = next;
|
|
48
|
+
rootRef.current?.style.setProperty("--tl-progress", `${next}px`);
|
|
49
|
+
let idx = -1;
|
|
50
|
+
for (let i = 0; i < offsets.length; i++) {
|
|
51
|
+
if (offsets[i] <= next + 0.5) idx = i;
|
|
40
52
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
setActiveIndex(idx);
|
|
54
|
+
setReady(true);
|
|
55
|
+
}, []);
|
|
56
|
+
const measure = useCallback(() => {
|
|
57
|
+
const track = timelineRef.current;
|
|
58
|
+
if (!track) return;
|
|
59
|
+
heightRef.current = track.offsetHeight;
|
|
60
|
+
setTimelineHeight(heightRef.current);
|
|
61
|
+
const next = [];
|
|
62
|
+
let firstDotCentre = DOT_HALF;
|
|
63
|
+
for (const row of rowEls.current) {
|
|
64
|
+
if (!row) continue;
|
|
65
|
+
const padTop = parseFloat(getComputedStyle(row).paddingTop) || 0;
|
|
66
|
+
const box = row.querySelector("[data-timeline-dot-box]");
|
|
67
|
+
const centre = box && box.offsetHeight ? box.offsetTop + box.offsetHeight / 2 : DOT_HALF;
|
|
68
|
+
if (next.length === 0) firstDotCentre = centre;
|
|
69
|
+
next.push(row.offsetTop + padTop + centre);
|
|
53
70
|
}
|
|
54
|
-
|
|
71
|
+
dotOffsets.current = next;
|
|
72
|
+
const label = track.querySelector("[data-timeline-label]");
|
|
73
|
+
const top = label ? parseFloat(getComputedStyle(label).top) : NaN;
|
|
74
|
+
readingLine.current = (Number.isFinite(top) ? top : STICKY_FALLBACK) + firstDotCentre;
|
|
75
|
+
place();
|
|
76
|
+
}, [place]);
|
|
77
|
+
useIsomorphicLayoutEffect(() => {
|
|
78
|
+
const onScroll = rafThrottle(place);
|
|
79
|
+
const onResize = rafThrottle(measure);
|
|
80
|
+
const resizeObserver = new ResizeObserver(() => onResize());
|
|
81
|
+
if (timelineRef.current) resizeObserver.observe(timelineRef.current);
|
|
55
82
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
56
|
-
|
|
83
|
+
window.addEventListener("resize", onResize, { passive: true });
|
|
84
|
+
measure();
|
|
57
85
|
return () => {
|
|
58
86
|
onScroll.cancel();
|
|
87
|
+
onResize.cancel();
|
|
59
88
|
resizeObserver.disconnect();
|
|
60
89
|
window.removeEventListener("scroll", onScroll);
|
|
90
|
+
window.removeEventListener("resize", onResize);
|
|
61
91
|
};
|
|
62
|
-
}, []);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
92
|
+
}, [place, measure]);
|
|
93
|
+
const itemCount = items.length;
|
|
94
|
+
const measuredCount = useRef(itemCount);
|
|
95
|
+
useIsomorphicLayoutEffect(() => {
|
|
96
|
+
if (measuredCount.current === itemCount) return;
|
|
97
|
+
measuredCount.current = itemCount;
|
|
98
|
+
measure();
|
|
99
|
+
}, [itemCount, measure]);
|
|
100
|
+
const rowState = (index) => {
|
|
101
|
+
if (index === activeIndex) return "active";
|
|
102
|
+
return index < activeIndex ? "past" : "upcoming";
|
|
103
|
+
};
|
|
104
|
+
const rootStyle = {
|
|
105
|
+
"--timeline-accent": accent,
|
|
106
|
+
"--tl-progress": `${head.current}px`
|
|
107
|
+
};
|
|
108
|
+
return /* @__PURE__ */ jsxs(
|
|
109
|
+
"div",
|
|
110
|
+
{
|
|
111
|
+
ref: rootRef,
|
|
112
|
+
className: cn("tl-root w-full font-sans md:px-10", className),
|
|
113
|
+
style: rootStyle,
|
|
114
|
+
"data-motion": reduced ? "reduced" : "full",
|
|
115
|
+
children: [
|
|
116
|
+
(title || description) && /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-7xl px-4 py-12 md:px-8 md:py-16 lg:px-10", children: [
|
|
117
|
+
title && /* @__PURE__ */ jsx("h2", { className: "text-foreground mb-3 max-w-4xl text-lg font-medium tracking-tight text-balance md:text-4xl", children: title }),
|
|
118
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground max-w-sm text-sm md:text-base", children: description })
|
|
73
119
|
] }),
|
|
74
|
-
/* @__PURE__ */
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
120
|
+
/* @__PURE__ */ jsxs(
|
|
121
|
+
"div",
|
|
122
|
+
{
|
|
123
|
+
ref: timelineRef,
|
|
124
|
+
className: "relative z-0 mx-auto max-w-7xl pb-20",
|
|
125
|
+
"data-timeline-track": "",
|
|
126
|
+
children: [
|
|
127
|
+
items.map((item, index) => /* @__PURE__ */ jsxs(
|
|
128
|
+
"div",
|
|
129
|
+
{
|
|
130
|
+
ref: (el) => {
|
|
131
|
+
rowEls.current[index] = el;
|
|
132
|
+
},
|
|
133
|
+
className: "tl-row flex justify-start pt-10 md:gap-10 md:pt-32",
|
|
134
|
+
"data-timeline-row": "",
|
|
135
|
+
"data-state": rowState(index),
|
|
136
|
+
children: [
|
|
137
|
+
/* @__PURE__ */ jsxs(
|
|
138
|
+
"div",
|
|
139
|
+
{
|
|
140
|
+
className: "tl-label sticky top-40 z-40 flex max-w-xs flex-col items-center self-start md:w-full md:flex-row lg:max-w-sm",
|
|
141
|
+
"data-timeline-label": "",
|
|
142
|
+
children: [
|
|
143
|
+
/* @__PURE__ */ jsx(
|
|
144
|
+
"div",
|
|
145
|
+
{
|
|
146
|
+
className: "absolute left-3 flex size-10 items-center justify-center md:left-3",
|
|
147
|
+
"data-timeline-dot-box": "",
|
|
148
|
+
"aria-hidden": "true",
|
|
149
|
+
children: /* @__PURE__ */ jsxs("span", { className: "tl-dot", "data-timeline-dot": "", "data-lit": index <= activeIndex, children: [
|
|
150
|
+
/* @__PURE__ */ jsx("span", { className: "tl-dot-flare" }),
|
|
151
|
+
/* @__PURE__ */ jsx("span", { className: "tl-dot-core" })
|
|
152
|
+
] })
|
|
153
|
+
}
|
|
154
|
+
),
|
|
155
|
+
/* @__PURE__ */ jsxs("div", { className: "tl-label-text relative hidden md:block md:pl-20", children: [
|
|
156
|
+
/* @__PURE__ */ jsx("span", { className: "tl-index", "aria-hidden": "true", children: pad(index + 1) }),
|
|
157
|
+
/* @__PURE__ */ jsx("h3", { className: "tl-heading text-xl font-semibold tracking-tight md:text-4xl", children: item.label })
|
|
158
|
+
] })
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
),
|
|
162
|
+
/* @__PURE__ */ jsx("div", { className: "tl-content w-full pr-4 pl-20 md:pl-4", children: /* @__PURE__ */ jsx(TimelineContent, { content, item }) })
|
|
163
|
+
]
|
|
164
|
+
},
|
|
165
|
+
`${item.id}-${index}`
|
|
166
|
+
)),
|
|
167
|
+
/* @__PURE__ */ jsxs("div", { className: "tl-rail", style: { height: `${timelineHeight}px` }, "aria-hidden": "true", children: [
|
|
168
|
+
/* @__PURE__ */ jsx("div", { className: "tl-rail-line" }),
|
|
169
|
+
/* @__PURE__ */ jsx("div", { className: "tl-trail" })
|
|
170
|
+
] }),
|
|
171
|
+
!reduced && items.length > 0 && /* @__PURE__ */ jsxs("div", { className: "tl-head", "data-timeline-head": "", "data-ready": ready, "aria-hidden": "true", children: [
|
|
172
|
+
/* @__PURE__ */ jsx("span", { className: "tl-head-streak" }),
|
|
173
|
+
/* @__PURE__ */ jsx("span", { className: "tl-head-halo" }),
|
|
174
|
+
/* @__PURE__ */ jsx("span", { className: "tl-head-core" })
|
|
175
|
+
] })
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
);
|
|
93
182
|
}
|
|
94
183
|
export {
|
|
95
184
|
Timeline
|
package/dist/index.js
CHANGED
|
@@ -105,7 +105,7 @@ import { Focus } from "./components/focus/Focus.js";
|
|
|
105
105
|
import { FormField } from "./components/form-field/FormField.js";
|
|
106
106
|
import { FrostedGlass } from "./components/frosted-glass/FrostedGlass.js";
|
|
107
107
|
import { GlareCard } from "./components/glare-card/GlareCard.js";
|
|
108
|
-
import { GlowBorder } from "./components/glow-border/GlowBorder.js";
|
|
108
|
+
import { GLOW_BORDER_PRESETS, GlowBorder } from "./components/glow-border/GlowBorder.js";
|
|
109
109
|
import { GlowingEffect } from "./components/glowing-effect/GlowingEffect.js";
|
|
110
110
|
import { GradientButton } from "./components/gradient-button/GradientButton.js";
|
|
111
111
|
import { HoverCard } from "./components/hover-card/HoverCard.js";
|
|
@@ -340,6 +340,7 @@ export {
|
|
|
340
340
|
Focus,
|
|
341
341
|
FormField,
|
|
342
342
|
FrostedGlass,
|
|
343
|
+
GLOW_BORDER_PRESETS,
|
|
343
344
|
GlareCard,
|
|
344
345
|
GlowBorder,
|
|
345
346
|
GlowingEffect,
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Every entry point is total: it returns a value for any input, including
|
|
14
14
|
* half-written markdown from a stream.
|
|
15
|
+
*
|
|
16
|
+
* The non-null assertions below are all of one kind: an index read inside a
|
|
17
|
+
* loop already bounded by `i < lines.length` / `i < src.length`, or a capture
|
|
18
|
+
* group of a regex whose match was checked truthy on the line above. Every
|
|
19
|
+
* invariant is visible next to the assertion that relies on it.
|
|
15
20
|
*/
|
|
16
21
|
export type InlineToken = {
|
|
17
22
|
type: "text";
|
|
@@ -6,10 +6,6 @@
|
|
|
6
6
|
* exists (Safari on iOS has never implemented it, for instance), and a
|
|
7
7
|
* missing rumble is a non-event a user never notices — a thrown error
|
|
8
8
|
* breaking the surrounding click handler very much would be.
|
|
9
|
-
*
|
|
10
|
-
* No hook: both functions are called from a click handler, never from a
|
|
11
|
-
* render path. `canVibrate()` reads `navigator` and is therefore
|
|
12
|
-
* server-safe only because it guards on `typeof navigator`.
|
|
13
9
|
*/
|
|
14
10
|
export declare const HAPTIC_PATTERNS: {
|
|
15
11
|
readonly light: 10;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared core: the framework-free state machine behind `usePresence`
|
|
3
|
+
* (`./presence.ts`), split into its own module so a Vue package can drive
|
|
4
|
+
* the identical mount/unmount clock. Tracked in `shared-cores.json` at the
|
|
5
|
+
* repo root as a byte-identical file across every framework package this
|
|
6
|
+
* library ships — see that manifest and `scripts/check-shared-cores.mjs`
|
|
7
|
+
* for the identity/purity gate this file must keep passing.
|
|
8
|
+
*
|
|
9
|
+
* Framework-free means exactly that: no `import ... from "react"` (or
|
|
10
|
+
* "svelte"/"vue"), no `import.meta.env`, no `window`/`document`/`navigator`
|
|
11
|
+
* read at module scope. DOM types (`HTMLElement`, `Element`) are fine — they
|
|
12
|
+
* come from `lib.dom.d.ts`, not from a framework — and so is calling into a
|
|
13
|
+
* DOM API from inside a function, which only ever happens once a real node
|
|
14
|
+
* is handed in through `register`.
|
|
15
|
+
*
|
|
16
|
+
* `presence.ts` carries the design rationale for the clock itself (why one
|
|
17
|
+
* hook owns both directions, why grouping matters, the SSR story); this file
|
|
18
|
+
* is the mechanism, with the framework-specific ref type swapped for
|
|
19
|
+
* `PresenceRefCallback` so the same bytes work in every package.
|
|
20
|
+
*/
|
|
21
|
+
import type { TransitionFn } from "./transitions.js";
|
|
22
|
+
/** The three values `Presence` renders into `data-state`, because its source
|
|
23
|
+
* renders three. An anchored surface renders `surfaceState`'s two instead —
|
|
24
|
+
* the two vocabularies are not interchangeable (convention C-5). */
|
|
25
|
+
export type PresenceState = "opening" | "open" | "closing";
|
|
26
|
+
/**
|
|
27
|
+
* A framework-free callback ref: called with the live node on attach, `null`
|
|
28
|
+
* on detach, returning nothing. This is the shape every framework's own ref
|
|
29
|
+
* callback type is compatible with — React's `RefCallback<HTMLElement>`
|
|
30
|
+
* resolves to `(instance: HTMLElement | null) => void | (() => void)`, and a
|
|
31
|
+
* function that always returns `void` satisfies that trivially — so each
|
|
32
|
+
* package's wrapper can hand a slot's `ref` straight through with no cast.
|
|
33
|
+
*/
|
|
34
|
+
export type PresenceRefCallback = (node: HTMLElement | null) => void;
|
|
35
|
+
/** Internal spelling of `register`'s second/third argument: a params value, or a
|
|
36
|
+
* factory called with the direction at the instant the leg starts. */
|
|
37
|
+
export type ParamsOrFactory<P> = P | ((entering: boolean) => P);
|
|
38
|
+
export interface PresenceCoreDeps {
|
|
39
|
+
setMounted: (mounted: boolean) => void;
|
|
40
|
+
setState: (state: PresenceState) => void;
|
|
41
|
+
onEnterStart: () => void;
|
|
42
|
+
onEnterEnd: () => void;
|
|
43
|
+
onExitStart: () => void;
|
|
44
|
+
onExitEnd: () => void;
|
|
45
|
+
/** Read live at the moment an exit starts, never captured per render. */
|
|
46
|
+
inertRef: {
|
|
47
|
+
readonly current: boolean;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The framework-free state machine behind `usePresence`. Allocation-only, so
|
|
52
|
+
* it is safe to build eagerly on first use: it installs no listener and
|
|
53
|
+
* starts no timer until `sync()` is called from a layout effect.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createPresenceCore(deps: PresenceCoreDeps): {
|
|
56
|
+
register: <P>(a: string | TransitionFn<P>, b?: TransitionFn<P> | ParamsOrFactory<P>, c?: ParamsOrFactory<P>) => PresenceRefCallback;
|
|
57
|
+
sync: (open: boolean, mounted: boolean, appear: boolean) => void;
|
|
58
|
+
teardown: () => void;
|
|
59
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { runTransition } from "./animate.js";
|
|
3
|
+
const DEFAULT_KEY = "default";
|
|
4
|
+
function createPresenceCore(deps) {
|
|
5
|
+
const slots = /* @__PURE__ */ new Map();
|
|
6
|
+
let leg = null;
|
|
7
|
+
let starting = false;
|
|
8
|
+
let inertApplied = false;
|
|
9
|
+
let firstRun = true;
|
|
10
|
+
function ensureSlot(key) {
|
|
11
|
+
const existing = slots.get(key);
|
|
12
|
+
if (existing) return existing;
|
|
13
|
+
const slot = {
|
|
14
|
+
node: null,
|
|
15
|
+
buildSpec: null,
|
|
16
|
+
run: void 0,
|
|
17
|
+
legTo: null,
|
|
18
|
+
finished: false,
|
|
19
|
+
pendingLeg: null,
|
|
20
|
+
// Block body, never a concise arrow: a returned value would be read
|
|
21
|
+
// as a cleanup function by a framework's ref-callback contract
|
|
22
|
+
// (React 19's included — convention C-3), and this ref never means to
|
|
23
|
+
// return one.
|
|
24
|
+
ref: (node) => {
|
|
25
|
+
if (node) {
|
|
26
|
+
slot.node = node;
|
|
27
|
+
const pending = slot.pendingLeg;
|
|
28
|
+
slot.pendingLeg = null;
|
|
29
|
+
if (pending !== null && leg === pending) startSlot(slot, pending);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
slot.pendingLeg = leg !== null && !slot.finished ? leg : null;
|
|
33
|
+
slot.run?.abort();
|
|
34
|
+
slot.run = void 0;
|
|
35
|
+
slot.node = null;
|
|
36
|
+
slot.legTo = null;
|
|
37
|
+
slot.finished = false;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
slots.set(key, slot);
|
|
41
|
+
return slot;
|
|
42
|
+
}
|
|
43
|
+
function register(a, b, c) {
|
|
44
|
+
const keyed = typeof a === "string";
|
|
45
|
+
const slot = ensureSlot(keyed ? a : DEFAULT_KEY);
|
|
46
|
+
const transition = keyed ? b : a;
|
|
47
|
+
const params = keyed ? c : b;
|
|
48
|
+
slot.buildSpec = (node, entering) => {
|
|
49
|
+
const resolved = typeof params === "function" ? params(entering) : params;
|
|
50
|
+
return transition(node, resolved, { direction: entering ? "in" : "out" });
|
|
51
|
+
};
|
|
52
|
+
return slot.ref;
|
|
53
|
+
}
|
|
54
|
+
function startSlot(slot, to) {
|
|
55
|
+
const node = slot.node;
|
|
56
|
+
const buildSpec = slot.buildSpec;
|
|
57
|
+
if (!node || !buildSpec) return;
|
|
58
|
+
const counterpart = slot.run;
|
|
59
|
+
slot.legTo = to;
|
|
60
|
+
slot.finished = false;
|
|
61
|
+
slot.run = void 0;
|
|
62
|
+
const handle = { current: void 0 };
|
|
63
|
+
const run = runTransition(node, buildSpec(node, to === 1), to, counterpart, () => {
|
|
64
|
+
slot.finished = true;
|
|
65
|
+
if (to === 1) {
|
|
66
|
+
handle.current?.abort();
|
|
67
|
+
slot.run = void 0;
|
|
68
|
+
}
|
|
69
|
+
settle(to);
|
|
70
|
+
});
|
|
71
|
+
handle.current = run;
|
|
72
|
+
if (!slot.finished) slot.run = run;
|
|
73
|
+
}
|
|
74
|
+
function startLegs(to) {
|
|
75
|
+
starting = true;
|
|
76
|
+
try {
|
|
77
|
+
for (const slot of slots.values()) {
|
|
78
|
+
if (!slot.node) continue;
|
|
79
|
+
if (slot.legTo === to) continue;
|
|
80
|
+
startSlot(slot, to);
|
|
81
|
+
}
|
|
82
|
+
} finally {
|
|
83
|
+
starting = false;
|
|
84
|
+
}
|
|
85
|
+
settle(to);
|
|
86
|
+
}
|
|
87
|
+
function settle(to) {
|
|
88
|
+
if (starting) return;
|
|
89
|
+
if (leg !== to) return;
|
|
90
|
+
for (const slot of slots.values()) {
|
|
91
|
+
if (!slot.node) continue;
|
|
92
|
+
if (slot.legTo !== to || !slot.finished) return;
|
|
93
|
+
}
|
|
94
|
+
leg = null;
|
|
95
|
+
if (to === 1) {
|
|
96
|
+
deps.setState("open");
|
|
97
|
+
deps.onEnterEnd();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
deps.setMounted(false);
|
|
101
|
+
deps.setState("open");
|
|
102
|
+
deps.onExitEnd();
|
|
103
|
+
}
|
|
104
|
+
function beginEnter() {
|
|
105
|
+
leg = 1;
|
|
106
|
+
if (inertApplied) {
|
|
107
|
+
inertApplied = false;
|
|
108
|
+
for (const slot of slots.values()) {
|
|
109
|
+
slot.node?.toggleAttribute("inert", false);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
deps.setState("opening");
|
|
113
|
+
deps.onEnterStart();
|
|
114
|
+
}
|
|
115
|
+
function beginExit() {
|
|
116
|
+
leg = 0;
|
|
117
|
+
deps.onExitStart();
|
|
118
|
+
deps.setState("closing");
|
|
119
|
+
if (deps.inertRef.current) {
|
|
120
|
+
inertApplied = true;
|
|
121
|
+
for (const slot of slots.values()) {
|
|
122
|
+
slot.node?.toggleAttribute("inert", true);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function sync(open, mounted, appear) {
|
|
127
|
+
const first = firstRun;
|
|
128
|
+
firstRun = false;
|
|
129
|
+
if (open) {
|
|
130
|
+
if (!mounted) {
|
|
131
|
+
beginEnter();
|
|
132
|
+
deps.setMounted(true);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (first && !appear) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (leg !== 1) beginEnter();
|
|
139
|
+
startLegs(1);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!mounted) return;
|
|
143
|
+
if (leg !== 0) beginExit();
|
|
144
|
+
startLegs(0);
|
|
145
|
+
}
|
|
146
|
+
function teardown() {
|
|
147
|
+
for (const slot of slots.values()) {
|
|
148
|
+
slot.run?.abort();
|
|
149
|
+
slot.run = void 0;
|
|
150
|
+
slot.legTo = null;
|
|
151
|
+
slot.finished = false;
|
|
152
|
+
slot.pendingLeg = null;
|
|
153
|
+
}
|
|
154
|
+
leg = null;
|
|
155
|
+
inertApplied = false;
|
|
156
|
+
firstRun = true;
|
|
157
|
+
}
|
|
158
|
+
return { register, sync, teardown };
|
|
159
|
+
}
|
|
160
|
+
export {
|
|
161
|
+
createPresenceCore
|
|
162
|
+
};
|
|
@@ -48,12 +48,10 @@
|
|
|
48
48
|
* `appear` defaults to false.
|
|
49
49
|
*/
|
|
50
50
|
import type { RefCallback } from "react";
|
|
51
|
+
import { type PresenceState } from "./presence-core.js";
|
|
51
52
|
import type { SurfaceState } from "./anchored.js";
|
|
52
53
|
import type { TransitionFn } from "./transitions.js";
|
|
53
|
-
|
|
54
|
-
* renders three. An anchored surface renders `surfaceState`'s two instead —
|
|
55
|
-
* the two vocabularies are not interchangeable (convention C-5). */
|
|
56
|
-
export type PresenceState = "opening" | "open" | "closing";
|
|
54
|
+
export type { PresenceState };
|
|
57
55
|
export interface UsePresenceOptions {
|
|
58
56
|
/**
|
|
59
57
|
* Animate an entrance when `open` is ALREADY true on the very first render.
|