@sfinterface/numbers 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Architecture
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,150 @@
1
+ <div align="center">
2
+
3
+ <img src="https://i.ibb.co/JW039HkW/wifmef.png" width="1920" height="400" alt="">
4
+
5
+ ### @sfinterface/numbers
6
+
7
+ Numbers that move the way they should. A React component from **The San Francisco Interface**.
8
+
9
+ <img src="https://github.com/wherescz/sfi-numbers/blob/7d99a099ac153e5b2cef80f33e264606d54ef0f1/tags.svg" alt="MIT · 13 kB · No dependencies · React 18 and 19 · TypeScript · SSR ready">
10
+
11
+ <br>
12
+ <br>
13
+ <br>
14
+
15
+ <img src="https://github.com/wherescz/sfi-numbers/blob/3311b4e84cd7e4ba7119cefd76a0bb57f8ebed8b/preview-gif.gif" width="1920" alt="A number rolling from 1,204 to 1,251">
16
+
17
+ </div>
18
+
19
+ <br>
20
+
21
+ ```sh
22
+ npm i @sfinterface/numbers
23
+ ```
24
+
25
+ ```tsx
26
+ import { Numbers } from "@sfinterface/numbers";
27
+ import "@sfinterface/numbers/styles.css";
28
+
29
+ <Numbers value={count} />
30
+ ```
31
+
32
+ <br>
33
+
34
+ Each column is a strip of digits behind a window. Changing the value turns only
35
+ the columns whose digit actually moved, and the box grows or shrinks to fit what
36
+ it now says.
37
+
38
+ Face, size, weight, colour and tracking are inherited — the stylesheet describes
39
+ motion and geometry and nothing else. A readout in a heading is the heading's
40
+ type.
41
+
42
+ The digits are `aria-hidden`; one formatted string sits behind them, so a screen
43
+ reader says *one thousand two hundred and four*, not twelve glyphs.
44
+
45
+ From **The San Francisco Interface**, shipped on its own.
46
+
47
+ <br>
48
+
49
+ ## Formatting
50
+
51
+ `Intl.NumberFormat`. Currency, percentages, compact notation and every locale
52
+ come from the platform.
53
+
54
+ ```tsx
55
+ <Numbers value={1125.64} format={{ style: "currency", currency: "USD" }} />
56
+ <Numbers value={0.0241} format={{ style: "percent", maximumFractionDigits: 2 }} />
57
+ <Numbers value={48200} format={{ notation: "compact" }} locale="en-GB" />
58
+ ```
59
+
60
+ <br>
61
+
62
+ ## Transitions
63
+
64
+ ```tsx
65
+ <Numbers value={n} transition="tick" />
66
+ ```
67
+
68
+ | | |
69
+ | --- | --- |
70
+ | `roll` | the strip turns, through every digit on the way |
71
+ | `tick` | the old digit slides out, the new one slides in |
72
+ | `blur` | it dissolves, defocused, in place |
73
+ | `flip` | it turns over, like a split-flap board |
74
+ | `scale` | it shrinks away and the new one grows |
75
+
76
+ <br>
77
+
78
+ ## Fade
79
+
80
+ How far the soft edge of the window reaches. While a column turns it deepens, so
81
+ the passing digits go ghostly and settle back.
82
+
83
+ ```tsx
84
+ <Numbers value={n} fade={0} />
85
+ <Numbers value={n} />
86
+ <Numbers value={n} fade={2.4} />
87
+ ```
88
+
89
+ <br>
90
+
91
+ ## Props
92
+
93
+ | prop | type | default |
94
+ | --- | --- | --- |
95
+ | `value` | `number` | required |
96
+ | `format` | `Intl.NumberFormatOptions` | — |
97
+ | `locale` | `string \| string[]` | the runtime's |
98
+ | `transition` | `"roll" \| "tick" \| "blur" \| "flip" \| "scale"` | `"roll"` |
99
+ | `trend` | `"auto" \| "up" \| "down"` | `"auto"` |
100
+ | `blur` | `boolean` | `true` |
101
+ | `fade` | `number` | `1` |
102
+ | `duration` | `number` ms | `520` |
103
+ | `label` | `string` | the formatted value |
104
+
105
+ Everything else is spread onto the root `<span>`.
106
+
107
+ <br>
108
+
109
+ ## Theming
110
+
111
+ Every value is a custom property — set one on `:root`, on a wrapper, or inline.
112
+
113
+ | token | default | |
114
+ | --- | --- | --- |
115
+ | `--sfi-numbers-roll` | `520ms` | how long a column takes to turn |
116
+ | `--sfi-numbers-exit` | `240ms` | how long a leaving column takes to go |
117
+ | `--sfi-numbers-cell` | `1.4em` | the pitch the digits are stacked at |
118
+ | `--sfi-numbers-blur` | `0.09em` | the deepest a fast column smears |
119
+ | `--sfi-numbers-fade` | `1` | how far the window's soft edge reaches |
120
+ | `--sfi-numbers-fade-roll` | `1.9` | how much further it reaches mid-turn |
121
+ | `--sfi-numbers-ease` | `cubic-bezier(0.32, 0.72, 0, 1)` | the curve a column turns on |
122
+ | `--sfi-numbers-ease-exit` | `cubic-bezier(0.4, 0, 1, 1)` | and the one it leaves on |
123
+
124
+ ```css
125
+ :root {
126
+ --sfi-numbers-roll: 700ms;
127
+ --sfi-numbers-cell: 1.25em;
128
+ }
129
+ ```
130
+
131
+ The stylesheet ships in one `@layer arc`, below your own rules, so a `className`
132
+ of yours wins without `!important`.
133
+
134
+ <br>
135
+
136
+ ## Reduced motion
137
+
138
+ With `prefers-reduced-motion: reduce` the columns do not turn, blur or fade. The
139
+ number changes, and nothing animates.
140
+
141
+ <br>
142
+
143
+ ## Requirements
144
+
145
+ React 18 or 19. No dependencies, types bundled. It renders on the server with
146
+ the formatted value already in the markup — no empty box, nothing to guard.
147
+
148
+ <br>
149
+
150
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,379 @@
1
+ "use client";
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.ts
32
+ var src_exports = {};
33
+ __export(src_exports, {
34
+ Numbers: () => Numbers
35
+ });
36
+ module.exports = __toCommonJS(src_exports);
37
+
38
+ // src/Numbers.tsx
39
+ var React = __toESM(require("react"), 1);
40
+
41
+ // src/utils/cx.ts
42
+ function cx(...values) {
43
+ let out = "";
44
+ for (const value of values) {
45
+ if (!value && value !== 0) continue;
46
+ const part = Array.isArray(value) ? cx(...value) : String(value);
47
+ if (part) out += out ? " " + part : part;
48
+ }
49
+ return out;
50
+ }
51
+
52
+ // src/utils/refs.ts
53
+ function assignRef(ref, value) {
54
+ if (!ref) return;
55
+ if (typeof ref === "function") ref(value);
56
+ else ref.current = value;
57
+ }
58
+ function mergeRefs(...refs) {
59
+ return (value) => {
60
+ for (const ref of refs) assignRef(ref, value);
61
+ };
62
+ }
63
+
64
+ // src/Numbers.tsx
65
+ var import_jsx_runtime = require("react/jsx-runtime");
66
+ var CYCLE = 10;
67
+ var HOME = CYCLE;
68
+ var CELLS = Array.from({ length: CYCLE * 3 }, (_, i) => i % CYCLE);
69
+ function numeralsOf(locale, format) {
70
+ let glyphs = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
71
+ try {
72
+ const numberingSystem = new Intl.NumberFormat(locale, format).resolvedOptions().numberingSystem;
73
+ const plain = new Intl.NumberFormat(locale, { numberingSystem, useGrouping: false });
74
+ glyphs = Array.from({ length: CYCLE }, (_, i) => plain.format(i));
75
+ } catch {
76
+ }
77
+ return { glyphs, index: new Map(glyphs.map((glyph, i) => [glyph, i])) };
78
+ }
79
+ function cellsFor(value, locale, format, index) {
80
+ const parts = new Intl.NumberFormat(locale, format).formatToParts(value);
81
+ let integers = 0;
82
+ for (const part of parts) if (part.type === "integer") integers += part.value.length;
83
+ const out = [];
84
+ let seen = 0;
85
+ let fraction = 0;
86
+ let marks = 0;
87
+ for (const part of parts) {
88
+ if (part.type === "integer") {
89
+ for (const glyph of part.value) {
90
+ out.push({ key: `d${integers - 1 - seen}`, kind: "digit", digit: index.get(glyph) ?? 0 });
91
+ seen += 1;
92
+ }
93
+ } else if (part.type === "fraction") {
94
+ for (const glyph of part.value) {
95
+ fraction += 1;
96
+ out.push({ key: `f${fraction}`, kind: "digit", digit: index.get(glyph) ?? 0 });
97
+ }
98
+ } else if (part.type === "group") {
99
+ out.push({ key: `g${integers - 1 - seen}`, kind: "mark", text: part.value });
100
+ } else {
101
+ marks += 1;
102
+ out.push({ key: `${part.type}${marks}`, kind: "mark", text: part.value });
103
+ }
104
+ }
105
+ return out;
106
+ }
107
+ function turn(from, to, dir) {
108
+ if (from === to) return 0;
109
+ const forward = ((to - from) * dir % CYCLE + CYCLE) % CYCLE;
110
+ return dir * forward;
111
+ }
112
+ function ms(value, fallback) {
113
+ const n = parseFloat(value);
114
+ if (!Number.isFinite(n)) return fallback;
115
+ return value.trim().endsWith("s") && !value.trim().endsWith("ms") ? n * 1e3 : n;
116
+ }
117
+ function px(value, fontSize, fallback) {
118
+ const n = parseFloat(value);
119
+ if (!Number.isFinite(n)) return fallback;
120
+ return value.includes("em") ? n * fontSize : n;
121
+ }
122
+ var reduced = () => typeof window !== "undefined" && !!window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
123
+ var Numbers = React.forwardRef(function Numbers2({ value, locale, format, transition = "roll", trend = "auto", blur = true, fade, duration, label, className, ...props }, ref) {
124
+ const numerals = React.useMemo(() => numeralsOf(locale, format), [locale, format]);
125
+ const cells = React.useMemo(
126
+ () => cellsFor(value, locale, format, numerals.index),
127
+ [value, locale, format, numerals]
128
+ );
129
+ const text = React.useMemo(() => new Intl.NumberFormat(locale, format).format(value), [value, locale, format]);
130
+ const host = React.useRef(null);
131
+ const row = React.useRef(null);
132
+ const shown = React.useRef(/* @__PURE__ */ new Map());
133
+ const seats = React.useRef(/* @__PURE__ */ new Map());
134
+ const previous = React.useRef(value);
135
+ const widthSeat = React.useRef(null);
136
+ const widthRun = React.useRef(null);
137
+ const [leaving, setLeaving] = React.useState([]);
138
+ const heading = React.useRef(1);
139
+ const spun = React.useRef(/* @__PURE__ */ new Set());
140
+ const [outgoing, setOutgoing] = React.useState(() => /* @__PURE__ */ new Map());
141
+ const outgoingTimer = React.useRef(null);
142
+ const [generation, setGeneration] = React.useState(0);
143
+ React.useEffect(() => () => {
144
+ if (outgoingTimer.current) clearTimeout(outgoingTimer.current);
145
+ }, []);
146
+ React.useLayoutEffect(() => {
147
+ const rowEl = row.current;
148
+ const hostEl = host.current;
149
+ if (!rowEl || !hostEl) return;
150
+ const from = previous.current;
151
+ previous.current = value;
152
+ const still = reduced();
153
+ const cs = getComputedStyle(rowEl);
154
+ const fontSize = parseFloat(cs.fontSize) || 16;
155
+ const roll = duration ?? ms(cs.getPropertyValue("--sfi-numbers-roll"), 520);
156
+ const shift = ms(cs.getPropertyValue("--sfi-settle"), 240);
157
+ const ease = cs.getPropertyValue("--sfi-ease").trim() || "ease";
158
+ const deepest = px(cs.getPropertyValue("--sfi-numbers-blur"), fontSize, fontSize * 0.1);
159
+ const dir = trend === "up" ? 1 : trend === "down" ? -1 : value >= from ? 1 : -1;
160
+ heading.current = dir;
161
+ const nextSeats = /* @__PURE__ */ new Map();
162
+ const gone = new Map(shown.current);
163
+ const swapped = /* @__PURE__ */ new Map();
164
+ rowEl.querySelectorAll("[data-cell]").forEach((el) => {
165
+ const key = el.dataset.cell;
166
+ const seat = el.offsetLeft;
167
+ nextSeats.set(key, seat);
168
+ gone.delete(key);
169
+ const was = seats.current.get(key);
170
+ const before = shown.current.get(key);
171
+ if (was === void 0) {
172
+ if (!still && seats.current.size > 0) el.setAttribute("data-arriving", "");
173
+ return;
174
+ }
175
+ el.removeAttribute("data-arriving");
176
+ if (still) return;
177
+ const dx = was - seat;
178
+ if (Math.abs(dx) >= 1) {
179
+ el.animate([{ transform: `translateX(${dx}px)` }, { transform: "translateX(0)" }], {
180
+ duration: shift,
181
+ easing: ease
182
+ });
183
+ }
184
+ const digit = el.dataset.digit;
185
+ if (digit === void 0 || !before || before.kind !== "digit") return;
186
+ if (Number(digit) === before.digit) return;
187
+ if (transition !== "roll") {
188
+ swapped.set(key, before.digit);
189
+ return;
190
+ }
191
+ const steps = turn(before.digit, Number(digit), dir);
192
+ if (steps === 0) return;
193
+ const strip = el.querySelector(".sfi-numbers-strip");
194
+ const cell = strip?.firstElementChild;
195
+ if (!strip || !cell) return;
196
+ const height = cell.getBoundingClientRect().height;
197
+ if (!height) return;
198
+ const start2 = (HOME + before.digit) * height;
199
+ const turning = strip.animate(
200
+ [{ transform: `translateY(${-start2}px)` }, { transform: `translateY(${-(start2 + steps * height)}px)` }],
201
+ { duration: roll, easing: ease }
202
+ );
203
+ el.setAttribute("data-turning", "");
204
+ turning.finished.then(() => el.removeAttribute("data-turning")).catch(() => {
205
+ });
206
+ if (blur && deepest > 0) {
207
+ const depth = Math.min(1, Math.abs(steps) / 4) * deepest;
208
+ el.animate(
209
+ [
210
+ { filter: "blur(0px)", offset: 0 },
211
+ { filter: `blur(${depth.toFixed(2)}px)`, offset: 0.15 },
212
+ { filter: "blur(0px)", offset: 0.62 },
213
+ { filter: "blur(0px)", offset: 1 }
214
+ ],
215
+ { duration: roll, easing: "linear" }
216
+ );
217
+ }
218
+ });
219
+ if (gone.size && !still) {
220
+ const ghosts = [];
221
+ gone.forEach((cell, key) => {
222
+ const seat = seats.current.get(key);
223
+ if (seat !== void 0) ghosts.push({ cell, left: seat });
224
+ });
225
+ if (ghosts.length) setLeaving((current) => [...current, ...ghosts]);
226
+ }
227
+ shown.current = new Map(cells.map((cell) => [cell.key, cell]));
228
+ seats.current = nextSeats;
229
+ if (swapped.size && !still) {
230
+ hostEl.style.setProperty("--sfi-numbers-dir", String(dir));
231
+ setOutgoing(swapped);
232
+ setGeneration((n) => n + 1);
233
+ if (outgoingTimer.current) clearTimeout(outgoingTimer.current);
234
+ outgoingTimer.current = setTimeout(() => {
235
+ outgoingTimer.current = null;
236
+ setOutgoing(/* @__PURE__ */ new Map());
237
+ }, roll + 40);
238
+ }
239
+ const running = widthRun.current;
240
+ const visual = running ? hostEl.getBoundingClientRect().width : null;
241
+ if (running) {
242
+ running.cancel();
243
+ widthRun.current = null;
244
+ }
245
+ const now = rowEl.offsetWidth;
246
+ const start = visual ?? widthSeat.current;
247
+ widthSeat.current = now;
248
+ if (start === null || Math.abs(start - now) < 0.5 || still) return;
249
+ const grow = hostEl.animate([{ width: `${start}px` }, { width: `${now}px` }], {
250
+ duration: shift,
251
+ easing: ease
252
+ });
253
+ widthRun.current = grow;
254
+ grow.finished.then(() => {
255
+ if (widthRun.current === grow) widthRun.current = null;
256
+ }).catch(() => {
257
+ });
258
+ }, [cells, value, trend, blur, duration, transition]);
259
+ React.useLayoutEffect(() => {
260
+ const rowEl = row.current;
261
+ if (!rowEl || leaving.length === 0 || reduced()) return;
262
+ const cs = getComputedStyle(rowEl);
263
+ const fontSize = parseFloat(cs.fontSize) || 16;
264
+ const going = ms(cs.getPropertyValue("--sfi-numbers-exit"), 240);
265
+ const ease = cs.getPropertyValue("--sfi-ease").trim() || "ease";
266
+ const deepest = px(cs.getPropertyValue("--sfi-numbers-blur"), fontSize, fontSize * 0.1);
267
+ const dir = heading.current;
268
+ rowEl.querySelectorAll("[data-ghost]").forEach((el) => {
269
+ const key = el.dataset.ghost;
270
+ if (spun.current.has(key)) return;
271
+ spun.current.add(key);
272
+ const strip = el.querySelector(".sfi-numbers-strip");
273
+ const cell = strip?.firstElementChild;
274
+ if (!strip || !cell) return;
275
+ const height = cell.getBoundingClientRect().height;
276
+ if (!height) return;
277
+ const start = (HOME + Number(el.dataset.digit ?? 0)) * height;
278
+ strip.animate(
279
+ [{ transform: `translateY(${-start}px)` }, { transform: `translateY(${-(start + dir * 3 * height)}px)` }],
280
+ { duration: going, easing: ease }
281
+ );
282
+ if (blur && deepest > 0) {
283
+ el.animate([{ filter: "blur(0px)" }, { filter: `blur(${deepest.toFixed(2)}px)` }], {
284
+ duration: going,
285
+ easing: ease
286
+ });
287
+ }
288
+ });
289
+ }, [leaving, blur, transition]);
290
+ const setHost = React.useMemo(() => mergeRefs(ref, host), [ref]);
291
+ const knobs = {};
292
+ if (duration !== void 0) knobs["--sfi-numbers-roll"] = `${duration}ms`;
293
+ if (fade !== void 0) knobs["--sfi-numbers-fade"] = `${fade}`;
294
+ const style = Object.keys(knobs).length ? { ...knobs, ...props.style } : props.style;
295
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
296
+ "span",
297
+ {
298
+ ref: setHost,
299
+ className: cx("sfi-numbers", className),
300
+ "data-transition": transition === "roll" ? void 0 : transition,
301
+ ...props,
302
+ style,
303
+ children: [
304
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sfi-numbers-said", children: label ?? text }),
305
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "sfi-numbers-row", ref: row, "aria-hidden": "true", children: [
306
+ cells.map((cell) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
307
+ Piece,
308
+ {
309
+ cell,
310
+ glyphs: numerals.glyphs,
311
+ transition,
312
+ from: cell.kind === "digit" ? outgoing.get(cell.key) : void 0,
313
+ generation
314
+ },
315
+ cell.key
316
+ )),
317
+ leaving.map((ghost) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
318
+ Piece,
319
+ {
320
+ cell: ghost.cell,
321
+ glyphs: numerals.glyphs,
322
+ transition,
323
+ generation,
324
+ leaving: true,
325
+ left: ghost.left,
326
+ onDone: () => {
327
+ spun.current.delete(ghost.cell.key);
328
+ setLeaving((current) => current.filter((x) => x.cell.key !== ghost.cell.key));
329
+ }
330
+ },
331
+ `leaving:${ghost.cell.key}`
332
+ ))
333
+ ] })
334
+ ]
335
+ }
336
+ );
337
+ });
338
+ var at = (digit) => ({ "--sfi-numbers-at": HOME + digit });
339
+ function Piece({
340
+ cell,
341
+ glyphs,
342
+ transition,
343
+ from,
344
+ generation,
345
+ leaving,
346
+ left,
347
+ onDone
348
+ }) {
349
+ const identity = leaving ? {
350
+ "data-leaving": "",
351
+ "data-ghost": cell.key,
352
+ style: { left },
353
+ onAnimationEnd: onDone
354
+ } : {
355
+ "data-cell": cell.key,
356
+ onAnimationEnd: (event) => event.currentTarget.removeAttribute("data-arriving")
357
+ };
358
+ if (cell.kind === "mark") {
359
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sfi-numbers-mark", ...identity, children: cell.text });
360
+ }
361
+ const swapping = from !== void 0 && from !== cell.digit;
362
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sfi-numbers-slot", "data-digit": cell.digit, ...identity, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sfi-numbers-window", children: transition === "roll" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sfi-numbers-strip", style: at(cell.digit), children: CELLS.map((n, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sfi-numbers-cell", children: glyphs[n] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
363
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
364
+ "span",
365
+ {
366
+ className: "sfi-numbers-face",
367
+ "data-in": swapping ? "" : void 0,
368
+ children: glyphs[cell.digit]
369
+ },
370
+ `in:${cell.digit}:${swapping ? generation : "rest"}`
371
+ ),
372
+ swapping ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sfi-numbers-face", "data-out": "", children: glyphs[from] }, `out:${from}:${generation}`) : null
373
+ ] }) }) });
374
+ }
375
+ // Annotate the CommonJS export names for ESM import in node:
376
+ 0 && (module.exports = {
377
+ Numbers
378
+ });
379
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/Numbers.tsx","../src/utils/cx.ts","../src/utils/refs.ts"],"sourcesContent":["export { Numbers, type NumbersProps, type NumbersTransition } from \"./Numbers\";\n","import * as React from \"react\";\nimport { cx } from \"./utils/cx\";\nimport { mergeRefs } from \"./utils/refs\";\n\nexport type NumbersTransition = \"roll\" | \"tick\" | \"blur\" | \"flip\" | \"scale\";\n\nexport interface NumbersProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, \"children\"> {\n value: number;\n locale?: string | string[];\n format?: Intl.NumberFormatOptions;\n trend?: \"auto\" | \"up\" | \"down\";\n transition?: NumbersTransition;\n blur?: boolean;\n fade?: number;\n duration?: number;\n label?: string;\n}\n\nconst CYCLE = 10;\nconst HOME = CYCLE;\n\nconst CELLS = Array.from({ length: CYCLE * 3 }, (_, i) => i % CYCLE);\n\nfunction numeralsOf(locale: NumbersProps[\"locale\"], format: NumbersProps[\"format\"]) {\n let glyphs = [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"];\n try {\n const numberingSystem = new Intl.NumberFormat(locale, format).resolvedOptions().numberingSystem;\n const plain = new Intl.NumberFormat(locale, { numberingSystem, useGrouping: false });\n glyphs = Array.from({ length: CYCLE }, (_, i) => plain.format(i));\n } catch {\n }\n return { glyphs, index: new Map(glyphs.map((glyph, i) => [glyph, i])) };\n}\n\ntype Cell =\n | { key: string; kind: \"digit\"; digit: number }\n | { key: string; kind: \"mark\"; text: string };\n\nfunction cellsFor(\n value: number,\n locale: NumbersProps[\"locale\"],\n format: NumbersProps[\"format\"],\n index: Map<string, number>,\n): Cell[] {\n const parts = new Intl.NumberFormat(locale, format).formatToParts(value);\n let integers = 0;\n for (const part of parts) if (part.type === \"integer\") integers += part.value.length;\n\n const out: Cell[] = [];\n let seen = 0;\n let fraction = 0;\n let marks = 0;\n for (const part of parts) {\n if (part.type === \"integer\") {\n for (const glyph of part.value) {\n out.push({ key: `d${integers - 1 - seen}`, kind: \"digit\", digit: index.get(glyph) ?? 0 });\n seen += 1;\n }\n } else if (part.type === \"fraction\") {\n for (const glyph of part.value) {\n fraction += 1;\n out.push({ key: `f${fraction}`, kind: \"digit\", digit: index.get(glyph) ?? 0 });\n }\n } else if (part.type === \"group\") {\n out.push({ key: `g${integers - 1 - seen}`, kind: \"mark\", text: part.value });\n } else {\n marks += 1;\n\n out.push({ key: `${part.type}${marks}`, kind: \"mark\", text: part.value });\n }\n }\n return out;\n}\n\nfunction turn(from: number, to: number, dir: 1 | -1): number {\n if (from === to) return 0;\n const forward = ((to - from) * dir % CYCLE + CYCLE) % CYCLE;\n return dir * forward;\n}\n\nfunction ms(value: string, fallback: number): number {\n const n = parseFloat(value);\n if (!Number.isFinite(n)) return fallback;\n return value.trim().endsWith(\"s\") && !value.trim().endsWith(\"ms\") ? n * 1000 : n;\n}\n\nfunction px(value: string, fontSize: number, fallback: number): number {\n const n = parseFloat(value);\n if (!Number.isFinite(n)) return fallback;\n return value.includes(\"em\") ? n * fontSize : n;\n}\n\nconst reduced = () =>\n typeof window !== \"undefined\" && !!window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches;\n\nexport const Numbers = React.forwardRef<HTMLSpanElement, NumbersProps>(function Numbers(\n { value, locale, format, transition = \"roll\", trend = \"auto\", blur = true, fade, duration, label, className, ...props },\n ref,\n) {\n const numerals = React.useMemo(() => numeralsOf(locale, format), [locale, format]);\n const cells = React.useMemo(\n () => cellsFor(value, locale, format, numerals.index),\n [value, locale, format, numerals],\n );\n const text = React.useMemo(() => new Intl.NumberFormat(locale, format).format(value), [value, locale, format]);\n\n const host = React.useRef<HTMLSpanElement | null>(null);\n const row = React.useRef<HTMLSpanElement | null>(null);\n\n const shown = React.useRef(new Map<string, Cell>());\n const seats = React.useRef(new Map<string, number>());\n const previous = React.useRef(value);\n\n const widthSeat = React.useRef<number | null>(null);\n const widthRun = React.useRef<Animation | null>(null);\n\n const [leaving, setLeaving] = React.useState<{ cell: Cell; left: number }[]>([]);\n\n const heading = React.useRef<1 | -1>(1);\n\n const spun = React.useRef(new Set<string>());\n\n const [outgoing, setOutgoing] = React.useState<Map<string, number>>(() => new Map());\n const outgoingTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const [generation, setGeneration] = React.useState(0);\n React.useEffect(() => () => {\n if (outgoingTimer.current) clearTimeout(outgoingTimer.current);\n }, []);\n\n React.useLayoutEffect(() => {\n const rowEl = row.current;\n const hostEl = host.current;\n if (!rowEl || !hostEl) return;\n\n const from = previous.current;\n previous.current = value;\n const still = reduced();\n\n const cs = getComputedStyle(rowEl);\n const fontSize = parseFloat(cs.fontSize) || 16;\n\n const roll = duration ?? ms(cs.getPropertyValue(\"--sfi-numbers-roll\"), 520);\n const shift = ms(cs.getPropertyValue(\"--sfi-settle\"), 240);\n const ease = cs.getPropertyValue(\"--sfi-ease\").trim() || \"ease\";\n const deepest = px(cs.getPropertyValue(\"--sfi-numbers-blur\"), fontSize, fontSize * 0.1);\n\n const dir: 1 | -1 = trend === \"up\" ? 1 : trend === \"down\" ? -1 : value >= from ? 1 : -1;\n heading.current = dir;\n\n const nextSeats = new Map<string, number>();\n const gone = new Map(shown.current);\n const swapped = new Map<string, number>();\n\n rowEl.querySelectorAll<HTMLElement>(\"[data-cell]\").forEach((el) => {\n const key = el.dataset.cell as string;\n const seat = el.offsetLeft;\n nextSeats.set(key, seat);\n gone.delete(key);\n\n const was = seats.current.get(key);\n const before = shown.current.get(key);\n\n if (was === undefined) {\n if (!still && seats.current.size > 0) el.setAttribute(\"data-arriving\", \"\");\n return;\n }\n el.removeAttribute(\"data-arriving\");\n if (still) return;\n\n const dx = was - seat;\n if (Math.abs(dx) >= 1) {\n el.animate([{ transform: `translateX(${dx}px)` }, { transform: \"translateX(0)\" }], {\n duration: shift,\n easing: ease,\n });\n }\n\n const digit = el.dataset.digit;\n if (digit === undefined || !before || before.kind !== \"digit\") return;\n if (Number(digit) === before.digit) return;\n\n if (transition !== \"roll\") {\n swapped.set(key, before.digit);\n return;\n }\n\n const steps = turn(before.digit, Number(digit), dir);\n if (steps === 0) return;\n\n const strip = el.querySelector<HTMLElement>(\".sfi-numbers-strip\");\n const cell = strip?.firstElementChild;\n if (!strip || !cell) return;\n const height = cell.getBoundingClientRect().height;\n if (!height) return;\n\n const start = (HOME + before.digit) * height;\n const turning = strip.animate(\n [{ transform: `translateY(${-start}px)` }, { transform: `translateY(${-(start + steps * height)}px)` }],\n { duration: roll, easing: ease },\n );\n\n el.setAttribute(\"data-turning\", \"\");\n turning.finished.then(() => el.removeAttribute(\"data-turning\")).catch(() => {});\n\n if (blur && deepest > 0) {\n const depth = Math.min(1, Math.abs(steps) / 4) * deepest;\n el.animate(\n [\n { filter: \"blur(0px)\", offset: 0 },\n { filter: `blur(${depth.toFixed(2)}px)`, offset: 0.15 },\n { filter: \"blur(0px)\", offset: 0.62 },\n { filter: \"blur(0px)\", offset: 1 },\n ],\n { duration: roll, easing: \"linear\" },\n );\n }\n });\n\n if (gone.size && !still) {\n const ghosts: { cell: Cell; left: number }[] = [];\n gone.forEach((cell, key) => {\n const seat = seats.current.get(key);\n if (seat !== undefined) ghosts.push({ cell, left: seat });\n });\n if (ghosts.length) setLeaving((current) => [...current, ...ghosts]);\n }\n\n shown.current = new Map(cells.map((cell) => [cell.key, cell]));\n seats.current = nextSeats;\n\n if (swapped.size && !still) {\n hostEl.style.setProperty(\"--sfi-numbers-dir\", String(dir));\n setOutgoing(swapped);\n setGeneration((n) => n + 1);\n if (outgoingTimer.current) clearTimeout(outgoingTimer.current);\n outgoingTimer.current = setTimeout(() => {\n outgoingTimer.current = null;\n setOutgoing(new Map());\n }, roll + 40);\n }\n\n const running = widthRun.current;\n const visual = running ? hostEl.getBoundingClientRect().width : null;\n if (running) {\n running.cancel();\n widthRun.current = null;\n }\n const now = rowEl.offsetWidth;\n const start = visual ?? widthSeat.current;\n widthSeat.current = now;\n if (start === null || Math.abs(start - now) < 0.5 || still) return;\n\n const grow = hostEl.animate([{ width: `${start}px` }, { width: `${now}px` }], {\n duration: shift,\n easing: ease,\n });\n widthRun.current = grow;\n grow.finished\n .then(() => {\n if (widthRun.current === grow) widthRun.current = null;\n })\n .catch(() => {});\n }, [cells, value, trend, blur, duration, transition]);\n\n React.useLayoutEffect(() => {\n const rowEl = row.current;\n if (!rowEl || leaving.length === 0 || reduced()) return;\n\n const cs = getComputedStyle(rowEl);\n const fontSize = parseFloat(cs.fontSize) || 16;\n const going = ms(cs.getPropertyValue(\"--sfi-numbers-exit\"), 240);\n const ease = cs.getPropertyValue(\"--sfi-ease\").trim() || \"ease\";\n const deepest = px(cs.getPropertyValue(\"--sfi-numbers-blur\"), fontSize, fontSize * 0.1);\n const dir = heading.current;\n\n rowEl.querySelectorAll<HTMLElement>(\"[data-ghost]\").forEach((el) => {\n const key = el.dataset.ghost as string;\n if (spun.current.has(key)) return;\n spun.current.add(key);\n const strip = el.querySelector<HTMLElement>(\".sfi-numbers-strip\");\n const cell = strip?.firstElementChild;\n if (!strip || !cell) return;\n const height = cell.getBoundingClientRect().height;\n if (!height) return;\n\n const start = (HOME + Number(el.dataset.digit ?? 0)) * height;\n strip.animate(\n [{ transform: `translateY(${-start}px)` }, { transform: `translateY(${-(start + dir * 3 * height)}px)` }],\n { duration: going, easing: ease },\n );\n if (blur && deepest > 0) {\n el.animate([{ filter: \"blur(0px)\" }, { filter: `blur(${deepest.toFixed(2)}px)` }], {\n duration: going,\n easing: ease,\n });\n }\n });\n }, [leaving, blur, transition]);\n\n const setHost = React.useMemo(() => mergeRefs(ref, host), [ref]);\n\n const knobs: Record<string, string> = {};\n if (duration !== undefined) knobs[\"--sfi-numbers-roll\"] = `${duration}ms`;\n if (fade !== undefined) knobs[\"--sfi-numbers-fade\"] = `${fade}`;\n const style = Object.keys(knobs).length\n ? ({ ...knobs, ...props.style } as React.CSSProperties)\n : props.style;\n\n return (\n <span\n ref={setHost}\n className={cx(\"sfi-numbers\", className)}\n data-transition={transition === \"roll\" ? undefined : transition}\n {...props}\n style={style}\n >\n\n <span className=\"sfi-numbers-said\">{label ?? text}</span>\n <span className=\"sfi-numbers-row\" ref={row} aria-hidden=\"true\">\n {cells.map((cell) => (\n <Piece\n key={cell.key}\n cell={cell}\n glyphs={numerals.glyphs}\n transition={transition}\n from={cell.kind === \"digit\" ? outgoing.get(cell.key) : undefined}\n generation={generation}\n />\n ))}\n {leaving.map((ghost) => (\n <Piece\n key={`leaving:${ghost.cell.key}`}\n cell={ghost.cell}\n glyphs={numerals.glyphs}\n transition={transition}\n generation={generation}\n leaving\n left={ghost.left}\n onDone={() => {\n spun.current.delete(ghost.cell.key);\n setLeaving((current) => current.filter((x) => x.cell.key !== ghost.cell.key));\n }}\n />\n ))}\n </span>\n </span>\n );\n});\n\nconst at = (digit: number) => ({ \"--sfi-numbers-at\": HOME + digit }) as React.CSSProperties;\n\nfunction Piece({\n cell,\n glyphs,\n transition,\n from,\n generation,\n leaving,\n left,\n onDone,\n}: {\n cell: Cell;\n glyphs: string[];\n transition: NumbersTransition;\n from?: number;\n generation: number;\n leaving?: boolean;\n left?: number;\n onDone?: () => void;\n}) {\n const identity = leaving\n ? {\n \"data-leaving\": \"\",\n \"data-ghost\": cell.key,\n style: { left } as React.CSSProperties,\n onAnimationEnd: onDone,\n }\n : {\n \"data-cell\": cell.key,\n onAnimationEnd: (event: React.AnimationEvent<HTMLElement>) =>\n event.currentTarget.removeAttribute(\"data-arriving\"),\n };\n\n if (cell.kind === \"mark\") {\n return (\n <span className=\"sfi-numbers-mark\" {...identity}>\n {cell.text}\n </span>\n );\n }\n\n const swapping = from !== undefined && from !== cell.digit;\n\n return (\n <span className=\"sfi-numbers-slot\" data-digit={cell.digit} {...identity}>\n\n <span className=\"sfi-numbers-window\">\n {transition === \"roll\" ? (\n <span className=\"sfi-numbers-strip\" style={at(cell.digit)}>\n {CELLS.map((n, i) => (\n <span className=\"sfi-numbers-cell\" key={i}>\n {glyphs[n]}\n </span>\n ))}\n </span>\n ) : (\n <>\n <span\n className=\"sfi-numbers-face\"\n\n data-in={swapping ? \"\" : undefined}\n key={`in:${cell.digit}:${swapping ? generation : \"rest\"}`}\n >\n {glyphs[cell.digit]}\n </span>\n {swapping ? (\n <span className=\"sfi-numbers-face\" data-out=\"\" key={`out:${from}:${generation}`}>\n {glyphs[from as number]}\n </span>\n ) : null}\n </>\n )}\n </span>\n </span>\n );\n}\n","export type ClassValue = string | number | null | undefined | false | ClassValue[];\n\nexport function cx(...values: ClassValue[]): string {\n let out = \"\";\n for (const value of values) {\n if (!value && value !== 0) continue;\n const part = Array.isArray(value) ? cx(...value) : String(value);\n if (part) out += out ? \" \" + part : part;\n }\n return out;\n}\n","import type { Ref } from \"react\";\n\nexport function assignRef<T>(ref: Ref<T> | undefined, value: T | null): void {\n if (!ref) return;\n if (typeof ref === \"function\") ref(value);\n else (ref as { current: T | null }).current = value;\n}\n\nexport function mergeRefs<T>(...refs: Array<Ref<T> | undefined>): (value: T | null) => void {\n return (value) => {\n for (const ref of refs) assignRef(ref, value);\n };\n}\n"],"mappings":"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","names":["Numbers","start"]}
@@ -0,0 +1,17 @@
1
+ import * as React from 'react';
2
+
3
+ type NumbersTransition = "roll" | "tick" | "blur" | "flip" | "scale";
4
+ interface NumbersProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> {
5
+ value: number;
6
+ locale?: string | string[];
7
+ format?: Intl.NumberFormatOptions;
8
+ trend?: "auto" | "up" | "down";
9
+ transition?: NumbersTransition;
10
+ blur?: boolean;
11
+ fade?: number;
12
+ duration?: number;
13
+ label?: string;
14
+ }
15
+ declare const Numbers: React.ForwardRefExoticComponent<NumbersProps & React.RefAttributes<HTMLSpanElement>>;
16
+
17
+ export { Numbers, type NumbersProps, type NumbersTransition };
@@ -0,0 +1,17 @@
1
+ import * as React from 'react';
2
+
3
+ type NumbersTransition = "roll" | "tick" | "blur" | "flip" | "scale";
4
+ interface NumbersProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> {
5
+ value: number;
6
+ locale?: string | string[];
7
+ format?: Intl.NumberFormatOptions;
8
+ trend?: "auto" | "up" | "down";
9
+ transition?: NumbersTransition;
10
+ blur?: boolean;
11
+ fade?: number;
12
+ duration?: number;
13
+ label?: string;
14
+ }
15
+ declare const Numbers: React.ForwardRefExoticComponent<NumbersProps & React.RefAttributes<HTMLSpanElement>>;
16
+
17
+ export { Numbers, type NumbersProps, type NumbersTransition };