@mkbabb/glass-ui 3.5.0 → 3.5.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.
@@ -1,5 +1,6 @@
1
1
  export * from "../constants";
2
2
  export * from "../useStaggerReveal";
3
+ export * from "../useStagger";
3
4
  export * from "../useScrollProgress";
4
5
  export * from "../useRAFLoop";
5
6
  export * from "../useIntersectionPause";
@@ -4,4 +4,5 @@ export * from "./useSpringMount";
4
4
  export * from "./useSpringPress";
5
5
  export * from "./useNumericTransition";
6
6
  export * from "./useAnimatedNumber";
7
+ export * from "./useAnimatedNumberMap";
7
8
  export * from "./useCountup";
@@ -0,0 +1,29 @@
1
+ import { type ComputedRef } from "vue";
2
+ import { type UseAnimatedNumberOptions } from "./useAnimatedNumber";
3
+ export interface UseAnimatedNumberMapOptions extends UseAnimatedNumberOptions {
4
+ }
5
+ /**
6
+ * Fan out N independent `useAnimatedNumber` instances into a `Record<K, ...>`.
7
+ *
8
+ * Each key gets one rAF-smoothed `ComputedRef<number | null>`. The smoothing
9
+ * options apply identically across the set; consumers that need per-key
10
+ * options should call `useAnimatedNumber` per key directly.
11
+ *
12
+ * The `null` propagation is preserved: when `source(key)` resolves to `null`,
13
+ * the corresponding ref returns `null` rather than freezing on the last
14
+ * smoothed sample. This matches the per-metric `raw → smoothed` contract.
15
+ *
16
+ * @example
17
+ * const keys = ["ping", "jitter", "download", "upload"] as const;
18
+ * const smoothed = useAnimatedNumberMap({
19
+ * keys,
20
+ * source: (k) => computed(() => store.resultFor(k).value ?? null),
21
+ * damping: DAMPING.domPill,
22
+ * snapThreshold: SNAP_THRESHOLD.dom,
23
+ * });
24
+ * // smoothed.ping.value, smoothed.jitter.value, ...
25
+ */
26
+ export declare function useAnimatedNumberMap<K extends string>(options: {
27
+ keys: readonly K[];
28
+ source: (key: K) => ComputedRef<number | null>;
29
+ } & UseAnimatedNumberMapOptions): Record<K, ComputedRef<number | null>>;
@@ -0,0 +1,50 @@
1
+ import { type Ref } from "vue";
2
+ export interface UseStaggerOptions {
3
+ /** Count of staggered slots. Each slot maps to one boolean in `revealed`. */
4
+ items: number;
5
+ /**
6
+ * Delay between consecutive reveals.
7
+ *
8
+ * Default 80ms — mirrors the `--motion-stagger-default` token in
9
+ * `tokens.css` (AJ-W4-κ canon). Consumers driving per-row CSS
10
+ * `animation-delay` cascades on the same surface should also read
11
+ * `--motion-stagger-default` so the JS-driven cascade (this composable)
12
+ * and the CSS-driven cascade stay in lockstep.
13
+ *
14
+ * The two adjacent canon tiers are:
15
+ * - `--motion-stagger-tight: 40ms` — dense row cascades
16
+ * - `--motion-stagger-relaxed: 120ms` — wide series cascades
17
+ */
18
+ delayMs?: number;
19
+ /** Delay before the first reveal fires. Default 0. */
20
+ initialDelayMs?: number;
21
+ /** Auto-start on creation. Default true. */
22
+ immediate?: boolean;
23
+ /**
24
+ * When true, honour `prefers-reduced-motion: reduce` by flipping every
25
+ * slot true synchronously instead of cascading the timeline. Mirrors
26
+ * the canonical opt-in on `useAnimatedNumber` and the global
27
+ * `transitions.css` / `utilities.css` reduced-motion brackets. SSR
28
+ * environments (no `window.matchMedia`) treat the option as a no-op.
29
+ * Default true.
30
+ */
31
+ respectReducedMotion?: boolean;
32
+ }
33
+ export interface UseStaggerControls {
34
+ /** Per-slot reveal state. `revealed.value[i]` flips true at `initialDelayMs + i * delayMs`. */
35
+ revealed: Ref<boolean[]>;
36
+ /** Re-arm the cascade. Resets `revealed` to all-false then fires the timeline. */
37
+ start: () => void;
38
+ /** Reset to all-false. Cancels any pending reveals. */
39
+ reset: () => void;
40
+ /** True once every slot has flipped. */
41
+ isComplete: Ref<boolean>;
42
+ }
43
+ /**
44
+ * Drive a fixed-count staggered reveal off a single timer cascade.
45
+ *
46
+ * @example
47
+ * const { revealed, start } = useStagger({ items: visible.value.length, delayMs: 80, initialDelayMs: 80, immediate: false });
48
+ * watch(allComplete, (done, prev) => { if (done && !prev) start(); });
49
+ */
50
+ export declare function useStagger(options: UseStaggerOptions): UseStaggerControls;
@@ -36,9 +36,50 @@ function x(e = {}) {
36
36
  };
