@zyncat/ui 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +2 -2
  2. package/dist/alert.js +6 -7
  3. package/dist/alert.js.map +1 -1
  4. package/dist/button.css +10 -2
  5. package/dist/button.d.ts +2 -6
  6. package/dist/button.js +1 -1
  7. package/dist/{chunk-U5QQZC3U.js → chunk-2F5PITAO.js} +2 -6
  8. package/dist/chunk-2F5PITAO.js.map +1 -0
  9. package/dist/chunk-2QWJ6FAX.js +78 -0
  10. package/dist/chunk-2QWJ6FAX.js.map +1 -0
  11. package/dist/{chunk-MXOPCFWR.js → chunk-356HZMT4.js} +25 -4
  12. package/dist/chunk-356HZMT4.js.map +1 -0
  13. package/dist/{chunk-QMSJZCK3.js → chunk-45XJ75GZ.js} +9 -9
  14. package/dist/chunk-45XJ75GZ.js.map +1 -0
  15. package/dist/{chunk-4R2GQO43.js → chunk-FLO4A6BI.js} +4 -4
  16. package/dist/{chunk-4R2GQO43.js.map → chunk-FLO4A6BI.js.map} +1 -1
  17. package/dist/{chunk-XZAEIQDR.js → chunk-GLT5IKQY.js} +5 -5
  18. package/dist/{chunk-XZAEIQDR.js.map → chunk-GLT5IKQY.js.map} +1 -1
  19. package/dist/{chunk-O4N2OBTR.js → chunk-W5YM2FV4.js} +4 -4
  20. package/dist/{chunk-O4N2OBTR.js.map → chunk-W5YM2FV4.js.map} +1 -1
  21. package/dist/{chunk-MKIQFK7S.js → chunk-WM5J5TDI.js} +3 -3
  22. package/dist/{chunk-MKIQFK7S.js.map → chunk-WM5J5TDI.js.map} +1 -1
  23. package/dist/{chunk-W3SHPKSB.js → chunk-XWUWYSCT.js} +16 -6
  24. package/dist/chunk-XWUWYSCT.js.map +1 -0
  25. package/dist/{chunk-2HORSLXJ.js → chunk-Y7AJQVWJ.js} +17 -8
  26. package/dist/chunk-Y7AJQVWJ.js.map +1 -0
  27. package/dist/{chunk-L3Q3VDIW.js → chunk-YGCVWGUG.js} +6 -32
  28. package/dist/chunk-YGCVWGUG.js.map +1 -0
  29. package/dist/date-field.js +8 -8
  30. package/dist/date-range-field.js +32 -13
  31. package/dist/date-range-field.js.map +1 -1
  32. package/dist/datetime-field.js +8 -8
  33. package/dist/dialog.js +4 -4
  34. package/dist/glide.js +2 -2
  35. package/dist/motion-devtools.js +2 -2
  36. package/dist/multi-select.js +6 -6
  37. package/dist/pagination.js +2 -2
  38. package/dist/popover.js +4 -4
  39. package/dist/radio-group.js +36 -9
  40. package/dist/radio-group.js.map +1 -1
  41. package/dist/select.js +6 -6
  42. package/dist/sheet.js +5 -5
  43. package/dist/table.js +7 -7
  44. package/dist/table.js.map +1 -1
  45. package/dist/tabs.js +2 -2
  46. package/dist/tag.js +7 -7
  47. package/dist/tag.js.map +1 -1
  48. package/dist/toast.js +5 -6
  49. package/dist/toast.js.map +1 -1
  50. package/dist/tooltip.js +3 -3
  51. package/llms.txt +9 -8
  52. package/package.json +11 -10
  53. package/src/components/composites/date-picker/calendar-panel.tsx +12 -3
  54. package/src/components/composites/date-picker/range-panel.tsx +19 -5
  55. package/src/components/composites/select/core/trigger.tsx +8 -12
  56. package/src/components/composites/table/Table.tsx +4 -2
  57. package/src/components/primitives/button/Button.tsx +0 -16
  58. package/src/components/primitives/button/button.css +10 -2
  59. package/src/components/primitives/radio-group/RadioGroup.tsx +26 -7
  60. package/src/components/primitives/tag/Tag.tsx +2 -1
  61. package/src/engine/ease.ts +7 -39
  62. package/src/engine/index.ts +1 -11
  63. package/src/motion/element.tsx +9 -9
  64. package/src/motion/flip.ts +61 -0
  65. package/src/motion/presence-context.ts +4 -3
  66. package/src/motion/presence.tsx +25 -1
  67. package/src/motion/use-motion.ts +13 -2
  68. package/dist/chunk-2HORSLXJ.js.map +0 -1
  69. package/dist/chunk-36WVAQKL.js +0 -40
  70. package/dist/chunk-36WVAQKL.js.map +0 -1
  71. package/dist/chunk-JSKXZMLH.js +0 -67
  72. package/dist/chunk-JSKXZMLH.js.map +0 -1
  73. package/dist/chunk-L3Q3VDIW.js.map +0 -1
  74. package/dist/chunk-MXOPCFWR.js.map +0 -1
  75. package/dist/chunk-QMSJZCK3.js.map +0 -1
  76. package/dist/chunk-U5QQZC3U.js.map +0 -1
  77. package/dist/chunk-W3SHPKSB.js.map +0 -1
  78. package/src/motion/flip.tsx +0 -87
