morphicons 1.7.1 → 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/README.md +61 -428
- package/dist/MorphIcon.astro +4 -2
- package/dist/MorphIcon.svelte +19 -8
- package/dist/adapters.d.ts +12 -11
- package/dist/adapters.js +223 -23
- package/dist/{controller-CXZuwJ_M.js → controller--7SSBggk.js} +45 -11
- package/dist/{controller-Dkruvf3Y.d.ts → controller-DKm8s2ni.d.ts} +15 -6
- package/dist/dom.d.ts +2 -2
- package/dist/dom.js +292 -31
- package/dist/element.d.ts +7 -6
- package/dist/element.js +18 -5
- package/dist/index-DDNCPQ4Z.d.ts +55 -0
- package/dist/{index-DXp5b6BU.d.ts → index-DpvKR75N.d.ts} +26 -3
- package/dist/index.d.ts +12 -47
- package/dist/index.js +2 -1
- package/dist/plan-DhaDhEVq.d.ts +76 -0
- package/dist/react-native.d.ts +7 -6
- package/dist/react-native.js +57 -12
- package/dist/react.d.ts +7 -6
- package/dist/react.js +32 -7
- package/dist/region-BjECuWZR.js +60 -0
- package/dist/region.d.ts +2 -0
- package/dist/region.js +819 -0
- package/dist/{spring-CFHloqPP.js → resample-CXlaZ5ZQ.js} +56 -142
- package/dist/spring-B_P92nf6.js +95 -0
- package/dist/spring-BgZHrI2W.d.ts +33 -0
- package/dist/svelte-shared.d.ts +6 -5
- package/dist/svelte-shared.js +1 -2
- package/dist/vue.d.ts +6 -5
- package/dist/vue.js +31 -7
- package/package.json +62 -36
- package/dist/types-JAF3s76Y.d.ts +0 -60
package/dist/dom.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { a as
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 };
|