37
37
  }
38
38
  //#endregion
39
- //#region src/composables/motion/useScrollProgress.ts
40
- var S = n();
39
+ //#region src/composables/motion/useStagger.ts
40
+ function S() {
41
+ return typeof window > "u" || typeof window.matchMedia != "function" ? !1 : window.matchMedia("(prefers-reduced-motion: reduce)").matches;
42
+ }
41
43
  function C(e) {
44
+ let t = Math.max(0, Math.floor(e.items)), n = e.delayMs ?? 80, r = e.initialDelayMs ?? 0, i = e.immediate !== !1, a = e.respectReducedMotion !== !1, o = _(Array(t).fill(!1)), s = _(t === 0), c = /* @__PURE__ */ new Set();
45
+ function l() {
46
+ for (let e of c) clearTimeout(e);
47
+ c.clear();
48
+ }
49
+ function u() {
50
+ l(), o.value = Array(t).fill(!1), s.value = t === 0;
51
+ }
52
+ function d() {
53
+ o.value = Array(t).fill(!0), s.value = !0;
54
+ }
55
+ function f() {
56
+ if (u(), t === 0) {
57
+ s.value = !0;
58
+ return;
59
+ }
60
+ if (a && S()) {
61
+ d();
62
+ return;
63
+ }
64
+ for (let e = 0; e < t; e++) {
65
+ let i = e, a = setTimeout(() => {
66
+ let e = o.value.slice();
67
+ e[i] = !0, o.value = e, c.delete(a), i === t - 1 && (s.value = !0);
68
+ }, r + i * n);
69
+ c.add(a);
70
+ }
71
+ }
72
+ return i && f(), m(l), {
73
+ revealed: o,
74
+ start: f,
75
+ reset: u,
76
+ isComplete: s
77
+ };
78
+ }
79
+ //#endregion
80
+ //#region src/composables/motion/useScrollProgress.ts
81
+ var w = n();
82
+ function T(e) {
42
83
  let { offset: t = 0, trackExit: n = !1 } = e, r = _(0), i = 0, a = null;
43
84
  function o() {
44
85
  let i = v(e.target);
@@ -57,11 +98,11 @@ function C(e) {
57
98
  });
58
99
  }
