@number-flow/react 0.5.3 → 0.5.5

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.
@@ -0,0 +1,179 @@
1
+ 'use client';
2
+ import * as React from 'react';
3
+ import { define, NumberFlowLite, formatToData, renderInnerHTML } from 'number-flow';
4
+ import { BROWSER } from 'esm-env';
5
+
6
+ const REACT_MAJOR = parseInt(React.version.match(/^(\d+)\./)?.[1]);
7
+ const isReact19 = REACT_MAJOR >= 19;
8
+ // Can't wait to not have to do this in React 19:
9
+ const OBSERVED_ATTRIBUTES = [
10
+ 'data',
11
+ 'digits'
12
+ ];
13
+ class NumberFlowElement extends NumberFlowLite {
14
+ attributeChangedCallback(attr, _oldValue, newValue) {
15
+ this[attr] = JSON.parse(newValue);
16
+ }
17
+ }
18
+ NumberFlowElement.observedAttributes = isReact19 ? [] : OBSERVED_ATTRIBUTES;
19
+ define('number-flow-react', NumberFlowElement);
20
+ // You're supposed to cache these between uses:
21
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
22
+ // Serialize to strings b/c React:
23
+ const formatters = {};
24
+ // Tiny workaround to support React 19 until it's released:
25
+ const serialize = isReact19 ? (p)=>p : JSON.stringify;
26
+ function splitProps(props) {
27
+ const { transformTiming, spinTiming, opacityTiming, animated, respectMotionPreference, trend, plugins, ...rest } = props;
28
+ return [
29
+ {
30
+ transformTiming,
31
+ spinTiming,
32
+ opacityTiming,
33
+ animated,
34
+ respectMotionPreference,
35
+ trend,
36
+ plugins
37
+ },
38
+ rest
39
+ ];
40
+ }
41
+ // We need a class component to use getSnapshotBeforeUpdate:
42
+ class NumberFlowImpl extends React.Component {
43
+ // Update the non-`data` props to avoid JSON serialization
44
+ // Data needs to be set in render still:
45
+ updateProperties(prevProps) {
46
+ if (!this.el) return;
47
+ this.el.manual = !this.props.isolate;
48
+ const [nonData] = splitProps(this.props);
49
+ Object.entries(nonData).forEach(([k, v])=>{
50
+ // @ts-ignore
51
+ this.el[k] = v ?? NumberFlowElement.defaultProps[k];
52
+ });
53
+ if (prevProps?.onAnimationsStart) this.el.removeEventListener('animationsstart', prevProps.onAnimationsStart);
54
+ if (this.props.onAnimationsStart) this.el.addEventListener('animationsstart', this.props.onAnimationsStart);
55
+ if (prevProps?.onAnimationsFinish) this.el.removeEventListener('animationsfinish', prevProps.onAnimationsFinish);
56
+ if (this.props.onAnimationsFinish) this.el.addEventListener('animationsfinish', this.props.onAnimationsFinish);
57
+ }
58
+ componentDidMount() {
59
+ this.updateProperties();
60
+ if (isReact19 && this.el) {
61
+ // React 19 needs this because the attributeChangedCallback isn't called:
62
+ this.el.digits = this.props.digits;
63
+ this.el.data = this.props.data;
64
+ }
65
+ }
66
+ getSnapshotBeforeUpdate(prevProps) {
67
+ this.updateProperties(prevProps);
68
+ if (prevProps.data !== this.props.data) {
69
+ if (this.props.group) {
70
+ this.props.group.willUpdate();
71
+ return ()=>this.props.group?.didUpdate();
72
+ }
73
+ if (!this.props.isolate) {
74
+ this.el?.willUpdate();
75
+ return ()=>this.el?.didUpdate();
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+ componentDidUpdate(_, __, didUpdate) {
81
+ didUpdate?.();
82
+ }
83
+ handleRef(el) {
84
+ if (this.props.innerRef) this.props.innerRef.current = el;
85
+ this.el = el;
86
+ }
87
+ render() {
88
+ const [_, { innerRef, className, data, willChange, isolate, group, digits, onAnimationsStart, onAnimationsFinish, ...rest }] = splitProps(this.props);
89
+ return(// @ts-expect-error missing types
90
+ /*#__PURE__*/ React.createElement("number-flow-react", {
91
+ ref: this.handleRef,
92
+ "data-will-change": willChange ? '' : undefined,
93
+ // Have to rename this:
94
+ class: className,
95
+ "aria-label": data.valueAsString,
96
+ ...rest,
97
+ role: "img",
98
+ dangerouslySetInnerHTML: {
99
+ __html: BROWSER ? '' : renderInnerHTML(data)
100
+ },
101
+ suppressHydrationWarning: true,
102
+ digits: serialize(digits),
103
+ // Make sure data is set last, everything else is updated:
104
+ data: serialize(data)
105
+ }));
106
+ }
107
+ constructor(props){
108
+ super(props);
109
+ this.handleRef = this.handleRef.bind(this);
110
+ }
111
+ }
112
+ const NumberFlow = /*#__PURE__*/ React.forwardRef(function NumberFlow({ value, locales, format, prefix, suffix, ...props }, _ref) {
113
+ React.useImperativeHandle(_ref, ()=>ref.current, []);
114
+ const ref = React.useRef();
115
+ const group = React.useContext(NumberFlowGroupContext);
116
+ group?.useRegister(ref);
117
+ const localesString = React.useMemo(()=>locales ? JSON.stringify(locales) : '', [
118
+ locales
119
+ ]);
120
+ const formatString = React.useMemo(()=>format ? JSON.stringify(format) : '', [
121
+ format
122
+ ]);
123
+ const data = React.useMemo(()=>{
124
+ const formatter = formatters[`${localesString}:${formatString}`] ??= new Intl.NumberFormat(locales, format);
125
+ return formatToData(value, formatter, prefix, suffix);
126
+ }, [
127
+ value,
128
+ localesString,
129
+ formatString,
130
+ prefix,
131
+ suffix
132
+ ]);
133
+ return /*#__PURE__*/ React.createElement(NumberFlowImpl, {
134
+ ...props,
135
+ group: group,
136
+ data: data,
137
+ innerRef: ref
138
+ });
139
+ });
140
+ const NumberFlowGroupContext = /*#__PURE__*/ React.createContext(undefined);
141
+ function NumberFlowGroup({ children }) {
142
+ const flows = React.useRef(new Set());
143
+ const updating = React.useRef(false);
144
+ const pending = React.useRef(new WeakMap());
145
+ const value = React.useMemo(()=>({
146
+ useRegister (ref) {
147
+ React.useEffect(()=>{
148
+ flows.current.add(ref);
149
+ return ()=>{
150
+ flows.current.delete(ref);
151
+ };
152
+ }, []);
153
+ },
154
+ willUpdate () {
155
+ if (updating.current) return;
156
+ updating.current = true;
157
+ flows.current.forEach((ref)=>{
158
+ const f = ref.current;
159
+ if (!f || !f.created) return;
160
+ f.willUpdate();
161
+ pending.current.set(f, true);
162
+ });
163
+ },
164
+ didUpdate () {
165
+ flows.current.forEach((ref)=>{
166
+ const f = ref.current;
167
+ if (!f || !pending.current.get(f)) return;
168
+ f.didUpdate();
169
+ pending.current.delete(f);
170
+ });
171
+ updating.current = false;
172
+ }
173
+ }), []);
174
+ return /*#__PURE__*/ React.createElement(NumberFlowGroupContext.Provider, {
175
+ value: value
176
+ }, children);
177
+ }
178
+
179
+ export { NumberFlow as N, NumberFlowElement as a, NumberFlowGroup as b };
@@ -0,0 +1,201 @@
1
+ 'use client';
2
+ var React = require('react');
3
+ var numberFlow = require('number-flow');
4
+ var esmEnv = require('esm-env');
5
+
6
+ function _interopNamespace(e) {
7
+ if (e && e.__esModule) return e;
8
+ var n = Object.create(null);
9
+ if (e) {
10
+ Object.keys(e).forEach(function (k) {
11
+ if (k !== 'default') {
12
+ var d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: function () { return e[k]; }
16
+ });
17
+ }
18
+ });
19
+ }
20
+ n.default = e;
21
+ return n;
22
+ }
23
+
24
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
25
+
26
+ const REACT_MAJOR = parseInt(React__namespace.version.match(/^(\d+)\./)?.[1]);
27
+ const isReact19 = REACT_MAJOR >= 19;
28
+ // Can't wait to not have to do this in React 19:
29
+ const OBSERVED_ATTRIBUTES = [
30
+ 'data',
31
+ 'digits'
32
+ ];
33
+ class NumberFlowElement extends numberFlow.NumberFlowLite {
34
+ attributeChangedCallback(attr, _oldValue, newValue) {
35
+ this[attr] = JSON.parse(newValue);
36
+ }
37
+ }
38
+ NumberFlowElement.observedAttributes = isReact19 ? [] : OBSERVED_ATTRIBUTES;
39
+ numberFlow.define('number-flow-react', NumberFlowElement);
40
+ // You're supposed to cache these between uses:
41
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
42
+ // Serialize to strings b/c React:
43
+ const formatters = {};
44
+ // Tiny workaround to support React 19 until it's released:
45
+ const serialize = isReact19 ? (p)=>p : JSON.stringify;
46
+ function splitProps(props) {
47
+ const { transformTiming, spinTiming, opacityTiming, animated, respectMotionPreference, trend, plugins, ...rest } = props;
48
+ return [
49
+ {
50
+ transformTiming,
51
+ spinTiming,
52
+ opacityTiming,
53
+ animated,
54
+ respectMotionPreference,
55
+ trend,
56
+ plugins
57
+ },
58
+ rest
59
+ ];
60
+ }
61
+ // We need a class component to use getSnapshotBeforeUpdate:
62
+ class NumberFlowImpl extends React__namespace.Component {
63
+ // Update the non-`data` props to avoid JSON serialization
64
+ // Data needs to be set in render still:
65
+ updateProperties(prevProps) {
66
+ if (!this.el) return;
67
+ this.el.manual = !this.props.isolate;
68
+ const [nonData] = splitProps(this.props);
69
+ Object.entries(nonData).forEach(([k, v])=>{
70
+ // @ts-ignore
71
+ this.el[k] = v ?? NumberFlowElement.defaultProps[k];
72
+ });
73
+ if (prevProps?.onAnimationsStart) this.el.removeEventListener('animationsstart', prevProps.onAnimationsStart);
74
+ if (this.props.onAnimationsStart) this.el.addEventListener('animationsstart', this.props.onAnimationsStart);
75
+ if (prevProps?.onAnimationsFinish) this.el.removeEventListener('animationsfinish', prevProps.onAnimationsFinish);
76
+ if (this.props.onAnimationsFinish) this.el.addEventListener('animationsfinish', this.props.onAnimationsFinish);
77
+ }
78
+ componentDidMount() {
79
+ this.updateProperties();
80
+ if (isReact19 && this.el) {
81
+ // React 19 needs this because the attributeChangedCallback isn't called:
82
+ this.el.digits = this.props.digits;
83
+ this.el.data = this.props.data;
84
+ }
85
+ }
86
+ getSnapshotBeforeUpdate(prevProps) {
87
+ this.updateProperties(prevProps);
88
+ if (prevProps.data !== this.props.data) {
89
+ if (this.props.group) {
90
+ this.props.group.willUpdate();
91
+ return ()=>this.props.group?.didUpdate();
92
+ }
93
+ if (!this.props.isolate) {
94
+ this.el?.willUpdate();
95
+ return ()=>this.el?.didUpdate();
96
+ }
97
+ }
98
+ return null;
99
+ }
100
+ componentDidUpdate(_, __, didUpdate) {
101
+ didUpdate?.();
102
+ }
103
+ handleRef(el) {
104
+ if (this.props.innerRef) this.props.innerRef.current = el;
105
+ this.el = el;
106
+ }
107
+ render() {
108
+ const [_, { innerRef, className, data, willChange, isolate, group, digits, onAnimationsStart, onAnimationsFinish, ...rest }] = splitProps(this.props);
109
+ return(// @ts-expect-error missing types
110
+ /*#__PURE__*/ React__namespace.createElement("number-flow-react", {
111
+ ref: this.handleRef,
112
+ "data-will-change": willChange ? '' : undefined,
113
+ // Have to rename this:
114
+ class: className,
115
+ "aria-label": data.valueAsString,
116
+ ...rest,
117
+ role: "img",
118
+ dangerouslySetInnerHTML: {
119
+ __html: esmEnv.BROWSER ? '' : numberFlow.renderInnerHTML(data)
120
+ },
121
+ suppressHydrationWarning: true,
122
+ digits: serialize(digits),
123
+ // Make sure data is set last, everything else is updated:
124
+ data: serialize(data)
125
+ }));
126
+ }
127
+ constructor(props){
128
+ super(props);
129
+ this.handleRef = this.handleRef.bind(this);
130
+ }
131
+ }
132
+ const NumberFlow = /*#__PURE__*/ React__namespace.forwardRef(function NumberFlow({ value, locales, format, prefix, suffix, ...props }, _ref) {
133
+ React__namespace.useImperativeHandle(_ref, ()=>ref.current, []);
134
+ const ref = React__namespace.useRef();
135
+ const group = React__namespace.useContext(NumberFlowGroupContext);
136
+ group?.useRegister(ref);
137
+ const localesString = React__namespace.useMemo(()=>locales ? JSON.stringify(locales) : '', [
138
+ locales
139
+ ]);
140
+ const formatString = React__namespace.useMemo(()=>format ? JSON.stringify(format) : '', [
141
+ format
142
+ ]);
143
+ const data = React__namespace.useMemo(()=>{
144
+ const formatter = formatters[`${localesString}:${formatString}`] ??= new Intl.NumberFormat(locales, format);
145
+ return numberFlow.formatToData(value, formatter, prefix, suffix);
146
+ }, [
147
+ value,
148
+ localesString,
149
+ formatString,
150
+ prefix,
151
+ suffix
152
+ ]);
153
+ return /*#__PURE__*/ React__namespace.createElement(NumberFlowImpl, {
154
+ ...props,
155
+ group: group,
156
+ data: data,
157
+ innerRef: ref
158
+ });
159
+ });
160
+ const NumberFlowGroupContext = /*#__PURE__*/ React__namespace.createContext(undefined);
161
+ function NumberFlowGroup({ children }) {
162
+ const flows = React__namespace.useRef(new Set());
163
+ const updating = React__namespace.useRef(false);
164
+ const pending = React__namespace.useRef(new WeakMap());
165
+ const value = React__namespace.useMemo(()=>({
166
+ useRegister (ref) {
167
+ React__namespace.useEffect(()=>{
168
+ flows.current.add(ref);
169
+ return ()=>{
170
+ flows.current.delete(ref);
171
+ };
172
+ }, []);
173
+ },
174
+ willUpdate () {
175
+ if (updating.current) return;
176
+ updating.current = true;
177
+ flows.current.forEach((ref)=>{
178
+ const f = ref.current;
179
+ if (!f || !f.created) return;
180
+ f.willUpdate();
181
+ pending.current.set(f, true);
182
+ });
183
+ },
184
+ didUpdate () {
185
+ flows.current.forEach((ref)=>{
186
+ const f = ref.current;
187
+ if (!f || !pending.current.get(f)) return;
188
+ f.didUpdate();
189
+ pending.current.delete(f);
190
+ });
191
+ updating.current = false;
192
+ }
193
+ }), []);
194
+ return /*#__PURE__*/ React__namespace.createElement(NumberFlowGroupContext.Provider, {
195
+ value: value
196
+ }, children);
197
+ }
198
+
199
+ exports.NumberFlow = NumberFlow;
200
+ exports.NumberFlowElement = NumberFlowElement;
201
+ exports.NumberFlowGroup = NumberFlowGroup;
package/dist/index.d.mts CHANGED
@@ -1,7 +1,43 @@
1
1
  export * from 'number-flow/plugins';
2
- export { NumberFlowElement, NumberFlowGroup, NumberFlowProps, default } from './NumberFlow.mjs';
2
+ import * as React from 'react';
3
+ import { NumberFlowLite, Value, Format, Props } from 'number-flow';
3
4
  export { Format, NumberPartType, Trend, Value } from 'number-flow';
4
- import 'react';
5
+
6
+ declare const OBSERVED_ATTRIBUTES: readonly ["data", "digits"];
7
+ type ObservedAttribute = (typeof OBSERVED_ATTRIBUTES)[number];
8
+ declare class NumberFlowElement extends NumberFlowLite {
9
+ static observedAttributes: readonly ["data", "digits"] | never[];
10
+ attributeChangedCallback(attr: ObservedAttribute, _oldValue: string, newValue: string): void;
11
+ }
12
+ type BaseProps = React.HTMLAttributes<NumberFlowElement> & Partial<Props> & {
13
+ isolate?: boolean;
14
+ willChange?: boolean;
15
+ onAnimationsStart?: (e: CustomEvent<undefined>) => void;
16
+ onAnimationsFinish?: (e: CustomEvent<undefined>) => void;
17
+ };
18
+ type NumberFlowProps = BaseProps & {
19
+ value: Value;
20
+ locales?: Intl.LocalesArgument;
21
+ format?: Format;
22
+ prefix?: string;
23
+ suffix?: string;
24
+ };
25
+ declare const NumberFlow: React.ForwardRefExoticComponent<React.HTMLAttributes<NumberFlowElement> & Partial<Props> & {
26
+ isolate?: boolean;
27
+ willChange?: boolean;
28
+ onAnimationsStart?: (e: CustomEvent<undefined>) => void;
29
+ onAnimationsFinish?: (e: CustomEvent<undefined>) => void;
30
+ } & {
31
+ value: Value;
32
+ locales?: Intl.LocalesArgument;
33
+ format?: Format;
34
+ prefix?: string;
35
+ suffix?: string;
36
+ } & React.RefAttributes<NumberFlowElement>>;
37
+
38
+ declare function NumberFlowGroup({ children }: {
39
+ children: React.ReactNode;
40
+ }): React.JSX.Element;
5
41
 
6
42
  declare const useIsSupported: () => boolean;
7
43
  declare const usePrefersReducedMotion: () => boolean;
@@ -9,4 +45,4 @@ declare function useCanAnimate({ respectMotionPreference }?: {
9
45
  respectMotionPreference?: boolean | undefined;
10
46
  }): boolean;
11
47
 
12
- export { useCanAnimate, useIsSupported, usePrefersReducedMotion };
48
+ export { NumberFlowElement, NumberFlowGroup, type NumberFlowProps, NumberFlow as default, useCanAnimate, useIsSupported, usePrefersReducedMotion };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,43 @@
1
1
  export * from 'number-flow/plugins';
2
- export { NumberFlowElement, NumberFlowGroup, NumberFlowProps, default } from './NumberFlow.js';
2
+ import * as React from 'react';
3
+ import { NumberFlowLite, Value, Format, Props } from 'number-flow';
3
4
  export { Format, NumberPartType, Trend, Value } from 'number-flow';
4
- import 'react';
5
+
6
+ declare const OBSERVED_ATTRIBUTES: readonly ["data", "digits"];
7
+ type ObservedAttribute = (typeof OBSERVED_ATTRIBUTES)[number];
8
+ declare class NumberFlowElement extends NumberFlowLite {
9
+ static observedAttributes: readonly ["data", "digits"] | never[];
10
+ attributeChangedCallback(attr: ObservedAttribute, _oldValue: string, newValue: string): void;
11
+ }
12
+ type BaseProps = React.HTMLAttributes<NumberFlowElement> & Partial<Props> & {
13
+ isolate?: boolean;
14
+ willChange?: boolean;
15
+ onAnimationsStart?: (e: CustomEvent<undefined>) => void;
16
+ onAnimationsFinish?: (e: CustomEvent<undefined>) => void;
17
+ };
18
+ type NumberFlowProps = BaseProps & {
19
+ value: Value;
20
+ locales?: Intl.LocalesArgument;
21
+ format?: Format;
22
+ prefix?: string;
23
+ suffix?: string;
24
+ };
25
+ declare const NumberFlow: React.ForwardRefExoticComponent<React.HTMLAttributes<NumberFlowElement> & Partial<Props> & {
26
+ isolate?: boolean;
27
+ willChange?: boolean;
28
+ onAnimationsStart?: (e: CustomEvent<undefined>) => void;
29
+ onAnimationsFinish?: (e: CustomEvent<undefined>) => void;
30
+ } & {
31
+ value: Value;
32
+ locales?: Intl.LocalesArgument;
33
+ format?: Format;
34
+ prefix?: string;
35
+ suffix?: string;
36
+ } & React.RefAttributes<NumberFlowElement>>;
37
+
38
+ declare function NumberFlowGroup({ children }: {
39
+ children: React.ReactNode;
40
+ }): React.JSX.Element;
5
41
 
6
42
  declare const useIsSupported: () => boolean;
7
43
  declare const usePrefersReducedMotion: () => boolean;
@@ -9,4 +45,4 @@ declare function useCanAnimate({ respectMotionPreference }?: {
9
45
  respectMotionPreference?: boolean | undefined;
10
46
  }): boolean;
11
47
 
12
- export { useCanAnimate, useIsSupported, usePrefersReducedMotion };
48
+ export { NumberFlowElement, NumberFlowGroup, type NumberFlowProps, NumberFlow as default, useCanAnimate, useIsSupported, usePrefersReducedMotion };