morphicons 1.7.0 → 2.0.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/dom.js CHANGED
@@ -1,5 +1,7 @@
1
- import { a as buildPlan, c as interpPolar, n as Spring, o as allocOutputs, r as resampleIcon, t as SPRING_PRESETS } from "./spring-CFHloqPP.js";
1
+ import { a as interpPolar, n as Spring, r as allocOutputs, t as SPRING_PRESETS } from "./spring-B_P92nf6.js";
2
2
  import { i as serialize, n as iconToCubics, r as cubicsToPathD } from "./normalize-CYnN3Npw.js";
3
+ import { a as buildPlan, d as reversePts, n as resamplePath, o as centroid, t as resampleIcon } from "./resample-CXlaZ5ZQ.js";
4
+ import { a as paintOf, c as solid, o as painted, r as inside, s as signedArea } from "./region-BjECuWZR.js";
3
5
  //#region src/dom/index.ts
4
6
  const tickers = /* @__PURE__ */ new Set();
5
7
  let rafId = 0;
@@ -32,11 +34,33 @@ function removeTicker(tick) {
32
34
  const samples = /* @__PURE__ */ new WeakMap();
33
35
  const canon = /* @__PURE__ */ new WeakMap();
34
36
  const plans = /* @__PURE__ */ new WeakMap();
37
+ function cubicsOf(icon) {
38
+ if (typeof icon === "string" || paintOf(icon) === "stroke") return iconToCubics(icon);
39
+ return solid(icon).flatMap((el) => {
40
+ const ps = iconToCubics([el]);
41
+ const odd = String(el[1]["fill-rule"]).trim() === "evenodd";
42
+ let sum = 0;
43
+ for (const p of ps) sum += signedArea(p.pts);
44
+ for (const p of ps) {
45
+ let flip = sum < 0;
46
+ if (odd) {
47
+ let pos = true;
48
+ for (const o of ps) if (o !== p && inside(p.pts[0], p.pts[1], o.pts)) pos = !pos;
49
+ flip = signedArea(p.pts) > 0 !== pos;
50
+ }
51
+ if (flip) p.pts = reversePts(p.pts);
52
+ }
53
+ return ps;
54
+ });
55
+ }
35
56
  function sampledOf(icon) {
36
57
  if (typeof icon === "string") return resampleIcon(icon);
37
58
  let s = samples.get(icon);
38
59
  if (!s) {
39
- s = resampleIcon(icon);
60
+ s = cubicsOf(icon).map((p) => ({
61
+ pts: resamplePath(p, 64),
62
+ closed: p.closed
63
+ }));
40
64
  samples.set(icon, s);
41
65
  }
42
66
  return s;
@@ -44,12 +68,13 @@ function sampledOf(icon) {
44
68
  /** Canonical `d` of an icon: the input string verbatim, or the real cubics
45
69
  * quantized to 4 decimals (the at-rest snap; engine-stable bytes so SSR
46
70
  * hydration matches, see fmtCanon in core/serialize). Exported because it
47
- * is what a binding renders at SSR/rest before any runtime exists. */
71
+ * is what a binding renders at SSR/rest before any runtime exists. A fill
72
+ * icon's translucent duotone layer is left out (one path can't paint it). */
48
73
  function canonicalD(icon) {
49
74
  if (typeof icon === "string") return icon;
50
75
  let d = canon.get(icon);
51
76
  if (!d) {
52
- d = cubicsToPathD(iconToCubics(icon));
77
+ d = cubicsToPathD(cubicsOf(icon));
53
78
  canon.set(icon, d);
54
79
  }
55
80
  return d;
@@ -68,6 +93,137 @@ function planBetween(src, dst) {
68
93
  }
69
94
  return p;
70
95
  }
96
+ let regionMod = null;
97
+ let regionLoading = null;
98
+ let regionSince = 0;
99
+ let regionFailed = false;
100
+ let regionUrl;
101
+ let regionGen = 0;
102
+ let regionLoader = () => import("./region.js");
103
+ /** Replaces how the region normalizer is loaded (default: dynamic import of
104
+ * the bundled chunk). E.g. `setRegionLoader(async () => region)` after a
105
+ * static `import * as region from "morphicons/region"` to never touch the
106
+ * network mid-interaction. The next need loads through the new loader. */
107
+ function setRegionLoader(loader) {
108
+ regionLoader = loader;
109
+ regionGen++;
110
+ regionLoading = null;
111
+ regionMod = null;
112
+ regionFailed = false;
113
+ regionUrl = void 0;
114
+ }
115
+ const reload = (url) => new Promise((ok, ko) => {
116
+ const s = document.createElement("script");
117
+ s.type = "module";
118
+ s.onload = s.onerror = () => {
119
+ s.remove();
120
+ const m = globalThis[Symbol.for("morphicons.region")];
121
+ m ? ok(m) : ko(new Error(url));
122
+ };
123
+ s.src = `${url}#${Date.now()}`;
124
+ document.head.append(s);
125
+ });
126
+ const fromSource = () => {
127
+ const spec = /import\(\s*["'`]([^"'`]+)/.exec(String(regionLoader))?.[1];
128
+ const here = /\w+:\/\/[^\s)]+?(?=:\d+:\d+)/.exec(String((/* @__PURE__ */ new Error()).stack))?.[0];
129
+ try {
130
+ return spec && new URL(spec, here).href;
131
+ } catch {}
132
+ };
133
+ /** Starts (or joins) loading the region normalizer. Call it early — on
134
+ * idle, on hover — to keep the first stroke↔fill morph off the network. */
135
+ function preloadRegion() {
136
+ if (regionMod) return Promise.resolve(regionMod);
137
+ if (!regionLoading) {
138
+ regionSince = Date.now();
139
+ const url = regionUrl;
140
+ const gen = regionGen;
141
+ regionLoading = regionLoader().catch((e) => url ? reload(url) : Promise.reject(e)).then((m) => {
142
+ if (gen === regionGen) regionMod = m;
143
+ return m;
144
+ }, (e) => {
145
+ if (gen !== regionGen) throw e;
146
+ regionLoading = null;
147
+ regionFailed = true;
148
+ regionUrl ??= /dynamically imported module:?\s*(\S+)/i.exec(String(e?.message))?.[1] ?? fromSource();
149
+ throw e;
150
+ });
151
+ }
152
+ return regionLoading;
153
+ }
154
+ const quietPreload = () => new Promise((ok, ko) => {
155
+ if (typeof navigator === "undefined" ? false : navigator.connection?.saveData) return;
156
+ const go = () => {
157
+ regionFailed ? ko() : preloadRegion().then(ok, ko);
158
+ };
159
+ typeof requestIdleCallback === "function" ? requestIdleCallback(go, { timeout: 2e3 }) : setTimeout(go, 200);
160
+ });
161
+ /** How long a morph waits for the region chunk before flying the fallback
162
+ * (RAIL's 100 ms response budget). A slow, failed or hanging load never
163
+ * leaves the icon stuck on the old shape; the load goes on in the
164
+ * background and the next morph flies. A load already older than the
165
+ * budget isn't waited for again. */
166
+ const REGION_WAIT_MS = 100;
167
+ const paintAttrs = (p) => p === "stroke" ? {
168
+ fill: "none",
169
+ stroke: "inherit"
170
+ } : {
171
+ fill: "currentColor",
172
+ stroke: p === "fill" ? "none" : "inherit",
173
+ "fill-rule": "nonzero"
174
+ };
175
+ const looks = /* @__PURE__ */ new WeakMap();
176
+ function restLook(icon) {
177
+ if (typeof icon === "string" || paintOf(icon) === "stroke") return "stroke";
178
+ let look = looks.get(icon);
179
+ if (look) return look;
180
+ let filled = 0;
181
+ let enclosed = -1;
182
+ look = "fill";
183
+ for (const el of solid(icon)) {
184
+ const s = painted(el[1].stroke);
185
+ const f = painted(el[1].fill) || el[1].fill === void 0 && !s;
186
+ if (s) look = "both";
187
+ let a = 0;
188
+ for (const p of iconToCubics([el])) a += Math.abs(signedArea(p.pts));
189
+ if (f) filled += a;
190
+ else if (s) enclosed = Math.max(enclosed, 0) + a;
191
+ }
192
+ if (enclosed > 1.5 * filled) look = "stroke";
193
+ looks.set(icon, look);
194
+ return look;
195
+ }
196
+ /** The paint attributes a binding renders on the `<path>` with its first
197
+ * icon (SSR included), so a fill icon is filled before any script runs.
198
+ * None for a stroke icon: it inherits the `<svg>`'s stroke as always. */
199
+ function restAttrs(icon) {
200
+ if (icon === void 0 || paintOf(icon) === "stroke") return void 0;
201
+ return paintAttrs(restLook(icon));
202
+ }
203
+ function popPlan(from, to) {
204
+ const n = 64;
205
+ const item = ({ pts, closed }, half) => {
206
+ if (pts.length > 128) pts = pts.filter((_, i) => i % 4 < 2);
207
+ const c = centroid(pts);
208
+ const p = pts.map((v, i) => v - c[i & 1]);
209
+ const z = /* @__PURE__ */ new Float64Array(128);
210
+ return {
211
+ aC: half ? z : p,
212
+ bT: half ? p : z,
213
+ ca: c,
214
+ cb: c,
215
+ theta: 0,
216
+ lnSigma: 0,
217
+ closed,
218
+ half,
219
+ block: null
220
+ };
221
+ };
222
+ return {
223
+ n,
224
+ items: [...from.map((s) => item(s, 0)), ...to.map((s) => item(s, 1))]
225
+ };
226
+ }
71
227
  function resolveSpring(s) {
72
228
  if (typeof s === "string") return SPRING_PRESETS[s];
73
229
  const d = SPRING_PRESETS.snappy;
@@ -80,6 +236,7 @@ function resolveSpring(s) {
80
236
  function createMorph(el, icon, options) {
81
237
  const spring = new Spring();
82
238
  let reducedMotion = options?.reducedMotion ?? "never";
239
+ let strokeWidth = options?.strokeWidth ?? 2;
83
240
  let target = icon;
84
241
  let rest = true;
85
242
  let plan = null;
@@ -88,7 +245,32 @@ function createMorph(el, icon, options) {
88
245
  let t = 1;
89
246
  let flying = false;
90
247
  let dead = false;
91
- el.setAttribute("d", canonicalD(icon));
248
+ let regionFlight = false;
249
+ let drawn = false;
250
+ let pending = null;
251
+ let paint = "stroke";
252
+ let split = 0;
253
+ let popFrom = "stroke";
254
+ let popBase = null;
255
+ const paintAs = (p) => {
256
+ if (p === paint) return;
257
+ for (const [k, v] of Object.entries(paintAttrs(p))) el.setAttribute(k, v);
258
+ paint = p;
259
+ };
260
+ const paintRest = (ic) => {
261
+ const r = regionMod && paintOf(ic) === "fill" ? regionMod.regionOf(ic, strokeWidth) : ic;
262
+ el.setAttribute("d", canonicalD(r));
263
+ paintAs(restLook(r));
264
+ };
265
+ const upgrade = (load) => {
266
+ const icon = target;
267
+ load.then(() => {
268
+ if (!dead && rest && target === icon && pending === null) paintRest(icon);
269
+ }, () => {});
270
+ };
271
+ paintRest(icon);
272
+ if (paintOf(icon) === "fill" && !regionMod) upgrade(quietPreload());
273
+ const shown = (tt) => !split ? [0, plan?.items.length ?? 0] : tt < .5 ? [0, split] : [split, plan?.items.length ?? 0];
92
274
  const render = (tt) => {
93
275
  const p = plan;
94
276
  const o = out;
@@ -96,7 +278,10 @@ function createMorph(el, icon, options) {
96
278
  if (!p || !o || !cl) return;
97
279
  t = tt;
98
280
  interpPolar(p, tt, o);
99
- el.setAttribute("d", serialize(o, cl));
281
+ drawn = true;
282
+ const [lo, hi] = shown(tt);
283
+ if (split) paintAs(tt < .5 ? popFrom : restLook(target));
284
+ el.setAttribute("d", serialize(o.slice(lo, hi), cl.slice(lo, hi)));
100
285
  };
101
286
  const stop = () => {
102
287
  if (!flying) return;
@@ -119,7 +304,10 @@ function createMorph(el, icon, options) {
119
304
  t = 1;
120
305
  spring.x = 1;
121
306
  spring.v = 0;
122
- el.setAttribute("d", canonicalD(target));
307
+ regionFlight = false;
308
+ split = 0;
309
+ paintRest(target);
310
+ if (paintOf(target) === "fill" && !regionMod && (regionLoading || !regionFailed)) upgrade(preloadRegion());
123
311
  };
124
312
  /** The current shape as plan source: the at-rest icon, or the rendered
125
313
  * buffers (already N points per subpath). */
@@ -127,20 +315,73 @@ function createMorph(el, icon, options) {
127
315
  const p = plan;
128
316
  const o = out;
129
317
  if (rest || !p || !o) return sampledOf(target);
130
- return o.map((buf, k) => ({
318
+ const [lo, hi] = shown(t);
319
+ return o.slice(lo, hi).map((buf, k) => ({
131
320
  pts: Float64Array.from(buf),
132
- closed: p.items[k].closed
321
+ closed: p.items[lo + k].closed,
322
+ hole: p.items[lo + k].hole
133
323
  }));
134
324
  };
135
- const retarget = (icon) => {
136
- plan = rest ? planBetween(target, icon) : buildPlan(snapshot(), sampledOf(icon));
325
+ /** A morph from the current shape to `icon` needs regions when either
326
+ * side is filled, or when the shape in flight already is a region. */
327
+ const needsRegion = (icon) => !rest && regionFlight || paintOf(icon) === "fill" || paintOf(target) === "fill";
328
+ const retarget = (icon, pop) => {
329
+ if (!rest && plan && out && !drawn) {
330
+ t = 0;
331
+ interpPolar(plan, 0, out);
332
+ }
333
+ if (pop || !rest && split) {
334
+ const from = snapshot();
335
+ popBase = rest ? target : null;
336
+ popFrom = paint;
337
+ plan = popPlan(from, sampledOf(icon));
338
+ split = from.length;
339
+ regionFlight = false;
340
+ } else if (needsRegion(icon)) {
341
+ const mod = regionMod;
342
+ plan = rest || !out ? mod.regionPlan(target, icon, strokeWidth) : mod.regionPlanFrom(regionFlight ? snapshot() : serialize(out, closed ?? void 0), icon, strokeWidth);
343
+ regionFlight = true;
344
+ split = 0;
345
+ paintAs("fill");
346
+ } else {
347
+ plan = rest ? planBetween(target, icon) : buildPlan(snapshot(), sampledOf(icon));
348
+ regionFlight = false;
349
+ }
137
350
  out = allocOutputs(plan);
351
+ drawn = false;
138
352
  closed = plan.items.map((it) => it.closed);
139
353
  target = icon;
140
354
  rest = false;
141
355
  };
356
+ const whenRegion = (icon, run, fallback) => {
357
+ if (regionMod || !rest && split || !needsRegion(icon)) return false;
358
+ const req = {};
359
+ pending = req;
360
+ const give = () => {
361
+ if (dead || pending !== req) return;
362
+ pending = null;
363
+ fallback();
364
+ };
365
+ const load = preloadRegion();
366
+ if (Date.now() - regionSince > REGION_WAIT_MS) {
367
+ give();
368
+ return true;
369
+ }
370
+ const timer = setTimeout(give, REGION_WAIT_MS - (Date.now() - regionSince));
371
+ load.then(() => {
372
+ clearTimeout(timer);
373
+ if (dead || pending !== req) return;
374
+ pending = null;
375
+ run();
376
+ }, () => {
377
+ clearTimeout(timer);
378
+ give();
379
+ });
380
+ return true;
381
+ };
142
382
  const setNow = (icon) => {
143
383
  stop();
384
+ pending = null;
144
385
  target = icon;
145
386
  settle();
146
387
  };
@@ -151,31 +392,44 @@ function createMorph(el, icon, options) {
151
392
  if (typeof matchMedia === "undefined") return false;
152
393
  return matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
153
394
  };
154
- const seek = (icon, tt) => {
395
+ const seek = (icon, tt, pop = false) => {
155
396
  if (dead) return;
156
- const reuse = !rest && plan !== null && icon === target;
397
+ if (!pop && whenRegion(icon, () => seek(icon, tt), () => seek(icon, tt, true))) return;
398
+ pending = null;
399
+ let reuse = !rest && plan !== null && icon === target;
400
+ if (reuse && split && regionMod && popBase !== null) {
401
+ target = popBase;
402
+ rest = true;
403
+ reuse = false;
404
+ }
157
405
  stop();
158
406
  spring.v = 0;
159
- if (!reuse) retarget(icon);
407
+ if (!reuse) retarget(icon, pop);
160
408
  render(tt);
161
409
  };
410
+ const fly = (icon, sp, pop) => {
411
+ if (dead) return;
412
+ if (icon === target && (rest || flying)) {
413
+ pending = null;
414
+ return;
415
+ }
416
+ if (motionOff()) {
417
+ setNow(icon);
418
+ return;
419
+ }
420
+ if (!pop && whenRegion(icon, () => fly(icon, sp, false), () => fly(icon, sp, true))) return;
421
+ pending = null;
422
+ const { k, c } = resolveSpring(sp);
423
+ spring.config(k, c);
424
+ retarget(icon, pop);
425
+ spring.start();
426
+ if (!flying) {
427
+ flying = true;
428
+ addTicker(tick);
429
+ }
430
+ };
162
431
  return {
163
- morphTo(icon, sp) {
164
- if (dead) return;
165
- if (icon === target && (rest || flying)) return;
166
- if (motionOff()) {
167
- setNow(icon);
168
- return;
169
- }
170
- const { k, c } = resolveSpring(sp);
171
- spring.config(k, c);
172
- retarget(icon);
173
- spring.start();
174
- if (!flying) {
175
- flying = true;
176
- addTicker(tick);
177
- }
178
- },
432
+ morphTo: (icon, sp) => fly(icon, sp, false),
179
433
  set(icon) {
180
434
  if (dead) return;
181
435
  setNow(icon);
@@ -193,9 +447,16 @@ function createMorph(el, icon, options) {
193
447
  set reducedMotion(v) {
194
448
  reducedMotion = v;
195
449
  },
450
+ get strokeWidth() {
451
+ return strokeWidth;
452
+ },
453
+ set strokeWidth(v) {
454
+ strokeWidth = v;
455
+ },
196
456
  destroy() {
197
457
  stop();
198
458
  dead = true;
459
+ pending = null;
199
460
  plan = null;
200
461
  out = null;
201
462
  closed = null;
@@ -203,4 +464,4 @@ function createMorph(el, icon, options) {
203
464
  };
204
465
  }
205
466
  //#endregion
206
- export { canonicalD, createMorph };
467
+ export { canonicalD, createMorph, preloadRegion, restAttrs, setRegionLoader };
package/dist/element.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { a as Sampled, c as SpringPreset, n as IconInput, r as IconNode } from "./types-JAF3s76Y.js";
2
- import { a as ReducedMotionMode, i as PathEl, n as Morph, r as MorphOptions } from "./index-DXp5b6BU.js";
3
- import { n as computeInitialD, t as MorphHandle } from "./controller-Dkruvf3Y.js";
1
+ import { a as IconInput, c as Sampled, o as IconNode } from "./plan-DhaDhEVq.js";
2
+ import { r as SpringPreset } from "./spring-BgZHrI2W.js";
3
+ import { a as ReducedMotionMode, i as PathEl, n as Morph, r as MorphOptions } from "./index-DpvKR75N.js";
4
+ import { n as computeInitialD, r as computeInitialPaint, t as MorphHandle } from "./controller-DKm8s2ni.js";
4
5
  //#region src/element/index.d.ts
5
6
  declare const Base: typeof HTMLElement;
6
- declare class MorphIconElement extends Base implements MorphHandle {
7
+ export declare class MorphIconElement extends Base implements MorphHandle {
7
8
  #private;
8
9
  static observedAttributes: string[];
9
10
  get icon(): IconInput | undefined;
@@ -26,11 +27,11 @@ declare class MorphIconElement extends Base implements MorphHandle {
26
27
  }
27
28
  /** Defines `<morph-icon>` (or a custom tag). Idempotent per tag; a no-op
28
29
  * without a DOM (safe to call from code that also runs during SSR). */
29
- declare function defineMorphIcon(tag?: string): void;
30
+ export declare function defineMorphIcon(tag?: string): void;
30
31
  declare global {
31
32
  interface HTMLElementTagNameMap {
32
33
  "morph-icon": MorphIconElement;
33
34
  }
34
35
  }
35
36
  //#endregion
36
- export { type IconInput, type IconNode, type Morph, type MorphHandle, MorphIconElement, type MorphOptions, type PathEl, type ReducedMotionMode, type Sampled, type SpringPreset, computeInitialD, defineMorphIcon };
37
+ export { type IconInput, type IconNode, type Morph, type MorphHandle, type MorphOptions, type PathEl, type ReducedMotionMode, type Sampled, type SpringPreset, computeInitialD, computeInitialPaint };
package/dist/element.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as createController, t as computeInitialD } from "./controller-CXZuwJ_M.js";
1
+ import { n as computeInitialPaint, r as createController, t as computeInitialD } from "./controller--7SSBggk.js";
2
2
  //#region src/element/index.ts
3
3
  const SVG_NS = "http://www.w3.org/2000/svg";
4
4
  const REACTIVE = [
@@ -107,6 +107,7 @@ var MorphIconElement = class extends Base {
107
107
  const path = this.#path;
108
108
  const gate = this.#adopted && !this.#preConnectDirty;
109
109
  this.#adopted = false;
110
+ this.#props.strokeWidth = this.#sw();
110
111
  this.#ctrl = createController(this.#props);
111
112
  if (gate) {
112
113
  let armed = false;
@@ -165,6 +166,8 @@ var MorphIconElement = class extends Base {
165
166
  if (PRESENTATION.has(name)) {
166
167
  const svg = this.querySelector("svg");
167
168
  if (svg) this.#applyPresentation(svg);
169
+ this.#props.strokeWidth = this.#sw();
170
+ this.#ctrl?.watch(this.#props);
168
171
  return;
169
172
  }
170
173
  this.#watch();
@@ -208,7 +211,12 @@ var MorphIconElement = class extends Base {
208
211
  const p = this.#props;
209
212
  if (p.icon === void 0 && p.from === void 0 && p.to === void 0) {
210
213
  const d = existing.getAttribute("d");
211
- if (d) p.icon = d;
214
+ const fill = existing.getAttribute("fill");
215
+ if (d) p.icon = fill && fill !== "none" ? [["path", {
216
+ d,
217
+ fill,
218
+ "fill-rule": existing.getAttribute("fill-rule") ?? "nonzero"
219
+ }]] : d;
212
220
  }
213
221
  return;
214
222
  }
@@ -222,16 +230,21 @@ var MorphIconElement = class extends Base {
222
230
  this.#applyPresentation(svg);
223
231
  const path = doc.createElementNS(SVG_NS, "path");
224
232
  path.setAttribute("d", computeInitialD(this.#props));
233
+ for (const [k, v] of Object.entries(computeInitialPaint(this.#props) ?? {})) path.setAttribute(k, v);
225
234
  svg.appendChild(path);
226
235
  this.appendChild(svg);
227
236
  this.#path = path;
228
237
  }
238
+ /** The stroke width on the 24 grid, absolute-stroke-width resolved. */
239
+ #sw() {
240
+ return this.#absoluteStrokeWidth ? String(Number(this.#strokeWidth) * 24 / Number(this.#size)) : this.#strokeWidth;
241
+ }
229
242
  #applyPresentation(svg) {
230
243
  svg.setAttribute("width", this.#size);
231
244
  svg.setAttribute("height", this.#size);
232
245
  svg.setAttribute("stroke", this.#color);
233
- const sw = this.#absoluteStrokeWidth ? String(Number(this.#strokeWidth) * 24 / Number(this.#size)) : this.#strokeWidth;
234
- svg.setAttribute("stroke-width", sw);
246
+ svg.setAttribute("color", this.#color);
247
+ svg.setAttribute("stroke-width", this.#sw());
235
248
  const label = this.#label;
236
249
  let title = svg.querySelector("title");
237
250
  if (label) {
@@ -258,4 +271,4 @@ function defineMorphIcon(tag = "morph-icon") {
258
271
  registered = true;
259
272
  }
260
273
  //#endregion
261
- export { MorphIconElement, computeInitialD, defineMorphIcon };
274
+ export { MorphIconElement, computeInitialD, computeInitialPaint, defineMorphIcon };
@@ -0,0 +1,55 @@
1
+ import { a as IconInput, c as Sampled, o as IconNode, t as MorphPlan } from "./plan-DhaDhEVq.js";
2
+ //#endregion
3
+ //#region src/region/plan.d.ts
4
+ /** Samples a normalized region: one Sampled per contour, every one closed,
5
+ * `hole` read from the winding (outers positive, holes negative). N per
6
+ * contour: 64, or 128 for every contour of an icon that 64 can't follow
7
+ * (snowflake tips, spiral turns: the landing would snap). */
8
+ declare function resampleRegion(icon: IconInput, N?: number): Sampled[];
9
+ /** Region plan: outers ↔ outers (holed contours never duplicated), holes ↔
10
+ * overlapping holes of the paired outer, direction locked; whatever has no
11
+ * partner collapses/grows without ever breaking the nesting (rules above).
12
+ * Every item flies closed. */
13
+ declare function buildRegionPlan(srcSubs: readonly Sampled[], dstSubs: readonly Sampled[]): MorphPlan;
14
+ //#endregion
15
+ //#region src/core/region.d.ts
16
+ /** How an icon is painted: centerlines with a stroke, or filled surface. */
17
+ type Paint = "stroke" | "fill";
18
+ /** Paint of an icon. Raw `d` strings and IconNodes that never declare a
19
+ * painted fill are stroke icons — the historical contract, so every
20
+ * existing input keeps its meaning. One element with a painted `fill` makes
21
+ * the whole icon a fill icon (mixed stroke+fill icons need the region path
22
+ * too: their silhouette is not a set of centerlines). */
23
+ declare function paintOf(icon: IconInput): Paint;
24
+ /** True when the icon is already a normalized region (no work left). */
25
+ declare function isRegion(icon: IconInput): boolean;
26
+ declare namespace index_d_exports {
27
+ export { Paint, RegionOptions, buildRegionPlan, isRegion, paintOf, regionContours, regionOf, regionPlan, regionPlanFrom, resampleRegion, toRegion };
28
+ }
29
+ interface RegionOptions {
30
+ /** Width for stroked elements that don't declare `stroke-width` (Lucide
31
+ * data carries none: the binding draws it). Default 2. */
32
+ strokeWidth?: number;
33
+ /** Grid step of the distance field, in icon units (24-unit grid).
34
+ * Default ⅛: corners are rebuilt exactly, curves deviate < 0.01. */
35
+ cell?: number;
36
+ }
37
+ /** Contours of the visible surface: closed, non-overlapping, outers
38
+ * positive / holes negative (nesting depth decides which is which). */
39
+ declare function regionContours(input: IconInput, options?: RegionOptions): Float64Array[];
40
+ /** Any icon → its canonical region: one `<path>` (nonzero) whose contours
41
+ * are the visible surface. Idempotent on regions. Stroke icons outline at
42
+ * `strokeWidth` (round caps and joins — Lucide, Tabler, Feather,
43
+ * Heroicons outline); fill icons honor each element's fill rule. */
44
+ declare function toRegion(input: IconInput, options?: RegionOptions): IconNode;
45
+ /** The region the driver draws for an icon (cached): a stroke icon
46
+ * outlined at the element's stroke width, a fill icon as is. */
47
+ declare function regionOf(icon: IconInput, strokeWidth: number): IconNode;
48
+ /** Region plan from `src` at rest to `dst` (cached by reference). */
49
+ declare function regionPlan(src: IconInput, dst: IconInput, strokeWidth: number): MorphPlan;
50
+ /** Region plan from the shape in flight to `dst` (never cached): region
51
+ * samples as they are, or a centerline `d` (a stroke morph interrupted
52
+ * toward a fill icon), outlined as it is. */
53
+ declare function regionPlanFrom(shape: Sampled[] | string, dst: IconInput, strokeWidth: number): MorphPlan;
54
+ //#endregion
55
+ export { regionPlan as a, Paint as c, buildRegionPlan as d, resampleRegion as f, regionOf as i, isRegion as l, index_d_exports as n, regionPlanFrom as o, regionContours as r, toRegion as s, RegionOptions as t, paintOf as u };
@@ -1,4 +1,6 @@
1
- import { c as SpringPreset, n as IconInput } from "./types-JAF3s76Y.js";
1
+ import { n as index_d_exports } from "./index-DDNCPQ4Z.js";
2
+ import { a as IconInput } from "./plan-DhaDhEVq.js";
3
+ import { r as SpringPreset } from "./spring-BgZHrI2W.js";
2
4
  //#region src/dom/index.d.ts
3
5
  /** Target element, structurally typed: any object with setAttribute works
4
6
  * (a real SVGPathElement, or a fake in tests). */
@@ -18,6 +20,11 @@ type ReducedMotionMode = "never" | "user" | "always";
18
20
  interface CreateMorphOptions {
19
21
  /** Reduced-motion policy; also live via the `reducedMotion` property. */
20
22
  reducedMotion?: ReducedMotionMode;
23
+ /** Stroke width the element draws with (24-unit grid). Only used to
24
+ * outline stroke icons when they morph with a fill icon, so the region
25
+ * in flight matches the stroke at rest. Default 2; live via the
26
+ * `strokeWidth` property. */
27
+ strokeWidth?: number;
21
28
  }
22
29
  interface Morph {
23
30
  /** Animates toward the icon with spring physics. Interruptible: mid-flight
@@ -34,15 +41,31 @@ interface Morph {
34
41
  progress: number;
35
42
  /** Reduced-motion policy, live: assigning it applies to the next morphTo. */
36
43
  reducedMotion: ReducedMotionMode;
44
+ /** Stroke width used to outline stroke icons in stroke↔fill morphs. */
45
+ strokeWidth: number;
37
46
  /** Unregisters the instance; later calls are no-ops. */
38
47
  destroy(): void;
39
48
  }
40
49
  /** Canonical `d` of an icon: the input string verbatim, or the real cubics
41
50
  * quantized to 4 decimals (the at-rest snap; engine-stable bytes so SSR
42
51
  * hydration matches, see fmtCanon in core/serialize). Exported because it
43
- * is what a binding renders at SSR/rest before any runtime exists. */
52
+ * is what a binding renders at SSR/rest before any runtime exists. A fill
53
+ * icon's translucent duotone layer is left out (one path can't paint it). */
44
54
  declare function canonicalD(icon: IconInput): string;
55
+ type RegionModule = typeof index_d_exports;
56
+ /** Replaces how the region normalizer is loaded (default: dynamic import of
57
+ * the bundled chunk). E.g. `setRegionLoader(async () => region)` after a
58
+ * static `import * as region from "morphicons/region"` to never touch the
59
+ * network mid-interaction. The next need loads through the new loader. */
60
+ declare function setRegionLoader(loader: () => Promise<RegionModule>): void;
61
+ /** Starts (or joins) loading the region normalizer. Call it early — on
62
+ * idle, on hover — to keep the first stroke↔fill morph off the network. */
63
+ declare function preloadRegion(): Promise<RegionModule>;
64
+ /** The paint attributes a binding renders on the `<path>` with its first
65
+ * icon (SSR included), so a fill icon is filled before any script runs.
66
+ * None for a stroke icon: it inherits the `<svg>`'s stroke as always. */
67
+ declare function restAttrs(icon?: IconInput): Record<string, string> | undefined;
45
68
  /** Creates the morph instance over a `<path>` and paints the initial icon. */
46
69
  declare function createMorph(el: PathEl, icon: IconInput, options?: CreateMorphOptions): Morph;
47
70
  //#endregion
48
- export { ReducedMotionMode as a, PathEl as i, Morph as n, canonicalD as o, MorphOptions as r, createMorph as s, CreateMorphOptions as t };
71
+ export { ReducedMotionMode as a, preloadRegion as c, PathEl as i, restAttrs as l, Morph as n, canonicalD as o, MorphOptions as r, createMorph as s, CreateMorphOptions as t, setRegionLoader as u };