@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 +21 -0
- package/README.md +150 -0
- package/dist/index.cjs +379 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +343 -0
- package/dist/index.js.map +1 -0
- package/dist/numbers.css +333 -0
- package/package.json +63 -0
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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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 };
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|