UNPKG

solid-number-flow

Version:

A SolidJS component to transition, format, and localize numbers. Forked from @barvian/number-flow.

197 lines (194 loc) 6.09 kB
import { createComponent, mergeProps, Dynamic } from 'solid-js/web'; import { define, NumberFlowLite, formatToData, canAnimate, renderInnerHTML, prefersReducedMotion } from 'number-flow'; import { createContext, splitProps, createMemo, useContext, onMount, onCleanup, createSignal, createEffect } from 'solid-js'; // src/index.tsx 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 createComponent(Dynamic, mergeProps({ component: "number-flow", ref: handleRef, get ["attr:data-will-change"]() { return props.willChange ? "" : void 0; }, get ["class"]() { return props.class; }, get ["aria-label"]() { return props.data()?.valueAsString; } }, others, { role: "img", get digits() { return props.digits; }, get data() { return props.data(); }, get innerHTML() { return 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 createComponent(NumberFlowImpl, mergeProps(others, { group, data, 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 createComponent(NumberFlowGroupContext.Provider, { value, get children() { return props.children; } }); } function usePrefersReducedMotion() { const [prefersReducedMotion$1, set] = createSignal(false); onMount(() => { set(prefersReducedMotion?.matches ?? false); const onChange = ({ matches }) => { set(matches); }; prefersReducedMotion?.addEventListener("change", onChange); onCleanup(() => { prefersReducedMotion?.removeEventListener("change", onChange); }); }); return prefersReducedMotion$1; } function useCanAnimate(props = { respectMotionPreference: true }) { const [canAnimate$1, setCanAnimate] = createSignal(canAnimate); onMount(() => { setCanAnimate(canAnimate); }); const prefersReducedMotion = usePrefersReducedMotion(); const canAnimateWithPreference = createMemo(() => { canAnimate$1() && !prefersReducedMotion(); }); const finalCanAnimate = createMemo(() => { return props.respectMotionPreference ? canAnimateWithPreference() : canAnimate$1(); }); return finalCanAnimate; } export { NumberFlowElement, NumberFlowGroup, NumberFlow as default, useCanAnimate };