@sarmal/react
Version:
React components and hooks for @sarmal/core animated curves
378 lines (374 loc) • 12.4 kB
JavaScript
"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