@w3ux/react-odometer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.css ADDED
@@ -0,0 +1 @@
1
+ .odometer{display:flex;align-items:flex-end;position:relative;overflow:hidden;height:inherit;line-height:inherit;width:fit-content}.odometer>.odometer-inner{display:inline-block;vertical-align:bottom}.odometer>.odometer-inner .odometer-digit{display:inline-block;position:relative;vertical-align:bottom;z-index:3}.odometer>.odometer-inner .odometer-digit.odometer-child{display:flex;align-items:flex-end;position:relative;left:0}@keyframes slide-up-1{from{top:0}to{top:100%}}@keyframes slide-up-2{from{top:0}to{top:200%}}@keyframes slide-up-3{from{top:0}to{top:300%}}@keyframes slide-up-4{from{top:0}to{top:400%}}@keyframes slide-up-5{from{top:0}to{top:500%}}@keyframes slide-up-6{from{top:0}to{top:600%}}@keyframes slide-up-7{from{top:0}to{top:700%}}@keyframes slide-up-8{from{top:0}to{top:800%}}@keyframes slide-up-9{from{top:0}to{top:900%}}@keyframes slide-up-10{from{top:0}to{top:1000%}}@keyframes slide-up-11{from{top:0}to{top:1100%}}@keyframes slide-up-12{from{top:0}to{top:1200%}}@keyframes slide-down-1{from{top:0}to{top:-100%}}@keyframes slide-down-2{from{top:0}to{top:-200%}}@keyframes slide-down-3{from{top:0}to{top:-300%}}@keyframes slide-down-4{from{top:0}to{top:-400%}}@keyframes slide-down-5{from{top:0}to{top:-500%}}@keyframes slide-down-6{from{top:0}to{top:-600%}}@keyframes slide-down-7{from{top:0}to{top:-700%}}@keyframes slide-down-8{from{top:0}to{top:-800%}}@keyframes slide-down-9{from{top:0}to{top:-900%}}@keyframes slide-down-10{from{top:0}to{top:-1000%}}@keyframes slide-down-11{from{top:0}to{top:-1100%}}@keyframes slide-down-12{from{top:0}to{top:-1200%}}
package/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { Props } from "./types";
2
+ import "./index.scss";
3
+ export declare const Odometer: ({ value, spaceBefore, spaceAfter, wholeColor, decimalColor, zeroDecimals, }: Props) => import("react/jsx-runtime").JSX.Element;
4
+ export default Odometer;
package/index.js ADDED
@@ -0,0 +1,133 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState, createRef, useRef } from "react";
3
+ import "./index.scss";
4
+ export var Odometer = function (_a) {
5
+ var value = _a.value, _b = _a.spaceBefore, spaceBefore = _b === void 0 ? 0 : _b, _c = _a.spaceAfter, spaceAfter = _c === void 0 ? "0.25rem" : _c, _d = _a.wholeColor, wholeColor = _d === void 0 ? "var(--text-color-primary)" : _d, _e = _a.decimalColor, decimalColor = _e === void 0 ? "var(--text-color-tertiary)" : _e, _f = _a.zeroDecimals, zeroDecimals = _f === void 0 ? 0 : _f;
6
+ var allDigits = useState([
7
+ "comma",
8
+ "dot",
9
+ "0",
10
+ "1",
11
+ "2",
12
+ "3",
13
+ "4",
14
+ "5",
15
+ "6",
16
+ "7",
17
+ "8",
18
+ "9",
19
+ ])[0];
20
+ var _g = useState([]), digits = _g[0], setDigits = _g[1];
21
+ var _h = useState([]), prevDigits = _h[0], setPrevDigits = _h[1];
22
+ var _j = useState("inactive"), status = _j[0], setStatus = _j[1];
23
+ var _k = useState(false), initialized = _k[0], setInitialized = _k[1];
24
+ var odometerRef = useState(createRef())[0];
25
+ var _l = useState([]), digitRefs = _l[0], setDigitRefs = _l[1];
26
+ var _m = useState({}), allDigitRefs = _m[0], setAllDigitRefs = _m[1];
27
+ var activeTransitionCounter = useRef(0);
28
+ var DURATION_MS = 750;
29
+ var DURATION_SECS = "".concat(DURATION_MS / 1000, "s");
30
+ useEffect(function () {
31
+ var all = Object.fromEntries(Object.values(allDigits).map(function (v) { return ["d_".concat(v), createRef()]; }));
32
+ setAllDigitRefs(all);
33
+ }, []);
34
+ useEffect(function () {
35
+ if (Object.keys(allDigitRefs)) {
36
+ value =
37
+ String(value) === "0" ? Number(value).toFixed(zeroDecimals) : value;
38
+ var newDigits = value
39
+ .toString()
40
+ .split("")
41
+ .map(function (v) { return (v === "." ? "dot" : v); })
42
+ .map(function (v) { return (v === "," ? "comma" : v); });
43
+ setDigits(newDigits);
44
+ if (!initialized) {
45
+ setInitialized(true);
46
+ }
47
+ else {
48
+ setStatus("new");
49
+ setPrevDigits(digits);
50
+ }
51
+ setDigitRefs(Array(newDigits.length).fill(createRef()));
52
+ }
53
+ }, [value]);
54
+ useEffect(function () {
55
+ if (status === "new" && !digitRefs.find(function (d) { return d.current === null; })) {
56
+ setStatus("transition");
57
+ activeTransitionCounter.current++;
58
+ setTimeout(function () {
59
+ activeTransitionCounter.current--;
60
+ if (activeTransitionCounter.current === 0) {
61
+ setStatus("inactive");
62
+ }
63
+ }, DURATION_MS);
64
+ }
65
+ }, [status, digitRefs]);
66
+ var odometerCurrent = odometerRef === null || odometerRef === void 0 ? void 0 : odometerRef.current;
67
+ var lineHeight = odometerCurrent
68
+ ? window.getComputedStyle(odometerCurrent).lineHeight
69
+ : "inherit";
70
+ lineHeight = lineHeight === "normal" ? "1.1rem" : lineHeight;
71
+ var foundDecimal = false;
72
+ return (_jsxs(_Fragment, { children: [allDigits.map(function (d, i) { return (_jsx("span", { ref: allDigitRefs["d_".concat(d)], style: {
73
+ opacity: 0,
74
+ position: "fixed",
75
+ top: "-999%",
76
+ left: " -999%",
77
+ userSelect: "none",
78
+ }, children: d === "dot" ? "." : d === "comma" ? "," : d }, "odometer_template_digit_".concat(i))); }), _jsx("span", { className: "odometer", children: _jsxs("span", { className: "odometer-inner", ref: odometerRef, children: [spaceBefore ? _jsx("span", { style: { paddingLeft: spaceBefore } }) : null, digits.map(function (d, i) {
79
+ var _a, _b, _c, _d;
80
+ if (d === "dot") {
81
+ foundDecimal = true;
82
+ }
83
+ var childDigits = null;
84
+ if (status === "transition") {
85
+ var digitsToAnimate = [];
86
+ var digitIndex = allDigits.indexOf(digits[i]);
87
+ var prevDigitIndex_1 = allDigits.indexOf(prevDigits[i]);
88
+ var difference = Math.abs(digitIndex - prevDigitIndex_1);
89
+ var delay = "".concat(0.01 * (digits.length - i - 1), "s");
90
+ var direction = digitIndex === prevDigitIndex_1 ? "none" : "down";
91
+ var animClass = "slide-".concat(direction, "-").concat(difference, " ");
92
+ digitsToAnimate.push(prevDigits[i]);
93
+ if (digitIndex < prevDigitIndex_1) {
94
+ digitsToAnimate.push.apply(digitsToAnimate, Array.from({ length: difference }, function (_, k) { return allDigits[prevDigitIndex_1 - k - 1]; }));
95
+ }
96
+ else {
97
+ digitsToAnimate.push.apply(digitsToAnimate, Array.from({ length: difference }, function (_, k) { return allDigits[k + prevDigitIndex_1 + 1]; }));
98
+ }
99
+ childDigits = (_jsx("span", { style: {
100
+ position: "absolute",
101
+ top: 0,
102
+ left: 0,
103
+ animationName: direction === "none" ? undefined : animClass,
104
+ animationDuration: direction === "none" ? undefined : DURATION_SECS,
105
+ animationFillMode: "forwards",
106
+ animationTimingFunction: "cubic-bezier(0.1, 1, 0.2, 1)",
107
+ animationDelay: delay,
108
+ color: foundDecimal ? decimalColor : wholeColor,
109
+ userSelect: "none",
110
+ }, children: digitsToAnimate.map(function (c, j) { return (_jsx("span", { className: "odometer-digit odometer-child", style: {
111
+ top: j === 0 ? 0 : "".concat(100 * j, "%"),
112
+ height: lineHeight,
113
+ lineHeight: lineHeight,
114
+ }, children: c === "dot" ? "." : c === "comma" ? "," : c }, "child_digit_".concat(j))); }) }));
115
+ }
116
+ return (_jsxs("span", { ref: digitRefs[i], className: "odometer-digit", style: {
117
+ color: foundDecimal ? decimalColor : wholeColor,
118
+ height: lineHeight,
119
+ lineHeight: lineHeight,
120
+ paddingRight: status === "transition"
121
+ ? "".concat((_b = (_a = allDigitRefs["d_".concat(d)]) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.offsetWidth, "px")
122
+ : "0",
123
+ }, children: [status === "inactive" && (_jsx("span", { className: "odometer-digit odometer-child", style: {
124
+ top: 0,
125
+ height: lineHeight,
126
+ lineHeight: lineHeight,
127
+ width: "".concat((_d = (_c = allDigitRefs["d_".concat(d)]) === null || _c === void 0 ? void 0 : _c.current) === null || _d === void 0 ? void 0 : _d.offsetWidth, "px"),
128
+ }, children: d === "dot" ? "." : d === "comma" ? "," : d })), status === "transition" && childDigits] }, "digit_".concat(i)));
129
+ }), spaceAfter ? _jsx("span", { style: { paddingRight: spaceAfter } }) : null] }) })] }));
130
+ };
131
+ export default Odometer;
132
+
133
+
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/* @license Copyright 2024 @polkadot-cloud/library authors & contributors\nSPDX-License-Identifier: GPL-3.0-only */\n\nimport { useEffect, useState, createRef, useRef } from \"react\";\nimport { Digit, DigitRef, Direction, Props, Status } from \"./types\";\nimport \"./index.scss\";\n\nexport const Odometer = ({\n value,\n spaceBefore = 0,\n spaceAfter = \"0.25rem\",\n wholeColor = \"var(--text-color-primary)\",\n decimalColor = \"var(--text-color-tertiary)\",\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<Record<string, DigitRef>>(\n {}\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<string, DigitRef> = 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: Element = 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/package.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "@w3ux/react-odometer",
3
+ "version": "0.0.1",
4
+ "license": "GPL-3.0-only",
5
+ "type": "module"
6
+ }
package/types.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { MutableRefObject } 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 = MutableRefObject<HTMLSpanElement>;
12
+ export type Status = "new" | "inactive" | "transition" | "finished";
13
+ export type Direction = "down" | "none";
package/types.js ADDED
@@ -0,0 +1,3 @@
1
+ export {};
2
+
3
+
package/types.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"names":[],"mappings":"AAAA;wCACwC","file":"types.js","sourcesContent":["/* @license Copyright 2024 @polkadot-cloud/library authors & contributors\nSPDX-License-Identifier: GPL-3.0-only */\n\nimport { MutableRefObject } 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 = MutableRefObject<HTMLSpanElement>;\n\nexport type Status = \"new\" | \"inactive\" | \"transition\" | \"finished\";\n\nexport type Direction = \"down\" | \"none\";\n"]}