59
100
  return p(() => {
60
- if (o(), S) return;
101
+ if (o(), w) return;
61
102
  window.addEventListener("scroll", s, { passive: !0 }), window.addEventListener("resize", s, { passive: !0 });
62
103
  let t = v(e.target);
63
104
  t && typeof ResizeObserver < "u" && (a = new ResizeObserver(s), a.observe(t));
64
- }), S || y(() => v(e.target), (e, t) => {
105
+ }), w || y(() => v(e.target), (e, t) => {
65
106
  t && a && a.unobserve(t), e && a && a.observe(e), s();
66
107
  }), f(() => {
67
108
  window.removeEventListener("scroll", s), window.removeEventListener("resize", s), i && cancelAnimationFrame(i), a?.disconnect(), a = null;
@@ -69,12 +110,12 @@ function C(e) {
69
110
  }
70
111
  //#endregion
71
112
  //#region src/composables/motion/useYieldToMain.ts
72
- function w() {
113
+ function E() {
73
114
  let e = globalThis.scheduler;
74
115
  return typeof e?.yield == "function" ? e.yield.bind(e) : null;
75
116
  }
76
- function T() {
77
- let e = w();
117
+ function D() {
118
+ let e = E();
78
119
  return e ? e() : typeof MessageChannel == "function" ? new Promise((e) => {
79
120
  let t = new MessageChannel();
80
121
  t.port1.onmessage = () => {
@@ -82,38 +123,38 @@ function T() {
82
123
  }, t.port2.postMessage(void 0);
83
124
  }) : new Promise((e) => setTimeout(e, 0));
84
125
  }
85
- function E() {
126
+ function O() {
86
127
  return {
87
- hasNativeYield: w() !== null,
88
- yield: T
128
+ hasNativeYield: E() !== null,
129
+ yield: D
89
130
  };
90
131
  }
91
132
  //#endregion
92
133
  //#region src/composables/motion/useRAFLoop.ts
93
- var D = "(prefers-reduced-motion: reduce)";
94
- function O() {
134
+ var k = "(prefers-reduced-motion: reduce)";
135
+ function A() {
95
136
  return typeof globalThis.requestAnimationFrame == "function" ? globalThis.requestAnimationFrame.bind(globalThis) : null;
96
137
  }
97
- function k() {
138
+ function j() {
98
139
  return typeof globalThis.cancelAnimationFrame == "function" ? globalThis.cancelAnimationFrame.bind(globalThis) : null;
99
140
  }
100
- function A() {
141
+ function M() {
101
142
  return typeof window > "u" ? null : window;
102
143
  }
103
- function j(e, t = {}) {
104
- let { immediate: n = !0, pauseWhenHidden: r = !0, respectReducedMotion: i = !0 } = t, a = _(!1), s = _(!1), c = _(!1), l = _(!1), f = _(!1), p = u(() => l.value && !a.value && (f.value || c.value || i && s.value || !O())), h = null, v = null, b = 0, x = 0, S = !1, C = null;
144
+ function N(e, t = {}) {
145
+ let { immediate: n = !0, pauseWhenHidden: r = !0, respectReducedMotion: i = !0 } = t, a = _(!1), s = _(!1), c = _(!1), l = _(!1), f = _(!1), p = u(() => l.value && !a.value && (f.value || c.value || i && s.value || !A())), h = null, v = null, b = 0, x = 0, S = !1, C = null;
105
146
  function w() {
106
- return !S && l.value && !f.value && !c.value && !(i && s.value) && !!O();
147
+ return !S && l.value && !f.value && !c.value && !(i && s.value) && !!A();
107
148
  }
108
- function E() {
109
- h !== null && (k()?.(h), h = null), a.value = !1, v = null;
149
+ function T() {
150
+ h !== null && (j()?.(h), h = null), a.value = !1, v = null;
110
151
  }
111
- function j() {
152
+ function E() {
112
153
  if (h !== null || !w()) return;
113
- let t = O();
154
+ let t = A();
114
155
  t && (a.value = !0, h = t((t) => {
115
156
  if (h = null, !w()) {
116
- E();
157
+ T();
117
158
  return;
118
159
  }
119
160
  let n = v === null ? 0 : Math.max(0, t - v);
@@ -122,38 +163,38 @@ function j(e, t = {}) {
122
163
  delta: n,
123
164
  elapsed: b,
124
165
  frame: x++
125
- }), w() ? j() : E();
166
+ }), w() ? E() : T();
126
167
  }));
127
168
  }
128
- function M() {
129
- w() ? j() : E();
169
+ function O() {
170
+ w() ? E() : T();
130
171
  }
131
172
  function N() {
132
173
  v = null, b = 0, x = 0;
133
174
  }
134
175
  function P() {
135
- S || (N(), l.value = !0, f.value = !1, M());
176
+ S || (N(), l.value = !0, f.value = !1, O());
136
177
  }
137
178
  function F() {
138
- l.value = !1, f.value = !1, N(), E();
179
+ l.value = !1, f.value = !1, N(), T();
139
180
  }
140
181
  function I() {
141
- l.value && (f.value = !0, M());
182
+ l.value && (f.value = !0, O());
142
183
  }
143
184
  function L() {
144
- S || (f.value = !1, M());
185
+ S || (f.value = !1, O());
145
186
  }
146
187
  if (r) {
147
188
  let { hidden: e } = o();
148
189
  c.value = e.value, y(e, (e) => {
149
- c.value = e, M();
190
+ c.value = e, O();
150
191
  }, { flush: "sync" });
151
192
  }
152
193
  if (i) {
153
- let e = A(), t = typeof e?.matchMedia == "function" ? e.matchMedia(D) : null;
194
+ let e = M(), t = typeof e?.matchMedia == "function" ? e.matchMedia(k) : null;
154
195
  if (t) {
155
196
  let e = (e) => {
156
- s.value = e.matches, M();
197
+ s.value = e.matches, O();
157
198
  };
158
199
  s.value = t.matches, typeof t.addEventListener == "function" ? (t.addEventListener("change", e), C = () => t.removeEventListener("change", e)) : typeof t.addListener == "function" && (t.addListener(e), C = () => t.removeListener?.(e));
159
200
  }
@@ -170,21 +211,21 @@ function j(e, t = {}) {
170
211
  pause: I,
171
212
  resume: L,
172
213
  dispose: R,
173
- yieldToMain: T
214
+ yieldToMain: D
174
215
  };
175
216
  }
176
217
  //#endregion
177
218
  //#region src/composables/motion/usePrioritizedTask.ts
178
- function M() {
219
+ function P() {
179
220
  if (!t()) return null;
180
221
  let e = globalThis.scheduler;
181
222
  return e?.postTask?.bind(e) ?? null;
182
223
  }
183
- function N() {
224
+ function F() {
184
225
  let e = /* @__PURE__ */ Error("The task was aborted.");
185
226
  return e.name = "AbortError", e;
186
227
  }
187
- function P(e) {
228
+ function I(e) {
188
229
  let t = e.filter((e) => e != null);
189
230
  if (t.length === 0) return;
190
231
  if (t.length === 1) return t[0];
@@ -199,20 +240,20 @@ function P(e) {
199
240
  }
200
241
  return n.signal;
201
242
  }
202
- function F(e, t = {}) {
203
- let { priority: n, signal: r, delay: i } = t, a = M();
243
+ function L(e, t = {}) {
244
+ let { priority: n, signal: r, delay: i } = t, a = P();
204
245
  return a ? a(e, {
205
246
  priority: n,
206
247
  signal: r,
207
248
  delay: i
208
249
  }) : new Promise((t, n) => {
209
250
  if (r?.aborted) {
210
- n(N());
251
+ n(F());
211
252
  return;
212
253
  }
213
254
  let a = () => {
214
255
  if (r?.aborted) {
215
- n(N());
256
+ n(F());
216
257
  return;
217
258
  }
218
259
  try {
@@ -229,20 +270,20 @@ function F(e, t = {}) {
229
270
  i && i > 0 ? setTimeout(o, i) : o();
230
271
  });
231
272
  }
232
- function I(e) {
273
+ function R(e) {
233
274
  let t = globalThis.TaskController;
234
275
  return typeof t == "function" ? new t(e ? { priority: e } : void 0) : null;
235
276
  }
236
- function L(e) {
237
- let t = I(e);
277
+ function z(e) {
278
+ let t = R(e);
238
279
  return {
239
- postTask: (n, r = {}) => F(n, {
280
+ postTask: (n, r = {}) => L(n, {
240
281
  priority: e,
241
282
  ...r,
242
- signal: P([t?.signal, r.signal])
283
+ signal: I([t?.signal, r.signal])
243
284
  }),
244
285
  abort: (e) => t?.abort(e)
245
286
  };
246
287
  }
247
288
  //#endregion
248
- export { l as DAMPING, c as SNAP_THRESHOLD, F as postTaskSafe, a as startViewTransition, i as supportsViewTransitions, s as useIntersectionPause, L as usePrioritizedTask, j as useRAFLoop, C as useScrollProgress, x as useStaggerReveal, E as useYieldToMain, r as vReveal, T as yieldToMain };
289
+ export { l as DAMPING, c as SNAP_THRESHOLD, L as postTaskSafe, a as startViewTransition, i as supportsViewTransitions, s as useIntersectionPause, z as usePrioritizedTask, N as useRAFLoop, T as useScrollProgress, C as useStagger, x as useStaggerReveal, O as useYieldToMain, r as vReveal, D as yieldToMain };
package/dist/motion.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { n as e, t } from "./useSpringMount-CnizvZGm.js";
2
2
  import { n, t as r } from "./constants-DsCdlK9I.js";
3
3
  import { t as i } from "./useAnimatedNumber-BF6r64lA.js";
4
- import { onBeforeUnmount as a, onScopeDispose as o, ref as s, shallowRef as c } from "vue";
5
- import { NumericAnimation as l } from "@mkbabb/keyframes.js";
4
+ import { computed as a, onBeforeUnmount as o, onScopeDispose as s, ref as c, shallowRef as l } from "vue";
5
+ import { NumericAnimation as u } from "@mkbabb/keyframes.js";
6
6
  //#region src/composables/motion/useSpringPress.ts
7
- function u(t = {}) {
8
- let n = s(0), r = e(n, {
7
+ function d(t = {}) {
8
+ let n = c(0), r = e(n, {
9
9
  response: t.response ?? .25,
10
10
  dampingFraction: t.dampingFraction ?? .7,
11
11
  initial: t.initial ?? 0,
@@ -35,10 +35,10 @@ function u(t = {}) {
35
35
  }
36
36
  //#endregion
37
37
  //#region src/composables/motion/useNumericTransition.ts
38
- function d(e) {
39
- let t = s(0), n = s(!1), { from: r, to: i, duration: o, timingFunction: u, onFrame: d } = e, f = c(new l([r, i], {
40
- duration: o,
41
- timingFunction: u
38
+ function f(e) {
39
+ let t = c(0), n = c(!1), { from: r, to: i, duration: a, timingFunction: s, onFrame: d } = e, f = l(new u([r, i], {
40
+ duration: a,
41
+ timingFunction: s
42
42
  }));
43
43
  async function p() {
44
44
  n.value && f.value.stop(), n.value = !0, t.value = 0;
@@ -61,7 +61,7 @@ function d(e) {
61
61
  function m() {
62
62
  f.value.stop(), n.value = !1;
63
63
  }
64
- return a(() => {
64
+ return o(() => {
65
65
  m();
66
66
  }), {
67
67
  start: p,
@@ -71,25 +71,35 @@ function d(e) {
71
71
  };
72
72
  }
73
73
  //#endregion
74
+ //#region src/composables/motion/useAnimatedNumberMap.ts
75
+ function p(e) {
76
+ let { keys: t, source: n, ...r } = e, o = {};
77
+ for (let e of t) {
78
+ let t = n(e), { current: s } = i(() => t.value, r);
79
+ o[e] = a(() => t.value === null ? null : s.value);
80
+ }
81
+ return o;
82
+ }
83
+ //#endregion
74
84
  //#region src/composables/motion/useCountup.ts
75
- function f() {
85
+ function m() {
76
86
  return typeof window > "u" || typeof window.matchMedia != "function" ? !1 : window.matchMedia("(prefers-reduced-motion: reduce)").matches;
77
87
  }
78
- function p(e, t) {
88
+ function h(e, t) {
79
89
  let { easeFn: n, skip: r } = t, i = /* @__PURE__ */ new Map();
80
90
  function a(e) {
81
91
  let t = i.get(e);
82
92
  t && (t.stop(), i.delete(e));
83
93
  }
84
- function s(e) {
94
+ function o(e) {
85
95
  e.querySelectorAll("[data-countup]").forEach((e) => {
86
96
  let t = parseFloat(e.getAttribute("data-countup") ?? "0") || 0, r = parseFloat(e.getAttribute("data-countup-dur") ?? "1200"), o = parseFloat(e.getAttribute("data-countup-delay") ?? "0");
87
- if (a(e), f()) {
97
+ if (a(e), m()) {
88
98
  e.textContent = String(t);
89
99
  return;
90
100
  }
91
101
  e.textContent = "0";
92
- let s = new l([{ v: 0 }, { v: t }], {
102
+ let s = new u([{ v: 0 }, { v: t }], {
93
103
  duration: r,
94
104
  timingFunction: n
95
105
  });
@@ -112,9 +122,9 @@ function p(e, t) {
112
122
  function c() {
113
123
  if (r?.()) return;
114
124
  let t = e.value?.querySelector("[data-state=\"active\"]");
115
- t && s(t);
125
+ t && o(t);
116
126
  }
117
- function u() {
127
+ function l() {
118
128
  document.querySelectorAll("[data-countup]").forEach((e) => {
119
129
  a(e), e.textContent = e.getAttribute("data-countup") ?? e.textContent;
120
130
  });
@@ -122,11 +132,11 @@ function p(e, t) {
122
132
  function d() {
123
133
  for (let e of [...i.keys()]) a(e);
124
134
  }
125
- return o(d), {
135
+ return s(d), {
126
136
  runActive: c,
127
- settle: u,
137
+ settle: l,
128
138
  cancel: d
129
139
  };
130
140
  }
131
141
  //#endregion
132
- export { r as DAMPING, n as SNAP_THRESHOLD, i as useAnimatedNumber, p as useCountup, d as useNumericTransition, e as useSpring, t as useSpringMount, u as useSpringPress };
142
+ export { r as DAMPING, n as SNAP_THRESHOLD, i as useAnimatedNumber, p as useAnimatedNumberMap, h as useCountup, f as useNumericTransition, e as useSpring, t as useSpringMount, d as useSpringPress };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mkbabb/glass-ui",
3
- "version": "3.5.0",
3
+ "version": "3.5.1",
4
4
  "description": "Glassmorphic design system — Vue 3.5 components, reka-ui primitives, Tailwind CSS v4 tokens",
5
5
  "license": "MIT",
6
6
  "repository": {