@@ -19,10 +19,6 @@ interface ButtonOwnProps {
19
19
  disabled?: boolean;
20
20
  /** Loading - swaps content for a spinner and makes the button inert. */
21
21
  loading?: boolean;
22
- /** Leading icon node (e.g. a 16px <Icon>). Sized & aligned by the component. */
23
- iconLeft?: ReactNode;
24
- /** Trailing icon node. */
25
- iconRight?: ReactNode;
26
22
  /** Stretch to fill the container width. */
27
23
  fullWidth?: boolean;
28
24
  /** Extra class(es) merged onto the button. */
@@ -51,8 +47,6 @@ export function Button({
51
47
  type = 'button',
52
48
  disabled = false,
53
49
  loading = false,
54
- iconLeft = null,
55
- iconRight = null,
56
50
  fullWidth = false,
57
51
  className = '',
58
52
  style,
@@ -85,17 +79,7 @@ export function Button({
85
79
  {...rest}
86
80
  {...htmlProps}
87
81
  >
88
- {iconLeft ? (
89
- <span className="btn__icon" aria-hidden="true">
90
- {iconLeft}
91
- </span>
92
- ) : null}
93
82
  <span className="btn__label">{children}</span>
94
- {iconRight ? (
95
- <span className="btn__icon" aria-hidden="true">
96
- {iconRight}
97
- </span>
98
- ) : null}
99
83
  {loading ? <span className="btn__spinner" aria-hidden="true" /> : null}
100
84
  </button>
101
85
  );
@@ -16,13 +16,14 @@
16
16
  .btn {
17
17
  --btn-shadow: none;
18
18
  --btn-ring: var(--ring-accent);
19
+ --btn-gap: var(--space-2);
19
20
 
20
21
  position: relative;
21
22
  isolation: isolate;
22
23
  display: inline-flex;
23
24
  align-items: center;
24
25
  justify-content: center;
25
- gap: var(--space-2);
26
+ gap: var(--btn-gap);
26
27
 
27
28
  height: var(--control-height);
28
29
  padding: 0 var(--space-4);
@@ -64,7 +65,7 @@
64
65
  padding: 0 var(--space-3);
65
66
  font-size: var(--size-caption);
66
67
  border-radius: var(--radius-sm);
67
- gap: var(--space-1);
68
+ --btn-gap: var(--space-1);
68
69
  }
69
70
  .btn--lg {
70
71
  height: var(--control-height-lg);
@@ -77,9 +78,16 @@
77
78
  .btn--icon {
78
79
  width: var(--control-height-sm);
79
80
  height: var(--control-height-sm);
81
+ padding: 0;
80
82
  border-radius: var(--radius-sm);
81
83
  }
82
84
 
85
+ .btn__label {
86
+ display: inline-flex;
87
+ align-items: center;
88
+ gap: var(--btn-gap);
89
+ }
90
+
83
91
  .btn__icon {
84
92
  display: inline-flex;
85
93
  flex: none;
@@ -6,13 +6,15 @@ import { Icon } from '../../internal/icon/Icon';
6
6
  import { IconSlot } from '../../internal/icon/IconSlot';
7
7
  import { Collapse } from '../collapse/Collapse';
8
8
  import { GlidePill, useGlide } from '../../../motion/glide';
9
- import { useSharedFlip } from '../../../motion/flip';
9
+ import { Motion } from '../../../motion/element';
10
10
  import { UIMotion } from '../../../tokens/motion-tokens';
11
11
  import type { DataAttributes } from '../../../dom-props';
12
12
  import { cx } from '../../internal/utils/cx';
13
13
  import type { FieldRequirementProps } from '../input/field-chrome';
14
14
 
15
15
  const SM = UIMotion;
16
+ const LAYOUT_FLIP = { timing: SM.t.layout };
17
+ const HOVER_FLIP = { scale: false, timing: SM.t.layout };
16
18
 
17
19
  export interface RadioOption {
18
20
  /** The stored value - what `onChange` returns and `value` matches. */
@@ -83,9 +85,6 @@ export function RadioGroup({
83
85
  const [hovered, setHovered] = useState<string | null>(null);
84
86
  const optionsRef = useRef<HTMLDivElement>(null);
85
87
  const glide = useGlide(optionsRef);
86
- const markerRef = useSharedFlip<HTMLSpanElement>(groupId + ':marker', { timing: SM.t.layout });
87
- const fillRef = useSharedFlip<HTMLSpanElement>(groupId + ':card-fill', { timing: SM.t.layout });
88
- const cardHoverRef = useSharedFlip<HTMLSpanElement>(groupId + ':card-hover', { scale: false, timing: SM.t.layout });
89
88
 
90
89
  const cls = cx(
91
90
  'rg',
@@ -148,10 +147,22 @@ export function RadioGroup({
148
147
  onChange={() => onChange && onChange(opt.value)}
149
148
  />
150
149
  {variant === 'cards' && hovered === opt.value && !selected && (
151
- <span className="rg__card-hover" aria-hidden="true" ref={cardHoverRef}></span>
150
+ <Motion
151
+ as="span"
152
+ layoutId={groupId + ':card-hover'}
153
+ layoutTransition={HOVER_FLIP}
154
+ className="rg__card-hover"
155
+ aria-hidden="true"
156
+ />
152
157
  )}
153
158
  {variant === 'cards' && selected && (
154
- <span className="rg__card-fill" aria-hidden="true" ref={fillRef}></span>
159
+ <Motion
160
+ as="span"
161
+ layoutId={groupId + ':card-fill'}
162
+ layoutTransition={LAYOUT_FLIP}
163
+ className="rg__card-fill"
164
+ aria-hidden="true"
165
+ />
155
166
  )}
156
167
  {variant === 'cards' && opt.icon && (
157
168
  <span className="rg-opt__icon">
@@ -160,7 +171,15 @@ export function RadioGroup({
160
171
  )}
161
172
  <span className="rg-opt__control">
162
173
  <span className="rg-opt__dot">
163
- {selected && <span className="rg__marker" aria-hidden="true" ref={markerRef}></span>}
174
+ {selected && (
175
+ <Motion
176
+ as="span"
177
+ layoutId={groupId + ':marker'}
178
+ layoutTransition={LAYOUT_FLIP}
179
+ className="rg__marker"
180
+ aria-hidden="true"
181
+ />
182
+ )}
164
183
  </span>
165
184
  </span>
166
185
  <span className="rg-opt__body">
@@ -102,7 +102,8 @@ export function Tag({
102
102
  ref={ref}
103
103
  animate={group?.animate}
104
104
  exit={group?.exit}
105
- layout={group?.layout}
105
+ layout={!!group}
106
+ layoutTransition={group?.layout}
106
107
  className={cls}
107
108
  style={style}
108
109
  data-disabled={disabled ? 'true' : undefined}
@@ -14,40 +14,13 @@ const NAMED: Record<string, Bezier> = {
14
14
  anticipate: [0.38, -0.4, 0.88, 1.08],
15
15
  };
16
16
 
17
- const bezierCurve = (a: number, b: number, t: number) => {
18
- const u = 1 - t;
19
- return 3 * a * u * u * t + 3 * b * u * t * t + t * t * t;
20
- };
21
-
22
- const bezierSlope = (a: number, b: number, t: number) => {
23
- const u = 1 - t;
24
- return 3 * a * (u * u - 2 * t * u) + 3 * b * (2 * t * u - t * t) + 3 * t * t;
25
- };
26
-
27
- export function bezierAt(b: Bezier, x: number): number {
28
- const [x1, y1, x2, y2] = b;
29
- if (x1 === y1 && x2 === y2) return x;
30
- if (x <= 0) return 0;
31
- if (x >= 1) return 1;
32
- let t = x;
33
- for (let i = 0; i < 8; i++) {
34
- const err = bezierCurve(x1, x2, t) - x;
35
- if (err < 1e-4 && err > -1e-4) break;
36
- const slope = bezierSlope(x1, x2, t);
37
- if (slope < 1e-6 && slope > -1e-6) break;
38
- t -= err / slope;
39
- }
40
- return bezierCurve(y1, y2, t);
41
- }
42
-
43
17
  export type EaseValue = NonNullable<MotionTransition['ease']>;
44
18
 
45
- export const toBezier = (ease: EaseValue): Bezier =>
46
- typeof ease === 'string' ? (NAMED[ease] ?? NAMED.easeInOut) : ease;
19
+ const toBezier = (ease: EaseValue): Bezier => (typeof ease === 'string' ? (NAMED[ease] ?? NAMED.easeInOut) : ease);
47
20
 
48
- export const cssBezier = (ease: EaseValue): string => `cubic-bezier(${toBezier(ease).join(',')})`;
21
+ const cssBezier = (ease: EaseValue): string => `cubic-bezier(${toBezier(ease).join(',')})`;
49
22
 
50
- export const isSegmented = (ease: unknown): ease is EaseValue[] =>
23
+ const isSegmented = (ease: unknown): ease is EaseValue[] =>
51
24
  Array.isArray(ease) && (Array.isArray(ease[0]) || typeof ease[0] === 'string');
52
25
 
53
26
  const ARRIVAL = 0.99;
@@ -103,7 +76,6 @@ function toLinearEasing(at: (progress: number) => number, durationMs: number): s
103
76
  interface SpringCurve {
104
77
  duration: number;
105
78
  easing: string;
106
- at(progress: number): number;
107
79
  }
108
80
 
109
81
  const springCache = new Map<string, SpringCurve>();
@@ -117,7 +89,7 @@ function springCurve(visual: number, bounce: number): SpringCurve {
117
89
  const seconds = settleSeconds(zeta, omega, visual);
118
90
  const duration = seconds * 1000;
119
91
  const at = (progress: number) => (progress >= 1 ? 1 : springAt(zeta, omega, progress * seconds));
120
- const curve: SpringCurve = { duration, easing: toLinearEasing(at, duration), at };
92
+ const curve: SpringCurve = { duration, easing: toLinearEasing(at, duration) };
121
93
  springCache.set(key, curve);
122
94
  return curve;
123
95
  }
@@ -133,7 +105,6 @@ export interface ResolvedTiming {
133
105
  delay: number;
134
106
  easing: string;
135
107
  segments: string[] | null;
136
- at(progress: number): number;
137
108
  }
138
109
 
139
110
  export function resolveTiming(timing: Timing = {}): ResolvedTiming {
@@ -142,14 +113,11 @@ export function resolveTiming(timing: Timing = {}): ResolvedTiming {
142
113
  if (spring) {
143
114
  const visual = Math.max(timing.visualDuration ?? timing.duration ?? 0.3, 0.01);
144
115
  const curve = springCurve(visual, timing.bounce ?? 0);
145
- return { duration: curve.duration, delay, easing: curve.easing, segments: null, at: curve.at };
116
+ return { duration: curve.duration, delay, easing: curve.easing, segments: null };
146
117
  }
147
118
  const duration = (timing.duration ?? 0.3) * 1000;
148
119
  const ease = timing.ease;
149
- if (isSegmented(ease)) {
150
- const first = toBezier(ease[0]);
151
- return { duration, delay, easing: 'linear', segments: ease.map(cssBezier), at: (p) => bezierAt(first, p) };
152
- }
120
+ if (isSegmented(ease)) return { duration, delay, easing: 'linear', segments: ease.map(cssBezier) };
153
121
  const bezier = ease === undefined ? NAMED.easeInOut : toBezier(ease);
154
- return { duration, delay, easing: cssBezier(bezier), segments: null, at: (p) => bezierAt(bezier, p) };
122
+ return { duration, delay, easing: cssBezier(bezier), segments: null };
155
123
  }
@@ -1,13 +1,3 @@
1
- export { animate, clock, type Layer, type Playback, type Size } from './animate';
2
- export {
3
- bezierAt,
4
- cssBezier,
5
- isSegmented,
6
- resolveTiming,
7
- toBezier,
8
- type EaseValue,
9
- type ResolvedTiming,
10
- type Timing,
11
- } from './ease';
1
+ export { animate, clock, type Layer, type Playback } from './animate';
12
2
  export { flip, measure, keepShared, readShared, dropShared, type Box, type FlipOptions } from './flip';
13
3
  export { startDrag, type PanInfo } from './drag';
@@ -1,16 +1,18 @@
1
1
  'use client';
2
2
 
3
3
  import { createElement, useCallback, useRef, type ReactNode, type Ref } from 'react';
4
- import { useSelfFlip, useSharedFlip, type FlipTuning } from './flip';
4
+ import { useFlip, type FlipTuning } from './flip';
5
5
  import { useMotion, type MotionSpecs } from './use-motion';
6
6
 
7
7
  export interface MotionProps extends MotionSpecs {
8
8
  /** Tag for the element this renders. @default 'div' */
9
9
  as?: string;
10
- /** FLIP this element from its previous box on every render; an object tunes the timing. */
11
- layout?: boolean | FlipTuning;
10
+ /** FLIP this element from its previous box on every render. Implied by `layoutId`. */
11
+ layout?: boolean;
12
12
  /** FLIP from whatever element last held this id, so the box travels between nodes. */
13
13
  layoutId?: string;
14
+ /** Tunes whichever layout animation is active; ignored without `layout` or `layoutId`. */
15
+ layoutTransition?: FlipTuning;
14
16
  ref?: Ref<HTMLElement | null>;
15
17
  children?: ReactNode;
16
18
  [prop: string]: unknown;
@@ -24,13 +26,12 @@ export function Motion({
24
26
  deps,
25
27
  layout,
26
28
  layoutId,
29
+ layoutTransition,
27
30
  ref,
28
31
  children,
29
32
  ...rest
30
33
  }: MotionProps) {
31
- const tuning = typeof layout === 'object' ? layout : {};
32
- const selfRef = useSelfFlip<HTMLElement>(tuning, !!layout && !layoutId);
33
- const sharedRef = useSharedFlip<HTMLElement>(layoutId ?? '', tuning, !!layoutId);
34
+ const flipRef = useFlip<HTMLElement>(layoutId ?? null, layoutTransition, !!layoutId || !!layout);
34
35
  const host = useRef<HTMLElement | null>(null);
35
36
  const forwarded = useRef(ref);
36
37
  forwarded.current = ref;
@@ -40,13 +41,12 @@ export function Motion({
40
41
  const attach = useCallback(
41
42
  (el: HTMLElement | null) => {
42
43
  host.current = el;
43
- selfRef.current = el;
44
- sharedRef.current = el;
44
+ flipRef.current = el;
45
45
  const outer = forwarded.current;
46
46
  if (typeof outer === 'function') outer(el);
47
47
  else if (outer) outer.current = el;
48
48
  },
49
- [selfRef, sharedRef],
49
+ [flipRef],
50
50
  );
51
51
 
52
52
  return createElement(as, { ...rest, ref: attach }, children);
@@ -0,0 +1,61 @@
1
+ 'use client';
2
+
3
+ import { useLayoutEffect, useRef, type RefObject } from 'react';
4
+ import {
5
+ dropShared,
6
+ flip,
7
+ keepShared,
8
+ measure,
9
+ readShared,
10
+ type Box,
11
+ type FlipOptions,
12
+ type Playback,
13
+ } from '../engine';
14
+ import { UIMotion } from '../tokens/motion-tokens';
15
+
16
+ export type FlipTuning = FlipOptions;
17
+
18
+ export function useFlip<T extends HTMLElement>(
19
+ sharedId: string | null,
20
+ tuning: FlipTuning = {},
21
+ enabled = true,
22
+ ): RefObject<T | null> {
23
+ const ref = useRef<T | null>(null);
24
+ const privateBox = useRef<Box | null>(null);
25
+ const playing = useRef<{ play: Playback; el: T } | null>(null);
26
+ const { scale, timing } = tuning;
27
+
28
+ useLayoutEffect(() => {
29
+ const el = ref.current;
30
+ if (!el || !enabled) return;
31
+ const live = playing.current;
32
+ const from = live && live.el === el ? measure(el) : sharedId === null ? privateBox.current : readShared(sharedId);
33
+ live?.play.stop();
34
+ const settled = measure(el);
35
+ if (sharedId === null) privateBox.current = settled;
36
+ else keepShared(sharedId, settled);
37
+ const next = from && !UIMotion.reduced ? flip(el, from, { scale, timing }) : null;
38
+ playing.current = next ? { play: next, el } : null;
39
+ next?.finished.then(() => {
40
+ if (playing.current?.play === next) playing.current = null;
41
+ });
42
+ });
43
+
44
+ useLayoutEffect(() => {
45
+ if (!enabled || sharedId === null) return;
46
+ return () => {
47
+ const live = playing.current;
48
+ if (live) {
49
+ const box = measure(live.el);
50
+ if (box.width && box.height) keepShared(sharedId, box);
51
+ live.play.stop();
52
+ }
53
+ const mine = readShared(sharedId);
54
+ queueMicrotask(() => {
55
+ if (readShared(sharedId) === mine) dropShared(sharedId);
56
+ });
57
+ };
58
+ }, [sharedId, enabled]);
59
+
60
+ return ref;
61
+ }
@@ -7,11 +7,12 @@ export interface PresenceState {
7
7
  isPresent: boolean;
8
8
  /** False for children present at the Presence's own first paint when `initial={false}`. */
9
9
  initial: boolean;
10
- /** Called by the child when its exit has finished; lets Presence drop it from the tree. */
11
- safeToRemove: () => void;
10
+ /** Claims responsibility for animating this child out; call the returned release once the
11
+ * exit has finished. A child nobody claims is dropped as soon as it leaves `children`. */
12
+ claimExit: () => () => void;
12
13
  }
13
14
 
14
- const DETACHED: PresenceState = { isPresent: true, initial: true, safeToRemove: () => {} };
15
+ const DETACHED: PresenceState = { isPresent: true, initial: true, claimExit: () => () => {} };
15
16
 
16
17
  export const PresenceContext = createContext<PresenceState>(DETACHED);
17
18
 
@@ -47,6 +47,29 @@ export function Presence({ children, initial = true, mode = 'sync', onExitComple
47
47
  const wasExiting = useRef(false);
48
48
  const complete = useRef(onExitComplete);
49
49
  complete.current = onExitComplete;
50
+ const claims = useRef(new Map<string, Set<object>>());
51
+
52
+ const drop = (key: string) =>
53
+ setShown((previous) => {
54
+ const next = previous.filter((entry) => entry.key !== key || !entry.exiting);
55
+ return next.length === previous.length ? previous : next;
56
+ });
57
+
58
+ const claimExit = (key: string) => (): (() => void) => {
59
+ let tokens = claims.current.get(key);
60
+ if (!tokens) {
61
+ tokens = new Set<object>();
62
+ claims.current.set(key, tokens);
63
+ }
64
+ const token = {};
65
+ const held = tokens;
66
+ held.add(token);
67
+ return () => {
68
+ if (!held.delete(token) || held.size) return;
69
+ if (claims.current.get(key) === held) claims.current.delete(key);
70
+ drop(key);
71
+ };
72
+ };
50
73
 
51
74
  useLayoutEffect(() => {
52
75
  setShown((previous) => {
@@ -54,6 +77,7 @@ export function Presence({ children, initial = true, mode = 'sync', onExitComple
54
77
  const merged: Entry[] = incoming.map((entry) => ({ ...entry }));
55
78
  previous.forEach((entry, index) => {
56
79
  if (live.has(entry.key)) return;
80
+ if (!claims.current.get(entry.key)?.size) return;
57
81
  merged.splice(index, 0, entry.exiting ? entry : { ...entry, exiting: true });
58
82
  });
59
83
  return merged;
@@ -81,7 +105,7 @@ export function Presence({ children, initial = true, mode = 'sync', onExitComple
81
105
  value: {
82
106
  isPresent: !entry.exiting,
83
107
  initial: initial || !bornAtMount.current.has(entry.key),
84
- safeToRemove: () => setShown((previous) => previous.filter((e) => e.key !== entry.key || !e.exiting)),
108
+ claimExit: claimExit(entry.key),
85
109
  },
86
110
  },
87
111
  fresh.get(entry.key) ?? entry.node,
@@ -36,12 +36,23 @@ export function useMotion(ref: RefObject<HTMLElement | null>, { animate, exit, i
36
36
  const mounted = useRef(false);
37
37
  const spec = useRef({ animate, exit, initial });
38
38
  spec.current = { animate, exit, initial };
39
+ const release = useRef<(() => void) | null>(null);
40
+ const ownsExit = !!exit;
39
41
 
40
42
  const stop = () => {
41
43
  for (const play of playing.current) play.stop();
42
44
  playing.current = [];
43
45
  };
44
46
 
47
+ useLayoutEffect(() => {
48
+ if (!ownsExit) return;
49
+ release.current = presence.claimExit();
50
+ return () => {
51
+ release.current?.();
52
+ release.current = null;
53
+ };
54
+ }, [ownsExit]); // eslint-disable-line react-hooks/exhaustive-deps
55
+
45
56
  useLayoutEffect(() => {
46
57
  const el = ref.current;
47
58
  const atMount = !mounted.current;
@@ -53,11 +64,11 @@ export function useMotion(ref: RefObject<HTMLElement | null>, { animate, exit, i
53
64
  }, deps ?? []); // eslint-disable-line react-hooks/exhaustive-deps
54
65
 
55
66
  useLayoutEffect(() => {
56
- if (presence.isPresent) return;
67
+ if (presence.isPresent || !ownsExit) return;
57
68
  const el = ref.current;
58
69
  stop();
59
70
  playing.current = el ? run(spec.current.exit, el) : [];
60
- allSettled(playing.current).then(presence.safeToRemove);
71
+ allSettled(playing.current).then(() => release.current?.());
61
72
  }, [presence.isPresent]); // eslint-disable-line react-hooks/exhaustive-deps
62
73
 
63
74
  useLayoutEffect(() => stop, []);
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/composites/date-picker/calendar-panel.tsx"],"names":[],"mappings":"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","file":"chunk-2HORSLXJ.js","sourcesContent":["'use client';\n\nimport './date-picker.css';\nimport { useEffect, useId, useRef, useState, type KeyboardEvent, type ReactNode } from 'react';\nimport { animate } from '../../../engine';\nimport { UIMotion } from '../../../tokens/motion-tokens';\nimport { Icon } from '../../internal/icon/Icon';\nimport { Button } from '../../primitives/button/Button';\nimport { GlidePill, useGlide } from '../../../motion/glide';\nimport { useSharedFlip } from '../../../motion/flip';\nimport { useDayFocus } from './use-day-focus';\nimport { MONTHS, DOW, toKey, parse, col, today, grid, tzLabel, within } from './date-utils';\nimport { cx } from '../../internal/utils/cx';\n\ninterface DtpPanelProps {\n val: string | null;\n commit: (key: string) => void;\n close: () => void;\n min?: string;\n max?: string;\n timezone?: string;\n label?: string;\n slot?: ReactNode;\n}\n\nexport function DtpPanel({ val, commit, min, max, timezone, label, close, slot }: DtpPanelProps) {\n const seed = val ? parse(val) : new Date();\n const [view, setView] = useState<{ y: number; m: number }>({ y: seed.getFullYear(), m: seed.getMonth() });\n const [focusKey, setFocusKey] = useState<string>(val || today());\n\n const daysRef = useRef<HTMLDivElement>(null);\n const prevViewRef = useRef<number | null>(null);\n const uid = useId();\n const pillId = 'dtp-pill-' + uid;\n\n const glide = useGlide(daysRef);\n const pillRef = useSharedFlip<HTMLSpanElement>(pillId, { timing: UIMotion.t.settle });\n\n const inRange = (key: string): boolean => within(key, min, max);\n\n function pickDay(key: string) {\n if (!inRange(key)) return;\n armFocus();\n setFocusKey(key);\n const d = parse(key);\n goToMonth(d.getFullYear(), d.getMonth());\n commit(key);\n }\n\n function goToMonth(y: number, m: number) {\n setView((v) => (v.y === y && v.m === m ? v : { y: y, m: m }));\n }\n function nav(dir: number) {\n const d = new Date(view.y, view.m + dir, 1);\n goToMonth(d.getFullYear(), d.getMonth());\n }\n const now = new Date();\n const viewIsCurrent = view.y === now.getFullYear() && view.m === now.getMonth();\n function goToToday() {\n const key = today();\n const d = parse(key);\n armFocus();\n setFocusKey(key);\n goToMonth(d.getFullYear(), d.getMonth());\n }\n const viewIdx = view.y * 12 + view.m;\n const armFocus = useDayFocus(daysRef, focusKey, viewIdx, '[tabindex=\"0\"]:not(:disabled)');\n useEffect(() => {\n const prev = prevViewRef.current;\n prevViewRef.current = viewIdx;\n const el = daysRef.current;\n if (prev == null || prev === viewIdx || !el) return;\n const dir = viewIdx > prev ? 1 : -1;\n animate(el, { x: [dir * 16, 0], opacity: [0, 1], timing: UIMotion.t.enter });\n }, [viewIdx]);\n\n function moveFocus(deltaDays: number, deltaMonths: number) {\n const d = parse(focusKey);\n if (deltaMonths) d.setMonth(d.getMonth() + deltaMonths);\n if (deltaDays) d.setDate(d.getDate() + deltaDays);\n const key = toKey(d);\n armFocus();\n setFocusKey(key);\n goToMonth(d.getFullYear(), d.getMonth());\n }\n function onGridKeyDown(e: KeyboardEvent<HTMLDivElement>) {\n const k = e.key;\n if (k === 'ArrowLeft') moveFocus(-1, 0);\n else if (k === 'ArrowRight') moveFocus(1, 0);\n else if (k === 'ArrowUp') moveFocus(-7, 0);\n else if (k === 'ArrowDown') moveFocus(7, 0);\n else if (k === 'PageUp') moveFocus(0, -1);\n else if (k === 'PageDown') moveFocus(0, 1);\n else if (k === 'Home') moveFocus(-col(parse(focusKey)), 0);\n else if (k === 'End') moveFocus(6 - col(parse(focusKey)), 0);\n else if (k === 'Enter' || k === ' ') pickDay(focusKey);\n else return;\n e.preventDefault();\n }\n\n const days = grid(view.y, view.m);\n const todayKey = today();\n const selKey = val || null;\n const gridKeys = days.map(toKey);\n const tabKey =\n gridKeys.indexOf(focusKey) >= 0\n ? focusKey\n : selKey && gridKeys.indexOf(selKey) >= 0\n ? selKey\n : gridKeys.indexOf(todayKey) >= 0\n ? todayKey\n : toKey(new Date(view.y, view.m, 1));\n\n const prevEnd = toKey(new Date(view.y, view.m, 0));\n const nextStart = toKey(new Date(view.y, view.m + 1, 1));\n const canPrev = !min || prevEnd >= min;\n const canNext = !max || nextStart <= max;\n\n const weeks: Date[][] = [];\n for (let w = 0; w < 6; w++) weeks.push(days.slice(w * 7, w * 7 + 7));\n\n return (\n <div className=\"dtp\" role=\"dialog\" aria-label={label || 'Pick a date'}>\n <div className=\"dtp__cal\">\n <div className=\"dtp__head\">\n <span className=\"dtp__month\" aria-live=\"polite\">\n {MONTHS[view.m]} <span className=\"dtp__year\">{view.y}</span>\n </span>\n <div className=\"dtp__navs\">\n <button\n type=\"button\"\n className=\"dtp__nav\"\n aria-label=\"Previous month\"\n disabled={!canPrev}\n onClick={() => nav(-1)}\n >\n <Icon name=\"caret-left\" size=\"sm\" />\n </button>\n <button\n type=\"button\"\n className=\"dtp__nav\"\n aria-label=\"Go to today\"\n disabled={viewIsCurrent}\n onClick={goToToday}\n >\n <Icon name=\"calendar-dot\" size=\"sm\" />\n </button>\n <button\n type=\"button\"\n className=\"dtp__nav\"\n aria-label=\"Next month\"\n disabled={!canNext}\n onClick={() => nav(1)}\n >\n <Icon name=\"caret-right\" size=\"sm\" />\n </button>\n </div>\n </div>\n <div className=\"dtp__dow\" aria-hidden=\"true\">\n {DOW.map((d) => (\n <span key={d}>{d}</span>\n ))}\n </div>\n <div\n className=\"dtp__days\"\n ref={daysRef}\n role=\"grid\"\n aria-label=\"Calendar\"\n onKeyDown={onGridKeyDown}\n onPointerLeave={glide.leave}\n >\n {weeks.map((week, wi) => (\n <div key={wi} role=\"row\" className=\"dtp__row\">\n {week.map((d) => {\n const key = toKey(d);\n const out = d.getMonth() !== view.m;\n const sel = key === selKey;\n const cls = cx('dtp__day', out && 'is-out', sel && 'is-selected', key === todayKey && 'is-today');\n return (\n <button\n key={key}\n type=\"button\"\n role=\"gridcell\"\n data-key={key}\n className={cls}\n disabled={!inRange(key)}\n tabIndex={key === tabKey ? 0 : -1}\n aria-selected={sel || undefined}\n aria-label={MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear()}\n onClick={() => pickDay(key)}\n onPointerEnter={(e) => glide.enter(e.currentTarget)}\n >\n {sel ? <span className=\"dtp__pill\" aria-hidden=\"true\" ref={pillRef}></span> : null}\n <span className=\"dtp__num\">{d.getDate()}</span>\n {key === todayKey ? <span className=\"dtp__dot\" aria-hidden=\"true\"></span> : null}\n </button>\n );\n })}\n </div>\n ))}\n <GlidePill className=\"dtp__hover\" glide={glide} />\n </div>\n </div>\n\n {slot || null}\n\n <div className=\"dtp__foot\">\n {timezone ? <span className=\"dtp__tz\">{tzLabel(timezone, selKey)}</span> : null}\n <span className=\"dtp__footSpacer\"></span>\n <Button variant=\"primary\" size=\"sm\" onClick={close}>\n Done\n </Button>\n </div>\n </div>\n );\n}\n"]}
@@ -1,40 +0,0 @@
1
- 'use client';import { useMotion } from './chunk-W3SHPKSB.js';
2
- import { useSelfFlip, useSharedFlip } from './chunk-JSKXZMLH.js';
3
- import { useRef, useCallback, createElement } from 'react';
4
-
5
- function Motion({
6
- as = "div",
7
- animate,
8
- exit,
9
- initial,
10
- deps,
11
- layout,
12
- layoutId,
13
- ref,
14
- children,
15
- ...rest
16
- }) {
17
- const tuning = typeof layout === "object" ? layout : {};
18
- const selfRef = useSelfFlip(tuning, !!layout && !layoutId);
19
- const sharedRef = useSharedFlip(layoutId ?? "", tuning, !!layoutId);
20
- const host = useRef(null);
21
- const forwarded = useRef(ref);
22
- forwarded.current = ref;
23
- useMotion(host, { animate, exit, initial, deps });
24
- const attach = useCallback(
25
- (el) => {
26
- host.current = el;
27
- selfRef.current = el;
28
- sharedRef.current = el;
29
- const outer = forwarded.current;
30
- if (typeof outer === "function") outer(el);
31
- else if (outer) outer.current = el;
32
- },
33
- [selfRef, sharedRef]
34
- );
35
- return createElement(as, { ...rest, ref: attach }, children);
36
- }
37
-
38
- export { Motion };
39
- //# sourceMappingURL=chunk-36WVAQKL.js.map
40
- //# sourceMappingURL=chunk-36WVAQKL.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/motion/element.tsx"],"names":[],"mappings":";;;;AAkBO,SAAS,MAAA,CAAO;AAAA,EACrB,EAAA,GAAK,KAAA;AAAA,EACL,OAAA;AAAA,EACA,IAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,GAAA;AAAA,EACA,QAAA;AAAA,EACA,GAAG;AACL,CAAA,EAAgB;AACd,EAAA,MAAM,MAAA,GAAS,OAAO,MAAA,KAAW,QAAA,GAAW,SAAS,EAAC;AACtD,EAAA,MAAM,UAAU,WAAA,CAAyB,MAAA,EAAQ,CAAC,CAAC,MAAA,IAAU,CAAC,QAAQ,CAAA;AACtE,EAAA,MAAM,YAAY,aAAA,CAA2B,QAAA,IAAY,IAAI,MAAA,EAAQ,CAAC,CAAC,QAAQ,CAAA;AAC/E,EAAA,MAAM,IAAA,GAAO,OAA2B,IAAI,CAAA;AAC5C,EAAA,MAAM,SAAA,GAAY,OAAO,GAAG,CAAA;AAC5B,EAAA,SAAA,CAAU,OAAA,GAAU,GAAA;AAEpB,EAAA,SAAA,CAAU,MAAM,EAAE,OAAA,EAAS,IAAA,EAAM,OAAA,EAAS,MAAM,CAAA;AAEhD,EAAA,MAAM,MAAA,GAAS,WAAA;AAAA,IACb,CAAC,EAAA,KAA2B;AAC1B,MAAA,IAAA,CAAK,OAAA,GAAU,EAAA;AACf,MAAA,OAAA,CAAQ,OAAA,GAAU,EAAA;AAClB,MAAA,SAAA,CAAU,OAAA,GAAU,EAAA;AACpB,MAAA,MAAM,QAAQ,SAAA,CAAU,OAAA;AACxB,MAAA,IAAI,OAAO,KAAA,KAAU,UAAA,EAAY,KAAA,CAAM,EAAE,CAAA;AAAA,WAAA,IAChC,KAAA,QAAa,OAAA,GAAU,EAAA;AAAA,IAClC,CAAA;AAAA,IACA,CAAC,SAAS,SAAS;AAAA,GACrB;AAEA,EAAA,OAAO,aAAA,CAAc,IAAI,EAAE,GAAG,MAAM,GAAA,EAAK,MAAA,IAAU,QAAQ,CAAA;AAC7D","file":"chunk-36WVAQKL.js","sourcesContent":["'use client';\n\nimport { createElement, useCallback, useRef, type ReactNode, type Ref } from 'react';\nimport { useSelfFlip, useSharedFlip, type FlipTuning } from './flip';\nimport { useMotion, type MotionSpecs } from './use-motion';\n\nexport interface MotionProps extends MotionSpecs {\n /** Tag for the element this renders. @default 'div' */\n as?: string;\n /** FLIP this element from its previous box on every render; an object tunes the timing. */\n layout?: boolean | FlipTuning;\n /** FLIP from whatever element last held this id, so the box travels between nodes. */\n layoutId?: string;\n ref?: Ref<HTMLElement | null>;\n children?: ReactNode;\n [prop: string]: unknown;\n}\n\nexport function Motion({\n as = 'div',\n animate,\n exit,\n initial,\n deps,\n layout,\n layoutId,\n ref,\n children,\n ...rest\n}: MotionProps) {\n const tuning = typeof layout === 'object' ? layout : {};\n const selfRef = useSelfFlip<HTMLElement>(tuning, !!layout && !layoutId);\n const sharedRef = useSharedFlip<HTMLElement>(layoutId ?? '', tuning, !!layoutId);\n const host = useRef<HTMLElement | null>(null);\n const forwarded = useRef(ref);\n forwarded.current = ref;\n\n useMotion(host, { animate, exit, initial, deps });\n\n const attach = useCallback(\n (el: HTMLElement | null) => {\n host.current = el;\n selfRef.current = el;\n sharedRef.current = el;\n const outer = forwarded.current;\n if (typeof outer === 'function') outer(el);\n else if (outer) outer.current = el;\n },\n [selfRef, sharedRef],\n );\n\n return createElement(as, { ...rest, ref: attach }, children);\n}\n"]}
@@ -1,67 +0,0 @@
1
- 'use client';import { measure, flip, readShared, keepShared, dropShared } from './chunk-L3Q3VDIW.js';
2
- import { UIMotion } from './chunk-S5ZTZANS.js';
3
- import { useRef, useLayoutEffect } from 'react';
4
-
5
- function useSelfFlip(tuning = {}, enabled = true) {
6
- const ref = useRef(null);
7
- const previous = useRef(null);
8
- const playing = useRef(null);
9
- const { scale, timing } = tuning;
10
- useLayoutEffect(() => {
11
- const el = ref.current;
12
- if (!el || !enabled) return;
13
- const from = playing.current ? measure(el) : previous.current;
14
- playing.current?.stop();
15
- previous.current = measure(el);
16
- const next = from && !UIMotion.reduced ? flip(el, from, { scale, timing }) : null;
17
- playing.current = next;
18
- next?.finished.then(() => {
19
- if (playing.current === next) playing.current = null;
20
- });
21
- });
22
- useLayoutEffect(
23
- () => () => {
24
- playing.current?.stop();
25
- },
26
- []
27
- );
28
- return ref;
29
- }
30
- function useSharedFlip(id, tuning = {}, enabled = true) {
31
- const ref = useRef(null);
32
- const playing = useRef(null);
33
- const { scale, timing } = tuning;
34
- useLayoutEffect(() => {
35
- const el = ref.current;
36
- if (!el || !enabled) return;
37
- const live = playing.current;
38
- const from = live && live.el === el ? measure(el) : readShared(id);
39
- live?.play.stop();
40
- keepShared(id, measure(el));
41
- const next = from && !UIMotion.reduced ? flip(el, from, { scale, timing }) : null;
42
- playing.current = next ? { play: next, el } : null;
43
- next?.finished.then(() => {
44
- if (playing.current?.play === next) playing.current = null;
45
- });
46
- });
47
- useLayoutEffect(() => {
48
- if (!enabled) return;
49
- return () => {
50
- const live = playing.current;
51
- if (live) {
52
- const box = measure(live.el);
53
- if (box.width && box.height) keepShared(id, box);
54
- live.play.stop();
55
- }
56
- const mine = readShared(id);
57
- queueMicrotask(() => {
58
- if (readShared(id) === mine) dropShared(id);
59
- });
60
- };
61
- }, [id, enabled]);
62
- return ref;
63
- }
64
-
65
- export { useSelfFlip, useSharedFlip };
66
- //# sourceMappingURL=chunk-JSKXZMLH.js.map
67
- //# sourceMappingURL=chunk-JSKXZMLH.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/motion/flip.tsx"],"names":[],"mappings":"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","file":"chunk-JSKXZMLH.js","sourcesContent":["'use client';\n\nimport { useLayoutEffect, useRef, type RefObject } from 'react';\nimport {\n dropShared,\n flip,\n keepShared,\n measure,\n readShared,\n type Box,\n type FlipOptions,\n type Playback,\n} from '../engine';\nimport { UIMotion } from '../tokens/motion-tokens';\n\nexport type FlipTuning = FlipOptions;\n\nexport function useSelfFlip<T extends HTMLElement>(tuning: FlipTuning = {}, enabled = true): RefObject<T | null> {\n const ref = useRef<T | null>(null);\n const previous = useRef<Box | null>(null);\n const playing = useRef<Playback | null>(null);\n const { scale, timing } = tuning;\n\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || !enabled) return;\n const from = playing.current ? measure(el) : previous.current;\n playing.current?.stop();\n previous.current = measure(el);\n const next = from && !UIMotion.reduced ? flip(el, from, { scale, timing }) : null;\n playing.current = next;\n next?.finished.then(() => {\n if (playing.current === next) playing.current = null;\n });\n });\n\n useLayoutEffect(\n () => () => {\n playing.current?.stop();\n },\n [],\n );\n\n return ref;\n}\n\nexport function useSharedFlip<T extends HTMLElement>(\n id: string,\n tuning: FlipTuning = {},\n enabled = true,\n): RefObject<T | null> {\n const ref = useRef<T | null>(null);\n const playing = useRef<{ play: Playback; el: T } | null>(null);\n const { scale, timing } = tuning;\n\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || !enabled) return;\n const live = playing.current;\n const from = live && live.el === el ? measure(el) : readShared(id);\n live?.play.stop();\n keepShared(id, measure(el));\n const next = from && !UIMotion.reduced ? flip(el, from, { scale, timing }) : null;\n playing.current = next ? { play: next, el } : null;\n next?.finished.then(() => {\n if (playing.current?.play === next) playing.current = null;\n });\n });\n\n useLayoutEffect(() => {\n if (!enabled) return;\n return () => {\n const live = playing.current;\n if (live) {\n const box = measure(live.el);\n if (box.width && box.height) keepShared(id, box);\n live.play.stop();\n }\n const mine = readShared(id);\n queueMicrotask(() => {\n if (readShared(id) === mine) dropShared(id);\n });\n };\n }, [id, enabled]);\n\n return ref;\n}\n"]}