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.
Files changed (86) hide show
  1. package/README.md +52 -19
  2. package/dist/components/animated-beam/AnimatedBeam.d.ts +47 -11
  3. package/dist/components/animated-beam/AnimatedBeam.js +293 -118
  4. package/dist/components/animated-tooltip/AnimatedTooltip.d.ts +10 -1
  5. package/dist/components/animated-tooltip/AnimatedTooltip.js +170 -101
  6. package/dist/components/bento-grid/BentoFrame.d.ts +16 -0
  7. package/dist/components/bento-grid/BentoFrame.js +28 -0
  8. package/dist/components/bento-grid/BentoGrid.d.ts +5 -1
  9. package/dist/components/bento-grid/BentoGrid.js +71 -2
  10. package/dist/components/bento-grid/BentoGridCard.js +34 -26
  11. package/dist/components/bento-grid/BentoGridItem.js +10 -10
  12. package/dist/components/border-beam/BorderBeam.d.ts +55 -10
  13. package/dist/components/border-beam/BorderBeam.js +93 -16
  14. package/dist/components/compare/Compare.d.ts +32 -8
  15. package/dist/components/compare/Compare.js +213 -95
  16. package/dist/components/container-scroll/ContainerScroll.d.ts +26 -4
  17. package/dist/components/container-scroll/ContainerScroll.js +88 -49
  18. package/dist/components/dock/Dock.d.ts +10 -7
  19. package/dist/components/dock/Dock.js +76 -31
  20. package/dist/components/dock/DockIcon.d.ts +14 -3
  21. package/dist/components/dock/DockIcon.js +23 -19
  22. package/dist/components/dock/DockSeparator.d.ts +4 -1
  23. package/dist/components/dock/DockSeparator.js +5 -2
  24. package/dist/components/dock/types.d.ts +13 -6
  25. package/dist/components/dropdown-menu/menu-shared.d.ts +11 -8
  26. package/dist/components/editorial-engine/engine.js +1 -14
  27. package/dist/components/fireworks-hdr/webgl2-renderer.js +0 -19
  28. package/dist/components/fireworks-hdr/webgpu-renderer.js +3 -22
  29. package/dist/components/flip-card/FlipCard.d.ts +42 -1
  30. package/dist/components/flip-card/FlipCard.js +178 -25
  31. package/dist/components/flip-card/index.d.ts +1 -1
  32. package/dist/components/fluid-cursor/FluidCursor.js +5 -4
  33. package/dist/components/fluid-cursor/dev.d.ts +1 -0
  34. package/dist/components/fluid-cursor/dev.js +8 -0
  35. package/dist/components/fluid-cursor/fluid-shared.d.ts +0 -2
  36. package/dist/components/fluid-cursor/fluid-shared.js +0 -2
  37. package/dist/components/fluid-cursor/webgpu-engine.js +10 -24
  38. package/dist/components/glow-border/GlowBorder.d.ts +41 -1
  39. package/dist/components/glow-border/GlowBorder.js +96 -16
  40. package/dist/components/glow-border/index.d.ts +2 -2
  41. package/dist/components/gradient-button/GradientButton.d.ts +10 -5
  42. package/dist/components/gradient-button/GradientButton.js +25 -18
  43. package/dist/components/image-trail-cursor/ImageTrailCursor.js +1 -0
  44. package/dist/components/image-trail-cursor/trail-variants.d.ts +37 -1
  45. package/dist/components/image-trail-cursor/trail-variants.js +331 -8
  46. package/dist/components/interactive-hover-button/InteractiveHoverButton.js +30 -35
  47. package/dist/components/marquee/Marquee.d.ts +7 -3
  48. package/dist/components/marquee/Marquee.js +134 -2
  49. package/dist/components/marquee/ReviewCard.d.ts +2 -1
  50. package/dist/components/marquee/ReviewCard.js +20 -9
  51. package/dist/components/meteors/Meteors.d.ts +32 -10
  52. package/dist/components/meteors/Meteors.js +53 -20
  53. package/dist/components/mosaic-glow/mosaic-glow-core.d.ts +98 -10
  54. package/dist/components/neon-border/NeonBorder.d.ts +24 -5
  55. package/dist/components/neon-border/NeonBorder.js +37 -7
  56. package/dist/components/pagination/Pagination.js +220 -82
  57. package/dist/components/rainbow-button/RainbowButton.js +13 -5
  58. package/dist/components/ripple-button/RippleButton.d.ts +20 -1
  59. package/dist/components/ripple-button/RippleButton.js +44 -21
  60. package/dist/components/shimmer-button/ShimmerButton.d.ts +5 -5
  61. package/dist/components/shimmer-button/ShimmerButton.js +17 -19
  62. package/dist/components/smooth-cursor/SmoothCursor.d.ts +13 -9
  63. package/dist/components/smooth-cursor/SmoothCursor.js +114 -163
  64. package/dist/components/smooth-cursor/smooth-cursor-core.d.ts +85 -0
  65. package/dist/components/smooth-cursor/smooth-cursor-core.js +327 -0
  66. package/dist/components/stepper/Step.js +32 -19
  67. package/dist/components/stepper/Stepper.js +18 -2
  68. package/dist/components/stepper/types.d.ts +13 -0
  69. package/dist/components/timeline/Timeline.d.ts +8 -1
  70. package/dist/components/timeline/Timeline.js +165 -76
  71. package/dist/index.js +2 -1
  72. package/dist/internals/markdown.d.ts +5 -0
  73. package/dist/internals/motion/haptics.d.ts +0 -4
  74. package/dist/internals/motion/presence-core.d.ts +59 -0
  75. package/dist/internals/motion/presence-core.js +162 -0
  76. package/dist/internals/motion/presence.d.ts +2 -4
  77. package/dist/internals/motion/presence.js +1 -156
  78. package/dist/internals/motion/raf.d.ts +1 -1
  79. package/dist/internals/motion/stagger.d.ts +3 -4
  80. package/dist/internals/motion/tokens.js +4 -0
  81. package/dist/internals/motion/transitions.d.ts +17 -17
  82. package/dist/internals/motion/types.d.ts +6 -7
  83. package/dist/internals/relative-time.d.ts +1 -1
  84. package/dist/styles.css +3470 -203
  85. package/package.json +18 -2
  86. 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
