UNPKG

@sarmal/react

Version:

React components and hooks for @sarmal/core animated curves

378 lines (374 loc) 12.4 kB
"use client"; import { memo, useLayoutEffect, useRef, useEffect } from 'react'; import { createSarmal, createSarmalSVG, createSarmalDotMatrix } from '@sarmal/core'; import { jsx } from 'react/jsx-runtime'; // src/use-sarmal.ts function useMorphEffect(curve, instance, morphOptions) { const committedCurveRef = useRef(curve); const morphOptionsRef = useRef(morphOptions); morphOptionsRef.current = morphOptions; useEffect(() => { if (curve === committedCurveRef.current) { return; } committedCurveRef.current = curve; if (instance.current == null) { return; } const rawOpts = morphOptionsRef.current; const opts = rawOpts?.morphDuration != null || rawOpts?.morphStrategy != null || rawOpts?.morphEasing != null || rawOpts?.morphAlign != null ? { ...rawOpts.morphDuration != null && { duration: rawOpts.morphDuration }, ...rawOpts.morphStrategy != null && { morphStrategy: rawOpts.morphStrategy }, ...rawOpts.morphEasing != null && { easing: rawOpts.morphEasing }, ...rawOpts.morphAlign != null && { align: rawOpts.morphAlign } } : void 0; instance.current.morphTo(curve, opts).catch(() => { }); }, [curve]); return committedCurveRef; } // src/utils.ts function resolveCanvasSize(canvas, initWidth, initHeight) { const parent = canvas.parentElement; const parentW = parent?.clientWidth ?? 0; const parentH = parent?.clientHeight ?? 0; const w = initWidth ?? parentW; const h = initHeight ?? parentH; if (w > 0 && h > 0) { return { width: w, height: h }; } console.warn( "[sarmal] Could not determine canvas dimensions. The parent container reports 0x0. It needs an explicit height (height: auto won't work). Falling back to 300x300." ); return { width: 300, height: 300 }; } function shallowEqualTrailColor(a, b) { if (a === b) { return true; } if (Array.isArray(a) && Array.isArray(b)) { return a.length === b.length && a.every((c, i) => c === b[i]); } return false; } // src/use-sarmal.ts function useSarmal(curve, options, init, morphOptions) { const canvasRef = useRef(null); const instance = useRef(null); const committedCurveRef = useMorphEffect(curve, instance, morphOptions); useLayoutEffect(() => { const canvas = canvasRef.current; if (canvas == null) { return; } const { width, height } = resolveCanvasSize(canvas, init?.width, init?.height); canvas.width = width; canvas.height = height; instance.current = createSarmal(canvas, curve, { ...options, ...init?.trailLength !== void 0 && { trailLength: init.trailLength }, ...init?.headRadius !== void 0 && { headRadius: init.headRadius }, ...init?.autoStart !== void 0 && { autoStart: init.autoStart }, ...init?.initialPhase !== void 0 && { initialPhase: init.initialPhase }, ...init?.pauseOnHidden !== void 0 && { pauseOnHidden: init.pauseOnHidden } }); committedCurveRef.current = curve; return () => { instance.current?.destroy(); instance.current = null; }; }, [ init?.width, init?.height, init?.trailLength, init?.headRadius, init?.autoStart, init?.initialPhase, init?.pauseOnHidden ]); return { canvasRef, instance }; } function useRenderOptions(instance, trailColor, skeletonColor, headColor, trailStyle, trailWidth) { const initializedRef = useRef(false); const prevTrailColorRef = useRef(trailColor); useEffect(() => { if (!initializedRef.current) { initializedRef.current = true; return; } const sarmal = instance.current; if (!sarmal) { return; } const trailColorChanged = !shallowEqualTrailColor(trailColor, prevTrailColorRef.current); prevTrailColorRef.current = trailColor; sarmal.setRenderOptions({ ...trailColorChanged && trailColor !== void 0 && { trailColor }, ...skeletonColor !== void 0 && { skeletonColor }, // null is the core API signal for "derive headColor from trailColor" headColor: headColor ?? null, ...trailStyle !== void 0 && { trailStyle }, ...trailWidth !== void 0 && { trailWidth } }); }, [trailColor, skeletonColor, headColor, trailStyle, trailWidth]); } var SarmalInner = ({ curve, className, style, trailColor, morphDuration, morphStrategy, morphEasing, morphAlign, onReady, skeletonColor, headColor, trailStyle, width, height, headRadius, trailLength, trailWidth, autoStart, initialPhase, pauseOnHidden }) => { const { canvasRef, instance } = useSarmal( curve, { ...skeletonColor !== void 0 && { skeletonColor }, ...trailColor !== void 0 && { trailColor }, ...headColor !== void 0 && { headColor }, ...trailStyle !== void 0 && { trailStyle } }, { ...width !== void 0 && { width }, ...height !== void 0 && { height }, ...headRadius !== void 0 && { headRadius }, ...trailLength !== void 0 && { trailLength }, ...autoStart !== void 0 && { autoStart }, ...initialPhase !== void 0 && { initialPhase }, ...pauseOnHidden !== void 0 && { pauseOnHidden } }, morphDuration !== void 0 || morphStrategy !== void 0 || morphEasing !== void 0 || morphAlign !== void 0 ? { ...morphDuration !== void 0 && { morphDuration }, ...morphStrategy !== void 0 && { morphStrategy }, ...morphEasing !== void 0 && { morphEasing }, ...morphAlign !== void 0 && { morphAlign } } : void 0 ); useRenderOptions(instance, trailColor, skeletonColor, headColor, trailStyle, trailWidth); useLayoutEffect(() => { if (instance.current) { onReady?.(instance.current); } }, []); return /* @__PURE__ */ jsx("canvas", { ref: canvasRef, className, style }); }; var Sarmal = memo(SarmalInner); function useSarmalSVG(curve, options, init, morphOptions) { const svgRef = useRef(null); const instance = useRef(null); const committedCurveRef = useMorphEffect(curve, instance, morphOptions); useLayoutEffect(() => { const svg = svgRef.current; if (svg == null) { return; } instance.current = createSarmalSVG(svg, curve, { ...options, ...init?.trailLength !== void 0 && { trailLength: init.trailLength }, ...init?.headRadius !== void 0 && { headRadius: init.headRadius }, ...init?.autoStart !== void 0 && { autoStart: init.autoStart }, ...init?.initialPhase !== void 0 && { initialPhase: init.initialPhase }, ...init?.pauseOnHidden !== void 0 && { pauseOnHidden: init.pauseOnHidden } }); committedCurveRef.current = curve; return () => { instance.current?.destroy(); instance.current = null; }; }, [ init?.trailLength, init?.headRadius, init?.autoStart, init?.initialPhase, init?.pauseOnHidden ]); return { svgRef, instance }; } var SarmalSVGInner = ({ curve, className, style, trailColor, morphDuration, morphStrategy, morphEasing, morphAlign, onReady, skeletonColor, headColor, trailStyle, headRadius, trailLength, trailWidth, autoStart, initialPhase, pauseOnHidden }) => { const { svgRef, instance } = useSarmalSVG( curve, { ...skeletonColor !== void 0 && { skeletonColor }, ...trailColor !== void 0 && { trailColor }, ...headColor !== void 0 && { headColor }, ...trailStyle !== void 0 && { trailStyle } }, { ...headRadius !== void 0 && { headRadius }, ...trailLength !== void 0 && { trailLength }, ...autoStart !== void 0 && { autoStart }, ...initialPhase !== void 0 && { initialPhase }, ...pauseOnHidden !== void 0 && { pauseOnHidden } }, morphDuration !== void 0 || morphStrategy !== void 0 || morphEasing !== void 0 || morphAlign !== void 0 ? { ...morphDuration !== void 0 && { morphDuration }, ...morphStrategy !== void 0 && { morphStrategy }, ...morphEasing !== void 0 && { morphEasing }, ...morphAlign !== void 0 && { morphAlign } } : void 0 ); useRenderOptions(instance, trailColor, skeletonColor, headColor, trailStyle, trailWidth); useLayoutEffect(() => { if (instance.current) { onReady?.(instance.current); } }, []); return /* @__PURE__ */ jsx("svg", { ref: svgRef, className, style }); }; var SarmalSVG = memo(SarmalSVGInner); function useSarmalDotMatrix(curve, options, init, morphOptions) { const canvasRef = useRef(null); const instance = useRef(null); const committedCurveRef = useMorphEffect(curve, instance, morphOptions); useLayoutEffect(() => { const canvas = canvasRef.current; if (canvas == null) { return; } const { width, height } = resolveCanvasSize(canvas, init?.width, init?.height); canvas.width = width; canvas.height = height; instance.current = createSarmalDotMatrix(canvas, curve, { ...options, ...init?.cols !== void 0 && { cols: init.cols }, ...init?.rows !== void 0 && { rows: init.rows }, ...init?.roundness !== void 0 && { roundness: init.roundness }, ...init?.trailLength !== void 0 && { trailLength: init.trailLength }, ...init?.autoStart !== void 0 && { autoStart: init.autoStart }, ...init?.initialPhase !== void 0 && { initialPhase: init.initialPhase }, ...init?.pauseOnHidden !== void 0 && { pauseOnHidden: init.pauseOnHidden } }); committedCurveRef.current = curve; return () => { instance.current?.destroy(); instance.current = null; }; }, [ init?.width, init?.height, init?.cols, init?.rows, init?.roundness, init?.trailLength, init?.autoStart, init?.initialPhase, init?.pauseOnHidden ]); return { canvasRef, instance }; } function useDotMatrixRenderOptions(instance, trailColor, trailStyle, skeletonColor, gridColor) { const initializedRef = useRef(false); const prevTrailColorRef = useRef(trailColor); useEffect(() => { if (!initializedRef.current) { initializedRef.current = true; return; } const sarmal = instance.current; if (!sarmal) { return; } const trailColorChanged = !shallowEqualTrailColor(trailColor, prevTrailColorRef.current); prevTrailColorRef.current = trailColor; sarmal.setRenderOptions({ ...trailColorChanged && trailColor !== void 0 && { trailColor }, ...skeletonColor !== void 0 && { skeletonColor }, ...trailStyle !== void 0 && { trailStyle }, ...gridColor !== void 0 && { gridColor } }); }, [trailColor, skeletonColor, trailStyle, gridColor]); } var SarmalDotMatrixInner = ({ curve, className, style, trailColor, trailStyle, skeletonColor, gridColor, morphDuration, morphStrategy, morphEasing, morphAlign, onReady, cols, rows, roundness, trailLength, width, height, autoStart, initialPhase, pauseOnHidden }) => { const { canvasRef, instance } = useSarmalDotMatrix( curve, { ...trailColor !== void 0 && { trailColor }, ...trailStyle !== void 0 && { trailStyle }, ...skeletonColor !== void 0 && { skeletonColor }, ...gridColor !== void 0 && { gridColor } }, { ...cols !== void 0 && { cols }, ...rows !== void 0 && { rows }, ...roundness !== void 0 && { roundness }, ...trailLength !== void 0 && { trailLength }, ...width !== void 0 && { width }, ...height !== void 0 && { height }, ...autoStart !== void 0 && { autoStart }, ...initialPhase !== void 0 && { initialPhase }, ...pauseOnHidden !== void 0 && { pauseOnHidden } }, morphDuration !== void 0 || morphStrategy !== void 0 || morphEasing !== void 0 || morphAlign !== void 0 ? { ...morphDuration !== void 0 && { morphDuration }, ...morphStrategy !== void 0 && { morphStrategy }, ...morphEasing !== void 0 && { morphEasing }, ...morphAlign !== void 0 && { morphAlign } } : void 0 ); useDotMatrixRenderOptions(instance, trailColor, trailStyle, skeletonColor, gridColor); useLayoutEffect(() => { if (instance.current) { onReady?.(instance.current); } }, []); return /* @__PURE__ */ jsx("canvas", { ref: canvasRef, className, style }); }; var SarmalDotMatrix = memo(SarmalDotMatrixInner); export { Sarmal, SarmalDotMatrix, SarmalSVG, useSarmal, useSarmalDotMatrix, useSarmalSVG }; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map