@w3ux/react-odometer 2.1.0 → 2.2.2

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/index.cjs ADDED
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.tsx
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Odometer: () => Odometer,
24
+ default: () => index_default
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var import_react = require("react");
28
+ var import_jsx_runtime = require("react/jsx-runtime");
29
+ var Odometer = ({
30
+ value,
31
+ spaceBefore = 0,
32
+ spaceAfter = "0.25rem",
33
+ wholeColor = "var(--text-color-primary)",
34
+ decimalColor = "var(--text-color-secondary)",
35
+ zeroDecimals = 0
36
+ }) => {
37
+ const [allDigits] = (0, import_react.useState)([
38
+ "comma",
39
+ "dot",
40
+ "0",
41
+ "1",
42
+ "2",
43
+ "3",
44
+ "4",
45
+ "5",
46
+ "6",
47
+ "7",
48
+ "8",
49
+ "9"
50
+ ]);
51
+ const [digits, setDigits] = (0, import_react.useState)([]);
52
+ const [prevDigits, setPrevDigits] = (0, import_react.useState)([]);
53
+ const [status, setStatus] = (0, import_react.useState)("inactive");
54
+ const [initialized, setInitialized] = (0, import_react.useState)(false);
55
+ const [odometerRef] = (0, import_react.useState)((0, import_react.createRef)());
56
+ const [digitRefs, setDigitRefs] = (0, import_react.useState)([]);
57
+ const [allDigitRefs, setAllDigitRefs] = (0, import_react.useState)({});
58
+ const activeTransitionCounter = (0, import_react.useRef)(0);
59
+ const DURATION_MS = 750;
60
+ const DURATION_SECS = `${DURATION_MS / 1e3}s`;
61
+ (0, import_react.useEffect)(() => {
62
+ const all = Object.fromEntries(
63
+ Object.values(allDigits).map((v) => [`d_${v}`, (0, import_react.createRef)()])
64
+ );
65
+ setAllDigitRefs(all);
66
+ }, []);
67
+ (0, import_react.useEffect)(() => {
68
+ if (Object.keys(allDigitRefs)) {
69
+ value = String(value) === "0" ? Number(value).toFixed(zeroDecimals) : value;
70
+ const newDigits = value.toString().split("").map((v) => v === "." ? "dot" : v).map((v) => v === "," ? "comma" : v);
71
+ setDigits(newDigits);
72
+ if (!initialized) {
73
+ setInitialized(true);
74
+ } else {
75
+ setStatus("new");
76
+ setPrevDigits(digits);
77
+ }
78
+ setDigitRefs(Array(newDigits.length).fill((0, import_react.createRef)()));
79
+ }
80
+ }, [value]);
81
+ (0, import_react.useEffect)(() => {
82
+ if (status === "new" && !digitRefs.find((d) => d.current === null)) {
83
+ setStatus("transition");
84
+ activeTransitionCounter.current++;
85
+ setTimeout(() => {
86
+ activeTransitionCounter.current--;
87
+ if (activeTransitionCounter.current === 0) {
88
+ setStatus("inactive");
89
+ }
90
+ }, DURATION_MS);
91
+ }
92
+ }, [status, digitRefs]);
93
+ const odometerCurrent = odometerRef.current;
94
+ let lineHeight = odometerCurrent ? window.getComputedStyle(odometerCurrent).lineHeight : "inherit";
95
+ lineHeight = lineHeight === "normal" ? "1.1rem" : lineHeight;
96
+ let foundDecimal = false;
97
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
98
+ allDigits.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
99
+ "span",
100
+ {
101
+ ref: allDigitRefs[`d_${d}`],
102
+ style: {
103
+ opacity: 0,
104
+ position: "fixed",
105
+ top: "-999%",
106
+ left: "-999%",
107
+ userSelect: "none"
108
+ },
109
+ children: d === "dot" ? "." : d === "comma" ? "," : d
110
+ },
111
+ `odometer_template_digit_${i}`
112
+ )),
113
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "odometer", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "odometer-inner", ref: odometerRef, children: [
114
+ spaceBefore ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { paddingLeft: spaceBefore } }) : null,
115
+ digits.map((d, i) => {
116
+ if (d === "dot") {
117
+ foundDecimal = true;
118
+ }
119
+ let childDigits = null;
120
+ if (status === "transition") {
121
+ const digitsToAnimate = [];
122
+ const digitIndex = allDigits.indexOf(digits[i]);
123
+ const prevDigitIndex = allDigits.indexOf(prevDigits[i]);
124
+ const difference = Math.abs(digitIndex - prevDigitIndex);
125
+ const delay = `${0.01 * (digits.length - i - 1)}s`;
126
+ const direction = digitIndex === prevDigitIndex ? "none" : "down";
127
+ const animClass = `slide-${direction}-${difference} `;
128
+ digitsToAnimate.push(prevDigits[i]);
129
+ if (digitIndex < prevDigitIndex) {
130
+ digitsToAnimate.push(
131
+ ...Array.from(
132
+ { length: difference },
133
+ (_, k) => allDigits[prevDigitIndex - k - 1]
134
+ )
135
+ );
136
+ } else {
137
+ digitsToAnimate.push(
138
+ ...Array.from(
139
+ { length: difference },
140
+ (_, k) => allDigits[k + prevDigitIndex + 1]
141
+ )
142
+ );
143
+ }
144
+ childDigits = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
145
+ "span",
146
+ {
147
+ style: {
148
+ position: "absolute",
149
+ top: 0,
150
+ left: 0,
151
+ animationName: direction === "none" ? void 0 : animClass,
152
+ animationDuration: direction === "none" ? void 0 : DURATION_SECS,
153
+ animationFillMode: "forwards",
154
+ animationTimingFunction: "cubic-bezier(0.1, 1, 0.2, 1)",
155
+ animationDelay: delay,
156
+ color: foundDecimal ? decimalColor : wholeColor,
157
+ userSelect: "none"
158
+ },
159
+ children: digitsToAnimate.map((c, j) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
160
+ "span",
161
+ {
162
+ className: "odometer-digit odometer-child",
163
+ style: {
164
+ top: j === 0 ? 0 : `${100 * j}%`,
165
+ height: lineHeight,
166
+ lineHeight
167
+ },
168
+ children: c === "dot" ? "." : c === "comma" ? "," : c
169
+ },
170
+ `child_digit_${j}`
171
+ ))
172
+ }
173
+ );
174
+ }
175
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
176
+ "span",
177
+ {
178
+ ref: digitRefs[i],
179
+ className: "odometer-digit",
180
+ style: {
181
+ color: foundDecimal ? decimalColor : wholeColor,
182
+ height: lineHeight,
183
+ lineHeight,
184
+ paddingRight: status === "transition" ? `${allDigitRefs[`d_${d}`]?.current?.offsetWidth}px` : "0"
185
+ },
186
+ children: [
187
+ status === "inactive" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
188
+ "span",
189
+ {
190
+ className: "odometer-digit odometer-child",
191
+ style: {
192
+ top: 0,
193
+ height: lineHeight,
194
+ lineHeight,
195
+ width: `${allDigitRefs[`d_${d}`]?.current?.offsetWidth}px`
196
+ },
197
+ children: d === "dot" ? "." : d === "comma" ? "," : d
198
+ }
199
+ ),
200
+ status === "transition" && childDigits
201
+ ]
202
+ },
203
+ `digit_${i}`
204
+ );
205
+ }),
206
+ spaceAfter ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { paddingRight: spaceAfter } }) : null
207
+ ] }) })
208
+ ] });
209
+ };
210
+ var index_default = Odometer;
211
+ // Annotate the CommonJS export names for ESM import in node:
212
+ 0 && (module.exports = {
213
+ Odometer
214
+ });
215
+ /* @license Copyright 2024 w3ux authors & contributors
216
+ SPDX-License-Identifier: GPL-3.0-only */
@@ -8,22 +8,22 @@ SPDX-License-Identifier: GPL-3.0-only */
8
8
  height: inherit;