- function writeProgressLine(line, progress, height) {
8
- if (!line) return;
9
- line.style.height = `${progress * height}px`;
10
- line.style.opacity = String(Math.min(progress / 0.1, 1));
11
- }
12
- function Timeline({ items = [], title, description, className, content }) {
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 progressRef = useRef(null);
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
- useIsomorphicLayoutEffect(() => {
19
- function measure(el2) {
20
- heightRef.current = el2.getBoundingClientRect().height;
21
- setTimelineHeight(heightRef.current);
22
- }
23
- function updateProgress() {
24
- const el2 = timelineRef.current;
25
- if (!el2) return;
26
- const rect = el2.getBoundingClientRect();
27
- const viewportHeight = window.innerHeight;
28
- const startOffset = viewportHeight * 0.1;
29
- const endOffset = viewportHeight * 0.5;
30
- const start = rect.top - startOffset;
31
- const end = rect.bottom - endOffset;
32
- const total = end - start;
33
- if (total <= 0) {
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
- const el = timelineRef.current;
42
- if (el) {
43
- measure(el);
44
- }
45
- const resizeObserver = new ResizeObserver(() => {
46
- if (timelineRef.current) {
47
- measure(timelineRef.current);
48
- writeProgressLine(progressRef.current, progressValueRef.current, heightRef.current);
49
- }
50
- });
51
- if (el) {
52
- resizeObserver.observe(el);
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
- const onScroll = rafThrottle(updateProgress);
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
- updateProgress();
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
- return /* @__PURE__ */ jsxs("div", { className: cn("w-full font-sans md:px-10", className), children: [
64
- (title || description) && /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-7xl px-4 py-20 md:px-8 lg:px-10", children: [
65
- title && /* @__PURE__ */ jsx("h2", { className: "text-foreground mb-4 max-w-4xl text-lg md:text-4xl", children: title }),
66
- description && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground max-w-sm text-sm md:text-base", children: description })
67
- ] }),
68
- /* @__PURE__ */ jsxs("div", { ref: timelineRef, className: "relative z-0 mx-auto max-w-7xl pb-20", children: [
69
- items.map((item, index) => /* @__PURE__ */ jsxs("div", { className: "flex justify-start pt-10 md:gap-10 md:pt-40", children: [
70
- /* @__PURE__ */ jsxs("div", { className: "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", children: [
71
- /* @__PURE__ */ jsx("div", { className: "bg-background absolute left-3 flex size-10 items-center justify-center rounded-full md:left-3", children: /* @__PURE__ */ jsx("div", { className: "size-4 rounded-full border border-neutral-300 bg-neutral-200 p-2 dark:border-neutral-700 dark:bg-neutral-800" }) }),
72
- /* @__PURE__ */ jsx("h3", { className: "hidden text-xl font-bold text-neutral-500 md:block md:pl-20 md:text-5xl dark:text-neutral-500", children: item.label })
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__ */ jsx("div", { className: "w-full pr-4 pl-20 md:pl-4", children: content ? content(item) : null })
75
- ] }, `${item.id}-${index}`)),
76
- /* @__PURE__ */ jsx(
77
- "div",
78
- {
79
- style: { height: `${timelineHeight}px` },
80
- className: "absolute top-0 left-8 w-[2px] overflow-hidden bg-[linear-gradient(to_bottom,var(--tw-gradient-stops))] from-transparent from-0% via-neutral-200 to-transparent to-[99%] [mask-image:linear-gradient(to_bottom,transparent_0%,black_10%,black_90%,transparent_100%)] md:left-8 dark:via-neutral-700",
81
- children: /* @__PURE__ */ jsx(
82
- "div",
83
- {
84
- ref: progressRef,
85
- style: { height: "0px", opacity: 0 },
86
- className: "absolute inset-x-0 top-0 w-[2px] rounded-full bg-gradient-to-t from-purple-500 from-0% via-blue-500 via-10% to-transparent"
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
- /** The three values `Presence` renders into `data-state`, because its source
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.