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