solid-number-flow
Version:
A SolidJS component to transition, format, and localize numbers. Forked from @barvian/number-flow.
202 lines (201 loc) • 5.96 kB
JSX
// src/index.tsx
import {
define,
formatToData,
NumberFlowLite,
renderInnerHTML
} from "number-flow";
import {
createContext,
createEffect,
createMemo,
createSignal,
onCleanup,
onMount,
splitProps,
useContext
} from "solid-js";
import { Dynamic } from "solid-js/web";
import {
canAnimate as _canAnimate,
prefersReducedMotion as _prefersReducedMotion
} from "number-flow";
var OBSERVED_ATTRIBUTES = ["data", "digits"];
var NumberFlowElement = class extends NumberFlowLite {
static observedAttributes = OBSERVED_ATTRIBUTES;
attributeChangedCallback(_attr, _oldValue, _newValue) {
}
};
define("number-flow", NumberFlowElement);
var formatters = {};
function NumberFlowImpl(props) {
let el;
const updateProperties = (prevProps) => {
if (!el) return;
if (props.transformTiming)
el.transformTiming ?? NumberFlowElement.defaultProps["transformTiming"];
if (props.spinTiming) el.spinTiming ?? NumberFlowElement.defaultProps["spinTiming"];
if (props.opacityTiming) el.opacityTiming ?? NumberFlowElement.defaultProps["opacityTiming"];
if (props.animated != null) el.animated = props.animated;
if (props.respectMotionPreference != null)
el.respectMotionPreference = props.respectMotionPreference;
if (props.trend != null) el.trend = props.trend;
if (props.plugins != null) el.plugins = props.plugins;
if (prevProps?.onAnimationsStart)
el.removeEventListener("onanimationsstart", prevProps.onAnimationsStart);
if (props.onAnimationsStart)
el.addEventListener("animationsstart", props.onAnimationsStart);
if (prevProps?.onAnimationsFinish)
el.removeEventListener("onanimationsfinish", prevProps.onAnimationsFinish);
if (props.onAnimationsFinish)
el.addEventListener("onanimationsfinish", props.onAnimationsFinish);
};
onMount(() => {
updateProperties();
if (el) {
el.digits = props.digits;
el.data = props.data();
}
});
createEffect((prevProps) => {
updateProperties(prevProps);
if (prevProps?.data !== props.data()) {
if (props.group()) {
props.group().willUpdate();
props.group().didUpdate();
return;
}
if (!props.isolate) {
el?.willUpdate();
el?.didUpdate();
return;
}
}
return {
...props,
group: props.group(),
data: props.data()
};
});
const handleRef = (elRef) => {
props.innerRef = elRef;
el = elRef;
};
const [_used, others] = splitProps(props, [
// Remove the 'used'
"class",
"aria-label",
"role",
"digits",
"data",
"innerHTML",
// Also remove the ones used in `updateProperties`
"transformTiming",
"spinTiming",
"opacityTiming",
"animated",
"respectMotionPreference",
"trend",
"plugins"
]);
return <Dynamic
component="number-flow"
ref={handleRef}
attr:data-will-change={props.willChange ? "" : void 0}
class={props.class}
aria-label={props.data()?.valueAsString}
{...others}
role="img"
digits={props.digits}
data={props.data()}
innerHTML={renderInnerHTML(props.data())}
/>;
}
function NumberFlow(props) {
const [_, others] = splitProps(props, ["value", "locales", "format", "prefix", "suffix"]);
let innerRef;
const group = useNumberFlowGroupContext();
const localesString = createMemo(() => props.locales ? JSON.stringify(props.locales) : "");
const formatString = createMemo(() => props.format ? JSON.stringify(props.format) : "");
const data = createMemo(() => {
const formatter = formatters[`${localesString()}:${formatString()}`] ??= new Intl.NumberFormat(
props.locales,
props.format
);
return formatToData(props.value, formatter, props.prefix, props.suffix);
});
return <NumberFlowImpl {...others} group={group} data={data} innerRef={innerRef} />;
}
var NumberFlowGroupContext = createContext(() => void 0);
var useNumberFlowGroupContext = () => useContext(NumberFlowGroupContext);
function NumberFlowGroup(props) {
let flows = /* @__PURE__ */ new Set();
let updating = false;
let pending = /* @__PURE__ */ new WeakMap();
const value = createMemo(() => ({
useRegister(ref) {
onMount(() => {
flows.add(ref);
onCleanup(() => {
flows.delete(ref);
});
});
},
willUpdate() {
if (updating) return;
updating = true;
flows.forEach((ref) => {
const f = ref;
if (!f || !f.created) return;
f.willUpdate();
pending.set(f, true);
});
},
didUpdate() {
flows.forEach((ref) => {
const f = ref;
if (!f || !pending.get(f)) return;
f.didUpdate();
pending.delete(f);
});
updating = false;
}
}));
return <NumberFlowGroupContext.Provider value={value}>
{props.children}
</NumberFlowGroupContext.Provider>;
}
function usePrefersReducedMotion() {
const [prefersReducedMotion, set] = createSignal(false);
onMount(() => {
set(_prefersReducedMotion?.matches ?? false);
const onChange = ({ matches }) => {
set(matches);
};
_prefersReducedMotion?.addEventListener("change", onChange);
onCleanup(() => {
_prefersReducedMotion?.removeEventListener("change", onChange);
});
});
return prefersReducedMotion;
}
function useCanAnimate(props = { respectMotionPreference: true }) {
const [canAnimate, setCanAnimate] = createSignal(_canAnimate);
onMount(() => {
setCanAnimate(_canAnimate);
});
const prefersReducedMotion = usePrefersReducedMotion();
const canAnimateWithPreference = createMemo(() => {
canAnimate() && !prefersReducedMotion();
});
const finalCanAnimate = createMemo(() => {
return props.respectMotionPreference ? canAnimateWithPreference() : canAnimate();
});
return finalCanAnimate;
}
export {
NumberFlowElement,
NumberFlowGroup,
NumberFlow as default,
useCanAnimate
};