@sarmal/core 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -31,8 +31,8 @@ import "./curves/star4.cjs";
31
31
  import "./curves/star7.cjs";
32
32
 
33
33
  interface SVGRendererOptions extends BaseRendererOptions {
34
- /** Container element that will contain the SVG */
35
- container: Element;
34
+ /** SVG element the renderer draws into directly */
35
+ container: SVGSVGElement;
36
36
  engine: Engine;
37
37
  /** @default 'Loading' */
38
38
  ariaLabel?: string;
@@ -47,22 +47,25 @@ interface SVGSarmalOptions extends Omit<SVGRendererOptions, "container" | "engin
47
47
  */
48
48
  declare function createSVGRenderer(options: SVGRendererOptions): SarmalInstance;
49
49
  /**
50
- * Creates a sarmal animation inside a container element using an SVG renderer
51
- * The SVG is appended to the container and animated via requestAnimationFrame
50
+ * Creates a sarmal animation directly inside an `<svg>` element using an SVG renderer.
51
+ * The passed `<svg>` element is set to `viewBox="0 0 100 100"` and animated via requestAnimationFrame
52
52
  *
53
53
  * @example
54
54
  * ```ts
55
55
  * import { createSarmalSVG, epitrochoid7 } from '@sarmal/core'
56
- * const sarmal = createSarmalSVG(document.getElementById('spinner'), epitrochoid7)
56
+ *
57
+ * // <svg id="spinner"></svg> in your HTML
58
+ * const svg = document.getElementById('spinner')
59
+ * const sarmal = createSarmalSVG(svg, epitrochoid7)
57
60
  *
58
61
  * // To control manually, use autoStart: false
59
- * const controlled = createSarmalSVG(container, rose5, { autoStart: false })
62
+ * const controlled = createSarmalSVG(svg, rose5, { autoStart: false })
60
63
  * controlled.play() // Start when ready
61
64
  * controlled.pause() // Pause later
62
65
  * ```
63
66
  */
64
67
  declare function createSarmalSVG(
65
- container: Element,
68
+ container: SVGSVGElement,
66
69
  curveDef: CurveDef,
67
70
  options?: SVGSarmalOptions,
68
71
  ): SarmalInstance;
package/dist/index.d.ts CHANGED
@@ -31,8 +31,8 @@ import "./curves/star4.js";
31
31
  import "./curves/star7.js";
32
32
 
33
33
  interface SVGRendererOptions extends BaseRendererOptions {
34
- /** Container element that will contain the SVG */
35
- container: Element;
34
+ /** SVG element the renderer draws into directly */
35
+ container: SVGSVGElement;
36
36
  engine: Engine;
37
37
  /** @default 'Loading' */
38
38
  ariaLabel?: string;
@@ -47,22 +47,25 @@ interface SVGSarmalOptions extends Omit<SVGRendererOptions, "container" | "engin
47
47
  */
48
48
  declare function createSVGRenderer(options: SVGRendererOptions): SarmalInstance;
49
49
  /**
50
- * Creates a sarmal animation inside a container element using an SVG renderer
51
- * The SVG is appended to the container and animated via requestAnimationFrame
50
+ * Creates a sarmal animation directly inside an `<svg>` element using an SVG renderer.
51
+ * The passed `<svg>` element is set to `viewBox="0 0 100 100"` and animated via requestAnimationFrame
52
52
  *
53
53
  * @example
54
54
  * ```ts
55
55
  * import { createSarmalSVG, epitrochoid7 } from '@sarmal/core'
56
- * const sarmal = createSarmalSVG(document.getElementById('spinner'), epitrochoid7)
56
+ *
57
+ * // <svg id="spinner"></svg> in your HTML
58
+ * const svg = document.getElementById('spinner')
59
+ * const sarmal = createSarmalSVG(svg, epitrochoid7)
57
60
  *
58
61
  * // To control manually, use autoStart: false
59
- * const controlled = createSarmalSVG(container, rose5, { autoStart: false })
62
+ * const controlled = createSarmalSVG(svg, rose5, { autoStart: false })
60
63
  * controlled.play() // Start when ready
61
64
  * controlled.pause() // Pause later
62
65
  * ```
63
66
  */
64
67
  declare function createSarmalSVG(
65
- container: Element,
68
+ container: SVGSVGElement,
66
69
  curveDef: CurveDef,
67
70
  options?: SVGSarmalOptions,
68
71
  ): SarmalInstance;
package/dist/index.js CHANGED
@@ -298,12 +298,20 @@ function computeNormal(trail, i) {
298
298
  const tangent = computeTangent(trail, i);
299
299
  return { x: -tangent.y, y: tangent.x };
300
300
  }
301
- function computeTrailQuad(trail, i, trailCount, toX, toY) {
301
+ function computeTrailQuad(
302
+ trail,
303
+ i,
304
+ trailCount,
305
+ toX,
306
+ toY,
307
+ minWidth = TRAIL_MIN_WIDTH,
308
+ maxWidth = TRAIL_MAX_WIDTH,
309
+ ) {
302
310
  const progress = i / (trailCount - 1);
303
311
  const nextProgress = (i + 1) / (trailCount - 1);
304
312
  const opacity = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
305
- const w0 = (TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH)) / 2;
306
- const w1 = (TRAIL_MIN_WIDTH + nextProgress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH)) / 2;
313
+ const w0 = (minWidth + progress * (maxWidth - minWidth)) / 2;
314
+ const w1 = (minWidth + nextProgress * (maxWidth - minWidth)) / 2;
307
315
  const curr = trail[i];
308
316
  const next = trail[i + 1];
309
317
  const n0 = computeNormal(trail, i);
@@ -867,62 +875,61 @@ function createSVGRenderer(options) {
867
875
  let trailPalette = resolveTrailPalette(trailColor);
868
876
  const ariaLabel = options.ariaLabel ?? "Loading";
869
877
  warnIfTrailColorMismatch(trailColor, trailStyle);
870
- const htmlContainer = container;
871
- const width = htmlContainer.offsetWidth || 200;
872
- const height = htmlContainer.offsetHeight || 200;
873
- const headRadius = options.headRadius ?? getHeadDotRadius(width, height);
874
- const svg = el("svg");
875
- svg.setAttribute("width", String(width));
876
- svg.setAttribute("height", String(height));
877
- svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
878
- svg.setAttribute("role", "img");
879
- svg.setAttribute("aria-label", ariaLabel);
878
+ const viewSize = 100;
879
+ const headRadius = options.headRadius ?? 1.5;
880
+ const svgTrailMinWidth = 0.25;
881
+ const svgTrailMaxWidth = 1.25;
882
+ const svgSkeletonStrokeWidth = "0.75";
883
+ container.setAttribute("viewBox", `0 0 ${viewSize} ${viewSize}`);
884
+ container.setAttribute("role", "img");
885
+ container.setAttribute("aria-label", ariaLabel);
886
+ const group = el("g");
880
887
  const titleEl = el("title");
881
888
  titleEl.textContent = ariaLabel;
882
- svg.appendChild(titleEl);
889
+ group.appendChild(titleEl);
883
890
  const skeletonPath = el("path");
884
891
  skeletonPath.setAttribute("data-sarmal-role", "skeleton");
885
892
  skeletonPath.setAttribute("fill", "none");
886
893
  skeletonPath.setAttribute("stroke", skeletonColor);
887
894
  skeletonPath.setAttribute("stroke-opacity", String(DEFAULT_SKELETON_OPACITY));
888
- skeletonPath.setAttribute("stroke-width", "1.5");
895
+ skeletonPath.setAttribute("stroke-width", svgSkeletonStrokeWidth);
889
896
  if (skeletonColor === "transparent") {
890
897
  skeletonPath.setAttribute("visibility", "hidden");
891
898
  }
892
- svg.appendChild(skeletonPath);
899
+ group.appendChild(skeletonPath);
893
900
  const skeletonPathA = el("path");
894
901
  skeletonPathA.setAttribute("fill", "none");
895
902
  skeletonPathA.setAttribute("stroke", skeletonColor);
896
- skeletonPathA.setAttribute("stroke-width", "1.5");
903
+ skeletonPathA.setAttribute("stroke-width", svgSkeletonStrokeWidth);
897
904
  skeletonPathA.setAttribute("visibility", "hidden");
898
- svg.appendChild(skeletonPathA);
905
+ group.appendChild(skeletonPathA);
899
906
  const skeletonPathB = el("path");
900
907
  skeletonPathB.setAttribute("fill", "none");
901
908
  skeletonPathB.setAttribute("stroke", skeletonColor);
902
- skeletonPathB.setAttribute("stroke-width", "1.5");
909
+ skeletonPathB.setAttribute("stroke-width", svgSkeletonStrokeWidth);
903
910
  skeletonPathB.setAttribute("visibility", "hidden");
904
- svg.appendChild(skeletonPathB);
911
+ group.appendChild(skeletonPathB);
905
912
  let morphPathABuilt = "";
906
913
  let morphPathBBuilt = "";
907
914
  const trailPaths = [];
908
915
  for (let i = 0; i < poolSize; i++) {
909
916
  const path = el("path");
910
917
  path.setAttribute("fill", trailSolid);
911
- svg.appendChild(path);
918
+ group.appendChild(path);
912
919
  trailPaths.push(path);
913
920
  }
914
921
  const headCircle = el("circle");
915
922
  headCircle.setAttribute("data-sarmal-role", "head");
916
923
  headCircle.setAttribute("fill", headColor);
917
924
  headCircle.setAttribute("r", String(headRadius));
918
- svg.appendChild(headCircle);
919
- container.appendChild(svg);
925
+ group.appendChild(headCircle);
926
+ container.appendChild(group);
920
927
  let gradientAnimTime = 0;
921
928
  let scale = 1;
922
929
  let offsetX = 0;
923
930
  let offsetY = 0;
924
931
  function applyBoundaries(skeleton2) {
925
- const b = computeBoundaries(skeleton2, width, height);
932
+ const b = computeBoundaries(skeleton2, viewSize, viewSize);
926
933
  if (b) {
927
934
  scale = b.scale;
928
935
  offsetX = b.offsetX;
@@ -958,6 +965,8 @@ function createSVGRenderer(options) {
958
965
  trailCount,
959
966
  px,
960
967
  py,
968
+ svgTrailMinWidth,
969
+ svgTrailMaxWidth,
961
970
  );
962
971
  const d = `M${l0x.toFixed(2)} ${l0y.toFixed(2)} L${l1x.toFixed(2)} ${l1y.toFixed(2)} L${r1x.toFixed(2)} ${r1y.toFixed(2)} L${r0x.toFixed(2)} ${r0y.toFixed(2)} Z`;
963
972
  trailPaths[i].setAttribute("d", d);
@@ -1080,7 +1089,7 @@ function createSVGRenderer(options) {
1080
1089
  morphResolve = null;
1081
1090
  morphReject = null;
1082
1091
  }
1083
- svg.remove();
1092
+ group.remove();
1084
1093
  },
1085
1094
  ...enginePassthroughs(engine),
1086
1095
  morphTo(target, options2) {