9
9
  line-height: inherit;
10
10
  width: fit-content;
11
- }
12
- .odometer > .odometer-inner {
13
- display: inline-block;
14
- vertical-align: bottom;
15
- }
16
- .odometer > .odometer-inner .odometer-digit {
17
- display: inline-block;
18
- position: relative;
19
- vertical-align: bottom;
20
- z-index: 3;
21
- }
22
- .odometer > .odometer-inner .odometer-digit.odometer-child {
23
- display: flex;
24
- align-items: flex-end;
25
- position: relative;
26
- left: 0;
11
+ > .odometer-inner {
12
+ display: inline-block;
13
+ vertical-align: bottom;
14
+ .odometer-digit {
15
+ display: inline-block;
16
+ position: relative;
17
+ vertical-align: bottom;
18
+ z-index: 3;
19
+ &.odometer-child {
20
+ display: flex;
21
+ align-items: flex-end;
22
+ position: relative;
23
+ left: 0;
24
+ }
25
+ }
26
+ }
27
27
  }
28
28
 
29
29
  /* NOTE: Stylelint does not like inline scss variables to generate these. */
@@ -218,4 +218,6 @@ SPDX-License-Identifier: GPL-3.0-only */
218
218
  to {
219
219
  top: -1200%;
220
220
  }
221
- }
221
+ }
222
+
223
+ /*# sourceMappingURL=index.css.map */
package/index.css.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sourceRoot":"","sources":["../src/index.css"],"names":[],"mappings":"AAAA;AAAA;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;IACE;IACA;IAEA;MACE;MACA;MACA;MACA;MAEA;QACE;QACA;QACA;QACA;;;;;;AAMR;AAEA;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;IACE;;EAGF;IACE","file":"index.css"}
package/index.d.cts ADDED
@@ -0,0 +1,14 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface Props {
4
+ value: number | string;
5
+ wholeColor?: string;
6
+ decimalColor?: string;
7
+ spaceBefore?: string | number;
8
+ spaceAfter?: string | number;
9
+ zeroDecimals?: number;
10
+ }
11
+
12
+ declare const Odometer: ({ value, spaceBefore, spaceAfter, wholeColor, decimalColor, zeroDecimals, }: Props) => react_jsx_runtime.JSX.Element;
13
+
14
+ export { Odometer, Odometer as default };
package/index.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface Props {
4
+ value: number | string;
5
+ wholeColor?: string;
6
+ decimalColor?: string;
7
+ spaceBefore?: string | number;
8
+ spaceAfter?: string | number;
9
+ zeroDecimals?: number;
10
+ }
11
+
12
+ declare const Odometer: ({ value, spaceBefore, spaceAfter, wholeColor, decimalColor, zeroDecimals, }: Props) => react_jsx_runtime.JSX.Element;
13
+
14
+ export { Odometer, Odometer as default };
package/index.js ADDED
@@ -0,0 +1,191 @@
1
+ // src/index.tsx
2
+ import { createRef, useEffect, useRef, useState } from "react";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ var Odometer = ({
5
+ value,
6
+ spaceBefore = 0,
7
+ spaceAfter = "0.25rem",
8
+ wholeColor = "var(--text-color-primary)",
9
+ decimalColor = "var(--text-color-secondary)",
10
+ zeroDecimals = 0
11
+ }) => {
12
+ const [allDigits] = useState([
13
+ "comma",
14
+ "dot",
15
+ "0",
16
+ "1",
17
+ "2",
18
+ "3",
19
+ "4",
20
+ "5",
21
+ "6",
22
+ "7",
23
+ "8",
24
+ "9"
25
+ ]);
26
+ const [digits, setDigits] = useState([]);
27
+ const [prevDigits, setPrevDigits] = useState([]);
28
+ const [status, setStatus] = useState("inactive");
29
+ const [initialized, setInitialized] = useState(false);
30
+ const [odometerRef] = useState(createRef());
31
+ const [digitRefs, setDigitRefs] = useState([]);
32
+ const [allDigitRefs, setAllDigitRefs] = useState({});
33
+ const activeTransitionCounter = useRef(0);
34
+ const DURATION_MS = 750;
35
+ const DURATION_SECS = `${DURATION_MS / 1e3}s`;
36
+ useEffect(() => {
37
+ const all = Object.fromEntries(
38
+ Object.values(allDigits).map((v) => [`d_${v}`, createRef()])
39
+ );
40
+ setAllDigitRefs(all);
41
+ }, []);
42
+ useEffect(() => {
43
+ if (Object.keys(allDigitRefs)) {
44
+ value = String(value) === "0" ? Number(value).toFixed(zeroDecimals) : value;
45
+ const newDigits = value.toString().split("").map((v) => v === "." ? "dot" : v).map((v) => v === "," ? "comma" : v);
46
+ setDigits(newDigits);
47
+ if (!initialized) {
48
+ setInitialized(true);
49
+ } else {
50
+ setStatus("new");
51
+ setPrevDigits(digits);
52
+ }
53
+ setDigitRefs(Array(newDigits.length).fill(createRef()));
54
+ }
55
+ }, [value]);
56
+ useEffect(() => {
57
+ if (status === "new" && !digitRefs.find((d) => d.current === null)) {
58
+ setStatus("transition");
59
+ activeTransitionCounter.current++;
60
+ setTimeout(() => {
61
+ activeTransitionCounter.current--;
62
+ if (activeTransitionCounter.current === 0) {
63
+ setStatus("inactive");
64
+ }
65
+ }, DURATION_MS);
66
+ }
67
+ }, [status, digitRefs]);
68
+ const odometerCurrent = odometerRef.current;
69
+ let lineHeight = odometerCurrent ? window.getComputedStyle(odometerCurrent).lineHeight : "inherit";
70
+ lineHeight = lineHeight === "normal" ? "1.1rem" : lineHeight;
71
+ let foundDecimal = false;
72
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
73
+ allDigits.map((d, i) => /* @__PURE__ */ jsx(
74
+ "span",
75
+ {
76
+ ref: allDigitRefs[`d_${d}`],
77
+ style: {
78
+ opacity: 0,
79
+ position: "fixed",
80
+ top: "-999%",
81
+ left: "-999%",
82
+ userSelect: "none"
83
+ },
84
+ children: d === "dot" ? "." : d === "comma" ? "," : d
85
+ },
86
+ `odometer_template_digit_${i}`
87
+ )),
88
+ /* @__PURE__ */ jsx("span", { className: "odometer", children: /* @__PURE__ */ jsxs("span", { className: "odometer-inner", ref: odometerRef, children: [
89
+ spaceBefore ? /* @__PURE__ */ jsx("span", { style: { paddingLeft: spaceBefore } }) : null,
90
+ digits.map((d, i) => {
91
+ if (d === "dot") {
92
+ foundDecimal = true;
93
+ }
94
+ let childDigits = null;
95
+ if (status === "transition") {
96
+ const digitsToAnimate = [];
97
+ const digitIndex = allDigits.indexOf(digits[i]);
98
+ const prevDigitIndex = allDigits.indexOf(prevDigits[i]);
99
+ const difference = Math.abs(digitIndex - prevDigitIndex);
100
+ const delay = `${0.01 * (digits.length - i - 1)}s`;
101
+ const direction = digitIndex === prevDigitIndex ? "none" : "down";
102
+ const animClass = `slide-${direction}-${difference} `;
103
+ digitsToAnimate.push(prevDigits[i]);
104
+ if (digitIndex < prevDigitIndex) {
105
+ digitsToAnimate.push(
106
+ ...Array.from(
107
+ { length: difference },
108
+ (_, k) => allDigits[prevDigitIndex - k - 1]
109
+ )
110
+ );
111
+ } else {
112
+ digitsToAnimate.push(
113
+ ...Array.from(
114
+ { length: difference },
115
+ (_, k) => allDigits[k + prevDigitIndex + 1]
116
+ )
117
+ );
118
+ }
119
+ childDigits = /* @__PURE__ */ jsx(
120
+ "span",
121
+ {
122
+ style: {
123
+ position: "absolute",
124
+ top: 0,
125
+ left: 0,
126
+ animationName: direction === "none" ? void 0 : animClass,
127
+ animationDuration: direction === "none" ? void 0 : DURATION_SECS,
128
+ animationFillMode: "forwards",
129
+ animationTimingFunction: "cubic-bezier(0.1, 1, 0.2, 1)",
130
+ animationDelay: delay,
131
+ color: foundDecimal ? decimalColor : wholeColor,
132
+ userSelect: "none"
133
+ },
134
+ children: digitsToAnimate.map((c, j) => /* @__PURE__ */ jsx(
135
+ "span",
136
+ {
137
+ className: "odometer-digit odometer-child",
138
+ style: {
139
+ top: j === 0 ? 0 : `${100 * j}%`,
140
+ height: lineHeight,
141
+ lineHeight
142
+ },
143
+ children: c === "dot" ? "." : c === "comma" ? "," : c
144
+ },
145
+ `child_digit_${j}`
146
+ ))
147
+ }
148
+ );
149
+ }
150
+ return /* @__PURE__ */ jsxs(
151
+ "span",
152
+ {
153
+ ref: digitRefs[i],
154
+ className: "odometer-digit",
155
+ style: {
156
+ color: foundDecimal ? decimalColor : wholeColor,
157
+ height: lineHeight,
158
+ lineHeight,
159
+ paddingRight: status === "transition" ? `${allDigitRefs[`d_${d}`]?.current?.offsetWidth}px` : "0"
160
+ },
161
+ children: [
162
+ status === "inactive" && /* @__PURE__ */ jsx(
163
+ "span",
164
+ {
165
+ className: "odometer-digit odometer-child",
166
+ style: {
167
+ top: 0,
168
+ height: lineHeight,
169
+ lineHeight,
170
+ width: `${allDigitRefs[`d_${d}`]?.current?.offsetWidth}px`
171
+ },
172
+ children: d === "dot" ? "." : d === "comma" ? "," : d
173
+ }
174
+ ),
175
+ status === "transition" && childDigits
176
+ ]
177
+ },
178
+ `digit_${i}`
179
+ );
180
+ }),
181
+ spaceAfter ? /* @__PURE__ */ jsx("span", { style: { paddingRight: spaceAfter } }) : null
182
+ ] }) })
183
+ ] });
184
+ };
185
+ var index_default = Odometer;
186
+ export {
187
+ Odometer,
188
+ index_default as default
189
+ };
190
+ /* @license Copyright 2024 w3ux authors & contributors
191
+ SPDX-License-Identifier: GPL-3.0-only */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@w3ux/react-odometer",
3
- "version": "2.1.0",
3
+ "version": "2.2.2",
4
4
  "license": "GPL-3.0-only",
