UNPKG

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
// 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 };