morphicons 1.1.0 → 1.1.1

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/README.md CHANGED
@@ -208,7 +208,7 @@ Let `aᶜᵢ = aᵢ − c_A` and `b̃ᵢ = R(−θ*)·(bᵢ − c_B)/σ*` (B bro
208
208
  ```
209
209
  P(t) = c(t) + σ*ᵗ · R(t·θ*) · [(1−t)·aᶜᵢ + t·b̃ᵢ]
210
210
 
211
- c(t) = lerp(c_A, c_B, t) linear translation
211
+ c(t) = lerp(c_A, c_B, t) linear translation (block transport under the global hybrid — below)
212
212
  t·θ* = linear angle (θ* is already in (−π, π], short path)
213
213
  σ*ᵗ = exp(t·ln σ*) log-linear scale (geodesic in ℝ⁺)
214
214
  ```
@@ -220,6 +220,14 @@ Exact at both endpoints (max error ~3.6e-15 in tests). Properties:
220
220
  - Mixed cases do both simultaneously and continuously.
221
221
  - With spring overshoot (t > 1) the formula **extrapolates** naturally: rotation and scale overshoot slightly and come back — free juice.
222
222
 
223
+ **Block transport (global hybrid).** When the whole icon is congruent (§4), lerping each subpath centroid reintroduces the chord disease one level up: every part spins with the shared θ*, but an off-center part travels the chord — inside the arc — and drifts toward the center mid-flight. An arrow's head sagged ~1 px toward its shaft at t = 0.5 (`r·(1 − cos(θ/2))` with r = 3.5, θ = 90°) while both parts rotated perfectly. Under the hybrid, each centroid rides the shared similarity around the global centroid `g_A` instead:
224
+
225
+ ```
226
+ c_k(t) = c_{A,k} + t·d_k + (σ*ᵗ·R(t·θ*) − I)·(c_{A,k} − g_A)
227
+ ```
228
+
229
+ `d_k` is fixed once so that `c_k(1) = c_{B,k}` exactly (it absorbs the tiny global residual). Every point of every subpath then moves under ONE similarity per frame: congruent icons are rigid through the whole flight, not only at the endpoints. With θ* = 0, or centroids sitting on `g_A`, or no hybrid at all, the formula degrades continuously to the plain lerp — no thresholds, no special cases.
230
+
223
231
  ### 6. Serialization and canonical snap
224
232
 
225
233
  In flight, each subpath is emitted as `M x y L x y …` (+ `Z` if the pair is closed↔closed; closed→open flies open, the loop opening at the cut chosen by the circular correspondence) with 2 decimals — invisible at 24px, and the frame's only allocation. On settle, the driver **snaps to the canonical `d`** of the target icon: exact fidelity at rest (real curves, not polylines), subpath count reset after duplications, and the DOM ends up identical to a static icon's. The jump is < 0.02px — imperceptible.
@@ -255,9 +263,9 @@ CI budget (size-limit, gzip) as an anti-regression tripwire — gates carry ~10%
255
263
 
256
264
  | entry | measured | gate |
257
265
  |---|---|---|
258
- | `morphicons` (core) | 6.45 KB | 7 KB |
259
- | `morphicons/dom` (core + driver) | 6.92 KB | 7.5 KB |
260
- | `morphicons/react` (all, react external) | 7.65 KB | 8.5 KB |
266
+ | `morphicons` (core) | 6.60 KB | 7 KB |
267
+ | `morphicons/dom` (core + driver) | 7.04 KB | 7.5 KB |
268
+ | `morphicons/react` (all, react external) | 7.77 KB | 8.5 KB |
261
269
 
262
270
  ## Playground
263
271
 
package/dist/dom.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as buildPlan, c as cubicsToPathD, f as interpPolar, l as serialize, n as Spring, r as resampleIcon, s as iconToCubics, t as SPRING_PRESETS, u as allocOutputs } from "./spring-BR0BCAfU.js";
1
+ import { a as buildPlan, c as cubicsToPathD, f as interpPolar, l as serialize, n as Spring, r as resampleIcon, s as iconToCubics, t as SPRING_PRESETS, u as allocOutputs } from "./spring-BTcHiHR5.js";
2
2
  //#region src/dom/index.ts
3
3
  const tickers = /* @__PURE__ */ new Set();
4
4
  let rafId = 0;
package/dist/index.d.ts CHANGED
@@ -18,6 +18,13 @@ interface PlanItem {
18
18
  /** true if both endpoints are closed loops: the subpath flies with Z.
19
19
  * Closed → open flies open: the loop opens at the chosen cut. */
20
20
  closed: boolean;
21
+ /** Block transport (set by the global hybrid, else null): mid-flight the
22
+ * centroid rides the shared similarity around the global centroid instead
23
+ * of lerping — off = c_A − g_A; drift closes c(1) = c_B exactly. */
24
+ block: {
25
+ off: readonly [number, number];
26
+ drift: readonly [number, number];
27
+ } | null;
21
28
  }