5
5
  "type": "module",
6
6
  "description": "An odometer effect used for number and balance transitions",
@@ -25,8 +25,12 @@
25
25
  },
26
26
  "exports": {
27
27
  ".": {
28
- "import": "./mjs/index.js",
29
- "require": "./cjs/index.js"
28
+ "import": "./index.js",
29
+ "require": "./index.cjs"
30
30
  }
31
+ },
32
+ "peerDependencies": {
33
+ "react": "^19.1.0",
34
+ "react-dom": "^19.1.0"
31
35
  }
32
36
  }
package/cjs/index.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import './index.css';
2
- import type { Props } from './types';
3
- export declare const Odometer: ({ value, spaceBefore, spaceAfter, wholeColor, decimalColor, zeroDecimals, }: Props) => import("react/jsx-runtime").JSX.Element;
4
- export default Odometer;
package/cjs/index.js DELETED
@@ -1,138 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Odometer = void 0;
4
- const jsx_runtime_1 = require("react/jsx-runtime");
5
- const react_1 = require("react");
6
- require("./index.css");
7
- const Odometer = ({ value, spaceBefore = 0, spaceAfter = '0.25rem', wholeColor = 'var(--text-color-primary)', decimalColor = 'var(--text-color-secondary)', zeroDecimals = 0, }) => {
8
- const [allDigits] = (0, react_1.useState)([
9
- 'comma',
10
- 'dot',
11
- '0',
12
- '1',
13
- '2',
14
- '3',
15
- '4',
16
- '5',
17
- '6',
18
- '7',
19
- '8',
20
- '9',
21
- ]);
22
- const [digits, setDigits] = (0, react_1.useState)([]);
23
- const [prevDigits, setPrevDigits] = (0, react_1.useState)([]);
24
- const [status, setStatus] = (0, react_1.useState)('inactive');
25
- const [initialized, setInitialized] = (0, react_1.useState)(false);
26
- const [odometerRef] = (0, react_1.useState)((0, react_1.createRef)());
27
- const [digitRefs, setDigitRefs] = (0, react_1.useState)([]);
28
- const [allDigitRefs, setAllDigitRefs] = (0, react_1.useState)({});
29
- const activeTransitionCounter = (0, react_1.useRef)(0);
30
- const DURATION_MS = 750;
31
- const DURATION_SECS = `${DURATION_MS / 1000}s`;
32
- (0, react_1.useEffect)(() => {
33
- const all = Object.fromEntries(Object.values(allDigits).map((v) => [`d_${v}`, (0, react_1.createRef)()]));
34
- setAllDigitRefs(all);
35
- }, []);
36
- (0, react_1.useEffect)(() => {
37
- if (Object.keys(allDigitRefs)) {
38
- value =
39
- String(value) === '0' ? Number(value).toFixed(zeroDecimals) : value;
40
- const newDigits = value
41
- .toString()
42
- .split('')
43
- .map((v) => (v === '.' ? 'dot' : v))
44
- .map((v) => (v === ',' ? 'comma' : v));
45
- setDigits(newDigits);
46
- if (!initialized) {
47
- setInitialized(true);
48
- }
49
- else {
50
- setStatus('new');
51
- setPrevDigits(digits);
52
- }
53
- setDigitRefs(Array(newDigits.length).fill((0, react_1.createRef)()));
54
- }
55
- }, [value]);
56
- (0, react_1.useEffect)(() => {
57
- if (status === 'new' && !digitRefs.find((d) => d.current === null)) {
58
- setStatus('transition');
59
- activeTransitionCounter.current++;
60
- setTimeout(() => {
61
- activeTransitionCounter.current--;
62
- if (activeTransitionCounter.current === 0) {
63
- setStatus('inactive');
64
- }
65
- }, DURATION_MS);
66
- }
67
- }, [status, digitRefs]);
68
- const odometerCurrent = odometerRef.current;
69
- let lineHeight = odometerCurrent
70
- ? window.getComputedStyle(odometerCurrent).lineHeight
71
- : 'inherit';
72
- lineHeight = lineHeight === 'normal' ? '1.1rem' : lineHeight;
73
- let foundDecimal = false;
74
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [allDigits.map((d, i) => ((0, jsx_runtime_1.jsx)("span", { ref: allDigitRefs[`d_${d}`], style: {
75
- opacity: 0,
76
- position: 'fixed',
77
- top: '-999%',
78
- left: '-999%',
79
- userSelect: 'none',
80
- }, children: d === 'dot' ? '.' : d === 'comma' ? ',' : d }, `odometer_template_digit_${i}`))), (0, jsx_runtime_1.jsx)("span", { className: "odometer", children: (0, jsx_runtime_1.jsxs)("span", { className: "odometer-inner", ref: odometerRef, children: [spaceBefore ? (0, jsx_runtime_1.jsx)("span", { style: { paddingLeft: spaceBefore } }) : null, digits.map((d, i) => {
81
- var _a, _b, _c, _d;
82
- if (d === 'dot') {
83
- foundDecimal = true;
84
- }
85
- let childDigits = null;
86
- if (status === 'transition') {
87
- const digitsToAnimate = [];
88
- const digitIndex = allDigits.indexOf(digits[i]);
89
- const prevDigitIndex = allDigits.indexOf(prevDigits[i]);
90
- const difference = Math.abs(digitIndex - prevDigitIndex);
91
- const delay = `${0.01 * (digits.length - i - 1)}s`;
92
- const direction = digitIndex === prevDigitIndex ? 'none' : 'down';
93
- const animClass = `slide-${direction}-${difference} `;
94
- digitsToAnimate.push(prevDigits[i]);
95
- if (digitIndex < prevDigitIndex) {
96
- digitsToAnimate.push(...Array.from({ length: difference }, (_, k) => allDigits[prevDigitIndex - k - 1]));
97
- }
98
- else {
99
- digitsToAnimate.push(...Array.from({ length: difference }, (_, k) => allDigits[k + prevDigitIndex + 1]));
100
- }
101
- childDigits = ((0, jsx_runtime_1.jsx)("span", { style: {
102
- position: 'absolute',
103
- top: 0,
104
- left: 0,
105
- animationName: direction === 'none' ? undefined : animClass,
106
- animationDuration: direction === 'none' ? undefined : DURATION_SECS,
107
- animationFillMode: 'forwards',
108
- animationTimingFunction: 'cubic-bezier(0.1, 1, 0.2, 1)',
109
- animationDelay: delay,
110
- color: foundDecimal ? decimalColor : wholeColor,
111
- userSelect: 'none',
112
- }, children: digitsToAnimate.map((c, j) => ((0, jsx_runtime_1.jsx)("span", { className: "odometer-digit odometer-child", style: {
113
- top: j === 0 ? 0 : `${100 * j}%`,
114
- height: lineHeight,
115
- lineHeight,
116
- }, children: c === 'dot' ? '.' : c === 'comma' ? ',' : c }, `child_digit_${j}`))) }));
117
- }
118
- return ((0, jsx_runtime_1.jsxs)("span", { ref: digitRefs[i], className: "odometer-digit", style: {
119
- color: foundDecimal ? decimalColor : wholeColor,
120
- height: lineHeight,
121
- lineHeight,
122
- paddingRight: status === 'transition'
123
- ? `${(_b = (_a = allDigitRefs[`d_${d}`]) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.offsetWidth}px`
124
- : '0',
125
- }, children: [status === 'inactive' && ((0, jsx_runtime_1.jsx)("span", { className: "odometer-digit odometer-child", style: {
126
- top: 0,
127
- height: lineHeight,
128
- lineHeight,
129
- width: `${(_d = (_c = allDigitRefs[`d_${d}`]) === null || _c === void 0 ? void 0 : _c.current) === null || _d === void 0 ? void 0 : _d.offsetWidth}px`,
130
- }, children: d === 'dot' ? '.' : d === 'comma' ? ',' : d })), status === 'transition' && childDigits] }, `digit_${i}`));
131
- }), spaceAfter ? (0, jsx_runtime_1.jsx)("span", { style: { paddingRight: spaceAfter } }) : null] }) })] }));
132
- };
133
- exports.Odometer = Odometer;
134
- exports.default = exports.Odometer;
135
-
136
- //# sourceMappingURL=index.js.map
137
-
138
- //# sourceMappingURL=index.js.map
package/cjs/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/* @license Copyright 2024 w3ux authors & contributors\nSPDX-License-Identifier: GPL-3.0-only */\n\nimport type { RefObject } from 'react'\nimport { createRef, useEffect, useRef, useState } from 'react'\nimport './index.css'\nimport type { Digit, DigitRef, Direction, Props, Status } from './types'\n\nexport const Odometer = ({\n value,\n spaceBefore = 0,\n spaceAfter = '0.25rem',\n wholeColor = 'var(--text-color-primary)',\n decimalColor = 'var(--text-color-secondary)',\n zeroDecimals = 0,\n}: Props) => {\n // Store all possible digits.\n const [allDigits] = useState<Digit[]>([\n 'comma',\n 'dot',\n '0',\n '1',\n '2',\n '3',\n '4',\n '5',\n '6',\n '7',\n '8',\n '9',\n ])\n\n // Store the digits of the current value.\n const [digits, setDigits] = useState<Digit[]>([])\n\n // Store digits of the previous value.\n const [prevDigits, setPrevDigits] = useState<Digit[]>([])\n\n // Store the status of the odometer (transitioning or stable).\n const [status, setStatus] = useState<Status>('inactive')\n\n // Store whether component has iniiialized.\n const [initialized, setInitialized] = useState<boolean>(false)\n\n // Store ref of the odometer.\n const [odometerRef] = useState(createRef<HTMLSpanElement>())\n\n // Store refs of each digit.\n const [digitRefs, setDigitRefs] = useState<DigitRef[]>([])\n\n // Store refs of each `all` digit.\n const [allDigitRefs, setAllDigitRefs] = useState<\n Record<string, RefObject<HTMLSpanElement | null>>\n >({})\n\n // Keep track of active transitions.\n const activeTransitionCounter = useRef<number>(0)\n\n // Transition duration.\n const DURATION_MS = 750\n const DURATION_SECS = `${DURATION_MS / 1000}s`\n\n // Phase 0: populate `allDigitRefs`.\n useEffect(() => {\n const all: Record<\n string,\n RefObject<HTMLSpanElement | null>\n > = Object.fromEntries(\n Object.values(allDigits).map((v) => [`d_${v}`, createRef()])\n )\n\n setAllDigitRefs(all)\n }, [])\n\n // Phase 1: new digits and refs are added to the odometer.\n useEffect(() => {\n if (Object.keys(allDigitRefs)) {\n value =\n String(value) === '0' ? Number(value).toFixed(zeroDecimals) : value\n\n const newDigits = value\n .toString()\n .split('')\n .map((v) => (v === '.' ? 'dot' : v))\n .map((v) => (v === ',' ? 'comma' : v)) as Digit[]\n\n setDigits(newDigits)\n\n if (!initialized) {\n setInitialized(true)\n } else {\n setStatus('new')\n setPrevDigits(digits)\n }\n setDigitRefs(Array(newDigits.length).fill(createRef()))\n }\n }, [value])\n\n // Phase 2: set up digit transition.\n useEffect(() => {\n if (status === 'new' && !digitRefs.find((d) => d.current === null)) {\n setStatus('transition')\n activeTransitionCounter.current++\n\n setTimeout(() => {\n activeTransitionCounter.current--\n if (activeTransitionCounter.current === 0) {\n setStatus('inactive')\n }\n }, DURATION_MS)\n }\n }, [status, digitRefs])\n\n const odometerCurrent: HTMLSpanElement | null = odometerRef.current\n let lineHeight = odometerCurrent\n ? window.getComputedStyle(odometerCurrent).lineHeight\n : 'inherit'\n\n // Fallback line height to `1.1rem` if `normal`.\n lineHeight = lineHeight === 'normal' ? '1.1rem' : lineHeight\n\n // Track whether decimal point has been found.\n let foundDecimal = false\n\n return (\n <>\n {allDigits.map((d, i) => (\n <span\n key={`odometer_template_digit_${i}`}\n ref={allDigitRefs[`d_${d}`]}\n style={{\n opacity: 0,\n position: 'fixed',\n top: '-999%',\n left: '-999%',\n userSelect: 'none',\n }}\n >\n {d === 'dot' ? '.' : d === 'comma' ? ',' : d}\n </span>\n ))}\n <span className=\"odometer\">\n <span className=\"odometer-inner\" ref={odometerRef}>\n {spaceBefore ? <span style={{ paddingLeft: spaceBefore }} /> : null}\n {digits.map((d, i) => {\n if (d === 'dot') {\n foundDecimal = true\n }\n\n // If transitioning, get digits needed to animate.\n let childDigits = null\n if (status === 'transition') {\n const digitsToAnimate = []\n const digitIndex = allDigits.indexOf(digits[i])\n const prevDigitIndex = allDigits.indexOf(prevDigits[i])\n const difference = Math.abs(digitIndex - prevDigitIndex)\n const delay = `${0.01 * (digits.length - i - 1)}s`\n const direction: Direction =\n digitIndex === prevDigitIndex ? 'none' : 'down'\n const animClass = `slide-${direction}-${difference} `\n\n // Push current prev digit to stop of stack.\n digitsToAnimate.push(prevDigits[i])\n\n // If transitioning between two digits, animate all digits in between.\n if (digitIndex < prevDigitIndex) {\n digitsToAnimate.push(\n ...Array.from(\n { length: difference },\n (_, k) => allDigits[prevDigitIndex - k - 1]\n )\n )\n } else {\n digitsToAnimate.push(\n ...Array.from(\n { length: difference },\n (_, k) => allDigits[k + prevDigitIndex + 1]\n )\n )\n }\n\n childDigits = (\n <span\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n animationName: direction === 'none' ? undefined : animClass,\n animationDuration:\n direction === 'none' ? undefined : DURATION_SECS,\n animationFillMode: 'forwards',\n animationTimingFunction: 'cubic-bezier(0.1, 1, 0.2, 1)',\n animationDelay: delay,\n color: foundDecimal ? decimalColor : wholeColor,\n userSelect: 'none',\n }}\n >\n {digitsToAnimate.map((c, j) => (\n <span\n key={`child_digit_${j}`}\n className=\"odometer-digit odometer-child\"\n style={{\n top: j === 0 ? 0 : `${100 * j}%`,\n height: lineHeight,\n lineHeight,\n }}\n >\n {c === 'dot' ? '.' : c === 'comma' ? ',' : c}\n </span>\n ))}\n </span>\n )\n }\n\n return (\n <span\n key={`digit_${i}`}\n ref={digitRefs[i]}\n className=\"odometer-digit\"\n style={{\n color: foundDecimal ? decimalColor : wholeColor,\n height: lineHeight,\n lineHeight,\n paddingRight:\n status === 'transition'\n ? `${allDigitRefs[`d_${d}`]?.current?.offsetWidth}px`\n : '0',\n }}\n >\n {status === 'inactive' && (\n <span\n className=\"odometer-digit odometer-child\"\n style={{\n top: 0,\n height: lineHeight,\n lineHeight,\n width: `${\n allDigitRefs[`d_${d}`]?.current?.offsetWidth\n }px`,\n }}\n >\n {d === 'dot' ? '.' : d === 'comma' ? ',' : d}\n </span>\n )}\n {status === 'transition' && childDigits}\n </span>\n )\n })}\n {spaceAfter ? <span style={{ paddingRight: spaceAfter }} /> : null}\n </span>\n </span>\n </>\n )\n}\n\nexport default Odometer\n"]}
package/cjs/types.d.ts DELETED
@@ -1,13 +0,0 @@
1
- import type { RefObject } from 'react';
2
- export interface Props {
3
- value: number | string;
4
- wholeColor?: string;
5
- decimalColor?: string;
6
- spaceBefore?: string | number;
7
- spaceAfter?: string | number;
8
- zeroDecimals?: number;
9
- }
10
- export type Digit = 'comma' | 'dot' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
11
- export type DigitRef = RefObject<HTMLSpanElement>;
12
- export type Status = 'new' | 'inactive' | 'transition' | 'finished';
13
- export type Direction = 'down' | 'none';
package/cjs/types.js DELETED
@@ -1,6 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
-
4
- //# sourceMappingURL=types.js.map
5
-
6
- //# sourceMappingURL=types.js.map
package/cjs/types.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/types.ts"],"names":[],"mappings":"","file":"types.js","sourcesContent":["/* @license Copyright 2024 w3ux authors & contributors\nSPDX-License-Identifier: GPL-3.0-only */\n\nimport type { RefObject } from 'react'\n\nexport interface Props {\n value: number | string\n wholeColor?: string\n decimalColor?: string\n spaceBefore?: string | number\n spaceAfter?: string | number\n zeroDecimals?: number\n}\n\nexport type Digit =\n | 'comma'\n | 'dot'\n | '0'\n | '1'\n | '2'\n | '3'\n | '4'\n | '5'\n | '6'\n | '7'\n | '8'\n | '9'\n\nexport type DigitRef = RefObject<HTMLSpanElement>\n\nexport type Status = 'new' | 'inactive' | 'transition' | 'finished'\n\nexport type Direction = 'down' | 'none'\n"]}
package/mjs/index.css DELETED
@@ -1,221 +0,0 @@
1
- /* @license Copyright 2024 w3ux authors & contributors
2
- SPDX-License-Identifier: GPL-3.0-only */
3
- .odometer {
4
- display: flex;
5
- align-items: flex-end;
6
- position: relative;
7
- overflow: hidden;
8
- height: inherit;
9
- line-height: inherit;
10
- width: fit-content;
11
- }
12
- .odometer > .odometer-inner {
13
- display: inline-block;
14
- vertical-align: bottom;
15
- }
16
- .odometer > .odometer-inner .odometer-digit {
17
- display: inline-block;
18
- position: relative;
19
- vertical-align: bottom;
20
- z-index: 3;
21
- }
22
- .odometer > .odometer-inner .odometer-digit.odometer-child {
23
- display: flex;
24
- align-items: flex-end;
25
- position: relative;
26
- left: 0;
27
- }
28
-
29
- /* NOTE: Stylelint does not like inline scss variables to generate these. */
30
- @keyframes slide-up-1 {
31
- from {
32
- top: 0;
33
- }
34
- to {
35
- top: 100%;
36
- }
37
- }
38
- @keyframes slide-up-2 {
39
- from {
40
- top: 0;
41
- }
42
- to {
43
- top: 200%;
44
- }
45
- }
46
- @keyframes slide-up-3 {
47
- from {
48
- top: 0;
49
- }
50
- to {
51
- top: 300%;
52
- }
53
- }
54
- @keyframes slide-up-4 {
55
- from {
56
- top: 0;
57
- }
58
- to {
59
- top: 400%;
60
- }
61
- }
62
- @keyframes slide-up-5 {
63
- from {
64
- top: 0;
65
- }
66
- to {
67
- top: 500%;
68
- }
69
- }
70
- @keyframes slide-up-6 {
71
- from {
72
- top: 0;
73
- }
74
- to {
75
- top: 600%;
76
- }
77
- }
78
- @keyframes slide-up-7 {
79
- from {
80
- top: 0;
81
- }
82
- to {
83
- top: 700%;
84
- }
85
- }
86
- @keyframes slide-up-8 {
87
- from {
88
- top: 0;
89
- }
90
- to {
91
- top: 800%;
92
- }
93
- }
94
- @keyframes slide-up-9 {
95
- from {
96
- top: 0;
97
- }
98
- to {
99
- top: 900%;
100
- }
101
- }
102
- @keyframes slide-up-10 {
103
- from {
104
- top: 0;
105
- }
106
- to {
107
- top: 1000%;
108
- }
109
- }
110
- @keyframes slide-up-11 {
111
- from {
112
- top: 0;
113
- }
114
- to {
115
- top: 1100%;
116
- }
117
- }
118
- @keyframes slide-up-12 {
119
- from {
120
- top: 0;
121
- }
122
- to {
123
- top: 1200%;
124
- }
125
- }
126
- @keyframes slide-down-1 {
127
- from {
128
- top: 0;
129
- }
130
- to {
131
- top: -100%;
132
- }
133
- }
134
- @keyframes slide-down-2 {
135
- from {
136
- top: 0;
137
- }
138
- to {
139
- top: -200%;
140
- }
141
- }
142
- @keyframes slide-down-3 {
143
- from {
144
- top: 0;
145
- }
146
- to {
147
- top: -300%;
148
- }
149
- }
150
- @keyframes slide-down-4 {
151
- from {
152
- top: 0;
153
- }
154
- to {
155
- top: -400%;
156
- }
157
- }
158
- @keyframes slide-down-5 {
159
- from {
160
- top: 0;
161
- }
162
- to {
163
- top: -500%;
164
- }
165
- }
166
- @keyframes slide-down-6 {
167
- from {
168
- top: 0;
169
- }
170
- to {
171
- top: -600%;
172
- }
173
- }
174
- @keyframes slide-down-7 {
175
- from {
176
- top: 0;
177
- }
178
- to {
179
- top: -700%;
180
- }
181
- }
182
- @keyframes slide-down-8 {
183
- from {
184
- top: 0;
185
- }
186
- to {
187
- top: -800%;
188
- }
189
- }
190
- @keyframes slide-down-9 {
191
- from {
192
- top: 0;
193
- }
194
- to {
195
- top: -900%;
196
- }
197
- }
198
- @keyframes slide-down-10 {
199
- from {
200
- top: 0;
201
- }
202
- to {
203
- top: -1000%;
204
- }
205
- }
206
- @keyframes slide-down-11 {
207
- from {
208
- top: 0;
209
- }
210
- to {
211
- top: -1100%;
212
- }
213
- }
214
- @keyframes slide-down-12 {
215
- from {
216
- top: 0;
217
- }
218
- to {
219
- top: -1200%;
220
- }
221
- }
package/mjs/index.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import './index.css';
2
- import type { Props } from './types';
3
- export declare const Odometer: ({ value, spaceBefore, spaceAfter, wholeColor, decimalColor, zeroDecimals, }: Props) => import("react/jsx-runtime").JSX.Element;
4
- export default Odometer;
package/mjs/index.js DELETED
@@ -1,133 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { createRef, useEffect, useRef, useState } from 'react';
3
- import './index.css';
4
- export const Odometer = ({ value, spaceBefore = 0, spaceAfter = '0.25rem', wholeColor = 'var(--text-color-primary)', decimalColor = 'var(--text-color-secondary)', zeroDecimals = 0, }) => {
5
- const [allDigits] = useState([
6
- 'comma',
7
- 'dot',
8
- '0',
9
- '1',
10
- '2',
11
- '3',
12
- '4',
13
- '5',
14
- '6',
15
- '7',
16
- '8',
17
- '9',
18
- ]);
19
- const [digits, setDigits] = useState([]);
20
- const [prevDigits, setPrevDigits] = useState([]);
21
- const [status, setStatus] = useState('inactive');
22
- const [initialized, setInitialized] = useState(false);
23
- const [odometerRef] = useState(createRef());
24
- const [digitRefs, setDigitRefs] = useState([]);
25
- const [allDigitRefs, setAllDigitRefs] = useState({});
26
- const activeTransitionCounter = useRef(0);
27
- const DURATION_MS = 750;
28
- const DURATION_SECS = `${DURATION_MS / 1000}s`;
29
- useEffect(() => {
30
- const all = Object.fromEntries(Object.values(allDigits).map((v) => [`d_${v}`, createRef()]));
31
- setAllDigitRefs(all);
32
- }, []);
33
- useEffect(() => {
34
- if (Object.keys(allDigitRefs)) {
35
- value =
36
- String(value) === '0' ? Number(value).toFixed(zeroDecimals) : value;
37
- const newDigits = value
38
- .toString()
39
- .split('')
40
- .map((v) => (v === '.' ? 'dot' : v))
41
- .map((v) => (v === ',' ? 'comma' : v));
42
- setDigits(newDigits);
43
- if (!initialized) {
44
- setInitialized(true);
45
- }
46
- else {
47
- setStatus('new');
48
- setPrevDigits(digits);
49
- }
50
- setDigitRefs(Array(newDigits.length).fill(createRef()));
51
- }
52
- }, [value]);
53
- useEffect(() => {
54
- if (status === 'new' && !digitRefs.find((d) => d.current === null)) {
55
- setStatus('transition');
56
- activeTransitionCounter.current++;
57
- setTimeout(() => {
58
- activeTransitionCounter.current--;
59
- if (activeTransitionCounter.current === 0) {
60
- setStatus('inactive');
61
- }
62
- }, DURATION_MS);
63
- }
64
- }, [status, digitRefs]);
65
- const odometerCurrent = odometerRef.current;
66
- let lineHeight = odometerCurrent
67
- ? window.getComputedStyle(odometerCurrent).lineHeight
68
- : 'inherit';
69
- lineHeight = lineHeight === 'normal' ? '1.1rem' : lineHeight;
70
- let foundDecimal = false;
71
- return (_jsxs(_Fragment, { children: [allDigits.map((d, i) => (_jsx("span", { ref: allDigitRefs[`d_${d}`], style: {
72
- opacity: 0,
73
- position: 'fixed',
74
- top: '-999%',
75
- left: '-999%',
76
- userSelect: 'none',
77
- }, children: d === 'dot' ? '.' : d === 'comma' ? ',' : d }, `odometer_template_digit_${i}`))), _jsx("span", { className: "odometer", children: _jsxs("span", { className: "odometer-inner", ref: odometerRef, children: [spaceBefore ? _jsx("span", { style: { paddingLeft: spaceBefore } }) : null, digits.map((d, i) => {
78
- if (d === 'dot') {
79
- foundDecimal = true;
80
- }
81
- let childDigits = null;
82
- if (status === 'transition') {
83
- const digitsToAnimate = [];
84
- const digitIndex = allDigits.indexOf(digits[i]);
85
- const prevDigitIndex = allDigits.indexOf(prevDigits[i]);
86
- const difference = Math.abs(digitIndex - prevDigitIndex);
87
- const delay = `${0.01 * (digits.length - i - 1)}s`;
88
- const direction = digitIndex === prevDigitIndex ? 'none' : 'down';
89
- const animClass = `slide-${direction}-${difference} `;
90
- digitsToAnimate.push(prevDigits[i]);
91
- if (digitIndex < prevDigitIndex) {
92
- digitsToAnimate.push(...Array.from({ length: difference }, (_, k) => allDigits[prevDigitIndex - k - 1]));
93
- }
94
- else {
95
- digitsToAnimate.push(...Array.from({ length: difference }, (_, k) => allDigits[k + prevDigitIndex + 1]));
96
- }
97
- childDigits = (_jsx("span", { style: {
98
- position: 'absolute',
99
- top: 0,
100
- left: 0,
101
- animationName: direction === 'none' ? undefined : animClass,
102
- animationDuration: direction === 'none' ? undefined : DURATION_SECS,
103
- animationFillMode: 'forwards',
104
- animationTimingFunction: 'cubic-bezier(0.1, 1, 0.2, 1)',
105
- animationDelay: delay,
106
- color: foundDecimal ? decimalColor : wholeColor,
107
- userSelect: 'none',
108
- }, children: digitsToAnimate.map((c, j) => (_jsx("span", { className: "odometer-digit odometer-child", style: {
109
- top: j === 0 ? 0 : `${100 * j}%`,
110
- height: lineHeight,
111
- lineHeight,
112
- }, children: c === 'dot' ? '.' : c === 'comma' ? ',' : c }, `child_digit_${j}`))) }));
113
- }
114
- return (_jsxs("span", { ref: digitRefs[i], className: "odometer-digit", style: {
115
- color: foundDecimal ? decimalColor : wholeColor,
116
- height: lineHeight,
117
- lineHeight,
118
- paddingRight: status === 'transition'
119
- ? `${allDigitRefs[`d_${d}`]?.current?.offsetWidth}px`
120
- : '0',
121
- }, children: [status === 'inactive' && (_jsx("span", { className: "odometer-digit odometer-child", style: {
122
- top: 0,
123
- height: lineHeight,
124
- lineHeight,
125
- width: `${allDigitRefs[`d_${d}`]?.current?.offsetWidth}px`,
126
- }, children: d === 'dot' ? '.' : d === 'comma' ? ',' : d })), status === 'transition' && childDigits] }, `digit_${i}`));
127
- }), spaceAfter ? _jsx("span", { style: { paddingRight: spaceAfter } }) : null] }) })] }));
128
- };
129
- export default Odometer;
130
-
131
- //# sourceMappingURL=index.js.map
132
-
133
- //# sourceMappingURL=index.js.map
package/mjs/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/* @license Copyright 2024 w3ux authors & contributors\nSPDX-License-Identifier: GPL-3.0-only */\n\nimport type { RefObject } from 'react'\nimport { createRef, useEffect, useRef, useState } from 'react'\nimport './index.css'\nimport type { Digit, DigitRef, Direction, Props, Status } from './types'\n\nexport const Odometer = ({\n value,\n spaceBefore = 0,\n spaceAfter = '0.25rem',\n wholeColor = 'var(--text-color-primary)',\n decimalColor = 'var(--text-color-secondary)',\n zeroDecimals = 0,\n}: Props) => {\n // Store all possible digits.\n const [allDigits] = useState<Digit[]>([\n 'comma',\n 'dot',\n '0',\n '1',\n '2',\n '3',\n '4',\n '5',\n '6',\n '7',\n '8',\n '9',\n ])\n\n // Store the digits of the current value.\n const [digits, setDigits] = useState<Digit[]>([])\n\n // Store digits of the previous value.\n const [prevDigits, setPrevDigits] = useState<Digit[]>([])\n\n // Store the status of the odometer (transitioning or stable).\n const [status, setStatus] = useState<Status>('inactive')\n\n // Store whether component has iniiialized.\n const [initialized, setInitialized] = useState<boolean>(false)\n\n // Store ref of the odometer.\n const [odometerRef] = useState(createRef<HTMLSpanElement>())\n\n // Store refs of each digit.\n const [digitRefs, setDigitRefs] = useState<DigitRef[]>([])\n\n // Store refs of each `all` digit.\n const [allDigitRefs, setAllDigitRefs] = useState<\n Record<string, RefObject<HTMLSpanElement | null>>\n >({})\n\n // Keep track of active transitions.\n const activeTransitionCounter = useRef<number>(0)\n\n // Transition duration.\n const DURATION_MS = 750\n const DURATION_SECS = `${DURATION_MS / 1000}s`\n\n // Phase 0: populate `allDigitRefs`.\n useEffect(() => {\n const all: Record<\n string,\n RefObject<HTMLSpanElement | null>\n > = Object.fromEntries(\n Object.values(allDigits).map((v) => [`d_${v}`, createRef()])\n )\n\n setAllDigitRefs(all)\n }, [])\n\n // Phase 1: new digits and refs are added to the odometer.\n useEffect(() => {\n if (Object.keys(allDigitRefs)) {\n value =\n String(value) === '0' ? Number(value).toFixed(zeroDecimals) : value\n\n const newDigits = value\n .toString()\n .split('')\n .map((v) => (v === '.' ? 'dot' : v))\n .map((v) => (v === ',' ? 'comma' : v)) as Digit[]\n\n setDigits(newDigits)\n\n if (!initialized) {\n setInitialized(true)\n } else {\n setStatus('new')\n setPrevDigits(digits)\n }\n setDigitRefs(Array(newDigits.length).fill(createRef()))\n }\n }, [value])\n\n // Phase 2: set up digit transition.\n useEffect(() => {\n if (status === 'new' && !digitRefs.find((d) => d.current === null)) {\n setStatus('transition')\n activeTransitionCounter.current++\n\n setTimeout(() => {\n activeTransitionCounter.current--\n if (activeTransitionCounter.current === 0) {\n setStatus('inactive')\n }\n }, DURATION_MS)\n }\n }, [status, digitRefs])\n\n const odometerCurrent: HTMLSpanElement | null = odometerRef.current\n let lineHeight = odometerCurrent\n ? window.getComputedStyle(odometerCurrent).lineHeight\n : 'inherit'\n\n // Fallback line height to `1.1rem` if `normal`.\n lineHeight = lineHeight === 'normal' ? '1.1rem' : lineHeight\n\n // Track whether decimal point has been found.\n let foundDecimal = false\n\n return (\n <>\n {allDigits.map((d, i) => (\n <span\n key={`odometer_template_digit_${i}`}\n ref={allDigitRefs[`d_${d}`]}\n style={{\n opacity: 0,\n position: 'fixed',\n top: '-999%',\n left: '-999%',\n userSelect: 'none',\n }}\n >\n {d === 'dot' ? '.' : d === 'comma' ? ',' : d}\n </span>\n ))}\n <span className=\"odometer\">\n <span className=\"odometer-inner\" ref={odometerRef}>\n {spaceBefore ? <span style={{ paddingLeft: spaceBefore }} /> : null}\n {digits.map((d, i) => {\n if (d === 'dot') {\n foundDecimal = true\n }\n\n // If transitioning, get digits needed to animate.\n let childDigits = null\n if (status === 'transition') {\n const digitsToAnimate = []\n const digitIndex = allDigits.indexOf(digits[i])\n const prevDigitIndex = allDigits.indexOf(prevDigits[i])\n const difference = Math.abs(digitIndex - prevDigitIndex)\n const delay = `${0.01 * (digits.length - i - 1)}s`\n const direction: Direction =\n digitIndex === prevDigitIndex ? 'none' : 'down'\n const animClass = `slide-${direction}-${difference} `\n\n // Push current prev digit to stop of stack.\n digitsToAnimate.push(prevDigits[i])\n\n // If transitioning between two digits, animate all digits in between.\n if (digitIndex < prevDigitIndex) {\n digitsToAnimate.push(\n ...Array.from(\n { length: difference },\n (_, k) => allDigits[prevDigitIndex - k - 1]\n )\n )\n } else {\n digitsToAnimate.push(\n ...Array.from(\n { length: difference },\n (_, k) => allDigits[k + prevDigitIndex + 1]\n )\n )\n }\n\n childDigits = (\n <span\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n animationName: direction === 'none' ? undefined : animClass,\n animationDuration:\n direction === 'none' ? undefined : DURATION_SECS,\n animationFillMode: 'forwards',\n animationTimingFunction: 'cubic-bezier(0.1, 1, 0.2, 1)',\n animationDelay: delay,\n color: foundDecimal ? decimalColor : wholeColor,\n userSelect: 'none',\n }}\n >\n {digitsToAnimate.map((c, j) => (\n <span\n key={`child_digit_${j}`}\n className=\"odometer-digit odometer-child\"\n style={{\n top: j === 0 ? 0 : `${100 * j}%`,\n height: lineHeight,\n lineHeight,\n }}\n >\n {c === 'dot' ? '.' : c === 'comma' ? ',' : c}\n </span>\n ))}\n </span>\n )\n }\n\n return (\n <span\n key={`digit_${i}`}\n ref={digitRefs[i]}\n className=\"odometer-digit\"\n style={{\n color: foundDecimal ? decimalColor : wholeColor,\n height: lineHeight,\n lineHeight,\n paddingRight:\n status === 'transition'\n ? `${allDigitRefs[`d_${d}`]?.current?.offsetWidth}px`\n : '0',\n }}\n >\n {status === 'inactive' && (\n <span\n className=\"odometer-digit odometer-child\"\n style={{\n top: 0,\n height: lineHeight,\n lineHeight,\n width: `${\n allDigitRefs[`d_${d}`]?.current?.offsetWidth\n }px`,\n }}\n >\n {d === 'dot' ? '.' : d === 'comma' ? ',' : d}\n </span>\n )}\n {status === 'transition' && childDigits}\n </span>\n )\n })}\n {spaceAfter ? <span style={{ paddingRight: spaceAfter }} /> : null}\n </span>\n </span>\n </>\n )\n}\n\nexport default Odometer\n"]}
package/mjs/types.d.ts DELETED
@@ -1,13 +0,0 @@
1
- import type { RefObject } from 'react';
2
- export interface Props {
3
- value: number | string;
4
- wholeColor?: string;
5
- decimalColor?: string;
6
- spaceBefore?: string | number;
7
- spaceAfter?: string | number;
8
- zeroDecimals?: number;
9
- }
10
- export type Digit = 'comma' | 'dot' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
11
- export type DigitRef = RefObject<HTMLSpanElement>;
12
- export type Status = 'new' | 'inactive' | 'transition' | 'finished';
13
- export type Direction = 'down' | 'none';
package/mjs/types.js DELETED
@@ -1,5 +0,0 @@
1
- export {};
2
-
3
- //# sourceMappingURL=types.js.map
4
-
5
- //# sourceMappingURL=types.js.map
package/mjs/types.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/types.ts"],"names":[],"mappings":"","file":"types.js","sourcesContent":["/* @license Copyright 2024 w3ux authors & contributors\nSPDX-License-Identifier: GPL-3.0-only */\n\nimport type { RefObject } from 'react'\n\nexport interface Props {\n value: number | string\n wholeColor?: string\n decimalColor?: string\n spaceBefore?: string | number\n spaceAfter?: string | number\n zeroDecimals?: number\n}\n\nexport type Digit =\n | 'comma'\n | 'dot'\n | '0'\n | '1'\n | '2'\n | '3'\n | '4'\n | '5'\n | '6'\n | '7'\n | '8'\n | '9'\n\nexport type DigitRef = RefObject<HTMLSpanElement>\n\nexport type Status = 'new' | 'inactive' | 'transition' | 'finished'\n\nexport type Direction = 'down' | 'none'\n"]}