morphicons 1.7.1 → 2.0.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 +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 +296 -31
- package/dist/element.d.ts +7 -6
- package/dist/element.js +21 -6
- 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,138 @@ 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
|
+
const paintedEls = /* @__PURE__ */ new WeakSet();
|
|
177
|
+
function restLook(icon) {
|
|
178
|
+
if (typeof icon === "string" || paintOf(icon) === "stroke") return "stroke";
|
|
179
|
+
let look = looks.get(icon);
|
|
180
|
+
if (look) return look;
|
|
181
|
+
let filled = 0;
|
|
182
|
+
let enclosed = -1;
|
|
183
|
+
look = "fill";
|
|
184
|
+
for (const el of solid(icon)) {
|
|
185
|
+
const s = painted(el[1].stroke);
|
|
186
|
+
const f = painted(el[1].fill) || el[1].fill === void 0 && !s;
|
|
187
|
+
if (s) look = "both";
|
|
188
|
+
let a = 0;
|
|
189
|
+
for (const p of iconToCubics([el])) a += Math.abs(signedArea(p.pts));
|
|
190
|
+
if (f) filled += a;
|
|
191
|
+
else if (s) enclosed = Math.max(enclosed, 0) + a;
|
|
192
|
+
}
|
|
193
|
+
if (enclosed > 1.5 * filled) look = "stroke";
|
|
194
|
+
looks.set(icon, look);
|
|
195
|
+
return look;
|
|
196
|
+
}
|
|
197
|
+
/** The paint attributes a binding renders on the `<path>` with its first
|
|
198
|
+
* icon (SSR included), so a fill icon is filled before any script runs.
|
|
199
|
+
* None for a stroke icon: it inherits the `<svg>`'s stroke as always. */
|
|
200
|
+
function restAttrs(icon) {
|
|
201
|
+
if (icon === void 0 || paintOf(icon) === "stroke") return void 0;
|
|
202
|
+
return paintAttrs(restLook(icon));
|
|
203
|
+
}
|
|
204
|
+
function popPlan(from, to) {
|
|
205
|
+
const n = 64;
|
|
206
|
+
const item = ({ pts, closed }, half) => {
|
|
207
|
+
if (pts.length > 128) pts = pts.filter((_, i) => i % 4 < 2);
|
|
208
|
+
const c = centroid(pts);
|
|
209
|
+
const p = pts.map((v, i) => v - c[i & 1]);
|
|
210
|
+
const z = /* @__PURE__ */ new Float64Array(128);
|
|
211
|
+
return {
|
|
212
|
+
aC: half ? z : p,
|
|
213
|
+
bT: half ? p : z,
|
|
214
|
+
ca: c,
|
|
215
|
+
cb: c,
|
|
216
|
+
theta: 0,
|
|
217
|
+
lnSigma: 0,
|
|
218
|
+
closed,
|
|
219
|
+
half,
|
|
220
|
+
block: null
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
return {
|
|
224
|
+
n,
|
|
225
|
+
items: [...from.map((s) => item(s, 0)), ...to.map((s) => item(s, 1))]
|
|
226
|
+
};
|
|
227
|
+
}
|
|
71
228
|
function resolveSpring(s) {
|
|
72
229
|
if (typeof s === "string") return SPRING_PRESETS[s];
|
|
73
230
|
const d = SPRING_PRESETS.snappy;
|
|
@@ -80,6 +237,7 @@ function resolveSpring(s) {
|
|
|
80
237
|
function createMorph(el, icon, options) {
|
|
81
238
|
const spring = new Spring();
|
|
82
239
|
let reducedMotion = options?.reducedMotion ?? "never";
|
|
240
|
+
let strokeWidth = options?.strokeWidth ?? 2;
|
|
83
241
|
let target = icon;
|
|
84
242
|
let rest = true;
|
|
85
243
|
let plan = null;
|
|
@@ -88,7 +246,35 @@ function createMorph(el, icon, options) {
|
|
|
88
246
|
let t = 1;
|
|
89
247
|
let flying = false;
|
|
90
248
|
let dead = false;
|
|
91
|
-
|
|
249
|
+
let regionFlight = false;
|
|
250
|
+
let drawn = false;
|
|
251
|
+
let pending = null;
|
|
252
|
+
let paint = "stroke";
|
|
253
|
+
let known = !paintedEls.has(el);
|
|
254
|
+
let split = 0;
|
|
255
|
+
let popFrom = "stroke";
|
|
256
|
+
let popBase = null;
|
|
257
|
+
const paintAs = (p) => {
|
|
258
|
+
if (p === paint && known) return;
|
|
259
|
+
for (const [k, v] of Object.entries(paintAttrs(p))) el.setAttribute(k, v);
|
|
260
|
+
paint = p;
|
|
261
|
+
known = true;
|
|
262
|
+
paintedEls.add(el);
|
|
263
|
+
};
|
|
264
|
+
const paintRest = (ic) => {
|
|
265
|
+
const r = regionMod && paintOf(ic) === "fill" ? regionMod.regionOf(ic, strokeWidth) : ic;
|
|
266
|
+
el.setAttribute("d", canonicalD(r));
|
|
267
|
+
paintAs(restLook(r));
|
|
268
|
+
};
|
|
269
|
+
const upgrade = (load) => {
|
|
270
|
+
const icon = target;
|
|
271
|
+
load.then(() => {
|
|
272
|
+
if (!dead && rest && target === icon && pending === null) paintRest(icon);
|
|
273
|
+
}, () => {});
|
|
274
|
+
};
|
|
275
|
+
paintRest(icon);
|
|
276
|
+
if (paintOf(icon) === "fill" && !regionMod) upgrade(quietPreload());
|
|
277
|
+
const shown = (tt) => !split ? [0, plan?.items.length ?? 0] : tt < .5 ? [0, split] : [split, plan?.items.length ?? 0];
|
|
92
278
|
const render = (tt) => {
|
|
93
279
|
const p = plan;
|
|
94
280
|
const o = out;
|
|
@@ -96,7 +282,10 @@ function createMorph(el, icon, options) {
|
|
|
96
282
|
if (!p || !o || !cl) return;
|
|
97
283
|
t = tt;
|
|
98
284
|
interpPolar(p, tt, o);
|
|
99
|
-
|
|
285
|
+
drawn = true;
|
|
286
|
+
const [lo, hi] = shown(tt);
|
|
287
|
+
if (split) paintAs(tt < .5 ? popFrom : restLook(target));
|
|
288
|
+
el.setAttribute("d", serialize(o.slice(lo, hi), cl.slice(lo, hi)));
|
|
100
289
|
};
|
|
101
290
|
const stop = () => {
|
|
102
291
|
if (!flying) return;
|
|
@@ -119,7 +308,10 @@ function createMorph(el, icon, options) {
|
|
|
119
308
|
t = 1;
|
|
120
309
|
spring.x = 1;
|
|
121
310
|
spring.v = 0;
|
|
122
|
-
|
|
311
|
+
regionFlight = false;
|
|
312
|
+
split = 0;
|
|
313
|
+
paintRest(target);
|
|
314
|
+
if (paintOf(target) === "fill" && !regionMod && (regionLoading || !regionFailed)) upgrade(preloadRegion());
|
|
123
315
|
};
|
|
124
316
|
/** The current shape as plan source: the at-rest icon, or the rendered
|
|
125
317
|
* buffers (already N points per subpath). */
|
|
@@ -127,20 +319,73 @@ function createMorph(el, icon, options) {
|
|
|
127
319
|
const p = plan;
|
|
128
320
|
const o = out;
|
|
129
321
|
if (rest || !p || !o) return sampledOf(target);
|
|
130
|
-
|
|
322
|
+
const [lo, hi] = shown(t);
|
|
323
|
+
return o.slice(lo, hi).map((buf, k) => ({
|
|
131
324
|
pts: Float64Array.from(buf),
|
|
132
|
-
closed: p.items[k].closed
|
|
325
|
+
closed: p.items[lo + k].closed,
|
|
326
|
+
hole: p.items[lo + k].hole
|
|
133
327
|
}));
|
|
134
328
|
};
|
|
135
|
-
|
|
136
|
-
|
|
329
|
+
/** A morph from the current shape to `icon` needs regions when either
|
|
330
|
+
* side is filled, or when the shape in flight already is a region. */
|
|
331
|
+
const needsRegion = (icon) => !rest && regionFlight || paintOf(icon) === "fill" || paintOf(target) === "fill";
|
|
332
|
+
const retarget = (icon, pop) => {
|
|
333
|
+
if (!rest && plan && out && !drawn) {
|
|
334
|
+
t = 0;
|
|
335
|
+
interpPolar(plan, 0, out);
|
|
336
|
+
}
|
|
337
|
+
if (pop || !rest && split) {
|
|
338
|
+
const from = snapshot();
|
|
339
|
+
popBase = rest ? target : null;
|
|
340
|
+
popFrom = paint;
|
|
341
|
+
plan = popPlan(from, sampledOf(icon));
|
|
342
|
+
split = from.length;
|
|
343
|
+
regionFlight = false;
|
|
344
|
+
} else if (needsRegion(icon)) {
|
|
345
|
+
const mod = regionMod;
|
|
346
|
+
plan = rest || !out ? mod.regionPlan(target, icon, strokeWidth) : mod.regionPlanFrom(regionFlight ? snapshot() : serialize(out, closed ?? void 0), icon, strokeWidth);
|
|
347
|
+
regionFlight = true;
|
|
348
|
+
split = 0;
|
|
349
|
+
paintAs("fill");
|
|
350
|
+
} else {
|
|
351
|
+
plan = rest ? planBetween(target, icon) : buildPlan(snapshot(), sampledOf(icon));
|
|
352
|
+
regionFlight = false;
|
|
353
|
+
}
|
|
137
354
|
out = allocOutputs(plan);
|
|
355
|
+
drawn = false;
|
|
138
356
|
closed = plan.items.map((it) => it.closed);
|
|
139
357
|
target = icon;
|
|
140
358
|
rest = false;
|
|
141
359
|
};
|
|
360
|
+
const whenRegion = (icon, run, fallback) => {
|
|
361
|
+
if (regionMod || !rest && split || !needsRegion(icon)) return false;
|
|
362
|
+
const req = {};
|
|
363
|
+
pending = req;
|
|
364
|
+
const give = () => {
|
|
365
|
+
if (dead || pending !== req) return;
|
|
366
|
+
pending = null;
|
|
367
|
+
fallback();
|
|
368
|
+
};
|
|
369
|
+
const load = preloadRegion();
|
|
370
|
+
if (Date.now() - regionSince > REGION_WAIT_MS) {
|
|
371
|
+
give();
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
const timer = setTimeout(give, REGION_WAIT_MS - (Date.now() - regionSince));
|
|
375
|
+
load.then(() => {
|
|
376
|
+
clearTimeout(timer);
|
|
377
|
+
if (dead || pending !== req) return;
|
|
378
|
+
pending = null;
|
|
379
|
+
run();
|
|
380
|
+
}, () => {
|
|
381
|
+
clearTimeout(timer);
|
|
382
|
+
give();
|
|
383
|
+
});
|
|
384
|
+
return true;
|
|
385
|
+
};
|
|
142
386
|
const setNow = (icon) => {
|
|
143
387
|
stop();
|
|
388
|
+
pending = null;
|
|
144
389
|
target = icon;
|
|
145
390
|
settle();
|
|
146
391
|
};
|
|
@@ -151,31 +396,44 @@ function createMorph(el, icon, options) {
|
|
|
151
396
|
if (typeof matchMedia === "undefined") return false;
|
|
152
397
|
return matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
|
|
153
398
|
};
|
|
154
|
-
const seek = (icon, tt) => {
|
|
399
|
+
const seek = (icon, tt, pop = false) => {
|
|
155
400
|
if (dead) return;
|
|
156
|
-
|
|
401
|
+
if (!pop && whenRegion(icon, () => seek(icon, tt), () => seek(icon, tt, true))) return;
|
|
402
|
+
pending = null;
|
|
403
|
+
let reuse = !rest && plan !== null && icon === target;
|
|
404
|
+
if (reuse && split && regionMod && popBase !== null) {
|
|
405
|
+
target = popBase;
|
|
406
|
+
rest = true;
|
|
407
|
+
reuse = false;
|
|
408
|
+
}
|
|
157
409
|
stop();
|
|
158
410
|
spring.v = 0;
|
|
159
|
-
if (!reuse) retarget(icon);
|
|
411
|
+
if (!reuse) retarget(icon, pop);
|
|
160
412
|
render(tt);
|
|
161
413
|
};
|
|
414
|
+
const fly = (icon, sp, pop) => {
|
|
415
|
+
if (dead) return;
|
|
416
|
+
if (icon === target && (rest || flying)) {
|
|
417
|
+
pending = null;
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (motionOff()) {
|
|
421
|
+
setNow(icon);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (!pop && whenRegion(icon, () => fly(icon, sp, false), () => fly(icon, sp, true))) return;
|
|
425
|
+
pending = null;
|
|
426
|
+
const { k, c } = resolveSpring(sp);
|
|
427
|
+
spring.config(k, c);
|
|
428
|
+
retarget(icon, pop);
|
|
429
|
+
spring.start();
|
|
430
|
+
if (!flying) {
|
|
431
|
+
flying = true;
|
|
432
|
+
addTicker(tick);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
162
435
|
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
|
-
},
|
|
436
|
+
morphTo: (icon, sp) => fly(icon, sp, false),
|
|
179
437
|
set(icon) {
|
|
180
438
|
if (dead) return;
|
|
181
439
|
setNow(icon);
|
|
@@ -193,9 +451,16 @@ function createMorph(el, icon, options) {
|
|
|
193
451
|
set reducedMotion(v) {
|
|
194
452
|
reducedMotion = v;
|
|
195
453
|
},
|
|
454
|
+
get strokeWidth() {
|
|
455
|
+
return strokeWidth;
|
|
456
|
+
},
|
|
457
|
+
set strokeWidth(v) {
|
|
458
|
+
strokeWidth = v;
|
|
459
|
+
},
|
|
196
460
|
destroy() {
|
|
197
461
|
stop();
|
|
198
462
|
dead = true;
|
|
463
|
+
pending = null;
|
|
199
464
|
plan = null;
|
|
200
465
|
out = null;
|
|
201
466
|
closed = null;
|
|
@@ -203,4 +468,4 @@ function createMorph(el, icon, options) {
|
|
|
203
468
|
};
|
|
204
469
|
}
|
|
205
470
|
//#endregion
|
|
206
|
-
export { canonicalD, createMorph };
|
|
471
|
+
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 = [
|
|
@@ -34,6 +34,7 @@ var MorphIconElement = class extends Base {
|
|
|
34
34
|
#props = {};
|
|
35
35
|
#ctrl = null;
|
|
36
36
|
#path = null;
|
|
37
|
+
#target = null;
|
|
37
38
|
#adopted = false;
|
|
38
39
|
#preConnectDirty = false;
|
|
39
40
|
#size = "24";
|
|
@@ -107,18 +108,20 @@ var MorphIconElement = class extends Base {
|
|
|
107
108
|
const path = this.#path;
|
|
108
109
|
const gate = this.#adopted && !this.#preConnectDirty;
|
|
109
110
|
this.#adopted = false;
|
|
111
|
+
this.#props.strokeWidth = this.#sw();
|
|
110
112
|
this.#ctrl = createController(this.#props);
|
|
111
113
|
if (gate) {
|
|
112
114
|
let armed = false;
|
|
113
115
|
const gated = { setAttribute(name, value) {
|
|
114
116
|
if (armed) path.setAttribute(name, value);
|
|
115
117
|
} };
|
|
118
|
+
this.#target = gated;
|
|
116
119
|
try {
|
|
117
120
|
this.#ctrl.mount(gated, this.#props);
|
|
118
121
|
} finally {
|
|
119
122
|
armed = true;
|
|
120
123
|
}
|
|
121
|
-
} else this.#ctrl.mount(path, this.#props);
|
|
124
|
+
} else this.#ctrl.mount(this.#target ?? path, this.#props);
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
disconnectedCallback() {
|
|
@@ -165,6 +168,8 @@ var MorphIconElement = class extends Base {
|
|
|
165
168
|
if (PRESENTATION.has(name)) {
|
|
166
169
|
const svg = this.querySelector("svg");
|
|
167
170
|
if (svg) this.#applyPresentation(svg);
|
|
171
|
+
this.#props.strokeWidth = this.#sw();
|
|
172
|
+
this.#ctrl?.watch(this.#props);
|
|
168
173
|
return;
|
|
169
174
|
}
|
|
170
175
|
this.#watch();
|
|
@@ -208,7 +213,12 @@ var MorphIconElement = class extends Base {
|
|
|
208
213
|
const p = this.#props;
|
|
209
214
|
if (p.icon === void 0 && p.from === void 0 && p.to === void 0) {
|
|
210
215
|
const d = existing.getAttribute("d");
|
|
211
|
-
|
|
216
|
+
const fill = existing.getAttribute("fill");
|
|
217
|
+
if (d) p.icon = fill && fill !== "none" ? [["path", {
|
|
218
|
+
d,
|
|
219
|
+
fill,
|
|
220
|
+
"fill-rule": existing.getAttribute("fill-rule") ?? "nonzero"
|
|
221
|
+
}]] : d;
|
|
212
222
|
}
|
|
213
223
|
return;
|
|
214
224
|
}
|
|
@@ -222,16 +232,21 @@ var MorphIconElement = class extends Base {
|
|
|
222
232
|
this.#applyPresentation(svg);
|
|
223
233
|
const path = doc.createElementNS(SVG_NS, "path");
|
|
224
234
|
path.setAttribute("d", computeInitialD(this.#props));
|
|
235
|
+
for (const [k, v] of Object.entries(computeInitialPaint(this.#props) ?? {})) path.setAttribute(k, v);
|
|
225
236
|
svg.appendChild(path);
|
|
226
237
|
this.appendChild(svg);
|
|
227
238
|
this.#path = path;
|
|
228
239
|
}
|
|
240
|
+
/** The stroke width on the 24 grid, absolute-stroke-width resolved. */
|
|
241
|
+
#sw() {
|
|
242
|
+
return this.#absoluteStrokeWidth ? String(Number(this.#strokeWidth) * 24 / Number(this.#size)) : this.#strokeWidth;
|
|
243
|
+
}
|
|
229
244
|
#applyPresentation(svg) {
|
|
230
245
|
svg.setAttribute("width", this.#size);
|
|
231
246
|
svg.setAttribute("height", this.#size);
|
|
232
247
|
svg.setAttribute("stroke", this.#color);
|
|
233
|
-
|
|
234
|
-
svg.setAttribute("stroke-width", sw);
|
|
248
|
+
svg.setAttribute("color", this.#color);
|
|
249
|
+
svg.setAttribute("stroke-width", this.#sw());
|
|
235
250
|
const label = this.#label;
|
|
236
251
|
let title = svg.querySelector("title");
|
|
237
252
|
if (label) {
|
|
@@ -258,4 +273,4 @@ function defineMorphIcon(tag = "morph-icon") {
|
|
|
258
273
|
registered = true;
|
|
259
274
|
}
|
|
260
275
|
//#endregion
|
|
261
|
-
export { MorphIconElement, computeInitialD, defineMorphIcon };
|
|
276
|
+
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 };
|