22
29
  interface MorphPlan {
23
30
  items: PlanItem[];
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as buildPlan, c as cubicsToPathD, d as interpLinear, f as interpPolar, i as resamplePath, l as serialize, n as Spring, o as fitIcon, r as resampleIcon, s as iconToCubics, t as SPRING_PRESETS, u as allocOutputs } from "./spring-BR0BCAfU.js";
1
+ import { a as buildPlan, c as cubicsToPathD, d as interpLinear, f as interpPolar, i as resamplePath, l as serialize, n as Spring, o as fitIcon, r as resampleIcon, s as iconToCubics, t as SPRING_PRESETS, u as allocOutputs } from "./spring-BTcHiHR5.js";
2
2
  export { SPRING_PRESETS, Spring, allocOutputs, buildPlan, cubicsToPathD, fitIcon, iconToCubics, interpLinear, interpPolar, resampleIcon, resamplePath, serialize };
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as buildPlan, f as interpPolar, l as serialize, r as resampleIcon, u as allocOutputs } from "./spring-BR0BCAfU.js";
1
+ import { a as buildPlan, f as interpPolar, l as serialize, r as resampleIcon, u as allocOutputs } from "./spring-BTcHiHR5.js";
2
2
  import { canonicalD, createMorph } from "./dom.js";
3
3
  import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -8,12 +8,21 @@ function interpPolar(plan, t, out) {
8
8
  const it = plan.items[k];
9
9
  const o = out[k];
10
10
  const n = plan.n;
11
- const cx = it.ca[0] + (it.cb[0] - it.ca[0]) * t;
12
- const cy = it.ca[1] + (it.cb[1] - it.ca[1]) * t;
13
11
  const s = Math.exp(it.lnSigma * t);
14
12
  const ang = it.theta * t;
15
13
  const cos = Math.cos(ang) * s;
16
14
  const sin = Math.sin(ang) * s;
15
+ let cx;
16
+ let cy;
17
+ if (it.block) {
18
+ const [ox, oy] = it.block.off;
19
+ const [dx, dy] = it.block.drift;
20
+ cx = it.ca[0] + dx * t + (ox * cos - oy * sin - ox);
21
+ cy = it.ca[1] + dy * t + (ox * sin + oy * cos - oy);
22
+ } else {
23
+ cx = it.ca[0] + (it.cb[0] - it.ca[0]) * t;
24
+ cy = it.ca[1] + (it.cb[1] - it.ca[1]) * t;
25
+ }
17
26
  for (let i = 0; i < n; i++) {
18
27
  const px = it.aC[2 * i] + (it.bT[2 * i] - it.aC[2 * i]) * t;
19
28
  const py = it.aC[2 * i + 1] + (it.bT[2 * i + 1] - it.aC[2 * i + 1]) * t;
@@ -806,7 +815,8 @@ function applyGlobal(items, n) {
806
815
  ga.set(it.a, 2 * n * k);
807
816
  gb.set(it.bO, 2 * n * k);
808
817
  });
809
- const g = procrustes(ga, gb, centroid(ga), centroid(gb));
818
+ const gca = centroid(ga);
819
+ const g = procrustes(ga, gb, gca, centroid(gb));
810
820
  if (g.res >= GLOBAL_EPS) return;
811
821
  const cos = Math.cos(-g.theta);
812
822
  const sin = Math.sin(-g.theta);
@@ -828,6 +838,17 @@ function applyGlobal(items, n) {
828
838
  it.theta = g.theta;
829
839
  it.lnSigma = Math.log(g.sigma);
830
840
  it.res = nb > 1e-12 ? Math.sqrt(e2 / nb) : 0;
841
+ const s1 = Math.exp(it.lnSigma);
842
+ const c1 = Math.cos(it.theta) * s1;
843
+ const n1 = Math.sin(it.theta) * s1;
844
+ const ox = it.ca[0] - gca[0];
845
+ const oy = it.ca[1] - gca[1];
846
+ const rx = ox * c1 - oy * n1 - ox;
847
+ const ry = ox * n1 + oy * c1 - oy;
848
+ it.block = {
849
+ off: [ox, oy],
850
+ drift: [it.cb[0] - it.ca[0] - rx, it.cb[1] - it.ca[1] - ry]
851
+ };
831
852
  }
832
853
  }
833
854
  /** Builds the morph plan between two lists of sampled subpaths. The plan is
@@ -879,7 +900,8 @@ function buildPlan(srcSubs, dstSubs) {
879
900
  theta: al.theta,
880
901
  lnSigma: Math.log(al.sigma),
881
902
  res: al.res,
882
- closed: srcSubs[si].closed && dstSubs[di].closed
903
+ closed: srcSubs[si].closed && dstSubs[di].closed,
904
+ block: null
883
905
  };
884
906
  });
885
907
  if (items.length > 1) applyGlobal(items, n);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morphicons",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Universal morphing for stroke-based icons with spring physics — zero runtime dependencies",
5
5
  "license": "MIT",
6
6
  "author": "Guillermo (https://guillermolg.com)",