@kitlangton/knobs 0.1.0 → 0.2.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.
Files changed (48) hide show
  1. package/README.md +174 -30
  2. package/dist/dev/{chunk-e13znr7c.js → chunk-4km87zfx.js} +3 -3
  3. package/dist/dev/{chunk-ktsn42p7.js → chunk-5cr85vv6.js} +7448 -5390
  4. package/dist/dev/{chunk-kdewv0mt.js → chunk-693jd1gj.js} +171 -70
  5. package/dist/dev/{chunk-qtt698hp.js → chunk-gezhc24g.js} +21 -7
  6. package/dist/dev/index.js +377 -106
  7. package/dist/index.js +392 -108
  8. package/dist/panel.js +9518 -6749
  9. package/dist/types/core/random.d.ts +61 -1
  10. package/dist/types/core/remote.d.ts +3 -1
  11. package/dist/types/core/schema.d.ts +30 -6
  12. package/dist/types/core/store.d.ts +68 -6
  13. package/dist/types/core/variants.d.ts +10 -2
  14. package/dist/types/index.d.ts +3 -2
  15. package/dist/types/panel/editors.d.ts +11 -0
  16. package/dist/types/panel/flash.d.ts +13 -0
  17. package/dist/types/panel/gpu/renderer.d.ts +78 -6
  18. package/dist/types/panel/haptics.d.ts +6 -2
  19. package/dist/types/panel/has.d.ts +0 -1
  20. package/dist/types/panel/index.d.ts +1 -0
  21. package/dist/types/panel/inspector.d.ts +32 -2
  22. package/dist/types/panel/optkeys.d.ts +7 -0
  23. package/dist/types/panel/presets.d.ts +17 -5
  24. package/dist/types/panel/ramp-morph.d.ts +117 -0
  25. package/dist/types/panel/ramps.d.ts +1 -1
  26. package/dist/types/panel/reel.d.ts +43 -0
  27. package/dist/types/panel/scope.d.ts +19 -0
  28. package/dist/types/panel/slider.d.ts +1 -1
  29. package/dist/types/panel/sound.d.ts +137 -0
  30. package/dist/types/panel/styles.d.ts +1 -1
  31. package/dist/types/panel/toolbar-grid.d.ts +6 -0
  32. package/dist/types/ui/activity-css.d.ts +1 -0
  33. package/dist/types/ui/activity-model.d.ts +122 -0
  34. package/dist/types/ui/activity.d.ts +67 -0
  35. package/dist/types/ui/follow.d.ts +17 -0
  36. package/dist/types/ui/index.d.ts +5 -1
  37. package/dist/types/ui/look.d.ts +9 -0
  38. package/dist/types/ui/picker-css.d.ts +1 -0
  39. package/dist/types/ui/picker-model.d.ts +53 -0
  40. package/dist/types/ui/picker.d.ts +67 -0
  41. package/dist/types/ui/react.d.ts +27 -0
  42. package/dist/types/ui/solid.d.ts +23 -0
  43. package/dist/types/ui/split-model.d.ts +54 -0
  44. package/dist/types/ui/split.d.ts +17 -0
  45. package/dist/ui-react.js +3239 -567
  46. package/dist/ui-solid.js +3123 -472
  47. package/dist/ui.js +2688 -100
  48. package/package.json +1 -1
package/dist/ui-solid.js CHANGED
@@ -1,5 +1,738 @@
1
1
  // src/ui/solid.ts
2
- import { children, createEffect, onCleanup, untrack } from "solid-js";
2
+ import {
3
+ children,
4
+ createEffect,
5
+ createRoot,
6
+ getOwner,
7
+ onCleanup,
8
+ untrack
9
+ } from "solid-js";
10
+ import { insert } from "solid-js/web";
11
+
12
+ // src/ui/follow.ts
13
+ function propChanges(initial, same = Object.is) {
14
+ let last = { ...initial };
15
+ return (next) => {
16
+ const changed = {};
17
+ for (const key of Object.keys(next))
18
+ if (!same(next[key], last[key]))
19
+ changed[key] = next[key];
20
+ last = { ...next };
21
+ return changed;
22
+ };
23
+ }
24
+ var anyChanged = (changed) => Object.keys(changed).length > 0;
25
+ function timelineChanges(now, changed) {
26
+ const next = {};
27
+ for (const key of Object.keys(changed)) {
28
+ const value = changed[key];
29
+ if (value === undefined)
30
+ continue;
31
+ if (key === "time" && Math.abs(value - now.time) < 0.000001)
32
+ continue;
33
+ if (key === "playing" && value === now.playing)
34
+ continue;
35
+ if ((key === "markers" || key === "ranges") && value === now[key])
36
+ continue;
37
+ next[key] = value;
38
+ }
39
+ return next;
40
+ }
41
+
42
+ // node_modules/@kitlangton/rolling-number/dist/chunk-vm1v7ncb.js
43
+ var z = new Map;
44
+ var le = /[\u0590-\u08ff\u200e\u200f\u202a-\u202e\u2066-\u2069\ufb1d-\ufeff]/u;
45
+ var U = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:.-/&+'";
46
+ var V = new Map;
47
+ function Re(e) {
48
+ let t = V.get(e);
49
+ if (!t) {
50
+ if (t = [...new Set(ue(e))], V.size >= 16)
51
+ V.delete(V.keys().next().value);
52
+ V.set(e, t);
53
+ }
54
+ return t;
55
+ }
56
+ function ue(e) {
57
+ if (typeof Intl.Segmenter === "function")
58
+ return [...new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(e)].map((t) => t.segment);
59
+ return [...e];
60
+ }
61
+ function pe(e, t = {}) {
62
+ let n = t.charset ?? U, i = !le.test(e);
63
+ if (!i)
64
+ return { text: e, tokens: [], rollable: i, signature: "text", magnitude: "" };
65
+ let r = ue(e).map((s, o) => {
66
+ if (t.transition === "direct")
67
+ return { key: `char:${o}`, identity: `char:${o}`, text: s, wheel: [s], index: 0 };
68
+ let h = Re(typeof n === "string" ? n : n[o] ?? n.at(-1) ?? U), c = `char:${o}`, m = h.indexOf(s);
69
+ return m >= 0 ? { key: c, identity: c, text: s, wheel: h, index: m } : { key: `${c}:${s}`, identity: c, text: s };
70
+ });
71
+ return { text: e, tokens: r, rollable: i, signature: `text:${t.transition ?? "wheel"}`, magnitude: "" };
72
+ }
73
+ function me(e, t, n = 0.14) {
74
+ return { target: 0, duration: t, points: Array.from({ length: 49 }, (i, r) => {
75
+ if (r === 48)
76
+ return 0;
77
+ let s = Math.max(0, Math.min(1, (r / 48 - n) / (1 - n)));
78
+ return e * (1 + 10 * s) * Math.exp(-10 * s);
79
+ }) };
80
+ }
81
+ function W(e, t) {
82
+ if (t <= 0 || e.duration <= 0)
83
+ return e;
84
+ let n = e.duration + t, i = Math.round((e.points.length - 1) * n / e.duration) + 1, r = e.points[0] ?? e.target;
85
+ return { target: e.target, duration: n, points: Array.from({ length: i }, (s, o) => {
86
+ if (o === i - 1)
87
+ return e.target;
88
+ let h = o / (i - 1) * n - t;
89
+ return h <= 0 ? r : J(e, h).position;
90
+ }) };
91
+ }
92
+ function L(e, t, n, i) {
93
+ if (i <= 0)
94
+ return { points: [t, t], duration: 0, target: t };
95
+ let r = i / 1000, s = e - t, o = Math.max(Math.abs(s), 1) * 12 / r, h = Math.max(-o, Math.min(o, n)) * r;
96
+ return { points: Array.from({ length: 49 }, (m, f) => {
97
+ if (f === 48)
98
+ return t;
99
+ let y = f / 48;
100
+ return t + (s + (h + 10 * s) * y) * Math.exp(-10 * y);
101
+ }), duration: i, target: t };
102
+ }
103
+ function fe(e, t = 0, n = 24) {
104
+ if (e.duration <= 0)
105
+ return { points: [0, 0], duration: 0, target: 0 };
106
+ let i = e.duration / (e.points.length - 1) / 1000;
107
+ return { duration: e.duration, target: 0, points: e.points.map((r, s, o) => {
108
+ if (s === 0)
109
+ return Math.max(0, Math.min(1, t));
110
+ if (s === o.length - 1)
111
+ return 0;
112
+ let h = Math.abs((o[s + 1] - o[s - 1]) / (2 * i)), c = n / 6;
113
+ return Math.max(0, Math.min(1, (h - c) / (n - c)));
114
+ }) };
115
+ }
116
+ function J(e, t) {
117
+ if (t >= e.duration || e.duration === 0)
118
+ return { position: e.target, velocity: 0 };
119
+ let n = Math.max(0, t) / e.duration * (e.points.length - 1), i = Math.min(Math.floor(n), e.points.length - 2), r = e.points[i] ?? e.target, s = e.points[i + 1] ?? e.target;
120
+ return { position: r + (s - r) * (n - i), velocity: (s - r) * (e.points.length - 1) * 1000 / e.duration };
121
+ }
122
+ function Q(e, t, n, i = 10) {
123
+ let r = Math.floor(e / i) * i + t;
124
+ if (n > 0 && r < e - 0.001)
125
+ r += i;
126
+ else if (n < 0 && r > e + 0.001)
127
+ r -= i;
128
+ else if (n === 0)
129
+ r += Math.round((e - r) / i) * i;
130
+ return r;
131
+ }
132
+ var P = (e, t) => e[(t % e.length + e.length) % e.length];
133
+ function ge(e, t, n) {
134
+ let i = Math.floor(t), r = t - i, s = [P(e, i)];
135
+ if (r > 0.00001)
136
+ s.push(P(e, i + 1));
137
+ if (s.at(-1) !== n)
138
+ s.push(n);
139
+ return { wheel: s, from: r, target: s.length - 1 };
140
+ }
141
+ function ye(e, t = "outward") {
142
+ if (t !== "outward") {
143
+ let r = e.map((o, h) => o ? -1 : h).filter((o) => o >= 0);
144
+ if (t === "end")
145
+ r.reverse();
146
+ let s = e.map((o) => o ? 0 : 1);
147
+ if (t !== "none")
148
+ r.forEach((o, h) => {
149
+ s[o] = h + 1;
150
+ });
151
+ return s;
152
+ }
153
+ let n = e.map((r, s) => r ? 0 : s + 1);
154
+ if (!e.includes(true))
155
+ return n;
156
+ let i = -1 / 0;
157
+ for (let r = 0;r < e.length; r++)
158
+ if (e[r])
159
+ i = r;
160
+ else
161
+ n[r] = r - i;
162
+ i = 1 / 0;
163
+ for (let r = e.length - 1;r >= 0; r--)
164
+ if (e[r])
165
+ i = r;
166
+ else
167
+ n[r] = Math.min(n[r], i - r);
168
+ return n;
169
+ }
170
+ function Z(e, t) {
171
+ let n = new Map, i = [], r = 0;
172
+ for (let s of e) {
173
+ let o = t.get(s);
174
+ if (!o) {
175
+ i.push(s);
176
+ continue;
177
+ }
178
+ for (let h of i)
179
+ n.set(h, o.x);
180
+ i.length = 0, r = o.x + o.width;
181
+ }
182
+ for (let s of i)
183
+ n.set(s, r);
184
+ return n;
185
+ }
186
+ var ee = new WeakMap;
187
+
188
+ class Y {
189
+ view;
190
+ media;
191
+ members = new Set;
192
+ pending = new Set;
193
+ sizes = new WeakMap;
194
+ intersections = new WeakMap;
195
+ resize;
196
+ intersection;
197
+ frame = 0;
198
+ static for(e) {
199
+ let t = ee.get(e);
200
+ if (!t)
201
+ t = new Y(e), ee.set(e, t);
202
+ return t;
203
+ }
204
+ constructor(e) {
205
+ this.view = e;
206
+ if (this.media = e.matchMedia("(prefers-reduced-motion: reduce)"), this.media.addEventListener("change", this.refresh), e.document.addEventListener("visibilitychange", this.refresh), e.document.fonts?.addEventListener("loadingdone", this.refresh), e.document.fonts?.ready.then(this.refresh), e.ResizeObserver)
207
+ this.resize = new e.ResizeObserver((t) => {
208
+ for (let n of t) {
209
+ let i = this.sizes.get(n.target);
210
+ if (i?.sizeChanged(n.target, n.contentRect.width, n.contentRect.height))
211
+ i.refresh();
212
+ }
213
+ });
214
+ if (e.IntersectionObserver)
215
+ this.intersection = new e.IntersectionObserver((t) => {
216
+ for (let n of t)
217
+ this.intersections.get(n.target)?.visibility(n.isIntersecting);
218
+ }, { rootMargin: "64px" });
219
+ }
220
+ refresh = () => {
221
+ for (let e of this.members)
222
+ e.refresh();
223
+ };
224
+ add(e, t) {
225
+ this.members.add(e), this.intersections.set(t, e), this.intersection?.observe(t);
226
+ }
227
+ watch(e, t) {
228
+ this.sizes.set(e, t), this.resize?.observe(e);
229
+ }
230
+ unwatch(e) {
231
+ this.resize?.unobserve(e), this.sizes.delete(e);
232
+ }
233
+ enqueue(e) {
234
+ if (this.pending.add(e), !this.frame)
235
+ this.frame = this.view.requestAnimationFrame(() => {
236
+ this.frame = 0;
237
+ let t = [...this.pending];
238
+ this.pending.clear();
239
+ let n = t.map((r) => r.stage());
240
+ for (let r of n)
241
+ r?.();
242
+ let i = t.map((r) => r.measure());
243
+ for (let r of i)
244
+ r?.();
245
+ });
246
+ }
247
+ remove(e, t) {
248
+ if (this.pending.delete(e), this.members.delete(e), this.intersection?.unobserve(t), this.intersections.delete(t), this.members.size)
249
+ return;
250
+ this.view.cancelAnimationFrame(this.frame), this.resize?.disconnect(), this.intersection?.disconnect(), this.media.removeEventListener("change", this.refresh), this.view.document.removeEventListener("visibilitychange", this.refresh), this.view.document.fonts?.removeEventListener("loadingdone", this.refresh), ee.delete(this.view);
251
+ }
252
+ }
253
+ var be = new WeakMap;
254
+ function F(e, t, n) {
255
+ let i = e.animate(t, n), r = e.ownerDocument.timeline?.currentTime;
256
+ if (typeof r === "number" && i.playState === "running")
257
+ i.startTime = r;
258
+ return i;
259
+ }
260
+ function ve(e) {
261
+ let t = e.ownerDocument.defaultView;
262
+ if (!t)
263
+ return false;
264
+ let n = be.get(t);
265
+ if (n === undefined)
266
+ n = t.CSS?.supports("animation-timing-function", "linear(0, 1)") ?? false, be.set(t, n);
267
+ return n;
268
+ }
269
+
270
+ class N {
271
+ element;
272
+ property;
273
+ animation;
274
+ motion;
275
+ value = 0;
276
+ constructor(e, t) {
277
+ this.element = e;
278
+ this.property = t;
279
+ }
280
+ read() {
281
+ let e = this.animation?.currentTime;
282
+ return this.animation && this.motion ? J(this.motion, typeof e === "number" ? e : 0) : { position: this.value, velocity: 0 };
283
+ }
284
+ set(e, t) {
285
+ this.cancel(), this.value = e, this.element.style.setProperty(this.property, t(e));
286
+ }
287
+ play(e, t, n) {
288
+ if (this.cancel(), this.value = e.target, this.element.style.setProperty(this.property, t(e.target)), !e.duration || e.points.every((f) => f === e.target)) {
289
+ n?.();
290
+ return;
291
+ }
292
+ let i = e.points[0] ?? e.target, r = e.target - i, s = this.property === "opacity" && ve(this.element), o = this.property === "transform" && Math.abs(r) > 0.00001 && ve(this.element), h = s ? [{ opacity: 0 }, { opacity: 1 }] : o ? [{ [this.property]: t(i) }, { [this.property]: t(e.target) }] : e.points.map((f) => ({ [this.property]: t(f) })), c = s ? `linear(${e.points.map(t).join(",")})` : o ? `linear(${e.points.map((f) => Number(((f - i) / r).toFixed(6))).join(",")})` : "linear", m = F(this.element, h, { duration: e.duration, easing: c });
293
+ this.animation = m, this.motion = e, m.onfinish = () => {
294
+ if (this.animation !== m)
295
+ return;
296
+ this.animation = undefined, this.motion = undefined, m.onfinish = null, m.cancel(), n?.();
297
+ };
298
+ }
299
+ cancel() {
300
+ if (this.animation)
301
+ this.animation.onfinish = null, this.animation.cancel(), this.animation = undefined;
302
+ this.motion = undefined;
303
+ }
304
+ }
305
+ var te = "http://www.w3.org/2000/svg";
306
+ var $e = 0;
307
+
308
+ class G {
309
+ host;
310
+ layers = new Map;
311
+ filter;
312
+ intensity = 1;
313
+ constructor(e) {
314
+ this.host = e;
315
+ }
316
+ filterUrl(e) {
317
+ if (!this.filter) {
318
+ let n = this.host.ownerDocument, i = n.createElementNS(te, "svg");
319
+ i.classList.add("rn-blur-defs"), i.setAttribute("aria-hidden", "true"), i.setAttribute("focusable", "false");
320
+ let r = n.createElementNS(te, "filter"), s;
321
+ do
322
+ s = `rn-vertical-blur-${++$e}`;
323
+ while (n.getElementById(s));
324
+ r.id = s, r.setAttribute("x", "-15%"), r.setAttribute("width", "130%"), r.setAttribute("color-interpolation-filters", "sRGB");
325
+ let o = n.createElementNS(te, "feGaussianBlur");
326
+ r.append(o), i.append(r), this.host.append(i), this.filter = { svg: i, blur: o, id: s, height: 0 };
327
+ }
328
+ let t = e * 0.035 * this.intensity;
329
+ if (this.filter.height !== t)
330
+ this.filter.blur.setAttribute("stdDeviation", `0 ${t}`), this.filter.height = t;
331
+ return `url("#${this.filter.id}")`;
332
+ }
333
+ apply(e, t, n, i, r = "roll") {
334
+ let s = fe(t, i, r === "entry" ? 6 : 24);
335
+ if (s.points.every((f) => f === 0))
336
+ return false;
337
+ let o = this.host.ownerDocument.createElement("span");
338
+ o.className = "rn-sharp", o.append(...e.childNodes);
339
+ let h = o.cloneNode(true);
340
+ h.className = "rn-smear", h.style.filter = this.filterUrl(n), e.append(o, h);
341
+ let c = new N(o, "opacity"), m = new N(h, "opacity");
342
+ return this.layers.set(e, { sharp: o, sharpOpacity: c, smearOpacity: m }), c.play(s, (f) => String(1 - f)), m.play(s, String), true;
343
+ }
344
+ remove(e) {
345
+ let t = this.layers.get(e);
346
+ if (!t)
347
+ return 0;
348
+ let n = t.smearOpacity.read().position;
349
+ return t.sharpOpacity.cancel(), t.smearOpacity.cancel(), e.replaceChildren(...t.sharp.childNodes), this.layers.delete(e), n;
350
+ }
351
+ destroy() {
352
+ for (let e of this.layers.keys())
353
+ this.remove(e);
354
+ this.filter?.svg.remove(), this.filter = undefined;
355
+ }
356
+ }
357
+ function xe(e) {
358
+ return Math.max(45, Math.min(110, e / 7));
359
+ }
360
+ function we(e, t, n) {
361
+ let i = Math.abs(t - e);
362
+ return { points: [e, t], target: t, duration: i * n };
363
+ }
364
+ function Ae(e) {
365
+ let i = Array.from({ length: e + 1 }, (o, h) => ({ "--rn-flap-step": String(h), offset: h / e, easing: "steps(1, end)" })), r = [], s = [];
366
+ for (let o = 0;o < e; o++) {
367
+ let h = o / e, c = (o + 0.5) / e, m = (o + 1) / e;
368
+ r.push({ transform: "perspective(5em) rotateX(0deg)", filter: "brightness(1)", offset: h, easing: "cubic-bezier(.6, 0, 1, .5)" }, { transform: "perspective(5em) rotateX(-90deg)", filter: "brightness(.45)", offset: c }, { transform: "perspective(5em) rotateX(-90deg)", filter: "brightness(.45)", offset: m }), s.push({ transform: "perspective(5em) rotateX(90deg)", filter: "brightness(.45)", offset: h }, { transform: "perspective(5em) rotateX(90deg)", filter: "brightness(.45)", offset: c, easing: "linear(0, 0.58, 0.9, 1, 1.045 78%, 1)" }, { transform: "perspective(5em) rotateX(0deg)", filter: "brightness(1)", offset: m });
369
+ }
370
+ return { index: i, falls: r, lands: s };
371
+ }
372
+ function Me(e) {
373
+ for (let t of e.querySelectorAll(".rn-flap-smear")) {
374
+ for (let n of t.getAnimations())
375
+ n.cancel();
376
+ t.remove();
377
+ }
378
+ for (let t of e.querySelectorAll(".rn-flap-sharp")) {
379
+ for (let n of t.getAnimations())
380
+ n.cancel();
381
+ t.classList.remove("rn-flap-sharp");
382
+ }
383
+ }
384
+ function ke(e, t, n, i, r, s, o, h) {
385
+ let c = e.ownerDocument, m = Math.abs(i - n), f = i >= n ? 1 : -1;
386
+ if (!m) {
387
+ e.replaceChildren();
388
+ return;
389
+ }
390
+ let y = Ae(m), M = Array.from({ length: m + 1 }, (p, w) => P(t, n + w * f)), S = [...M.slice(1), M.at(-1)], u = M.some((p) => /[\r\n\f\u2028\u2029]/u.test(p)), g = c.createElement("span");
391
+ g.style.cssText = `display:block;position:relative;height:${r}px`, F(g, y.index, { delay: o, duration: m * s, fill: "both" });
392
+ let T = (p, w) => {
393
+ let k = c.createElement("span");
394
+ k.className = `rn-face rn-flap rn-flap-${p}`, k.style.height = `${r}px`, k.style.overflow = "hidden";
395
+ let v = c.createElement("span");
396
+ v.style.cssText = `display:block;white-space:pre;line-height:${r}px`;
397
+ let l = w ? S : M;
398
+ if (u)
399
+ for (let b of l) {
400
+ let O = c.createElement("span");
401
+ O.style.cssText = `display:block;height:${r}px`, O.textContent = b, v.append(O);
402
+ }
403
+ else
404
+ v.textContent = l.join(`
405
+ `);
406
+ return k.append(v), v.style.transform = `translateY(calc(var(--rn-flap-step) * ${-r}px))`, k;
407
+ }, H = T("bottom", false), j = T("top", true), d = T("top", false), a = T("bottom", true);
408
+ if (h) {
409
+ let p = (w, k) => {
410
+ let v = w.firstElementChild, l = v.cloneNode(true);
411
+ v.classList.add("rn-flap-sharp");
412
+ let b = c.createElement("span");
413
+ b.className = "rn-flap-smear", b.style.cssText = "display:block;position:absolute;inset:0;overflow:hidden", b.style.filter = h, b.append(l), w.append(b);
414
+ let O = k.map((C) => ({ offset: C.offset, easing: C.easing ?? "linear", opacity: C.filter === "brightness(1)" ? 0 : 1 })), A = { delay: o, duration: m * s, fill: "both" };
415
+ F(b, O, A), F(v, O.map((C) => ({ ...C, opacity: 1 - C.opacity })), A);
416
+ };
417
+ p(d, y.falls), p(a, y.lands);
418
+ }
419
+ d.style.transform = "perspective(5em) rotateX(-90deg)", F(d, y.falls, { delay: o, duration: m * s, fill: "backwards" }), a.style.transform = "perspective(5em) rotateX(90deg)", F(a, y.lands, { delay: o, duration: m * s, fill: "forwards" }), e.style.height = `${r}px`, g.append(H, j, a, d), e.replaceChildren(g);
420
+ }
421
+ var Ee = new WeakMap;
422
+ var _ = new WeakSet;
423
+ var ne = (e) => `translateX(${e}px)`;
424
+ var ie = (e) => `scale(${e})`;
425
+ var re = (e) => String(Math.max(0, Math.min(1, e)));
426
+ var se = (e) => ("transition" in e) && e.transition === "direct";
427
+ function Te(e) {
428
+ if (e.duration !== undefined && (!Number.isFinite(e.duration) || e.duration < 0 || e.duration > 1e4))
429
+ throw RangeError("duration must be between 0 and 10000 milliseconds");
430
+ if (e.flipDuration !== undefined && (!Number.isFinite(e.flipDuration) || e.flipDuration < 1 || e.flipDuration > 1e4))
431
+ throw RangeError("flipDuration must be between 1 and 10000 milliseconds");
432
+ }
433
+ var Fe = { validate(e) {
434
+ if (typeof e.text !== "string")
435
+ throw TypeError("text must be a string");
436
+ if (e.transition !== undefined && e.transition !== "direct" && e.transition !== "wheel")
437
+ throw RangeError("transition must be direct or wheel");
438
+ if (e.transition === "direct" && e.mode === "flap")
439
+ throw RangeError("Direct text transitions require roll mode");
440
+ Te(e);
441
+ }, model: (e) => pe(e.text, e), direction: () => 1 };
442
+
443
+ class oe {
444
+ host;
445
+ source;
446
+ options;
447
+ target;
448
+ displayed;
449
+ semantic;
450
+ measurement;
451
+ visual;
452
+ measures = new Map;
453
+ columns = new Map;
454
+ sizes = new Map;
455
+ scheduler;
456
+ enhanced = false;
457
+ destroyed = false;
458
+ visible = true;
459
+ reset = true;
460
+ measurementPending = false;
461
+ hadClass;
462
+ previousLeft;
463
+ blur;
464
+ blurIntensity = 1;
465
+ constructor(e, t, n) {
466
+ this.host = e;
467
+ this.source = n;
468
+ n.validate(t), this.options = { ...t }, this.target = this.displayed = n.model(t);
469
+ let i = e.ownerDocument, r = (o) => {
470
+ let h = i.createElement("span");
471
+ return h.className = o, h;
472
+ };
473
+ this.semantic = r("rn-value"), this.measurement = r("rn-measure"), this.visual = r("rn-visual"), this.measurement.setAttribute("aria-hidden", "true"), this.visual.setAttribute("aria-hidden", "true"), this.semantic.textContent = this.target.text, this.hadClass = e.classList.contains("rn-root"), e.classList.add("rn-root"), e.replaceChildren(this.semantic, this.measurement, this.visual);
474
+ let s = i.defaultView;
475
+ if (s && typeof s.matchMedia === "function" && typeof s.requestAnimationFrame === "function" && typeof e.animate === "function")
476
+ this.scheduler = Y.for(s), this.scheduler.add(this, e), this.scheduler.watch(this.measurement, this);
477
+ this.prepare();
478
+ }
479
+ canAnimate() {
480
+ return !!this.scheduler && this.options.animated !== false && (this.options.duration ?? 500) > 0 && !this.scheduler.media.matches && !this.host.ownerDocument.hidden && (this.visible || this.options.pauseOffscreen === false) && this.target.rollable && this.host.isConnected;
481
+ }
482
+ update(e) {
483
+ if (this.destroyed)
484
+ return;
485
+ let t = { ...this.options, ...e };
486
+ this.source.validate(t);
487
+ let n = this.source.model(t), i = n.text === this.target.text && n.signature === this.target.signature;
488
+ if (this.options.motionBlur && !t.motionBlur)
489
+ this.blur?.destroy(), this.blur = undefined, Me(this.visual);
490
+ if (se(this.options) !== se(t))
491
+ this.reset = true;
492
+ if (this.options = t, this.target = n, !this.canAnimate()) {
493
+ this.finish();
494
+ return;
495
+ }
496
+ if (i && this.enhanced && !this.reset)
497
+ return;
498
+ this.semantic.textContent = n.text, this.prepare();
499
+ }
500
+ prepare() {
501
+ if (!this.canAnimate()) {
502
+ this.finish();
503
+ return;
504
+ }
505
+ this.measurementPending = true, this.scheduler?.enqueue(this);
506
+ }
507
+ stage() {
508
+ if (this.destroyed)
509
+ return;
510
+ if (!this.canAnimate())
511
+ return () => this.finish();
512
+ return this.previousLeft = this.enhanced && !this.reset ? this.measurement.getBoundingClientRect().left : undefined, () => this.stageMeasurement();
513
+ }
514
+ stageMeasurement() {
515
+ let e = new Set(this.target.tokens.map((n) => n.key));
516
+ for (let [n, i] of this.measures) {
517
+ if (e.has(n))
518
+ continue;
519
+ this.scheduler?.unwatch(i), this.sizes.delete(i), i.remove(), this.measures.delete(n);
520
+ }
521
+ let t = null;
522
+ for (let n of this.target.tokens) {
523
+ let i = this.measures.get(n.key);
524
+ if (!i)
525
+ i = this.host.ownerDocument.createElement("span"), i.className = "rn-token", this.measures.set(n.key, i), this.scheduler?.watch(i, this);
526
+ if (i.textContent !== n.text)
527
+ i.textContent = n.text;
528
+ let r = t ? t.nextSibling : this.measurement.firstChild;
529
+ if (i !== r)
530
+ this.measurement.insertBefore(i, r);
531
+ t = i;
532
+ }
533
+ this.host.dataset.rnMeasuring = "";
534
+ }
535
+ measure() {
536
+ if (this.destroyed)
537
+ return;
538
+ if (!this.canAnimate())
539
+ return () => this.finish();
540
+ let e = this.measurement.getBoundingClientRect(), t = this.host.ownerDocument.defaultView;
541
+ if (!t)
542
+ return () => this.finish();
543
+ let n = t.getComputedStyle(this.measurement);
544
+ if (n.direction === "rtl")
545
+ return () => this.finish();
546
+ let i = parseFloat(n.width), r = parseFloat(n.height);
547
+ if (!i || !r || !e.width || !e.height)
548
+ return () => this.finish();
549
+ let s = e.width / i, o = e.height / r, h = parseFloat(n.getPropertyValue("--rn-blur"));
550
+ this.blurIntensity = Number.isFinite(h) ? Math.max(0, h) : 1, this.sizes.set(this.measurement, { width: i, height: r });
551
+ let c = new Map;
552
+ for (let [f, y] of this.measures) {
553
+ let M = y.getBoundingClientRect(), S = { width: M.width / s, height: M.height / o };
554
+ this.sizes.set(y, S), c.set(f, { ...S, x: (M.left - e.left) / s, y: (M.top - e.top) / o });
555
+ }
556
+ let m = this.previousLeft === undefined ? 0 : (this.previousLeft - e.left) / s;
557
+ return () => this.commit(c, m);
558
+ }
559
+ makeColumn(e) {
560
+ let t = this.host.ownerDocument.createElement("span");
561
+ if (t.className = "rn-slot", t.dataset.rnKey = e.key, e.index !== undefined)
562
+ t.dataset.rnWheel = "";
563
+ if (this.options.mode === "flap")
564
+ t.dataset.rnFlap = "";
565
+ let n = this.host.ownerDocument.createElement("span");
566
+ return n.className = "rn-reel", t.append(n), this.visual.append(t), { token: e, element: t, reel: n, x: new N(t, "transform"), opacity: new N(t, "opacity"), roll: new N(n, "transform"), exiting: false, height: 0, width: 0 };
567
+ }
568
+ face(e, t) {
569
+ let n = this.host.ownerDocument.createElement("span");
570
+ n.className = "rn-face", n.textContent = t, n.style.height = `${e.height}px`;
571
+ let i = e.reel.children.length;
572
+ n.style.position = "absolute", n.style.top = "0", n.style.left = "0", n.style.width = "100%", n.style.transform = `translateY(${i * e.height}px)`, e.reel.style.height = `${(i + 1) * e.height}px`, e.reel.append(n);
573
+ }
574
+ rest(e) {
575
+ if (this.blur?.remove(e.reel), e.reel.replaceChildren(), e.reel.style.removeProperty("height"), this.face(e, e.token.text), this.wrapInk(e), e.token.index === undefined)
576
+ e.roll.set(1, ie);
577
+ else
578
+ e.roll.set(e.token.index, () => "translateY(0px)");
579
+ }
580
+ wrapInk(e) {
581
+ if (this.options.mode === "flap")
582
+ return;
583
+ let t = this.host.ownerDocument.createElement("span");
584
+ t.className = "rn-ink", t.append(...e.reel.childNodes), e.reel.append(t);
585
+ }
586
+ finishEntry(e) {
587
+ if (!e.entry)
588
+ return;
589
+ if (e.entry.blurred)
590
+ this.blur?.remove(e.reel);
591
+ e.entry.track.cancel(), e.entry.element.replaceWith(e.reel), e.entry = undefined;
592
+ }
593
+ enter(e, t, n, i) {
594
+ let r = this.host.ownerDocument.createElement("span");
595
+ r.className = "rn-enter", e.reel.replaceWith(r), r.append(e.reel);
596
+ let s = new N(r, "transform");
597
+ e.entry = { element: r, track: s, blurred: false };
598
+ let o = W(me(e.height * (i?.entryDistance ?? 1), i?.entryDuration ?? t, i?.entryHold), n);
599
+ if (this.options.motionBlur && e.token.text.trim()) {
600
+ this.blur ??= new G(this.host), this.blur.intensity = this.blurIntensity;
601
+ let h = { ...o, points: o.points.map((c) => c / e.height) };
602
+ e.entry.blurred = this.blur.apply(e.reel, h, e.height, 0, "entry");
603
+ }
604
+ s.play(o, (h) => `translateY(${h}px)`, () => this.finishEntry(e));
605
+ }
606
+ commit(e, t) {
607
+ if (this.destroyed)
608
+ return;
609
+ this.measurementPending = false;
610
+ let n = this.enhanced && !this.reset, i = n ? this.options.duration ?? 500 : 0, r = Ee.get(this.host), s = i ? r?.widthDuration ?? i : 0, o = this.options.mode === "flap", h = this.options.direction === "up" ? 1 : this.options.direction === "down" ? -1 : this.source.direction(this.displayed, this.target);
611
+ if (this.target.text !== this.displayed.text)
612
+ this.host.dataset.rnTrend = h > 0 ? "up" : h < 0 ? "down" : "none";
613
+ let c = new Map([...this.columns].map(([d, a]) => {
614
+ let p = a.x.read();
615
+ return [d, { ...p, x: p.position, width: a.width }];
616
+ })), m = Z(this.target.tokens.map((d) => d.key), c), f = [...c.keys()].sort((d, a) => c.get(d).x - c.get(a).x), y = Z(f, e), M = new Map(this.displayed.tokens.filter((d) => d.index === undefined).map((d) => [d.identity, d.key])), S = new Map(this.target.tokens.filter((d) => d.index === undefined).map((d) => [d.identity, d.key])), u = this.options.stagger === "start" || this.options.stagger === "end", g = this.target.tokens.map((d) => {
617
+ let a = this.columns.get(d.key);
618
+ return !a || a.exiting || u && d.index !== undefined && a.token.text !== d.text;
619
+ }), T = ye(this.target.tokens.map((d, a) => d.index !== undefined && !g[a]), this.options.stagger), H = Math.max(0, ...this.target.tokens.map((d, a) => g[a] ? T[a] - 1 : 0)), j = Math.min(i * 0.045, i * 0.3 / Math.max(1, H));
620
+ for (let [d, a] of this.target.tokens.entries()) {
621
+ let p = e.get(a.key);
622
+ if (!p)
623
+ continue;
624
+ let w = Math.max(0, T[d] - 1) * j, k = M.get(a.identity), v = k !== undefined && k !== a.key ? c.get(k) : undefined, l = this.columns.get(a.key), b = !l;
625
+ if (!l) {
626
+ l = this.makeColumn(a), this.columns.set(a.key, l);
627
+ let x = (v?.x ?? m.get(a.key) ?? p.x) + t;
628
+ l.x.set(n ? x + (p.x - x) * (v ? 0 : r?.entryOrigin ?? 0) : p.x, ne), l.opacity.set(n ? 0 : 1, re);
629
+ }
630
+ let O = l.token.text !== a.text, A = Math.abs(l.height - p.height) > 0.1, C = l.exiting;
631
+ l.exiting = false, l.element.style.width = `${p.width}px`, l.element.style.height = `${p.height}px`, l.element.style.top = `${p.y}px`;
632
+ let q = c.get(a.key);
633
+ if (l.x.play(L(q ? q.position + t : l.x.read().position, p.x, q?.velocity ?? 0, s), ne), b || C || !n) {
634
+ let x = l.opacity.read(), E = L(x.position, 1, x.velocity, a.index === undefined ? Math.min(i, 180) : i ? r?.fadeDuration ?? i : 0), R = !o && a.identity.startsWith("group:") && !v ? (r?.entryDuration ?? i) * (r?.entryHold ?? 0.14) : 0;
635
+ l.opacity.play(b ? W(E, w + R) : E, re);
636
+ }
637
+ if (l.height = p.height, l.width = p.width, !n || A)
638
+ this.finishEntry(l);
639
+ if (b && o && a.wheel && i)
640
+ l.roll.set(Math.max(0, a.wheel.indexOf(" ")), () => "translateY(0px)");
641
+ if (o && !A && (O || b) && a.index !== undefined && a.wheel && i && l.roll.read().position !== a.index) {
642
+ let x = l.roll.read(), E = Math.round(x.position), R = Q(E, a.index, h, a.wheel.length), D = this.options.flipDuration ?? xe(i);
643
+ this.blur?.remove(l.reel);
644
+ let I;
645
+ if (this.options.motionBlur && this.blurIntensity > 0)
646
+ this.blur ??= new G(this.host), this.blur.intensity = this.blurIntensity, I = this.blur.filterUrl(p.height);
647
+ ke(l.reel, a.wheel, E, R, p.height, D, w, I), l.token = a;
648
+ let X = l;
649
+ l.roll.play(W(we(E, R, D), w), () => "translateY(0px)", () => this.rest(X));
650
+ } else if (!b && !A && O && a.index !== undefined && a.wheel && l.token.index !== undefined && i) {
651
+ let x = l.roll.read(), E = se(this.options) ? ge(l.token.wheel, x.position, a.text) : undefined, R = E?.from ?? x.position, D = E?.target ?? Q(x.position, a.index, h, a.wheel.length), I = E?.wheel ?? a.wheel, X = E ? Math.min(Math.max(0, x.velocity), (D - R) * 1e4 / i) : x.velocity, K = u ? W(L(R, D, X, i), w) : L(R, D, X, i), ae = Math.floor(Math.min(...K.points)), Oe = Math.ceil(Math.max(...K.points));
652
+ if (l.entry)
653
+ l.entry.blurred = false;
654
+ let Se = this.blur?.remove(l.reel) ?? 0;
655
+ l.reel.replaceChildren();
656
+ for (let B = ae;B <= Oe; B++)
657
+ this.face(l, P(I, B));
658
+ if (this.wrapInk(l), this.options.motionBlur)
659
+ this.blur ??= new G(this.host), this.blur.intensity = this.blurIntensity, this.blur.apply(l.reel, K, p.height, Se);
660
+ l.token = E ? { ...a, wheel: I, index: D } : a;
661
+ let Ce = l;
662
+ l.roll.play(K, (B) => `translateY(${(ae - B) * p.height}px)`, () => this.rest(Ce));
663
+ } else if (b || A || O || !n)
664
+ l.token = a, this.rest(l);
665
+ if (b && i && a.index !== undefined && !o)
666
+ this.enter(l, i, w, r);
667
+ if (v && i && (b || C)) {
668
+ let x = l.roll.read(), E = l;
669
+ l.roll.play(L(b ? 0.96 : x.position, 1, x.velocity, Math.min(i, 180)), ie, () => this.rest(E));
670
+ }
671
+ }
672
+ for (let [d, a] of this.columns) {
673
+ if (e.has(d))
674
+ continue;
675
+ let p = c.get(d), w = S.get(a.token.identity), k = w ? e.get(w) : undefined;
676
+ if (a.x.play(L(p.position + t, k?.x ?? y.get(d) ?? p.position, p.velocity, s), ne), a.exiting)
677
+ continue;
678
+ if (a.exiting = true, k && i) {
679
+ let l = a.roll.read();
680
+ a.roll.play(L(l.position, 1.04, l.velocity, Math.min(i, 180)), ie);
681
+ }
682
+ let v = a.opacity.read();
683
+ a.opacity.play(L(v.position, 0, v.velocity, a.token.index === undefined ? Math.min(i, 180) : i * 0.65), re, () => {
684
+ if (!a.exiting)
685
+ return;
686
+ this.removeColumn(a), this.columns.delete(d);
687
+ });
688
+ }
689
+ this.enhanced = true, this.reset = false, this.displayed = this.target, this.host.dataset.rnReady = "";
690
+ }
691
+ removeColumn(e) {
692
+ this.blur?.remove(e.reel), this.finishEntry(e), e.x.cancel(), e.roll.cancel(), e.opacity.cancel(), e.element.remove();
693
+ }
694
+ refresh() {
695
+ if (this.destroyed)
696
+ return;
697
+ this.reset = true, this.prepare();
698
+ }
699
+ sizeChanged(e, t, n) {
700
+ if (this.measurementPending || !this.host.hasAttribute("data-rn-measuring"))
701
+ return false;
702
+ let i = this.sizes.get(e);
703
+ return !i || Math.abs(i.width - t) > 0.2 || Math.abs(i.height - n) > 0.2;
704
+ }
705
+ visibility(e) {
706
+ if (this.visible === e)
707
+ return;
708
+ if (this.visible = e, e || this.options.pauseOffscreen !== false)
709
+ this.refresh();
710
+ }
711
+ finish() {
712
+ if (this.destroyed)
713
+ return;
714
+ this.measurementPending = false;
715
+ for (let e of this.columns.values())
716
+ this.removeColumn(e);
717
+ this.columns.clear(), this.blur?.destroy(), this.blur = undefined, this.semantic.textContent = this.target.text, delete this.host.dataset.rnReady, delete this.host.dataset.rnMeasuring, delete this.host.dataset.rnTrend, this.enhanced = false, this.reset = true, this.displayed = this.target;
718
+ }
719
+ destroy() {
720
+ if (this.destroyed)
721
+ return;
722
+ this.finish(), this.destroyed = true;
723
+ for (let e of this.measures.values())
724
+ this.scheduler?.unwatch(e);
725
+ if (this.scheduler?.unwatch(this.measurement), this.scheduler?.remove(this, this.host), this.host.replaceChildren(this.host.ownerDocument.createTextNode(this.target.text)), !this.hadClass)
726
+ this.host.classList.remove("rn-root");
727
+ _.delete(this.host);
728
+ }
729
+ }
730
+ function rt(e, t) {
731
+ if (_.has(e))
732
+ throw Error("A rolling number is already mounted on this element");
733
+ let n = new oe(e, t, Fe);
734
+ return _.add(e), n;
735
+ }
3
736
 
4
737
  // src/core/spring.ts
5
738
  class Spring {
@@ -83,323 +816,247 @@ function springCss(value) {
83
816
  return { easing, duration: ms };
84
817
  }
85
818
 
86
- // src/ui/bento-model.ts
87
- var FIT_MAX = 24;
88
- var clamp = (x, a, b) => Math.min(b, Math.max(a, x));
89
- var sum = (xs) => xs.reduce((s, x) => s + x, 0);
90
- var rowTarget = (width, height) => clamp(Math.round(Math.min(height / 3, width / 2.6)), 120, 420);
91
- function justify(aspects, start, end, width, target, gap, tol) {
819
+ // src/ui/activity-model.ts
820
+ function flattenActivity(events, expanded) {
92
821
  const rows = [];
93
- let i = start;
94
- while (i < end) {
95
- let total = 0;
96
- let j = i;
97
- let h = Number.POSITIVE_INFINITY;
98
- let closed = false;
99
- while (j < end) {
100
- const k = j - i + 1;
101
- const next = (width - (k - 1) * gap) / (total + aspects[j]);
102
- if (next <= target) {
103
- if (k === 1 || Math.abs(Math.log(next / target)) <= Math.abs(Math.log(h / target))) {
104
- total += aspects[j];
105
- j++;
106
- h = next;
107
- }
108
- closed = true;
109
- break;
110
- }
111
- total += aspects[j];
112
- h = next;
113
- j++;
114
- }
115
- const slice = aspects.slice(i, j);
116
- if (closed || h <= target * 1.25) {
117
- rows.push({ start: i, end: j, h, ws: slice.map((a) => a * h) });
118
- } else {
119
- const free = width - (j - i - 1) * gap;
120
- const f = free / (target * total);
121
- const ws = slice.map((a) => a * target * (f - 1 <= tol ? f : 1));
122
- rows.push({ start: i, end: j, h: target, ws });
123
- }
124
- i = j;
822
+ for (const event of events) {
823
+ const kids = event.children ?? [];
824
+ const group = kids.length > 0;
825
+ const open = group && expanded.has(event.id);
826
+ rows.push({ id: event.id, event, depth: 0, parent: null, group, expanded: open });
827
+ if (open)
828
+ for (const child of flatChildren(kids))
829
+ rows.push({
830
+ id: child.id,
831
+ event: child,
832
+ depth: 1,
833
+ parent: event.id,
834
+ group: false,
835
+ expanded: false
836
+ });
125
837
  }
126
838
  return rows;
127
839
  }
128
- function partition(xs, k) {
129
- const n = xs.length;
130
- k = clamp(k, 1, n);
131
- const prefix = [0];
132
- for (const x of xs)
133
- prefix.push(prefix.at(-1) + x);
134
- const cost = Array.from({ length: k + 1 }, () => new Float64Array(n + 1).fill(Infinity));
135
- const cut = Array.from({ length: k + 1 }, () => new Int32Array(n + 1));
136
- cost[0][0] = 0;
137
- for (let j = 1;j <= k; j++)
138
- for (let i = j;i <= n; i++)
139
- for (let m = j - 1;m < i; m++) {
140
- const c = Math.max(cost[j - 1][m], prefix[i] - prefix[m]);
141
- if (c < cost[j][i]) {
142
- cost[j][i] = c;
143
- cut[j][i] = m;
144
- }
145
- }
146
- const counts = [];
147
- for (let j = k, i = n;j > 0; j--) {
148
- const m = cut[j][i];
149
- counts.unshift(i - m);
150
- i = m;
840
+ function flatChildren(events) {
841
+ const out = [];
842
+ for (const e of events) {
843
+ out.push(e);
844
+ if (e.children?.length)
845
+ out.push(...flatChildren(e.children));
151
846
  }
152
- return counts;
847
+ return out;
153
848
  }
154
- function rowsBlock(aspects, w, h, gap, tol) {
155
- const n = aspects.length;
156
- if (!n || w <= 0 || h <= 0)
849
+ function indexActivity(events) {
850
+ const index = new Map;
851
+ for (const event of events) {
852
+ index.set(event.id, { event, parent: null });
853
+ for (const child of flatChildren(event.children ?? []))
854
+ index.set(child.id, { event: child, parent: event.id });
855
+ }
856
+ return index;
857
+ }
858
+ var isActivityKey = (key) => key === "ArrowUp" || key === "ArrowDown" || key === "ArrowLeft" || key === "ArrowRight" || key === "Home" || key === "End";
859
+ function navigateActivity(rows, selected, key) {
860
+ if (!rows.length)
861
+ return {};
862
+ const i = selected === null ? -1 : rows.findIndex((r) => r.id === selected);
863
+ const row = i >= 0 ? rows[i] : undefined;
864
+ switch (key) {
865
+ case "ArrowDown":
866
+ return { select: rows[i < 0 ? 0 : Math.min(rows.length - 1, i + 1)].id };
867
+ case "ArrowUp":
868
+ return { select: rows[i < 0 ? rows.length - 1 : Math.max(0, i - 1)].id };
869
+ case "Home":
870
+ return { select: rows[0].id };
871
+ case "End":
872
+ return { select: rows[rows.length - 1].id };
873
+ case "ArrowRight":
874
+ if (!row?.group)
875
+ return {};
876
+ if (!row.expanded)
877
+ return { toggle: { id: row.id, expanded: true } };
878
+ return rows[i + 1]?.parent === row.id ? { select: rows[i + 1].id } : {};
879
+ case "ArrowLeft":
880
+ if (!row)
881
+ return {};
882
+ if (row.group && row.expanded)
883
+ return { toggle: { id: row.id, expanded: false } };
884
+ return row.parent !== null ? { select: row.parent } : {};
885
+ }
886
+ }
887
+ function takesActivityKey(key, result, rows) {
888
+ if (result.select !== undefined || result.toggle !== undefined)
889
+ return true;
890
+ return rows > 0 && key !== "ArrowLeft" && key !== "ArrowRight";
891
+ }
892
+ function keepSelection(rows, selected, parentOf) {
893
+ if (selected === null)
157
894
  return null;
158
- let best = null;
159
- const most = Math.min(n, Math.max(2, Math.ceil(Math.sqrt(n) * 2)));
160
- for (let r = 1;r <= most; r++) {
161
- const counts = partition(aspects, r);
162
- let at = 0;
163
- const groups = counts.map((c) => aspects.slice(at, at += c));
164
- const heights = groups.map((g) => (w - (g.length - 1) * gap) / sum(g));
165
- const budget = h - (r - 1) * gap;
166
- if (budget <= 0 || heights.some((x) => x <= 0))
167
- break;
168
- const s = Math.min(1, budget / sum(heights));
169
- const fill = s < 1 && 1 / s - 1 <= tol ? 1 / s : 1;
170
- const rects = [];
171
- let y = 0;
172
- let blockW = 0;
173
- groups.forEach((g, k) => {
174
- const rh = heights[k] * s;
175
- const rowW = sum(g.map((a) => a * rh * fill)) + (g.length - 1) * gap;
176
- blockW = Math.max(blockW, rowW);
177
- let x = 0;
178
- for (const a of g) {
179
- rects.push({ x, y, w: a * rh * fill, h: rh });
180
- x += a * rh * fill + gap;
181
- }
182
- y += rh + gap;
183
- });
184
- let i = 0;
185
- groups.forEach((g, k) => {
186
- const rowW = sum(g.map((a) => a * heights[k] * s * fill)) + (g.length - 1) * gap;
187
- const dx = (blockW - rowW) / 2;
188
- for (let m = 0;m < g.length; m++, i++) {
189
- const q = rects[i];
190
- rects[i] = { ...q, x: q.x + dx };
191
- }
192
- });
193
- const area = sum(rects.map((q) => q.w * q.h));
194
- const block = {
195
- rects,
196
- w: blockW,
197
- h: y - gap,
198
- area,
199
- crop: fill - 1,
200
- minH: Math.min(...rects.map((q) => q.h))
201
- };
202
- if (!best || block.area > best.area * 1.0001)
203
- best = block;
204
- }
205
- return best;
895
+ if (rows.some((r) => r.id === selected))
896
+ return selected;
897
+ const parent = parentOf(selected);
898
+ return parent && rows.some((r) => r.id === parent) ? parent : null;
206
899
  }
207
- var offset = (rects, dx, dy) => rects.map((q) => ({ x: q.x + dx, y: q.y + dy, w: q.w, h: q.h }));
208
- function heroBlock(aspects, hero, w, h, gap, tol) {
209
- const a = aspects[hero];
210
- const rest = aspects.filter((_, i) => i !== hero);
211
- if (!rest.length)
212
- return null;
213
- let best = null;
214
- const place = (heroRect, restBlock, restAt, bw, bh) => {
215
- if (!restBlock)
216
- return;
217
- const [rx, ry] = restAt(restBlock);
218
- const others = offset(restBlock.rects, rx, ry);
219
- const rects = [...others.slice(0, hero), heroRect, ...others.slice(hero)];
220
- const area = heroRect.w * heroRect.h + restBlock.area;
221
- if (!best || area > best.area * 1.0001)
222
- best = { rects, w: bw, h: bh, area, crop: restBlock.crop, minH: restBlock.minH };
223
- };
224
- for (let f = 0.4;f <= 0.761; f += 0.04) {
225
- {
226
- const hw = Math.min(w * f, h * a);
227
- const hh = hw / a;
228
- const block = rowsBlock(rest, w - hw - gap, h, gap, tol);
229
- if (block) {
230
- const bw = hw + gap + block.w;
231
- const bh = Math.max(hh, block.h);
232
- place({ x: 0, y: (bh - hh) / 2, w: hw, h: hh }, block, (b) => [hw + gap, (bh - b.h) / 2], bw, bh);
233
- }
234
- }
235
- {
236
- const hh = Math.min(h * f, w / a);
237
- const hw = hh * a;
238
- const block = rowsBlock(rest, w, h - hh - gap, gap, tol);
239
- if (block) {
240
- const bw = Math.max(hw, block.w);
241
- place({ x: (bw - hw) / 2, y: 0, w: hw, h: hh }, block, (b) => [(bw - b.w) / 2, hh + gap], bw, hh + gap + block.h);
242
- }
243
- }
244
- }
245
- return best;
900
+ function showsSelection(showing, input, keys = "focus") {
901
+ if (input === "key")
902
+ return true;
903
+ if (input === "pointer")
904
+ return false;
905
+ if (input === "blur")
906
+ return keys === "page" ? showing : false;
907
+ return showing;
246
908
  }
247
- function planBento(aspects, layout) {
248
- const gap = layout.gap ?? 2;
249
- const pad = layout.padding ?? gap;
250
- const tol = layout.tolerance ?? 0.08;
251
- const vw = Math.max(0, layout.width);
252
- const vh = Math.max(0, layout.height);
253
- const w = Math.max(1, vw - pad * 2);
254
- const h = Math.max(1, vh - pad * 2);
255
- const as = aspects.map((a) => Number.isFinite(a) && a > 0 ? clamp(a, 0.05, 20) : 1);
256
- const empty = {
257
- mode: "rows",
258
- rects: [],
259
- width: vw,
260
- height: 0,
261
- coverage: 0,
262
- crop: 0,
263
- headers: []
909
+ function diffActivity(before, after) {
910
+ const was = new Set(before);
911
+ const now = new Set(after);
912
+ return {
913
+ entered: after.filter((id) => !was.has(id)),
914
+ left: before.filter((id) => !now.has(id)),
915
+ kept: after.filter((id) => was.has(id))
264
916
  };
265
- if (!as.length || vw <= 0)
266
- return empty;
267
- const grouped = layout.groups?.some((g) => g !== undefined) ?? false;
268
- const minTile = layout.minTile ?? Math.max(80, vh / 6);
269
- if ((layout.fit ?? true) && !grouped && as.length <= FIT_MAX && vh > 0) {
270
- const rows = rowsBlock(as, w, h, gap, tol);
271
- let pick = rows && rows.minH >= minTile ? { mode: "fit", block: rows } : null;
272
- const asked = layout.hero !== undefined && layout.hero >= 0 && layout.hero < as.length;
273
- const hero = as.length > 2 ? heroBlock(as, asked ? layout.hero : 0, w, h, gap, tol) : null;
274
- if (hero && hero.minH >= minTile && (asked || !pick || hero.area > pick.block.area * 1.04))
275
- pick = { mode: "hero", block: hero };
276
- if (pick) {
277
- const { block } = pick;
278
- const rects = offset(block.rects, pad + (w - block.w) / 2, pad + (h - block.h) / 2);
279
- return {
280
- mode: pick.mode,
281
- rects,
282
- width: vw,
283
- height: vh,
284
- coverage: block.area / (vw * vh),
285
- crop: block.crop,
286
- headers: []
287
- };
288
- }
289
- }
290
- const target = layout.rowHeight ?? rowTarget(vw, vh);
291
- const header = layout.header ?? 28;
292
- const rects = new Array(as.length);
293
- const headers = [];
294
- let y = pad;
295
- let crop = 0;
296
- let area = 0;
297
- let i = 0;
298
- while (i < as.length) {
299
- const group = layout.groups?.[i];
300
- let end = i + 1;
301
- while (end < as.length && layout.groups?.[end] === group)
302
- end++;
303
- if (group !== undefined) {
304
- if (i > 0)
305
- y += header / 2;
306
- headers.push({ group, y, index: i });
307
- y += header;
308
- }
309
- for (const row of justify(as, i, end, w, target, gap, tol)) {
310
- let x = pad;
311
- row.ws.forEach((rw, k) => {
312
- rects[row.start + k] = { x, y, w: rw, h: row.h };
313
- crop = Math.max(crop, rw / (as[row.start + k] * row.h) - 1);
314
- area += rw * row.h;
315
- x += rw + gap;
316
- });
317
- y += row.h + gap;
318
- }
319
- i = end;
320
- }
321
- const height = y - gap + pad;
322
- return { mode: "rows", rects, width: vw, height, coverage: area / (vw * height), crop, headers };
323
917
  }
324
- function windowOf(plan, top, bottom) {
325
- const { rects } = plan;
326
- if (plan.mode !== "rows")
327
- return rects.map((_, i) => i);
328
- let lo = 0;
329
- let hi = rects.length;
330
- while (lo < hi) {
331
- const mid = lo + hi >> 1;
332
- if (rects[mid].y + rects[mid].h < top)
333
- lo = mid + 1;
334
- else
335
- hi = mid;
336
- }
918
+ function stackRows(heights, top = 0, gap = 0) {
919
+ const y = [];
920
+ let at = top;
921
+ heights.forEach((h, i) => {
922
+ y.push(at);
923
+ at += h + (i < heights.length - 1 ? gap : 0);
924
+ });
925
+ return { y, bottom: at };
926
+ }
927
+ function railSegments(nodes, from, to) {
928
+ const sorted = [...nodes].sort((a, b) => a.y - b.y);
337
929
  const out = [];
338
- for (let i = lo;i < rects.length && rects[i].y <= bottom; i++)
339
- out.push(i);
930
+ for (let i = 0;i < sorted.length - 1; i++) {
931
+ const a = sorted[i];
932
+ const b = sorted[i + 1];
933
+ const top = Math.max(from, a.y + a.r);
934
+ const bottom = Math.min(to, b.y - b.r);
935
+ if (bottom - top > 0.25)
936
+ out.push([top, bottom]);
937
+ }
340
938
  return out;
341
939
  }
342
- var overlapY = (a, b) => Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y) >= Math.min(a.h, b.h) * 0.5;
343
- function neighbour(rects, from, dir) {
344
- const f = rects[from];
345
- if (!f)
346
- return from;
347
- const fx = f.x + f.w / 2;
348
- const fy = f.y + f.h / 2;
349
- if (dir === "left" || dir === "right") {
350
- const right = dir === "right";
351
- let best = -1;
352
- let bestD = Number.POSITIVE_INFINITY;
353
- rects.forEach((r, i) => {
354
- if (i === from || !overlapY(f, r))
355
- return;
356
- const d = (r.x + r.w / 2 - fx) * (right ? 1 : -1);
357
- if (d > 1 && d < bestD) {
358
- bestD = d;
359
- best = i;
360
- }
361
- });
362
- if (best >= 0)
363
- return best;
364
- return clamp(from + (right ? 1 : -1), 0, rects.length - 1);
365
- }
366
- const down = dir === "down";
367
- let edge = Number.POSITIVE_INFINITY;
368
- for (const r of rects) {
369
- const d = (r.y + r.h / 2 - fy) * (down ? 1 : -1);
370
- if (d > f.h * 0.25 && !overlapY(f, r))
371
- edge = Math.min(edge, down ? r.y : -(r.y + r.h));
372
- }
373
- if (!Number.isFinite(edge))
374
- return from;
375
- let best = from;
376
- let bestD = Number.POSITIVE_INFINITY;
377
- rects.forEach((r, i) => {
378
- const d = (r.y + r.h / 2 - fy) * (down ? 1 : -1);
379
- if (i === from || d <= f.h * 0.25 || overlapY(f, r))
380
- return;
381
- const near = down ? r.y - edge : -(r.y + r.h) - edge;
382
- if (near > Math.min(r.h, f.h) * 0.5)
383
- return;
384
- const dx = Math.abs(r.x + r.w / 2 - fx);
385
- if (dx < bestD) {
386
- bestD = dx;
387
- best = i;
388
- }
389
- });
390
- return best;
940
+ function holdScroll(hold, height, view) {
941
+ if (hold.from <= 0)
942
+ return { scroll: 0, space: height };
943
+ const end = Math.min(hold.from, Math.max(0, hold.h1 - view));
944
+ const span = hold.h1 - hold.h0;
945
+ const p = Math.abs(span) < 0.5 ? 1 : Math.min(1, Math.max(0, (height - hold.h0) / span));
946
+ const scroll = hold.from + (end - hold.from) * p;
947
+ return { scroll, space: Math.max(height, scroll + view) };
391
948
  }
392
- function fitRect(aspect, w, h, inset = 0) {
393
- const bw = Math.max(1, w - inset * 2);
394
- const bh = Math.max(1, h - inset * 2);
395
- const fw = Math.min(bw, bh * aspect);
396
- const fh = fw / aspect;
397
- return { x: (w - fw) / 2, y: (h - fh) / 2, w: fw, h: fh };
949
+ function formatDuration(duration) {
950
+ if (typeof duration === "string")
951
+ return duration;
952
+ const s = Math.max(0, Math.round(duration));
953
+ const h = Math.floor(s / 3600);
954
+ const m = Math.floor(s % 3600 / 60);
955
+ const sec = String(s % 60).padStart(2, "0");
956
+ return h ? `${h}:${String(m).padStart(2, "0")}:${sec}` : `${m}:${sec}`;
398
957
  }
399
- function motionBlur(speed, max = 3, per = 1600) {
400
- const b = Math.min(max, Math.abs(speed) / per);
401
- return b < 0.25 ? 0 : Math.round(b * 4) / 4;
958
+ function groupTitle(count) {
959
+ return `${count} update${count === 1 ? "" : "s"}`;
960
+ }
961
+
962
+ // src/panel/rolling-css.ts
963
+ var rolling_css_default = `.rn-root {
964
+ display: inline-block;
965
+ position: relative;
966
+ white-space: pre;
967
+ unicode-bidi: isolate;
968
+ vertical-align: baseline;
969
+ }
970
+ .rn-root > .rn-measure,
971
+ .rn-root > .rn-visual { display: none; }
972
+ .rn-root[data-rn-measuring] > .rn-measure {
973
+ display: inline-flex;
974
+ position: relative;
975
+ visibility: hidden;
976
+ white-space: pre;
977
+ padding: 0;
978
+ border: 0;
979
+ }
980
+ .rn-token { display: inline-block; padding: 0; border: 0; }
981
+ .rn-root[data-rn-measuring] > .rn-value,
982
+ .rn-react[data-rn-hydrated] > .rn-semantic,
983
+ .rn-solid[data-rn-hydrated] > .rn-semantic {
984
+ position: absolute;
985
+ inset: 0;
986
+ padding: 0;
987
+ margin: 0;
988
+ color: transparent;
989
+ z-index: 1;
990
+ }
991
+ /* A single native text layer is selectable; decorative reels never enter a copy. */
992
+ .rn-value::selection, .rn-semantic::selection {
993
+ color: HighlightText;
994
+ background: Highlight;
995
+ }
996
+ .rn-root[data-rn-ready] > .rn-visual {
997
+ display: block;
998
+ position: absolute;
999
+ inset: 0;
1000
+ pointer-events: none;
1001
+ user-select: none;
1002
+ }
1003
+ .rn-slot {
1004
+ position: absolute;
1005
+ left: 0;
1006
+ overflow: hidden;
1007
+ /* Space for italic overhang without changing the measured advance width. */
1008
+ box-sizing: content-box;
1009
+ padding-inline: 0.15em;
1010
+ margin-inline-start: -0.15em;
1011
+ -webkit-mask-image: var(--rn-mask, linear-gradient(
1012
+ to bottom,
1013
+ transparent,
1014
+ #000 var(--rn-edge-fade, 0.12em),
1015
+ #000 calc(100% - var(--rn-edge-fade, 0.12em)),
1016
+ transparent
1017
+ ));
1018
+ mask-image: var(--rn-mask, linear-gradient(
1019
+ to bottom,
1020
+ transparent,
1021
+ #000 var(--rn-edge-fade, 0.12em),
1022
+ #000 calc(100% - var(--rn-edge-fade, 0.12em)),
1023
+ transparent
1024
+ ));
1025
+ -webkit-mask-repeat: no-repeat;
1026
+ mask-repeat: no-repeat;
402
1027
  }
1028
+ @supports (overflow: clip) {
1029
+ .rn-slot { overflow-x: visible; overflow-y: clip; }
1030
+ }
1031
+ .rn-slot:not([data-rn-wheel]) {
1032
+ overflow: visible;
1033
+ -webkit-mask-image: none;
1034
+ mask-image: none;
1035
+ }
1036
+ .rn-enter, .rn-reel, .rn-face { display: block; }
1037
+ .rn-reel { position: relative; }
1038
+ .rn-ink { display: block; position: absolute; inset: 0; will-change: transform; }
1039
+ /* Safari's SVG filter must rasterize the smear's descendants into its input. */
1040
+ .rn-smear .rn-ink { will-change: auto; }
1041
+ /* Each turning half carries its own perspective transform, not a shared 3D scene. */
1042
+ /* The slot acts as the bezel: the card stays vertically framed while it hinges. */
1043
+ .rn-slot[data-rn-flap] { -webkit-mask-image: none; mask-image: none; }
1044
+ /* Hinged faces must occlude the cards behind them, including with default CSS. */
1045
+ :where(.rn-slot[data-rn-flap]) .rn-face { background: var(--rn-flap-background, Canvas); }
1046
+ .rn-flap { position: absolute; inset: 0; transform-origin: 50% 50%; backface-visibility: hidden; }
1047
+ /* --rn-crease is the visible hinge gap between the two halves. */
1048
+ .rn-flap-top { clip-path: inset(0 0 calc(50% + var(--rn-crease, 1px) / 2) 0); }
1049
+ .rn-flap-bottom { clip-path: inset(calc(50% + var(--rn-crease, 1px) / 2) 0 0 0); }
1050
+ .rn-sharp, .rn-smear { display: block; }
1051
+ .rn-smear { position: absolute; inset: 0; pointer-events: none; }
1052
+ .rn-blur-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
1053
+ .rn-face { text-align: center; white-space: pre; }
1054
+ .rn-react, .rn-solid { display: inline-block; position: relative; white-space: pre; unicode-bidi: isolate; }
1055
+ .rn-mount { display: inline-block; user-select: none; }
1056
+ .rn-mount .rn-value { pointer-events: none; }
1057
+ .rn-react:not([data-rn-hydrated]) > .rn-mount,
1058
+ .rn-solid:not([data-rn-hydrated]) > .rn-mount { display: none; }
1059
+ `;
403
1060
 
404
1061
  // src/core/theme.ts
405
1062
  var block = (tokens) => Object.entries(tokens).map(([k, v]) => `${k.startsWith("--") || k === "color-scheme" ? k : `--k-${k}`}:${v}`).join(";");
@@ -493,6 +1150,88 @@ var problemsPreset = {
493
1150
  }
494
1151
  };
495
1152
 
1153
+ // src/ui/activity-css.ts
1154
+ var open = springEasing(2 * Math.PI / 0.22, 1);
1155
+ var activityCss = `
1156
+ :where(.knobs-activity,.knobs-activity *){box-sizing:border-box}
1157
+ :where(.knobs-activity){color-scheme:dark}
1158
+ .knobs-ui.knobs-activity{position:relative;display:block;height:100%;overflow:auto;overflow-anchor:none;outline:none;scrollbar-width:thin;overscroll-behavior:contain;user-select:none;-webkit-user-select:none;
1159
+ font:var(--ku-font-weight) var(--ku-font-size)/1.35 var(--ku-font);color:var(--ku-text);text-align:left;letter-spacing:normal;-webkit-font-smoothing:antialiased;
1160
+ --ka-gutter:var(--k-activity-gutter,28px);--ka-row:var(--k-activity-row,var(--ku-row));--ka-row-child:var(--k-activity-row-child,calc(var(--ka-row) - 6px));
1161
+ --ka-line:var(--k-activity-line,var(--ku-line-hi));--ka-select:var(--k-activity-select,var(--ku-wash-hi));--ka-hover:var(--k-activity-hover,var(--ku-wash));
1162
+ --ka-you:var(--k-activity-you,light-dark(#007aff,#0a84ff));--ka-agent:var(--k-activity-agent,var(--ku-label));--ka-proposal:var(--k-activity-proposal,var(--ku-text));
1163
+ --ka-review:var(--k-activity-review,var(--ku-text));--ka-pr:var(--k-activity-pr,light-dark(#8944ab,#bf5af2));--ka-build:var(--k-activity-build,var(--ku-label));
1164
+ --ka-blocked:var(--k-activity-blocked,var(--ku-warn));--ka-shipped:var(--k-activity-shipped,light-dark(#248a3d,#30d158));--ka-open-ease:${open.easing};--ka-open-ms:${open.ms}ms}
1165
+ .knobs-activity [hidden]{display:none!important}
1166
+ /* Clipped below: the content grows on a spring, so a new last row shows as the rail makes room. */
1167
+ .knobs-activity-space{position:relative;width:100%;overflow-x:visible;overflow-y:clip;pointer-events:none}
1168
+ .knobs-activity-line{position:absolute;top:0;left:calc(var(--ku-row-x) + var(--ka-gutter) / 2 - 1px);width:2px;height:100%;overflow:visible;pointer-events:none;z-index:1}
1169
+ .knobs-activity-line path{fill:none;stroke:var(--ka-line);stroke-width:1px}
1170
+ /* The selection's tint: a wash that glides between rows, shown only once keys move it. It leaves
1171
+ in 100 ms when the pointer is used, and comes back at once. */
1172
+ .knobs-activity-hl{position:absolute;left:var(--ku-row-x);right:var(--ku-row-x);top:0;height:0;border-radius:var(--ku-row-radius);background:var(--ka-select);opacity:0;
1173
+ transition:opacity 100ms ease-out;pointer-events:none;will-change:transform}
1174
+ .knobs-activity[data-keys] .knobs-activity-hl{opacity:1;transition:none}
1175
+ .knobs-activity-row{position:absolute;left:var(--ku-row-x);right:var(--ku-row-x);top:0;display:flex;align-items:center;height:var(--ka-row);padding-right:calc(var(--ku-inset) - 4px);
1176
+ color:var(--ku-text);white-space:nowrap;cursor:default;will-change:transform;transform-origin:calc(var(--ka-gutter) / 2) 50%;pointer-events:auto;--ka-tint:var(--ka-agent)}
1177
+ .knobs-activity-row[data-leaving]{pointer-events:none}
1178
+ .knobs-activity-row[data-depth="1"]{height:var(--ka-row-child)}
1179
+ .knobs-activity-row>*{position:relative}
1180
+ /* Hover: in at once, out over 100 ms; not while keys drive the selection. */
1181
+ .knobs-activity-row::before{content:"";position:absolute;inset:0;border-radius:var(--ku-row-radius);background:var(--ka-hover);opacity:0;transition:opacity calc(var(--k-hover-out,100) * 1ms) ease-out;pointer-events:none}
1182
+ .knobs-activity:not([data-keys]) .knobs-activity-row:not([data-leaving]):hover::before{opacity:1;transition-duration:calc(var(--k-hover-in,0) * 1ms)}
1183
+ .knobs-activity-row[data-kind="you"]{--ka-tint:var(--ka-you)}
1184
+ .knobs-activity-row[data-kind="agent"]{--ka-tint:var(--ka-agent)}
1185
+ .knobs-activity-row[data-kind="proposal"]{--ka-tint:var(--ka-proposal)}
1186
+ .knobs-activity-row[data-kind="review"]{--ka-tint:var(--ka-review)}
1187
+ .knobs-activity-row[data-kind="pr"]{--ka-tint:var(--ka-pr)}
1188
+ .knobs-activity-row[data-kind="build"]{--ka-tint:var(--ka-build)}
1189
+ .knobs-activity-row[data-kind="blocked"]{--ka-tint:var(--ka-blocked)}
1190
+ .knobs-activity-row[data-kind="shipped"]{--ka-tint:var(--ka-shipped)}
1191
+ /* The node: the kind's icon on the line (which stops clear of it), or a small dot inside a group. */
1192
+ .knobs-activity-node{flex:none;display:flex;align-items:center;justify-content:center;width:var(--ka-gutter);height:100%;color:var(--ka-tint)}
1193
+ .knobs-activity-node svg{display:block;width:14px;height:14px}
1194
+ .knobs-activity-glyph{display:flex;align-items:center;justify-content:center;width:14px;height:14px;font-size:calc(var(--ku-font-size) - 1px);line-height:1}
1195
+ .knobs-activity-node::before{content:"";position:absolute;left:50%;top:50%;width:22px;height:22px;margin:-11px 0 0 -11px;border-radius:50%;background:color-mix(in srgb,var(--ka-tint) 15%,transparent);
1196
+ opacity:0;scale:.8;transition:opacity .16s ease-out,scale var(--ka-open-ms) var(--ka-open-ease)}
1197
+ .knobs-activity-row[aria-selected="true"] .knobs-activity-node::before{opacity:1;scale:1}
1198
+ .knobs-activity-node::after{content:"";position:absolute;left:50%;top:50%;width:22px;height:22px;margin:-11px 0 0 -11px;border-radius:50%;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ka-tint) 55%,transparent);opacity:0;pointer-events:none}
1199
+ .knobs-activity-row[data-current] .knobs-activity-node::after{opacity:1;animation:knobs-activity-breathe 2.6s ease-in-out infinite}
1200
+ @keyframes knobs-activity-breathe{0%,100%{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ka-tint) 55%,transparent),0 0 0 0 color-mix(in srgb,var(--ka-tint) 0%,transparent)}
1201
+ 50%{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ka-tint) 70%,transparent),0 0 0 3px color-mix(in srgb,var(--ka-tint) 14%,transparent)}}
1202
+ .knobs-activity-dot{display:block;width:5px;height:5px;border-radius:50%;background:currentColor}
1203
+ .knobs-activity-row[data-depth="1"] .knobs-activity-node::before,.knobs-activity-row[data-depth="1"] .knobs-activity-node::after{width:13px;height:13px;margin:-6.5px 0 0 -6.5px}
1204
+ /* The words: who in the text colour, then the title, one line that fades where it's cut off. */
1205
+ .knobs-activity-main{flex:1;min-width:0;overflow:hidden;white-space:nowrap}
1206
+ .knobs-activity-actor{margin-right:6px;font-weight:var(--ku-label-weight);color:var(--ku-text)}
1207
+ .knobs-activity-actor:empty{display:none}
1208
+ .knobs-activity-title{color:var(--ku-label)}
1209
+ .knobs-activity-row[data-depth="1"] .knobs-activity-main{font-size:var(--ku-text-meta)}
1210
+ .knobs-activity-row[data-depth="1"] .knobs-activity-actor{font-weight:var(--ku-font-weight);color:var(--ku-label)}
1211
+ .knobs-activity-row[data-depth="1"] .knobs-activity-title{color:var(--ku-dim)}
1212
+ .knobs-activity-row[aria-selected="true"] .knobs-activity-title{color:var(--ku-text)}
1213
+ .knobs-activity-row[data-dimmed] :is(.knobs-activity-main,.knobs-activity-meta){opacity:.55}
1214
+ .knobs-activity-row[data-dimmed] .knobs-activity-node{opacity:.6}
1215
+ /* Trailing: thumbnails, a duration chip, the group's chevron and the time. */
1216
+ .knobs-activity-meta{flex:none;display:flex;align-items:center;gap:6px;margin-left:8px;color:var(--ku-dim);font-size:var(--ku-text-meta);font-variant-numeric:tabular-nums}
1217
+ .knobs-activity-meta:empty{display:none}
1218
+ .knobs-activity-thumbs{display:flex;gap:3px}
1219
+ .knobs-activity-thumbs img{display:block;width:18px;height:18px;border-radius:4px;object-fit:cover;background:var(--ku-surface);box-shadow:inset 0 0 0 .5px var(--ku-line)}
1220
+ .knobs-activity-row[data-depth="1"] .knobs-activity-thumbs img{width:15px;height:15px}
1221
+ :where(.knobs-ui) .knobs-activity-chip{all:revert;box-sizing:border-box;display:inline-flex;align-items:center;gap:4px;height:18px;padding:0 6px 0 5px;border:0;margin:0;border-radius:9px;
1222
+ background:var(--ku-surface);color:var(--ku-label);font:inherit;font-variant-numeric:tabular-nums;letter-spacing:inherit;outline:none;transition:background calc(var(--k-hover-out,100) * 1ms) ease-out}
1223
+ .knobs-activity-chip svg{width:8px;height:8px}
1224
+ button.knobs-activity-chip{cursor:pointer}
1225
+ button.knobs-activity-chip:hover{background:var(--ku-surface-hi);color:var(--ku-text);transition-duration:0s}
1226
+ .knobs-activity-caret{display:flex;width:12px;height:12px;color:var(--ku-dim);transition:rotate .18s var(--ku-ease)}
1227
+ .knobs-activity-caret svg{width:10px;height:10px;margin:1px}
1228
+ .knobs-activity-row[aria-expanded="true"] .knobs-activity-caret{rotate:90deg;transition:rotate var(--ka-open-ms) var(--ka-open-ease)}
1229
+ .knobs-activity-time{min-width:2ch;text-align:right;white-space:nowrap}
1230
+ .knobs-activity-time:empty{display:none}
1231
+ .knobs-activity-empty{padding:10px var(--ku-inset);color:var(--ku-dim)}
1232
+ @media(prefers-reduced-motion:reduce){.knobs-activity :where(*,::before,::after){transition:none!important;animation:none!important}}
1233
+ `;
1234
+
496
1235
  // src/ui/bento-css.ts
497
1236
  var bentoCss = `
498
1237
  .knobs-ui.knobs-bento{--ku-bento-radius:var(--k-ui-bento-radius,max(0px,calc(var(--ku-radius) - 5px)));
@@ -625,6 +1364,59 @@ var overlayCss = `
625
1364
  .knobs-toast .knobs-toast-action[hidden]{display:none}
626
1365
  `;
627
1366
 
1367
+ // src/ui/picker-css.ts
1368
+ var snappy = springEasing(2 * Math.PI / 0.25, 1);
1369
+ var pickerCss = `
1370
+ :where(.knobs-picker,.knobs-picker-option,.knobs-picker-head,.knobs-picker-head *,.knobs-picker-card,.knobs-picker-body){box-sizing:border-box}
1371
+ .knobs-ui.knobs-picker{position:relative;display:flex;flex-direction:column;gap:2px;padding:0;outline:none;
1372
+ font:var(--ku-font-weight) var(--ku-font-size)/1.35 var(--ku-font);color:var(--ku-text);text-align:left;letter-spacing:normal;-webkit-font-smoothing:antialiased;
1373
+ --kp-card:var(--k-picker-card,var(--ku-surface));--kp-radius:var(--k-picker-radius,var(--ku-panel-radius));--kp-snappy:${snappy.easing};--kp-snappy-ms:${snappy.ms}ms}
1374
+ .knobs-picker [hidden]{display:none!important}
1375
+ /* The selection: one card behind the selected option and its parameters, gliding between them. */
1376
+ .knobs-picker-card{position:absolute;left:0;right:0;top:0;height:0;border-radius:var(--kp-radius);background:var(--kp-card);box-shadow:inset 0 0 0 1px var(--ku-line);pointer-events:none}
1377
+ .knobs-picker[data-frame="flat"] .knobs-picker-card{box-shadow:none}
1378
+ .knobs-picker-option{position:relative;flex:none}
1379
+ :where(.knobs-ui) .knobs-picker-head{all:revert;box-sizing:border-box;position:relative;display:flex;align-items:center;gap:8px;width:100%;height:var(--ku-row);padding:0 calc(var(--ku-inset) - 2px) 0 calc(var(--ku-inset) - 4px);
1380
+ border:0;margin:0;background:transparent;color:var(--ku-label);font:inherit;letter-spacing:inherit;text-align:left;cursor:default;outline:none;user-select:none;-webkit-user-select:none}
1381
+ /* Hover: in at once, out over 100 ms. Keyboard focus: the same wash, a little firmer. */
1382
+ .knobs-picker-head::before{content:"";position:absolute;inset:0;border-radius:var(--kp-radius);background:var(--ku-wash);opacity:0;transition:opacity calc(var(--k-hover-out,100) * 1ms) ease-out;pointer-events:none}
1383
+ .knobs-picker-option:not([data-selected]) .knobs-picker-head:hover::before{opacity:1;transition-duration:calc(var(--k-hover-in,0) * 1ms)}
1384
+ .knobs-picker-head:focus-visible::before{opacity:1;background:var(--ku-wash-hi);transition-duration:0s}
1385
+ .knobs-picker-head>*{position:relative}
1386
+ .knobs-picker-option[data-selected] .knobs-picker-head{color:var(--ku-text)}
1387
+ /* Every row is numbered (only 1–9 are keys): tabular digits in a column two digits wide, so the
1388
+ names line up past the ninth. */
1389
+ .knobs-picker-key{flex:none;display:inline-flex;align-items:center;justify-content:center;width:max(17px,calc(2ch + 6px));height:17px;padding:0;border-radius:5px;background:var(--ku-surface);
1390
+ color:var(--ku-dim);font:var(--ku-label-weight) calc(var(--ku-font-size) - 1.5px)/1 var(--ku-font);font-variant-numeric:tabular-nums;transition:background .15s,color .15s}
1391
+ .knobs-picker-key:empty{visibility:hidden}
1392
+ /* An option's picture: cover-fit, the rows' corner language and a hairline drawn over it. Its
1393
+ space is kept while it loads (nothing drawn), it fades in once loaded, and one that fails
1394
+ gives its space back on the same ease. */
1395
+ .knobs-picker-thumb{flex:none;display:block;width:var(--k-picker-thumb,min(24px,calc(var(--ku-row) - 10px)));aspect-ratio:1;margin:0;border-radius:max(3px,calc(var(--ku-radius) - 2px));
1396
+ object-fit:cover;background:none;outline:1px solid var(--ku-line);outline-offset:-1px;opacity:0;
1397
+ transition:opacity .2s ease-out,width .18s ease-out,margin .18s ease-out}
1398
+ .knobs-picker-thumb[data-state="loaded"]{opacity:1}
1399
+ .knobs-picker-thumb[data-instant]{transition:none}
1400
+ .knobs-picker-thumb[data-state="failed"]{width:0;margin-right:-8px;outline-color:transparent}
1401
+ .knobs-picker-option[data-selected] .knobs-picker-key{background:var(--ku-surface-hi);color:var(--ku-text)}
1402
+ .knobs-picker-name{flex:1;min-width:0;overflow:hidden;white-space:nowrap;font-weight:var(--ku-label-weight)}
1403
+ .knobs-picker-suggested{flex:none;color:var(--ku-dim);font-size:var(--ku-text-meta)}
1404
+ .knobs-picker-check{flex:none;display:flex;width:14px;height:14px;color:var(--ku-accent);opacity:0;scale:.6;filter:blur(2px);
1405
+ transition:opacity .11s ease-in,scale .11s ease-in,filter .11s ease-in}
1406
+ .knobs-picker-check svg{display:block;width:14px;height:14px}
1407
+ .knobs-picker-option[data-chosen] .knobs-picker-check{opacity:1;scale:1;filter:none;transition:opacity .15s ease-out,scale var(--kp-snappy-ms) var(--kp-snappy),filter .15s ease-out}
1408
+ .knobs-picker-body{position:relative;overflow:visible}
1409
+ .knobs-picker-body[data-moving]{overflow:hidden}
1410
+ /* The picker insets its parameters itself (--k-picker-pad), whatever a panel's own pad: a panel
1411
+ inside sheds its card and pad for the picker's, so nothing draws a card inside a card and no
1412
+ heading sits at the card's edge. */
1413
+ .knobs-picker-inner{padding:var(--k-picker-pad,0 8px 8px)}
1414
+ .knobs-picker-inner:empty{padding:0}
1415
+ .knobs-picker-inner .knobs-panel.knobs-inline{--k-chrome:0;padding:0}
1416
+ .knobs-picker-empty{padding:8px var(--ku-inset);color:var(--ku-dim)}
1417
+ @media(prefers-reduced-motion:reduce){.knobs-picker :where(*,::before,::after){transition-duration:.15s!important;scale:none!important;filter:none!important}}
1418
+ `;
1419
+
628
1420
  // src/ui/readout-css.ts
629
1421
  var readoutCss = `
630
1422
  /* Readouts: live values, sparklines, graphs, meters, tables and logs. */
@@ -809,6 +1601,28 @@ var css = `
809
1601
  :is(.knobs-tree,.knobs-split>.knobs-pane>.knobs-pane-clip>.knobs-pane-inner>.knobs-pane-head) [hidden]{display:none!important}
810
1602
  :is(.knobs-pane-head,.knobs-pane-rail,.knobs-tree) svg{display:block;flex:none}
811
1603
 
1604
+ /* Scroll fades: a scroller's content fades out over --knobs-fade at an edge with more beyond it,
1605
+ coming in over the first --knobs-fade-travel of scroll, so reaching an end never pops. They're
1606
+ scroll-driven (no script), so where those aren't supported there's no fade. --knobs-fade-inset
1607
+ starts the top one below a sticky header; .knobs-scroll-fade-x fades across instead (a single
1608
+ line that's cut off, like a tree's labels: no ellipsis). Pane bodies, trees and the palette's
1609
+ list carry the class; around a panel the GPU draws, the mask goes (it would trap the panel's
1610
+ overlays under it). */
1611
+ @property --knobs-fade-a{syntax:"<number>";inherits:false;initial-value:1}
1612
+ @property --knobs-fade-b{syntax:"<number>";inherits:false;initial-value:1}
1613
+ @keyframes knobs-fade-a{from{--knobs-fade-a:1}to{--knobs-fade-a:0}}
1614
+ @keyframes knobs-fade-b{from{--knobs-fade-b:0}to{--knobs-fade-b:1}}
1615
+ @supports (animation-timeline:scroll()){
1616
+ :is(.knobs-scroll-fade,.knobs-scroll-fade-x){--knobs-fade:20px;--knobs-fade-travel:24px;animation:knobs-fade-a linear both,knobs-fade-b linear both;animation-duration:auto;
1617
+ animation-timeline:scroll(self block),scroll(self block);animation-range:0 var(--knobs-fade-travel),calc(100% - var(--knobs-fade-travel)) 100%}
1618
+ .knobs-scroll-fade{--knobs-fade-mask:linear-gradient(to bottom,#000 var(--knobs-fade-inset,0px),rgb(0 0 0/var(--knobs-fade-a)) var(--knobs-fade-inset,0px),#000 calc(var(--knobs-fade-inset,0px) + var(--knobs-fade)),#000 calc(100% - var(--knobs-fade)),rgb(0 0 0/var(--knobs-fade-b)));-webkit-mask-image:var(--knobs-fade-mask);mask-image:var(--knobs-fade-mask)}
1619
+ .knobs-scroll-fade-x{--knobs-fade:16px;--knobs-fade-travel:4px;animation-timeline:scroll(self inline),scroll(self inline);
1620
+ --knobs-fade-mask:linear-gradient(to right,rgb(0 0 0/var(--knobs-fade-a)),#000 var(--knobs-fade),#000 calc(100% - var(--knobs-fade)),rgb(0 0 0/var(--knobs-fade-b)));-webkit-mask-image:var(--knobs-fade-mask);mask-image:var(--knobs-fade-mask)}
1621
+ .knobs-scroll-fade:has(.knobs-panel[data-gpu-active="true"]){-webkit-mask-image:none;mask-image:none}
1622
+ .knobs-tree-row[data-editing] .knobs-tree-label{-webkit-mask-image:none;mask-image:none}
1623
+ }
1624
+ @supports not (animation-timeline:scroll()){.knobs-scroll-fade-x{text-overflow:ellipsis}}
1625
+
812
1626
  /* Split: panes along a row or column, hairline dividers with a wider grip. */
813
1627
  .knobs-split{position:relative;display:flex;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden}
814
1628
  .knobs-split[data-direction="column"]{flex-direction:column}
@@ -821,7 +1635,7 @@ var css = `
821
1635
  .knobs-pane-inner{position:absolute;inset:0;display:flex;flex-direction:column;min-width:0;min-height:0}
822
1636
  .knobs-pane-body{position:relative;flex:1;min-width:0;min-height:0;overflow:hidden}
823
1637
  .knobs-pane[data-scroll] .knobs-pane-body{overflow:auto;scrollbar-width:thin}
824
- .knobs-pane[data-rail][data-collapsed][data-settled]>.knobs-pane-clip,.knobs-pane:not([data-rail])[data-collapsed][data-settled]>.knobs-pane-clip>.knobs-pane-inner>.knobs-pane-body{visibility:hidden}
1638
+ .knobs-pane[data-rail][data-collapsed][data-settled]>.knobs-pane-clip>.knobs-pane-inner,.knobs-pane:not([data-rail])[data-collapsed][data-settled]>.knobs-pane-clip>.knobs-pane-inner>.knobs-pane-body{visibility:hidden}
825
1639
  .knobs-ui .knobs-pane-head{display:flex;align-items:center;gap:6px;flex:none;height:var(--ku-row);padding:0 calc(var(--ku-inset) - 4px) 0 var(--ku-inset);user-select:none;-webkit-user-select:none}
826
1640
  .knobs-pane-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:var(--ku-title-weight);color:var(--ku-text)}
827
1641
  .knobs-pane-count{flex:none;color:var(--ku-dim);font-variant-numeric:tabular-nums}
@@ -832,17 +1646,31 @@ var css = `
832
1646
  .knobs-ui .knobs-pane-btn[aria-pressed="true"]{color:var(--ku-accent)}
833
1647
  .knobs-pane-btn svg{width:13px;height:13px}
834
1648
  .knobs-pane-head[data-toggles]{cursor:pointer}
835
- .knobs-pane-caret{transition:transform var(--ku-ease-ms) var(--ku-ease)}
836
- .knobs-pane[data-collapsed] .knobs-pane-caret{transform:rotate(-90deg)}
837
- /* A collapsed pane's rail: its title along the edge; a click opens it. */
838
- .knobs-ui .knobs-pane-rail{position:absolute;inset:0;margin:var(--ku-pane-m);border-radius:var(--ku-pane-radius);background-color:var(--ku-pane-bg);box-shadow:var(--ku-pane-ring);display:flex;align-items:center;gap:8px;color:var(--ku-label);cursor:pointer;opacity:0;pointer-events:none;
1649
+ /* A collapsed pane's rail: its title along the edge; a click opens it. It holds on to the edge the
1650
+ pane folds toward, as wide as the rail, over the card (which folds to its size): only its label
1651
+ and chevron draw, and a nested split's slot, which has no card, gives it one of its own. */
1652
+ .knobs-ui .knobs-pane-rail{position:absolute;margin:var(--ku-pane-m);border-radius:var(--ku-pane-radius);background-color:transparent;display:flex;align-items:center;gap:8px;color:var(--ku-label);cursor:pointer;opacity:0;pointer-events:none;
839
1653
  font-weight:var(--ku-title-weight);transition:background .15s,color .15s}
840
- .knobs-split[data-direction="row"]>.knobs-pane>.knobs-pane-rail{writing-mode:vertical-rl;padding:10px 0}
841
- .knobs-split[data-direction="column"]>.knobs-pane>.knobs-pane-rail{padding:0 var(--ku-inset)}
1654
+ .knobs-ui .knobs-pane[data-nested]>.knobs-pane-rail{background-color:var(--ku-pane-bg);box-shadow:var(--ku-pane-ring)}
1655
+ .knobs-split[data-direction="row"]>.knobs-pane>.knobs-pane-rail{top:0;bottom:0;width:var(--ku-rail);writing-mode:vertical-rl;padding:10px 0}
1656
+ .knobs-split[data-direction="row"]>.knobs-pane[data-edge="start"]>.knobs-pane-rail{right:0}
1657
+ .knobs-split[data-direction="row"]>.knobs-pane[data-edge="end"]>.knobs-pane-rail{left:0}
1658
+ .knobs-split[data-direction="column"]>.knobs-pane>.knobs-pane-rail{left:0;right:0;height:var(--ku-rail);padding:0 var(--ku-inset)}
1659
+ .knobs-split[data-direction="column"]>.knobs-pane[data-edge="start"]>.knobs-pane-rail{bottom:0}
1660
+ .knobs-split[data-direction="column"]>.knobs-pane[data-edge="end"]>.knobs-pane-rail{top:0}
842
1661
  .knobs-pane[data-collapsed]>.knobs-pane-rail{pointer-events:auto}
843
- .knobs-ui .knobs-pane-rail:is(:hover,:focus-visible){background-image:linear-gradient(var(--ku-wash),var(--ku-wash));color:var(--ku-text)}
844
- .knobs-pane-rail span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
845
- .knobs-pane-rail svg{width:11px;height:11px;color:var(--ku-dim);writing-mode:horizontal-tb}
1662
+ .knobs-ui .knobs-pane[data-collapsed]>.knobs-pane-rail:is(:hover,:focus-visible){background-image:linear-gradient(var(--ku-wash),var(--ku-wash));color:var(--ku-text)}
1663
+ .knobs-pane-rail-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
1664
+ .knobs-pane-chevron{display:flex;flex:none;width:11px;height:11px;writing-mode:horizontal-tb;color:var(--ku-dim)}
1665
+ .knobs-pane-turn{display:flex;width:100%;height:100%;transform-origin:50% 50%}
1666
+ .knobs-pane-rail svg{width:11px;height:11px}
1667
+ /* The rail's chevron stands in for the header's collapse icon (it turns into the rail's), so the
1668
+ icon itself isn't drawn; hovering its button lights the chevron instead. */
1669
+ .knobs-pane[data-morph]>.knobs-pane-clip>.knobs-pane-inner>.knobs-pane-head>.knobs-pane-collapse>svg{visibility:hidden}
1670
+ /* So does the rail's label for the title, when it travels (split's fold: { title: "travel" }). */
1671
+ .knobs-pane[data-travel]>.knobs-pane-clip>.knobs-pane-inner>.knobs-pane-head>.knobs-pane-title{visibility:hidden}
1672
+ .knobs-pane[data-morph]:not([data-collapsed]):has(>.knobs-pane-clip>.knobs-pane-inner>.knobs-pane-head>.knobs-pane-collapse:is(:hover,:focus-visible))>.knobs-pane-rail .knobs-pane-chevron{color:var(--ku-text)}
1673
+ .knobs-pane[data-morph]:not([data-collapsed])>.knobs-pane-rail .knobs-pane-chevron{transition:color .15s}
846
1674
 
847
1675
  .knobs-divider{position:relative;z-index:2;flex:none;width:1px;background:var(--ku-divider);touch-action:none;outline:none}
848
1676
  .knobs-split[data-direction="column"]>.knobs-divider{width:auto;height:1px}
@@ -885,7 +1713,7 @@ var css = `
885
1713
  .knobs-tree-row[aria-expanded="true"]>.knobs-tree-caret{transform:rotate(90deg)}
886
1714
  .knobs-tree-row:not([aria-expanded])>.knobs-tree-caret{visibility:hidden}
887
1715
  .knobs-tree-icon{flex:none;display:flex;align-items:center;justify-content:center;width:14px;margin-right:6px;color:var(--ku-dim);font-size:calc(var(--ku-font-size) - 1px)}
888
- .knobs-tree-label{position:relative;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;font-weight:var(--ku-font-weight)}
1716
+ .knobs-tree-label{position:relative;flex:1;min-width:0;overflow:hidden;font-weight:var(--ku-font-weight)}
889
1717
  .knobs-tree-row[data-ancestor] .knobs-tree-label{color:var(--ku-label)}
890
1718
  .knobs-tree-label .knobs-tree-mark{color:var(--ku-accent)}
891
1719
  .knobs-tree-row[data-off~="visible"] :is(.knobs-tree-label,.knobs-tree-icon){opacity:.45}
@@ -923,7 +1751,7 @@ var css = `
923
1751
  .knobs-tl-play svg{width:12px;height:12px}
924
1752
  .knobs-timeline[data-playing] .knobs-tl-play{background:var(--ku-accent);color:var(--ku-bg)}
925
1753
  .knobs-tl-time{display:flex;flex-direction:column;line-height:1.2;font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
926
- .knobs-tl-now{font:var(--ku-font-weight) var(--ku-value-size)/1.2 var(--ku-value-font);color:var(--ku-text);white-space:nowrap}
1754
+ .knobs-tl-now{font:var(--ku-font-weight) var(--ku-value-size)/1.2 var(--ku-value-font);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";color:var(--ku-text);white-space:nowrap}
927
1755
  .knobs-tl-sub{font-size:var(--ku-text-meta);color:var(--ku-dim);white-space:nowrap}
928
1756
  .knobs-tl-track{position:relative;flex:1;min-width:0;overflow:hidden;border-radius:var(--ku-radius);background:var(--ku-surface);cursor:col-resize;touch-action:none;
929
1757
  transition:background calc(var(--ku-hover-out) * 1ms)}
@@ -975,7 +1803,7 @@ function injectUiStyles(doc) {
975
1803
  return;
976
1804
  const style = doc.createElement("style");
977
1805
  style.dataset.knobsUi = "";
978
- style.textContent = css + readoutCss + overlayCss + bentoCss;
1806
+ style.textContent = css + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + rolling_css_default;
979
1807
  doc.head.append(style);
980
1808
  }
981
1809
  function injectUiPreset(doc, preset) {
@@ -1015,6 +1843,12 @@ function syncFrame(root) {
1015
1843
  }
1016
1844
  var lookDefaults = { current: {} };
1017
1845
  var reducedMotion = (win) => win?.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
1846
+ var tune = (s, m) => {
1847
+ s.omega = m.omega;
1848
+ s.zeta = m.zeta;
1849
+ return s;
1850
+ };
1851
+ var isTypingTarget = (t) => t instanceof HTMLElement && (t.isContentEditable || t instanceof HTMLSelectElement || t instanceof HTMLTextAreaElement || t instanceof HTMLInputElement && !t.readOnly && !["range", "checkbox", "radio", "button"].includes(t.type));
1018
1852
  function notifyLayout(doc) {
1019
1853
  doc.dispatchEvent(new Event("knobs-layout"));
1020
1854
  }
@@ -1044,26 +1878,1035 @@ function frameLoop(win, step) {
1044
1878
  }
1045
1879
  };
1046
1880
  }
1047
- var svg = (body, box = 16) => `<svg viewBox="0 0 ${box} ${box}" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`;
1048
- var ICONS = {
1049
- caret: svg('<path d="M6 3.5 10.5 8 6 12.5"/>'),
1050
- chevronDown: svg('<path d="M3.5 6 8 10.5 12.5 6"/>'),
1051
- eye: svg('<path d="M1.5 8s2.4-4.5 6.5-4.5S14.5 8 14.5 8 12.1 12.5 8 12.5 1.5 8 1.5 8Z"/><circle cx="8" cy="8" r="2"/>'),
1052
- eyeOff: svg('<path d="M2.5 2.5l11 11M6.6 4.2A6.6 6.6 0 0 1 8 3.5c4.1 0 6.5 4.5 6.5 4.5a11 11 0 0 1-1.7 2.2M10.2 11.9A6 6 0 0 1 8 12.5C3.9 12.5 1.5 8 1.5 8a11 11 0 0 1 2.4-2.9"/>'),
1053
- lock: svg('<rect x="3.5" y="7" width="9" height="6.5" rx="1.2"/><path d="M5.5 7V5a2.5 2.5 0 0 1 5 0v2"/>'),
1054
- unlock: svg('<rect x="3.5" y="7" width="9" height="6.5" rx="1.2"/><path d="M5.5 7V5a2.5 2.5 0 0 1 4.8-1"/>')
1055
- };
1881
+ var svg = (body, box = 16) => `<svg viewBox="0 0 ${box} ${box}" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`;
1882
+ var ICONS = {
1883
+ caret: svg('<path d="M6 3.5 10.5 8 6 12.5"/>'),
1884
+ chevronDown: svg('<path d="M3.5 6 8 10.5 12.5 6"/>'),
1885
+ eye: svg('<path d="M1.5 8s2.4-4.5 6.5-4.5S14.5 8 14.5 8 12.1 12.5 8 12.5 1.5 8 1.5 8Z"/><circle cx="8" cy="8" r="2"/>'),
1886
+ eyeOff: svg('<path d="M2.5 2.5l11 11M6.6 4.2A6.6 6.6 0 0 1 8 3.5c4.1 0 6.5 4.5 6.5 4.5a11 11 0 0 1-1.7 2.2M10.2 11.9A6 6 0 0 1 8 12.5C3.9 12.5 1.5 8 1.5 8a11 11 0 0 1 2.4-2.9"/>'),
1887
+ lock: svg('<rect x="3.5" y="7" width="9" height="6.5" rx="1.2"/><path d="M5.5 7V5a2.5 2.5 0 0 1 5 0v2"/>'),
1888
+ unlock: svg('<rect x="3.5" y="7" width="9" height="6.5" rx="1.2"/><path d="M5.5 7V5a2.5 2.5 0 0 1 4.8-1"/>')
1889
+ };
1890
+
1891
+ // src/ui/activity.ts
1892
+ var ACTIVITY_ICONS = {
1893
+ you: svg('<circle cx="8" cy="5.5" r="2.5"/><path d="M3.2 13.5c.8-2.4 2.6-3.8 4.8-3.8s4 1.4 4.8 3.8"/>'),
1894
+ agent: svg('<path d="M8 2.2 9.4 6.6 13.8 8 9.4 9.4 8 13.8 6.6 9.4 2.2 8 6.6 6.6Z"/>'),
1895
+ proposal: svg('<rect x="2.5" y="2.5" width="4.5" height="4.5" rx="1.2"/><rect x="9" y="2.5" width="4.5" height="4.5" rx="1.2"/><rect x="2.5" y="9" width="4.5" height="4.5" rx="1.2"/><rect x="9" y="9" width="4.5" height="4.5" rx="1.2"/>'),
1896
+ review: svg('<rect x="2" y="3.5" width="12" height="9" rx="2.2"/><path d="M7 6.3v3.4L9.8 8Z"/>'),
1897
+ pr: svg('<circle cx="4.5" cy="3.8" r="1.6"/><circle cx="4.5" cy="12.2" r="1.6"/><circle cx="11.5" cy="12.2" r="1.6"/><path d="M4.5 5.4v5.2M11.5 10.6V6.8a2 2 0 0 0-2-2H7.2M8.8 3.2 7.2 4.8l1.6 1.6"/>'),
1898
+ build: svg('<path d="M8 2.2 13.5 5v6L8 13.8 2.5 11V5Z"/><path d="M2.5 5 8 7.9 13.5 5M8 7.9v5.9"/>'),
1899
+ blocked: svg('<path d="M3.8 14V2.6M3.8 3h8l-1.9 3 1.9 3h-8"/>'),
1900
+ shipped: svg('<circle cx="8" cy="8" r="5.8"/><path d="m5.6 8.2 1.7 1.7 3.2-3.6"/>')
1901
+ };
1902
+ var PLAY = '<svg viewBox="0 0 8 8" aria-hidden="true"><path d="M1.5 1v6l5.2-3Z" fill="currentColor"/></svg>';
1903
+ var REFLOW = springMotion({ duration: 0.4, bounce: 0 });
1904
+ var OPEN = springMotion({ duration: 0.22, bounce: 0 });
1905
+ var CLOSE = springMotion({ duration: 0.18, bounce: 0 });
1906
+ var BLOOM = springMotion({ duration: 0.3, bounce: 0 });
1907
+ var EXIT = springMotion({ duration: 0.24, bounce: 0 });
1908
+ var FADE = springMotion({ duration: 0.16, bounce: 0 });
1909
+ var CLEAR = [11, 5];
1910
+ var PAD = 6;
1911
+ var SLIDE = 8;
1912
+ function activity(container, options) {
1913
+ const doc = container.ownerDocument;
1914
+ const win = doc.defaultView;
1915
+ let newest = options.newest ?? "bottom";
1916
+ const keysFrom = options.keys ?? "focus";
1917
+ let events = options.events;
1918
+ let index = indexActivity(events);
1919
+ const expanded = new Set(options.expanded ?? []);
1920
+ let rows = [];
1921
+ let selected = options.selected ?? null;
1922
+ let showing = false;
1923
+ const root = doc.createElement("div");
1924
+ root.className = "knobs-activity knobs-scroll-fade";
1925
+ root.tabIndex = 0;
1926
+ root.setAttribute("role", "tree");
1927
+ if (options.label)
1928
+ root.setAttribute("aria-label", options.label);
1929
+ applyLook(root, options);
1930
+ const space = doc.createElement("div");
1931
+ space.className = "knobs-activity-space";
1932
+ const hl = doc.createElement("div");
1933
+ hl.className = "knobs-activity-hl";
1934
+ hl.setAttribute("aria-hidden", "true");
1935
+ const line = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
1936
+ line.setAttribute("class", "knobs-activity-line");
1937
+ line.setAttribute("aria-hidden", "true");
1938
+ const path = doc.createElementNS("http://www.w3.org/2000/svg", "path");
1939
+ line.append(path);
1940
+ const empty = doc.createElement("div");
1941
+ empty.className = "knobs-activity-empty";
1942
+ empty.hidden = true;
1943
+ space.append(hl, line);
1944
+ root.append(empty, space);
1945
+ container.append(root);
1946
+ let heights = [32, 26];
1947
+ const measure = () => {
1948
+ syncFrame(root);
1949
+ const probe = (depth) => {
1950
+ const p = doc.createElement("div");
1951
+ p.className = "knobs-activity-row";
1952
+ p.dataset.depth = String(depth);
1953
+ p.style.visibility = "hidden";
1954
+ space.append(p);
1955
+ const h = p.getBoundingClientRect().height;
1956
+ p.remove();
1957
+ return h;
1958
+ };
1959
+ heights = [probe(0) || 32, probe(1) || 26];
1960
+ };
1961
+ measure();
1962
+ const uid = `ka${Math.random().toString(36).slice(2, 7)}`;
1963
+ const domIds = new Map;
1964
+ const domId = (id) => {
1965
+ let d = domIds.get(id);
1966
+ if (!d)
1967
+ domIds.set(id, d = `${uid}-${domIds.size}`);
1968
+ return d;
1969
+ };
1970
+ const views = new Map;
1971
+ const makeView = (row) => {
1972
+ const el = doc.createElement("div");
1973
+ el.className = "knobs-activity-row";
1974
+ el.setAttribute("role", "treeitem");
1975
+ el.id = domId(row.id);
1976
+ el.dataset.id = row.id;
1977
+ const node = doc.createElement("span");
1978
+ node.className = "knobs-activity-node";
1979
+ const main = doc.createElement("span");
1980
+ main.className = "knobs-activity-main knobs-scroll-fade-x";
1981
+ const actor = doc.createElement("span");
1982
+ actor.className = "knobs-activity-actor";
1983
+ const title = doc.createElement("span");
1984
+ title.className = "knobs-activity-title";
1985
+ main.append(actor, title);
1986
+ const meta = doc.createElement("span");
1987
+ meta.className = "knobs-activity-meta";
1988
+ const thumbs = doc.createElement("span");
1989
+ thumbs.className = "knobs-activity-thumbs";
1990
+ const caret = doc.createElement("span");
1991
+ caret.className = "knobs-activity-caret";
1992
+ caret.innerHTML = ICONS.caret;
1993
+ const time = doc.createElement("span");
1994
+ time.className = "knobs-activity-time";
1995
+ el.append(node, main, meta);
1996
+ space.append(el);
1997
+ const v = {
1998
+ id: row.id,
1999
+ el,
2000
+ node,
2001
+ actor,
2002
+ title,
2003
+ meta,
2004
+ thumbs,
2005
+ chip: null,
2006
+ caret,
2007
+ time,
2008
+ roll: null,
2009
+ chipRoll: null,
2010
+ y: new Spring(0, REFLOW.omega, REFLOW.zeta, 0.05),
2011
+ p: new Spring(1, BLOOM.omega, BLOOM.zeta, 0.002),
2012
+ depth: row.depth,
2013
+ under: null,
2014
+ from: 0,
2015
+ leaving: false,
2016
+ event: undefined,
2017
+ state: ""
2018
+ };
2019
+ views.set(row.id, v);
2020
+ return v;
2021
+ };
2022
+ const dropView = (v) => {
2023
+ v.roll?.destroy();
2024
+ v.chipRoll?.destroy();
2025
+ v.el.remove();
2026
+ views.delete(v.id);
2027
+ };
2028
+ const rollInto = (host, roll, text) => {
2029
+ if (!text) {
2030
+ roll?.destroy();
2031
+ host.textContent = "";
2032
+ return null;
2033
+ }
2034
+ if (roll) {
2035
+ roll.update({ text });
2036
+ return roll;
2037
+ }
2038
+ return rt(host, {
2039
+ text,
2040
+ charset: "0123456789",
2041
+ transition: "direct",
2042
+ animated: !reducedMotion(win)
2043
+ });
2044
+ };
2045
+ const bind = (v, row) => {
2046
+ const e = row.event;
2047
+ const state = `${row.depth}|${row.group}|${row.expanded}|${selected === row.id}`;
2048
+ if (v.event === e && v.state === state)
2049
+ return;
2050
+ const changed = v.event !== e;
2051
+ v.event = e;
2052
+ v.state = state;
2053
+ v.depth = row.depth;
2054
+ const el = v.el;
2055
+ el.dataset.depth = String(row.depth);
2056
+ el.setAttribute("aria-level", String(row.depth + 1));
2057
+ el.setAttribute("aria-selected", String(selected === row.id));
2058
+ if (row.group)
2059
+ el.setAttribute("aria-expanded", String(row.expanded));
2060
+ else
2061
+ el.removeAttribute("aria-expanded");
2062
+ if (!changed)
2063
+ return;
2064
+ if (e.kind)
2065
+ el.dataset.kind = e.kind;
2066
+ else
2067
+ delete el.dataset.kind;
2068
+ el.toggleAttribute("data-current", Boolean(e.current));
2069
+ el.toggleAttribute("data-dimmed", Boolean(e.dimmed));
2070
+ const tint = e.tint ?? (e.kind ? options.tints?.[e.kind] : undefined);
2071
+ if (tint)
2072
+ el.style.setProperty("--ka-tint", tint);
2073
+ else
2074
+ el.style.removeProperty("--ka-tint");
2075
+ const icon = e.icon ?? (row.depth === 1 ? undefined : (e.kind && (options.icons?.[e.kind] ?? ACTIVITY_ICONS[e.kind])) ?? undefined);
2076
+ if (icon instanceof Node)
2077
+ v.node.replaceChildren(icon);
2078
+ else if (icon === undefined) {
2079
+ if (!v.node.firstElementChild?.classList.contains("knobs-activity-dot"))
2080
+ v.node.innerHTML = '<i class="knobs-activity-dot"></i>';
2081
+ } else if (icon.trimStart().startsWith("<")) {
2082
+ if (v.node.innerHTML !== icon)
2083
+ v.node.innerHTML = icon;
2084
+ } else {
2085
+ const glyph = doc.createElement("span");
2086
+ glyph.className = "knobs-activity-glyph";
2087
+ glyph.textContent = icon;
2088
+ v.node.replaceChildren(glyph);
2089
+ }
2090
+ const kids = e.children?.length ?? 0;
2091
+ v.actor.textContent = e.actor ?? "";
2092
+ const title = e.title ?? (kids ? groupTitle(kids) : "");
2093
+ if (v.title.textContent !== title)
2094
+ v.title.textContent = title;
2095
+ el.setAttribute("aria-label", [e.actor, title, e.time].filter(Boolean).join(", "));
2096
+ const parts = [];
2097
+ const thumbs = e.thumbs?.slice(0, 4) ?? [];
2098
+ const shown = [...v.thumbs.querySelectorAll("img")].map((i) => i.getAttribute("src"));
2099
+ if (thumbs.join(`
2100
+ `) !== shown.join(`
2101
+ `))
2102
+ v.thumbs.replaceChildren(...thumbs.map((src) => {
2103
+ const img = doc.createElement("img");
2104
+ img.src = src;
2105
+ img.alt = "";
2106
+ img.loading = "lazy";
2107
+ img.draggable = false;
2108
+ return img;
2109
+ }));
2110
+ if (thumbs.length)
2111
+ parts.push(v.thumbs);
2112
+ if (e.duration !== undefined) {
2113
+ const button = Boolean(options.onPlay);
2114
+ if (!v.chip || v.chip.tagName === "BUTTON" !== button) {
2115
+ v.chipRoll?.destroy();
2116
+ v.chipRoll = null;
2117
+ v.chip = doc.createElement(button ? "button" : "span");
2118
+ v.chip.className = "knobs-activity-chip";
2119
+ if (button) {
2120
+ v.chip.type = "button";
2121
+ v.chip.tabIndex = -1;
2122
+ v.chip.setAttribute("aria-label", "Play");
2123
+ }
2124
+ v.chip.innerHTML = `${PLAY}<span></span>`;
2125
+ }
2126
+ v.chipRoll = rollInto(v.chip.lastElementChild, v.chipRoll, formatDuration(e.duration));
2127
+ parts.push(v.chip);
2128
+ }
2129
+ if (kids)
2130
+ parts.push(v.caret);
2131
+ v.roll = rollInto(v.time, v.roll, e.time ?? "");
2132
+ if (e.time)
2133
+ parts.push(v.time);
2134
+ const now = [...v.meta.children];
2135
+ if (now.length !== parts.length || now.some((n, i) => n !== parts[i]))
2136
+ v.meta.replaceChildren(...parts);
2137
+ };
2138
+ const nodeY = (v) => v.y.x + heights[v.depth] / 2;
2139
+ let targets = new Map;
2140
+ const lineTop = new Spring(0, REFLOW.omega, REFLOW.zeta, 0.05);
2141
+ const lineBottom = new Spring(0, REFLOW.omega, REFLOW.zeta, 0.05);
2142
+ const hlY = new Spring(0, REFLOW.omega, REFLOW.zeta, 0.05);
2143
+ const hlH = new Spring(0, REFLOW.omega, REFLOW.zeta, 0.05);
2144
+ const height = new Spring(0, REFLOW.omega, REFLOW.zeta, 0.05);
2145
+ let following = false;
2146
+ let scrollHold = null;
2147
+ const place = (v, reduced) => {
2148
+ const p = Math.max(0, Math.min(1, v.p.x));
2149
+ let y = v.y.x;
2150
+ let clip = 0;
2151
+ let transform = "";
2152
+ let filter = "";
2153
+ if (v.under !== null) {
2154
+ const parent = views.get(v.under);
2155
+ const bottom = (parent ? parent.y.x : targets.get(v.under) ?? 0) + heights[0];
2156
+ clip = Math.min(heights[v.depth], Math.max(0, bottom - y));
2157
+ } else if (p < 1 && !reduced) {
2158
+ y += (1 - p) * SLIDE * v.from;
2159
+ transform = ` scale(${(0.97 + 0.03 * p).toFixed(4)})`;
2160
+ const blur = (1 - p) * 3;
2161
+ if (blur > 0.05)
2162
+ filter = `blur(${blur.toFixed(2)}px)`;
2163
+ }
2164
+ v.el.style.transform = `translate3d(0,${Math.round(y * 2) / 2}px,0)${transform}`;
2165
+ v.el.style.clipPath = clip > 0 ? `inset(${clip}px 0 0 0)` : "";
2166
+ v.el.style.opacity = p < 0.999 ? p.toFixed(3) : "";
2167
+ v.el.style.filter = filter;
2168
+ };
2169
+ const drawLine = () => {
2170
+ const nodes = [];
2171
+ for (const v of views.values()) {
2172
+ const p = Math.max(0, Math.min(1, v.p.x));
2173
+ if (p <= 0.001)
2174
+ continue;
2175
+ const y = nodeY(v);
2176
+ if (v.under !== null) {
2177
+ const parent = views.get(v.under);
2178
+ if (parent && y < parent.y.x + heights[0])
2179
+ continue;
2180
+ }
2181
+ nodes.push({ y, r: CLEAR[v.depth] * Math.min(1, p * 1.5) });
2182
+ }
2183
+ const segs = railSegments(nodes, lineTop.x, lineBottom.x);
2184
+ path.setAttribute("d", segs.map(([a, b]) => `M1 ${a.toFixed(2)}V${b.toFixed(2)}`).join(""));
2185
+ };
2186
+ const placeHighlight = () => {
2187
+ hl.style.transform = `translate3d(0,${Math.round(hlY.x * 2) / 2}px,0)`;
2188
+ hl.style.height = `${Math.max(0, hlH.x).toFixed(2)}px`;
2189
+ };
2190
+ const loop = frameLoop(win, (dt) => {
2191
+ const reduced = reducedMotion(win);
2192
+ let moving = false;
2193
+ for (const s of [lineTop, lineBottom, hlY, hlH, height]) {
2194
+ s.step(dt);
2195
+ if (!s.resting)
2196
+ moving = true;
2197
+ }
2198
+ for (const v of [...views.values()]) {
2199
+ v.y.step(dt);
2200
+ v.p.step(dt);
2201
+ if (v.y.resting && v.p.resting) {
2202
+ if (v.leaving) {
2203
+ dropView(v);
2204
+ continue;
2205
+ }
2206
+ v.under = null;
2207
+ v.from = 0;
2208
+ } else
2209
+ moving = true;
2210
+ place(v, reduced);
2211
+ }
2212
+ drawLine();
2213
+ placeHighlight();
2214
+ if (scrollHold && !following) {
2215
+ const { scroll, space: reserved } = holdScroll(scrollHold, height.x, root.clientHeight);
2216
+ space.style.height = `${Math.max(0, reserved).toFixed(1)}px`;
2217
+ root.scrollTop = scroll;
2218
+ } else
2219
+ space.style.height = `${Math.max(0, height.x).toFixed(1)}px`;
2220
+ if (following)
2221
+ root.scrollTop = newest === "bottom" ? root.scrollHeight : 0;
2222
+ if (!moving) {
2223
+ following = false;
2224
+ scrollHold = null;
2225
+ }
2226
+ return moving;
2227
+ });
2228
+ const atNewestEnd = () => newest === "bottom" ? root.scrollTop + root.clientHeight >= root.scrollHeight - 4 : root.scrollTop <= 4;
2229
+ function sync(cause, toggled) {
2230
+ const reduced = reducedMotion(win);
2231
+ const animate = cause !== "snap";
2232
+ const glide = cause === "open" ? OPEN : cause === "close" ? CLOSE : REFLOW;
2233
+ const before = [...views.values()].filter((v) => !v.leaving).map((v) => v.id);
2234
+ rows = flattenActivity(events, expanded);
2235
+ selected = keepSelection(rows, selected, (id) => index.get(id)?.parent);
2236
+ const stack = stackRows(rows.map((r) => heights[r.depth]), PAD);
2237
+ targets = new Map(rows.map((r, i) => [r.id, stack.y[i]]));
2238
+ const { entered } = diffActivity(before, rows.map((r) => r.id));
2239
+ const fresh = new Set(entered);
2240
+ let opened = 0;
2241
+ if (cause === "open") {
2242
+ for (const r of rows)
2243
+ if (r.parent === toggled && fresh.has(r.id))
2244
+ opened += heights[r.depth];
2245
+ }
2246
+ let shut = 0;
2247
+ const live = new Set(rows.map((r) => r.id));
2248
+ if (cause === "close") {
2249
+ for (const v of views.values())
2250
+ if (!live.has(v.id) && !v.leaving && index.get(v.id)?.parent === toggled)
2251
+ shut += heights[v.depth];
2252
+ }
2253
+ const following0 = animate && options.follow !== false && atNewestEnd();
2254
+ const lastIndex = rows.length - 1;
2255
+ rows.forEach((row, i) => {
2256
+ const goal = stack.y[i];
2257
+ let v = views.get(row.id);
2258
+ if (v && v.leaving) {
2259
+ v.leaving = false;
2260
+ delete v.el.dataset.leaving;
2261
+ tune(v.p, cause === "open" ? OPEN : BLOOM).set(1);
2262
+ }
2263
+ if (!v) {
2264
+ v = makeView(row);
2265
+ if (!animate) {
2266
+ v.y.snap(goal);
2267
+ v.p.snap(1);
2268
+ } else if (reduced) {
2269
+ v.y.snap(goal);
2270
+ tune(v.p, FADE).snap(0).set(1);
2271
+ } else {
2272
+ const parent = row.parent !== null ? views.get(row.parent) : undefined;
2273
+ if (parent && !parent.leaving) {
2274
+ v.under = row.parent;
2275
+ const drawer = cause === "open" && row.parent === toggled;
2276
+ v.y.snap(drawer ? goal - opened + parent.y.x - parent.y.target : parent.y.x);
2277
+ tune(v.y, drawer ? OPEN : glide).set(goal);
2278
+ tune(v.p, drawer ? OPEN : BLOOM).snap(0).set(1);
2279
+ } else {
2280
+ v.y.snap(goal);
2281
+ v.from = newest === "bottom" ? 1 : -1;
2282
+ tune(v.p, BLOOM).snap(0).set(1);
2283
+ }
2284
+ }
2285
+ } else if (!animate || reduced)
2286
+ v.y.snap(goal);
2287
+ else
2288
+ tune(v.y, glide).set(goal);
2289
+ bind(v, row);
2290
+ });
2291
+ for (const v of [...views.values()]) {
2292
+ if (live.has(v.id) || v.leaving)
2293
+ continue;
2294
+ if (!animate) {
2295
+ dropView(v);
2296
+ continue;
2297
+ }
2298
+ v.leaving = true;
2299
+ v.el.dataset.leaving = "";
2300
+ v.el.setAttribute("aria-selected", "false");
2301
+ if (reduced) {
2302
+ tune(v.p, FADE).set(0);
2303
+ continue;
2304
+ }
2305
+ const group = index.get(v.id)?.parent ?? null;
2306
+ const parent = group !== null ? views.get(group) : undefined;
2307
+ if (parent && !parent.leaving && v.depth === 1) {
2308
+ v.under = group;
2309
+ const drawer = cause === "close" && group === toggled;
2310
+ tune(v.y, drawer ? CLOSE : glide).set(drawer ? v.y.target - shut : targets.get(group) ?? parent.y.target);
2311
+ tune(v.p, drawer ? CLOSE : EXIT).set(0);
2312
+ } else {
2313
+ v.from = 0;
2314
+ tune(v.p, EXIT).set(0);
2315
+ }
2316
+ }
2317
+ const first = rows[0];
2318
+ const last = rows[lastIndex];
2319
+ const firstY = first ? targets.get(first.id) + heights[first.depth] / 2 : PAD;
2320
+ const lastY = last ? targets.get(last.id) + heights[last.depth] / 2 : PAD;
2321
+ const bottom = stack.bottom + PAD;
2322
+ if (!animate || reduced) {
2323
+ lineTop.snap(firstY);
2324
+ lineBottom.snap(lastY);
2325
+ height.snap(bottom);
2326
+ } else {
2327
+ if (!before.length) {
2328
+ lineTop.snap(firstY);
2329
+ lineBottom.snap(firstY);
2330
+ }
2331
+ tune(lineTop, glide).set(firstY);
2332
+ tune(lineBottom, cause === "set" ? BLOOM : glide).set(lastY);
2333
+ tune(height, glide).set(bottom);
2334
+ }
2335
+ moveHighlight(animate && !reduced ? glide : null);
2336
+ hl.hidden = !selected;
2337
+ empty.hidden = rows.length > 0;
2338
+ empty.textContent = options.empty ?? "Nothing yet";
2339
+ if (selected)
2340
+ root.setAttribute("aria-activedescendant", domId(selected));
2341
+ else
2342
+ root.removeAttribute("aria-activedescendant");
2343
+ following = following || following0 && cause === "set" && fresh.size > 0;
2344
+ scrollHold = animate && !following && !reduced ? { from: root.scrollTop, h0: height.x, h1: height.target } : null;
2345
+ for (const v of views.values())
2346
+ place(v, reduced);
2347
+ drawLine();
2348
+ placeHighlight();
2349
+ space.style.height = `${height.x.toFixed(1)}px`;
2350
+ loop.wake();
2351
+ }
2352
+ const moveHighlight = (motion) => {
2353
+ if (!showing)
2354
+ return;
2355
+ const y = selected ? targets.get(selected) : undefined;
2356
+ if (y === undefined)
2357
+ return;
2358
+ const h = heights[rows.find((r) => r.id === selected)?.depth ?? 0];
2359
+ if (!motion) {
2360
+ hlY.snap(y);
2361
+ hlH.snap(h);
2362
+ return;
2363
+ }
2364
+ tune(hlY, motion).set(y);
2365
+ tune(hlH, motion).set(h);
2366
+ };
2367
+ const setShowing = (input) => {
2368
+ const next = showsSelection(showing, input, keysFrom);
2369
+ if (next === showing)
2370
+ return;
2371
+ showing = next;
2372
+ root.toggleAttribute("data-keys", next);
2373
+ };
2374
+ const rebind = () => {
2375
+ for (const row of rows) {
2376
+ const v = views.get(row.id);
2377
+ if (v && !v.leaving)
2378
+ bind(v, row);
2379
+ }
2380
+ };
2381
+ const reveal = (id) => {
2382
+ const y = targets.get(id);
2383
+ if (y === undefined)
2384
+ return;
2385
+ const h = heights[rows.find((r) => r.id === id)?.depth ?? 0];
2386
+ const top = root.scrollTop;
2387
+ const view = root.clientHeight;
2388
+ if (view <= 0 || root.scrollHeight <= view)
2389
+ return;
2390
+ const behavior = reducedMotion(win) ? "auto" : "smooth";
2391
+ if (y - PAD < top)
2392
+ root.scrollTo({ top: y - PAD, behavior });
2393
+ else if (y + h + PAD > top + view)
2394
+ root.scrollTo({ top: y + h + PAD - view, behavior });
2395
+ };
2396
+ const choose = (id, source) => {
2397
+ if (source === "key") {
2398
+ const wasShowing = showing;
2399
+ setShowing("key");
2400
+ if (!wasShowing)
2401
+ moveHighlight(null);
2402
+ } else if (source === "pointer")
2403
+ setShowing("pointer");
2404
+ if (id === selected)
2405
+ return;
2406
+ const from = selected ? targets.get(selected) : undefined;
2407
+ selected = id;
2408
+ rebind();
2409
+ hl.hidden = !id;
2410
+ if (id) {
2411
+ const to = targets.get(id);
2412
+ const far = from !== undefined && to !== undefined && Math.abs(to - from) > 400;
2413
+ const reduced = reducedMotion(win);
2414
+ moveHighlight(reduced || from === undefined ? null : springMotion({ duration: far ? 0.42 : 0.3, bounce: 0 }));
2415
+ root.setAttribute("aria-activedescendant", domId(id));
2416
+ if (source === "key") {
2417
+ scrollHold = null;
2418
+ reveal(id);
2419
+ }
2420
+ const event = index.get(id)?.event;
2421
+ if (event && source !== "code")
2422
+ options.onSelect?.(id, event, source);
2423
+ } else
2424
+ root.removeAttribute("aria-activedescendant");
2425
+ loop.wake();
2426
+ };
2427
+ const toggle = (id, open) => {
2428
+ const row = rows.find((r) => r.id === id);
2429
+ if (!row?.group || row.expanded === open)
2430
+ return;
2431
+ if (open)
2432
+ expanded.add(id);
2433
+ else
2434
+ expanded.delete(id);
2435
+ sync(open ? "open" : "close", id);
2436
+ options.onToggle?.(id, open);
2437
+ };
2438
+ root.addEventListener("pointerdown", (e) => {
2439
+ if (e.button !== 0)
2440
+ return;
2441
+ setShowing("pointer");
2442
+ const el = e.target.closest(".knobs-activity-row");
2443
+ const id = el?.dataset.id;
2444
+ if (!el || !id || el.dataset.leaving !== undefined)
2445
+ return;
2446
+ e.preventDefault();
2447
+ root.focus({ preventScroll: true });
2448
+ if (e.target.closest(".knobs-activity-chip"))
2449
+ return;
2450
+ const row = rows.find((r) => r.id === id);
2451
+ choose(id, "pointer");
2452
+ if (row?.group)
2453
+ toggle(id, !row.expanded);
2454
+ });
2455
+ root.addEventListener("click", (e) => {
2456
+ const chip = e.target.closest(".knobs-activity-chip");
2457
+ const id = chip?.closest(".knobs-activity-row")?.dataset.id;
2458
+ const event = id ? index.get(id)?.event : undefined;
2459
+ if (chip && id && event && options.onPlay) {
2460
+ e.stopPropagation();
2461
+ options.onPlay(id, event);
2462
+ }
2463
+ });
2464
+ const release = () => {
2465
+ scrollHold = null;
2466
+ following = false;
2467
+ };
2468
+ root.addEventListener("wheel", release, { passive: true });
2469
+ root.addEventListener("touchstart", release, { passive: true });
2470
+ const onPointer = () => setShowing("pointer");
2471
+ if (keysFrom === "page")
2472
+ win.addEventListener("pointerdown", onPointer, true);
2473
+ const onKey = (e) => {
2474
+ if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.altKey)
2475
+ return;
2476
+ if (keysFrom === "page") {
2477
+ if (e.target !== root && (isTypingTarget(e.target) || !root.getClientRects().length))
2478
+ return;
2479
+ const t = e.target;
2480
+ if (t !== root && t?.closest?.(".knobs-panel,[role=tree],[role=listbox],[role=grid],[role=slider]"))
2481
+ return;
2482
+ }
2483
+ let handled = true;
2484
+ if (isActivityKey(e.key) && !e.shiftKey) {
2485
+ const r = navigateActivity(rows, selected, e.key);
2486
+ if (!takesActivityKey(e.key, r, rows.length))
2487
+ return;
2488
+ if (r.toggle) {
2489
+ setShowing("key");
2490
+ toggle(r.toggle.id, r.toggle.expanded);
2491
+ } else if (r.select)
2492
+ choose(r.select, "key");
2493
+ else
2494
+ setShowing("key");
2495
+ } else if (e.key === "Enter" && selected) {
2496
+ setShowing("key");
2497
+ const row = rows.find((r) => r.id === selected);
2498
+ const event = index.get(selected)?.event;
2499
+ if (options.onActivate && event)
2500
+ options.onActivate(selected, event);
2501
+ else if (row?.group)
2502
+ toggle(row.id, !row.expanded);
2503
+ else
2504
+ handled = false;
2505
+ } else
2506
+ handled = false;
2507
+ if (handled) {
2508
+ e.preventDefault();
2509
+ e.stopPropagation();
2510
+ }
2511
+ };
2512
+ if (keysFrom === "page")
2513
+ win.addEventListener("keydown", onKey);
2514
+ else
2515
+ root.addEventListener("keydown", onKey);
2516
+ root.addEventListener("blur", () => setShowing("blur"));
2517
+ const remeasure = () => {
2518
+ const was = heights.join();
2519
+ measure();
2520
+ if (heights.join() !== was)
2521
+ sync("snap");
2522
+ };
2523
+ const ro = new ResizeObserver(remeasure);
2524
+ ro.observe(root);
2525
+ root.addEventListener("knobs-restyle", () => {
2526
+ measure();
2527
+ sync("snap");
2528
+ });
2529
+ sync("snap");
2530
+ const handle = {
2531
+ element: root,
2532
+ set(next) {
2533
+ handle.update({ events: next });
2534
+ },
2535
+ add(event) {
2536
+ handle.set(newest === "bottom" ? [...events, event] : [event, ...events]);
2537
+ },
2538
+ update(next) {
2539
+ if (next.newest)
2540
+ newest = next.newest;
2541
+ if (next.events) {
2542
+ events = next.events;
2543
+ index = indexActivity(events);
2544
+ for (const id of [...expanded])
2545
+ if (!index.has(id))
2546
+ expanded.delete(id);
2547
+ }
2548
+ sync("set");
2549
+ },
2550
+ select(id) {
2551
+ if (id !== null) {
2552
+ const parent = index.get(id)?.parent;
2553
+ if (parent && !expanded.has(parent)) {
2554
+ expanded.add(parent);
2555
+ sync("open", parent);
2556
+ }
2557
+ }
2558
+ choose(id, "code");
2559
+ },
2560
+ get selected() {
2561
+ return selected;
2562
+ },
2563
+ expand: (id, open = !expanded.has(id)) => toggle(id, open),
2564
+ isExpanded: (id) => expanded.has(id),
2565
+ focus: () => root.focus({ preventScroll: true }),
2566
+ destroy() {
2567
+ loop.stop();
2568
+ ro.disconnect();
2569
+ if (keysFrom === "page") {
2570
+ win.removeEventListener("keydown", onKey);
2571
+ win.removeEventListener("pointerdown", onPointer, true);
2572
+ }
2573
+ for (const v of [...views.values()])
2574
+ dropView(v);
2575
+ root.remove();
2576
+ },
2577
+ dispose: () => handle.destroy()
2578
+ };
2579
+ return handle;
2580
+ }
2581
+
2582
+ // src/ui/bento-model.ts
2583
+ var FIT_MAX = 24;
2584
+ var clamp = (x, a, b) => Math.min(b, Math.max(a, x));
2585
+ var sum = (xs) => xs.reduce((s, x) => s + x, 0);
2586
+ var rowTarget = (width, height) => clamp(Math.round(Math.min(height / 3, width / 2.6)), 120, 420);
2587
+ function justify(aspects, start, end, width, target, gap, tol) {
2588
+ const rows = [];
2589
+ let i = start;
2590
+ while (i < end) {
2591
+ let total = 0;
2592
+ let j = i;
2593
+ let h = Number.POSITIVE_INFINITY;
2594
+ let closed = false;
2595
+ while (j < end) {
2596
+ const k = j - i + 1;
2597
+ const next = (width - (k - 1) * gap) / (total + aspects[j]);
2598
+ if (next <= target) {
2599
+ if (k === 1 || Math.abs(Math.log(next / target)) <= Math.abs(Math.log(h / target))) {
2600
+ total += aspects[j];
2601
+ j++;
2602
+ h = next;
2603
+ }
2604
+ closed = true;
2605
+ break;
2606
+ }
2607
+ total += aspects[j];
2608
+ h = next;
2609
+ j++;
2610
+ }
2611
+ const slice = aspects.slice(i, j);
2612
+ if (closed || h <= target * 1.25) {
2613
+ rows.push({ start: i, end: j, h, ws: slice.map((a) => a * h) });
2614
+ } else {
2615
+ const free = width - (j - i - 1) * gap;
2616
+ const f = free / (target * total);
2617
+ const ws = slice.map((a) => a * target * (f - 1 <= tol ? f : 1));
2618
+ rows.push({ start: i, end: j, h: target, ws });
2619
+ }
2620
+ i = j;
2621
+ }
2622
+ return rows;
2623
+ }
2624
+ function partition(xs, k) {
2625
+ const n = xs.length;
2626
+ k = clamp(k, 1, n);
2627
+ const prefix = [0];
2628
+ for (const x of xs)
2629
+ prefix.push(prefix.at(-1) + x);
2630
+ const cost = Array.from({ length: k + 1 }, () => new Float64Array(n + 1).fill(Infinity));
2631
+ const cut = Array.from({ length: k + 1 }, () => new Int32Array(n + 1));
2632
+ cost[0][0] = 0;
2633
+ for (let j = 1;j <= k; j++)
2634
+ for (let i = j;i <= n; i++)
2635
+ for (let m = j - 1;m < i; m++) {
2636
+ const c = Math.max(cost[j - 1][m], prefix[i] - prefix[m]);
2637
+ if (c < cost[j][i]) {
2638
+ cost[j][i] = c;
2639
+ cut[j][i] = m;
2640
+ }
2641
+ }
2642
+ const counts = [];
2643
+ for (let j = k, i = n;j > 0; j--) {
2644
+ const m = cut[j][i];
2645
+ counts.unshift(i - m);
2646
+ i = m;
2647
+ }
2648
+ return counts;
2649
+ }
2650
+ function rowsBlock(aspects, w, h, gap, tol) {
2651
+ const n = aspects.length;
2652
+ if (!n || w <= 0 || h <= 0)
2653
+ return null;
2654
+ let best = null;
2655
+ const most = Math.min(n, Math.max(2, Math.ceil(Math.sqrt(n) * 2)));
2656
+ for (let r = 1;r <= most; r++) {
2657
+ const counts = partition(aspects, r);
2658
+ let at = 0;
2659
+ const groups = counts.map((c) => aspects.slice(at, at += c));
2660
+ const heights = groups.map((g) => (w - (g.length - 1) * gap) / sum(g));
2661
+ const budget = h - (r - 1) * gap;
2662
+ if (budget <= 0 || heights.some((x) => x <= 0))
2663
+ break;
2664
+ const s = Math.min(1, budget / sum(heights));
2665
+ const fill = s < 1 && 1 / s - 1 <= tol ? 1 / s : 1;
2666
+ const rects = [];
2667
+ let y = 0;
2668
+ let blockW = 0;
2669
+ groups.forEach((g, k) => {
2670
+ const rh = heights[k] * s;
2671
+ const rowW = sum(g.map((a) => a * rh * fill)) + (g.length - 1) * gap;
2672
+ blockW = Math.max(blockW, rowW);
2673
+ let x = 0;
2674
+ for (const a of g) {
2675
+ rects.push({ x, y, w: a * rh * fill, h: rh });
2676
+ x += a * rh * fill + gap;
2677
+ }
2678
+ y += rh + gap;
2679
+ });
2680
+ let i = 0;
2681
+ groups.forEach((g, k) => {
2682
+ const rowW = sum(g.map((a) => a * heights[k] * s * fill)) + (g.length - 1) * gap;
2683
+ const dx = (blockW - rowW) / 2;
2684
+ for (let m = 0;m < g.length; m++, i++) {
2685
+ const q = rects[i];
2686
+ rects[i] = { ...q, x: q.x + dx };
2687
+ }
2688
+ });
2689
+ const area = sum(rects.map((q) => q.w * q.h));
2690
+ const block = {
2691
+ rects,
2692
+ w: blockW,
2693
+ h: y - gap,
2694
+ area,
2695
+ crop: fill - 1,
2696
+ minH: Math.min(...rects.map((q) => q.h))
2697
+ };
2698
+ if (!best || block.area > best.area * 1.0001)
2699
+ best = block;
2700
+ }
2701
+ return best;
2702
+ }
2703
+ var offset = (rects, dx, dy) => rects.map((q) => ({ x: q.x + dx, y: q.y + dy, w: q.w, h: q.h }));
2704
+ function heroBlock(aspects, hero, w, h, gap, tol) {
2705
+ const a = aspects[hero];
2706
+ const rest = aspects.filter((_, i) => i !== hero);
2707
+ if (!rest.length)
2708
+ return null;
2709
+ let best = null;
2710
+ const place = (heroRect, restBlock, restAt, bw, bh) => {
2711
+ if (!restBlock)
2712
+ return;
2713
+ const [rx, ry] = restAt(restBlock);
2714
+ const others = offset(restBlock.rects, rx, ry);
2715
+ const rects = [...others.slice(0, hero), heroRect, ...others.slice(hero)];
2716
+ const area = heroRect.w * heroRect.h + restBlock.area;
2717
+ if (!best || area > best.area * 1.0001)
2718
+ best = { rects, w: bw, h: bh, area, crop: restBlock.crop, minH: restBlock.minH };
2719
+ };
2720
+ for (let f = 0.4;f <= 0.761; f += 0.04) {
2721
+ {
2722
+ const hw = Math.min(w * f, h * a);
2723
+ const hh = hw / a;
2724
+ const block = rowsBlock(rest, w - hw - gap, h, gap, tol);
2725
+ if (block) {
2726
+ const bw = hw + gap + block.w;
2727
+ const bh = Math.max(hh, block.h);
2728
+ place({ x: 0, y: (bh - hh) / 2, w: hw, h: hh }, block, (b) => [hw + gap, (bh - b.h) / 2], bw, bh);
2729
+ }
2730
+ }
2731
+ {
2732
+ const hh = Math.min(h * f, w / a);
2733
+ const hw = hh * a;
2734
+ const block = rowsBlock(rest, w, h - hh - gap, gap, tol);
2735
+ if (block) {
2736
+ const bw = Math.max(hw, block.w);
2737
+ place({ x: (bw - hw) / 2, y: 0, w: hw, h: hh }, block, (b) => [(bw - b.w) / 2, hh + gap], bw, hh + gap + block.h);
2738
+ }
2739
+ }
2740
+ }
2741
+ return best;
2742
+ }
2743
+ function planBento(aspects, layout) {
2744
+ const gap = layout.gap ?? 2;
2745
+ const pad = layout.padding ?? gap;
2746
+ const tol = layout.tolerance ?? 0.08;
2747
+ const vw = Math.max(0, layout.width);
2748
+ const vh = Math.max(0, layout.height);
2749
+ const w = Math.max(1, vw - pad * 2);
2750
+ const h = Math.max(1, vh - pad * 2);
2751
+ const as = aspects.map((a) => Number.isFinite(a) && a > 0 ? clamp(a, 0.05, 20) : 1);
2752
+ const empty = {
2753
+ mode: "rows",
2754
+ rects: [],
2755
+ width: vw,
2756
+ height: 0,
2757
+ coverage: 0,
2758
+ crop: 0,
2759
+ headers: []
2760
+ };
2761
+ if (!as.length || vw <= 0)
2762
+ return empty;
2763
+ const grouped = layout.groups?.some((g) => g !== undefined) ?? false;
2764
+ const minTile = layout.minTile ?? Math.max(80, vh / 6);
2765
+ if ((layout.fit ?? true) && !grouped && as.length <= FIT_MAX && vh > 0) {
2766
+ const rows = rowsBlock(as, w, h, gap, tol);
2767
+ let pick = rows && rows.minH >= minTile ? { mode: "fit", block: rows } : null;
2768
+ const asked = layout.hero !== undefined && layout.hero >= 0 && layout.hero < as.length;
2769
+ const hero = as.length > 2 ? heroBlock(as, asked ? layout.hero : 0, w, h, gap, tol) : null;
2770
+ if (hero && hero.minH >= minTile && (asked || !pick || hero.area > pick.block.area * 1.04))
2771
+ pick = { mode: "hero", block: hero };
2772
+ if (pick) {
2773
+ const { block } = pick;
2774
+ const rects = offset(block.rects, pad + (w - block.w) / 2, pad + (h - block.h) / 2);
2775
+ return {
2776
+ mode: pick.mode,
2777
+ rects,
2778
+ width: vw,
2779
+ height: vh,
2780
+ coverage: block.area / (vw * vh),
2781
+ crop: block.crop,
2782
+ headers: []
2783
+ };
2784
+ }
2785
+ }
2786
+ const target = layout.rowHeight ?? rowTarget(vw, vh);
2787
+ const header = layout.header ?? 28;
2788
+ const rects = new Array(as.length);
2789
+ const headers = [];
2790
+ let y = pad;
2791
+ let crop = 0;
2792
+ let area = 0;
2793
+ let i = 0;
2794
+ while (i < as.length) {
2795
+ const group = layout.groups?.[i];
2796
+ let end = i + 1;
2797
+ while (end < as.length && layout.groups?.[end] === group)
2798
+ end++;
2799
+ if (group !== undefined) {
2800
+ if (i > 0)
2801
+ y += header / 2;
2802
+ headers.push({ group, y, index: i });
2803
+ y += header;
2804
+ }
2805
+ for (const row of justify(as, i, end, w, target, gap, tol)) {
2806
+ let x = pad;
2807
+ row.ws.forEach((rw, k) => {
2808
+ rects[row.start + k] = { x, y, w: rw, h: row.h };
2809
+ crop = Math.max(crop, rw / (as[row.start + k] * row.h) - 1);
2810
+ area += rw * row.h;
2811
+ x += rw + gap;
2812
+ });
2813
+ y += row.h + gap;
2814
+ }
2815
+ i = end;
2816
+ }
2817
+ const height = y - gap + pad;
2818
+ return { mode: "rows", rects, width: vw, height, coverage: area / (vw * height), crop, headers };
2819
+ }
2820
+ function windowOf(plan, top, bottom) {
2821
+ const { rects } = plan;
2822
+ if (plan.mode !== "rows")
2823
+ return rects.map((_, i) => i);
2824
+ let lo = 0;
2825
+ let hi = rects.length;
2826
+ while (lo < hi) {
2827
+ const mid = lo + hi >> 1;
2828
+ if (rects[mid].y + rects[mid].h < top)
2829
+ lo = mid + 1;
2830
+ else
2831
+ hi = mid;
2832
+ }
2833
+ const out = [];
2834
+ for (let i = lo;i < rects.length && rects[i].y <= bottom; i++)
2835
+ out.push(i);
2836
+ return out;
2837
+ }
2838
+ var overlapY = (a, b) => Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y) >= Math.min(a.h, b.h) * 0.5;
2839
+ function neighbour(rects, from, dir) {
2840
+ const f = rects[from];
2841
+ if (!f)
2842
+ return from;
2843
+ const fx = f.x + f.w / 2;
2844
+ const fy = f.y + f.h / 2;
2845
+ if (dir === "left" || dir === "right") {
2846
+ const right = dir === "right";
2847
+ let best = -1;
2848
+ let bestD = Number.POSITIVE_INFINITY;
2849
+ rects.forEach((r, i) => {
2850
+ if (i === from || !overlapY(f, r))
2851
+ return;
2852
+ const d = (r.x + r.w / 2 - fx) * (right ? 1 : -1);
2853
+ if (d > 1 && d < bestD) {
2854
+ bestD = d;
2855
+ best = i;
2856
+ }
2857
+ });
2858
+ if (best >= 0)
2859
+ return best;
2860
+ return clamp(from + (right ? 1 : -1), 0, rects.length - 1);
2861
+ }
2862
+ const down = dir === "down";
2863
+ let edge = Number.POSITIVE_INFINITY;
2864
+ for (const r of rects) {
2865
+ const d = (r.y + r.h / 2 - fy) * (down ? 1 : -1);
2866
+ if (d > f.h * 0.25 && !overlapY(f, r))
2867
+ edge = Math.min(edge, down ? r.y : -(r.y + r.h));
2868
+ }
2869
+ if (!Number.isFinite(edge))
2870
+ return from;
2871
+ let best = from;
2872
+ let bestD = Number.POSITIVE_INFINITY;
2873
+ rects.forEach((r, i) => {
2874
+ const d = (r.y + r.h / 2 - fy) * (down ? 1 : -1);
2875
+ if (i === from || d <= f.h * 0.25 || overlapY(f, r))
2876
+ return;
2877
+ const near = down ? r.y - edge : -(r.y + r.h) - edge;
2878
+ if (near > Math.min(r.h, f.h) * 0.5)
2879
+ return;
2880
+ const dx = Math.abs(r.x + r.w / 2 - fx);
2881
+ if (dx < bestD) {
2882
+ bestD = dx;
2883
+ best = i;
2884
+ }
2885
+ });
2886
+ return best;
2887
+ }
2888
+ function fitRect(aspect, w, h, inset = 0) {
2889
+ const bw = Math.max(1, w - inset * 2);
2890
+ const bh = Math.max(1, h - inset * 2);
2891
+ const fw = Math.min(bw, bh * aspect);
2892
+ const fh = fw / aspect;
2893
+ return { x: (w - fw) / 2, y: (h - fh) / 2, w: fw, h: fh };
2894
+ }
2895
+ function motionBlur(speed, max = 3, per = 1600) {
2896
+ const b = Math.min(max, Math.abs(speed) / per);
2897
+ return b < 0.25 ? 0 : Math.round(b * 4) / 4;
2898
+ }
1056
2899
 
1057
2900
  // src/ui/bento.ts
1058
2901
  var LAYOUT = springMotion({ duration: 0.4, bounce: 0 });
1059
2902
  var ZOOM_IN = springMotion({ duration: 0.45, bounce: 0 });
1060
2903
  var ZOOM_OUT = springMotion({ duration: 0.38, bounce: 0 });
1061
- var SLIDE = springMotion({ duration: 0.3, bounce: 0 });
2904
+ var SLIDE2 = springMotion({ duration: 0.3, bounce: 0 });
1062
2905
  var SLIDE_OUT = springMotion({ duration: 0.25, bounce: 0 });
1063
2906
  var ENTER = springMotion({ duration: 0.3, bounce: 0 });
1064
- var EXIT = springMotion({ duration: 0.25, bounce: 0 });
2907
+ var EXIT2 = springMotion({ duration: 0.25, bounce: 0 });
1065
2908
  var WASH = springMotion({ duration: 0.25, bounce: 0 });
1066
- var FADE = springMotion({ duration: 0.18, bounce: 0 });
2909
+ var FADE2 = springMotion({ duration: 0.18, bounce: 0 });
1067
2910
  var INSTANT = springMotion({ duration: 0.1, bounce: 0 });
1068
2911
  var SLIDE_BY = 0.12;
1069
2912
  var EXIT_BLUR = 3;
@@ -1233,7 +3076,7 @@ function bento(container, options) {
1233
3076
  if (enter && !reduced())
1234
3077
  drive(t.life.snap(0), ENTER, 1);
1235
3078
  else if (enter)
1236
- drive(t.life.snap(0), FADE, 1);
3079
+ drive(t.life.snap(0), FADE2, 1);
1237
3080
  if (reduced() || !from)
1238
3081
  for (const s of [t.x, t.y, t.w, t.h])
1239
3082
  s.snap(s.target);
@@ -1563,7 +3406,7 @@ function bento(container, options) {
1563
3406
  if (r)
1564
3407
  setBox(t, r.w, r.h);
1565
3408
  if (f.out || f.veil)
1566
- drive(t.life.snap(0), reduced() ? FADE : ENTER, 1);
3409
+ drive(t.life.snap(0), reduced() ? FADE2 : ENTER, 1);
1567
3410
  t.drawn = "";
1568
3411
  draw(t);
1569
3412
  syncZoomed();
@@ -1596,11 +3439,11 @@ function bento(container, options) {
1596
3439
  f.z.snap(1);
1597
3440
  f.dx.snap(0);
1598
3441
  f.veil = true;
1599
- drive(f.alpha.snap(f.alpha.target === 0 ? f.alpha.x : 0), FADE, 1);
3442
+ drive(f.alpha.snap(f.alpha.target === 0 ? f.alpha.x : 0), FADE2, 1);
1600
3443
  } else {
1601
3444
  f.veil = false;
1602
3445
  drive(f.z, ZOOM_IN, 1);
1603
- drive(f.dx, SLIDE, 0);
3446
+ drive(f.dx, SLIDE2, 0);
1604
3447
  drive(f.alpha, ENTER, 1);
1605
3448
  }
1606
3449
  syncZoomed();
@@ -1631,7 +3474,7 @@ function bento(container, options) {
1631
3474
  revealUnder(f.tile.id);
1632
3475
  if (reduced()) {
1633
3476
  f.veil = true;
1634
- drive(f.alpha, FADE, 0);
3477
+ drive(f.alpha, FADE2, 0);
1635
3478
  } else
1636
3479
  drive(f.z, ZOOM_OUT, 0);
1637
3480
  syncZoomed();
@@ -1652,7 +3495,7 @@ function bento(container, options) {
1652
3495
  cur.out = true;
1653
3496
  cur.veil = false;
1654
3497
  drive(cur.dx, SLIDE_OUT, -dir * by);
1655
- drive(cur.alpha, reduced() ? FADE : SLIDE_OUT, 0);
3498
+ drive(cur.alpha, reduced() ? FADE2 : SLIDE_OUT, 0);
1656
3499
  let f = focusOf(t);
1657
3500
  if (!f) {
1658
3501
  f = beginFocus(t);
@@ -1663,8 +3506,8 @@ function bento(container, options) {
1663
3506
  f.out = false;
1664
3507
  f.veil = false;
1665
3508
  drive(f.z, ZOOM_IN, 1);
1666
- drive(f.dx, SLIDE, 0);
1667
- drive(f.alpha, reduced() ? FADE : SLIDE, 1);
3509
+ drive(f.dx, SLIDE2, 0);
3510
+ drive(f.alpha, reduced() ? FADE2 : SLIDE2, 1);
1668
3511
  setActive(id);
1669
3512
  syncZoomed();
1670
3513
  loop.wake();
@@ -1988,7 +3831,7 @@ function bento(container, options) {
1988
3831
  if (f)
1989
3832
  endFocus(f);
1990
3833
  t.leaving = true;
1991
- drive(t.life, reduced() ? FADE : EXIT, 0);
3834
+ drive(t.life, reduced() ? FADE2 : EXIT2, 0);
1992
3835
  }
1993
3836
  items = next;
1994
3837
  reindex();
@@ -2052,6 +3895,560 @@ function bento(container, options) {
2052
3895
  };
2053
3896
  }
2054
3897
 
3898
+ // src/ui/picker-model.ts
3899
+ function digitPick(ids, key) {
3900
+ if (!/^[1-9]$/.test(key))
3901
+ return;
3902
+ return ids[Number(key) - 1];
3903
+ }
3904
+ function stepPick(ids, current, key) {
3905
+ if (!ids.length)
3906
+ return;
3907
+ if (key === "Home")
3908
+ return ids[0];
3909
+ if (key === "End")
3910
+ return ids[ids.length - 1];
3911
+ const i = current === null ? -1 : ids.indexOf(current);
3912
+ if (i < 0)
3913
+ return key === "ArrowDown" ? ids[0] : ids[ids.length - 1];
3914
+ return ids[Math.max(0, Math.min(ids.length - 1, i + (key === "ArrowDown" ? 1 : -1)))];
3915
+ }
3916
+ function keepPick(ids, selected, wanted = null) {
3917
+ if (wanted !== null && ids.includes(wanted))
3918
+ return wanted;
3919
+ if (selected !== null && ids.includes(selected))
3920
+ return selected;
3921
+ return ids[0] ?? null;
3922
+ }
3923
+ function askPick(ids, id) {
3924
+ return ids.includes(id) ? { pick: id, wanted: null } : { pick: undefined, wanted: id };
3925
+ }
3926
+ var stillWanted = (ids, wanted) => wanted !== null && ids.includes(wanted) ? null : wanted;
3927
+ var pickDigit = (index) => String(index + 1);
3928
+ function claimsDigit(event, typing = false) {
3929
+ return /^[1-9]$/.test(event.key) && !event.defaultPrevented && !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey && !event.repeat && !event.typing && !typing;
3930
+ }
3931
+ function cardTarget(heads, index, body, gap = 0, top = 0) {
3932
+ if (body === undefined)
3933
+ return null;
3934
+ let y = top;
3935
+ for (let i = 0;i < index; i++)
3936
+ y += heads[i] + gap;
3937
+ return { y, h: (heads[index] ?? 0) + body };
3938
+ }
3939
+ function settleHeight(previous, now, frames, limit = 6) {
3940
+ if (previous !== undefined && Math.abs(now - previous) < 0.5)
3941
+ return now;
3942
+ return frames >= limit ? now : undefined;
3943
+ }
3944
+
3945
+ // src/ui/picker.ts
3946
+ var OPEN2 = springMotion({ duration: 0.22, bounce: 0 });
3947
+ var CLOSE2 = springMotion({ duration: 0.18, bounce: 0 });
3948
+ var CARD = springMotion({ duration: 0.3, bounce: 0 });
3949
+ var FADE3 = springMotion({ duration: 0.16, bounce: 0 });
3950
+ var DRIFT = 6;
3951
+ var CHECK = svg('<path d="m3.5 8.4 3 3 6-6.6"/>');
3952
+ function optionPicker(container, options) {
3953
+ const doc = container.ownerDocument;
3954
+ const win = doc.defaultView;
3955
+ let list = options.options;
3956
+ let wanted = null;
3957
+ let selected = null;
3958
+ {
3959
+ const ids = list.map((o) => o.id);
3960
+ if (options.selected != null)
3961
+ wanted = askPick(ids, options.selected).wanted;
3962
+ selected = keepPick(ids, options.selected ?? null);
3963
+ }
3964
+ let chosen = options.chosen ?? null;
3965
+ const digits = options.digits ?? "focus";
3966
+ const root = doc.createElement("div");
3967
+ root.className = "knobs-picker";
3968
+ root.setAttribute("role", "radiogroup");
3969
+ if (options.label)
3970
+ root.setAttribute("aria-label", options.label);
3971
+ applyLook(root, options);
3972
+ const card = doc.createElement("div");
3973
+ card.className = "knobs-picker-card";
3974
+ card.setAttribute("aria-hidden", "true");
3975
+ root.append(card);
3976
+ container.append(root);
3977
+ const entries = new Map;
3978
+ const uid = `kp${Math.random().toString(36).slice(2, 7)}`;
3979
+ let serial = 0;
3980
+ const make = (option) => {
3981
+ const el = doc.createElement("div");
3982
+ el.className = "knobs-picker-option";
3983
+ el.dataset.id = option.id;
3984
+ const head = doc.createElement("button");
3985
+ head.type = "button";
3986
+ head.className = "knobs-picker-head";
3987
+ head.setAttribute("role", "radio");
3988
+ head.id = `${uid}-${serial++}`;
3989
+ const key = doc.createElement("span");
3990
+ key.className = "knobs-picker-key";
3991
+ key.setAttribute("aria-hidden", "true");
3992
+ const name = doc.createElement("span");
3993
+ name.className = "knobs-picker-name knobs-scroll-fade-x";
3994
+ const suggested = doc.createElement("span");
3995
+ suggested.className = "knobs-picker-suggested";
3996
+ const check = doc.createElement("span");
3997
+ check.className = "knobs-picker-check";
3998
+ check.innerHTML = CHECK;
3999
+ check.setAttribute("aria-hidden", "true");
4000
+ head.append(key, name, suggested, check);
4001
+ const body = doc.createElement("div");
4002
+ body.className = "knobs-picker-body";
4003
+ body.hidden = true;
4004
+ body.setAttribute("role", "group");
4005
+ body.setAttribute("aria-labelledby", head.id);
4006
+ const inner = doc.createElement("div");
4007
+ inner.className = "knobs-picker-inner";
4008
+ body.append(inner);
4009
+ el.append(head, body);
4010
+ const entry = {
4011
+ option,
4012
+ el,
4013
+ head,
4014
+ key,
4015
+ thumb: undefined,
4016
+ name,
4017
+ suggested,
4018
+ body,
4019
+ inner,
4020
+ mounted: false,
4021
+ measured: true,
4022
+ cleanup: undefined,
4023
+ h: new Spring(0, OPEN2.omega, OPEN2.zeta, 0.05),
4024
+ o: new Spring(0, OPEN2.omega, OPEN2.zeta, 0.002),
4025
+ moving: false,
4026
+ open: false,
4027
+ presence: new Spring(1, OPEN2.omega, OPEN2.zeta, 0.002),
4028
+ leaving: false
4029
+ };
4030
+ entries.set(option.id, entry);
4031
+ return entry;
4032
+ };
4033
+ const setThumb = (e, url) => {
4034
+ if ((e.thumb?.getAttribute("src") ?? undefined) === (url || undefined))
4035
+ return;
4036
+ if (!url) {
4037
+ e.thumb?.remove();
4038
+ e.thumb = undefined;
4039
+ return;
4040
+ }
4041
+ let img = e.thumb;
4042
+ if (!img) {
4043
+ const el = doc.createElement("img");
4044
+ el.className = "knobs-picker-thumb";
4045
+ el.alt = "";
4046
+ el.loading = "lazy";
4047
+ el.decoding = "async";
4048
+ el.draggable = false;
4049
+ el.addEventListener("load", () => el.dataset.state = "loaded");
4050
+ el.addEventListener("error", () => el.dataset.state = "failed");
4051
+ e.key.after(el);
4052
+ e.thumb = img = el;
4053
+ }
4054
+ const shown = img;
4055
+ shown.dataset.state = "loading";
4056
+ shown.src = url;
4057
+ if (shown.complete && shown.naturalWidth > 0) {
4058
+ shown.dataset.state = "loaded";
4059
+ shown.dataset.instant = "";
4060
+ win.requestAnimationFrame(() => delete shown.dataset.instant);
4061
+ }
4062
+ };
4063
+ const bind = (e, i) => {
4064
+ e.option = list[i];
4065
+ e.key.textContent = pickDigit(i);
4066
+ setThumb(e, e.option.thumb);
4067
+ if (e.name.textContent !== e.option.label)
4068
+ e.name.textContent = e.option.label;
4069
+ e.suggested.textContent = e.option.suggested ? options.suggestedLabel ?? "Suggested" : "";
4070
+ e.suggested.hidden = !e.option.suggested;
4071
+ const on = e.option.id === selected;
4072
+ e.el.toggleAttribute("data-selected", on);
4073
+ e.head.setAttribute("aria-checked", String(on));
4074
+ e.head.tabIndex = on ? 0 : -1;
4075
+ e.el.toggleAttribute("data-chosen", e.option.id === chosen);
4076
+ e.head.setAttribute("aria-label", [
4077
+ e.option.label,
4078
+ e.option.suggested ? options.suggestedLabel ?? "Suggested" : "",
4079
+ e.option.id === chosen ? "chosen" : ""
4080
+ ].filter(Boolean).join(", "));
4081
+ };
4082
+ const mount = (e) => {
4083
+ if (e.mounted || !options.body)
4084
+ return;
4085
+ e.mounted = true;
4086
+ const out = options.body(e.option, e.inner);
4087
+ if (typeof out === "function")
4088
+ e.cleanup = out;
4089
+ };
4090
+ const cardY = new Spring(0, CARD.omega, CARD.zeta, 0.05);
4091
+ const cardH = new Spring(0, CARD.omega, CARD.zeta, 0.05);
4092
+ let gliding = false;
4093
+ const liveCard = () => {
4094
+ const e = selected ? entries.get(selected) : undefined;
4095
+ return e && !e.leaving ? { y: e.el.offsetTop, h: e.el.offsetHeight } : null;
4096
+ };
4097
+ const finalCard = () => {
4098
+ const live = list.map((o) => entries.get(o.id)).filter((e) => !!e && !e.leaving);
4099
+ const index = live.findIndex((e) => e.option.id === selected);
4100
+ if (index < 0)
4101
+ return null;
4102
+ const gap = parseFloat(win.getComputedStyle(root).rowGap) || 0;
4103
+ const e = live[index];
4104
+ return cardTarget(live.map((x) => x.head.offsetHeight), index, e.measured ? natural(e) : undefined, gap);
4105
+ };
4106
+ const placeCard = () => {
4107
+ card.style.transform = `translate3d(0,${Math.round(cardY.x * 2) / 2}px,0)`;
4108
+ card.style.height = `${Math.max(0, cardH.x).toFixed(2)}px`;
4109
+ card.hidden = cardH.x < 0.5;
4110
+ };
4111
+ const natural = (e) => e.inner.offsetHeight;
4112
+ const setOpen = (e, open, animate) => {
4113
+ if (e.open === open && (e.moving || e.body.hidden === !open))
4114
+ return;
4115
+ e.open = open;
4116
+ const reduced = reducedMotion(win);
4117
+ if (open && !e.mounted) {
4118
+ if (animate && e.body.hidden) {
4119
+ e.body.hidden = false;
4120
+ e.body.style.height = "0px";
4121
+ e.body.dataset.moving = "";
4122
+ e.h.snap(0);
4123
+ e.o.snap(0);
4124
+ e.moving = true;
4125
+ }
4126
+ mount(e);
4127
+ }
4128
+ if (!animate) {
4129
+ e.body.hidden = !open;
4130
+ e.moving = false;
4131
+ e.body.style.height = "";
4132
+ delete e.body.dataset.moving;
4133
+ e.inner.style.opacity = "";
4134
+ e.inner.style.transform = "";
4135
+ return;
4136
+ }
4137
+ if (!e.moving) {
4138
+ const from = e.body.hidden ? 0 : e.body.offsetHeight;
4139
+ e.h.snap(from);
4140
+ e.o.snap(from > 0.5 ? 1 : 0);
4141
+ }
4142
+ e.body.hidden = false;
4143
+ e.moving = true;
4144
+ e.body.dataset.moving = "";
4145
+ if (reduced) {
4146
+ e.h.snap(open ? natural(e) : 0);
4147
+ tune(e.o, FADE3).set(open ? 1 : 0);
4148
+ } else {
4149
+ tune(e.h, open ? OPEN2 : CLOSE2).set(open ? natural(e) : 0);
4150
+ tune(e.o, open ? OPEN2 : CLOSE2).set(open ? 1 : 0);
4151
+ }
4152
+ loop.wake();
4153
+ };
4154
+ let pending = null;
4155
+ const premount = (e) => {
4156
+ e.body.hidden = false;
4157
+ e.body.style.height = "0px";
4158
+ e.body.dataset.moving = "";
4159
+ e.inner.style.opacity = "0";
4160
+ e.measured = false;
4161
+ mount(e);
4162
+ };
4163
+ const shelve = (e) => {
4164
+ if (e.open || e.moving)
4165
+ return;
4166
+ e.body.hidden = true;
4167
+ e.body.style.height = "";
4168
+ delete e.body.dataset.moving;
4169
+ e.inner.style.opacity = "";
4170
+ e.measured = true;
4171
+ };
4172
+ const start = (was, now, animate) => {
4173
+ now.measured = true;
4174
+ if (was && was !== now)
4175
+ setOpen(was, false, animate);
4176
+ setOpen(now, true, animate);
4177
+ if (animate && !reducedMotion(win)) {
4178
+ tune(cardY, CARD);
4179
+ tune(cardH, CARD);
4180
+ gliding = true;
4181
+ }
4182
+ loop.wake();
4183
+ };
4184
+ const flush = () => {
4185
+ const p = pending;
4186
+ if (!p)
4187
+ return;
4188
+ pending = null;
4189
+ start(p.was, p.now, true);
4190
+ };
4191
+ const loop = frameLoop(win, (dt) => {
4192
+ let moving = false;
4193
+ if (pending) {
4194
+ const p = pending;
4195
+ const h = natural(p.now);
4196
+ p.frames++;
4197
+ if (settleHeight(p.last, h, p.frames) !== undefined)
4198
+ flush();
4199
+ else {
4200
+ p.last = h;
4201
+ moving = true;
4202
+ }
4203
+ }
4204
+ for (const e of [...entries.values()]) {
4205
+ if (e.moving) {
4206
+ if (e.open && !reducedMotion(win))
4207
+ e.h.set(natural(e));
4208
+ e.h.step(dt);
4209
+ e.o.step(dt);
4210
+ const fade = Math.max(0, Math.min(1, e.o.x));
4211
+ e.body.style.height = `${Math.max(0, e.h.x).toFixed(2)}px`;
4212
+ const shown = e.open ? fade : Math.max(0, (fade - 0.4) / 0.6);
4213
+ e.inner.style.opacity = shown < 0.999 ? shown.toFixed(3) : "";
4214
+ const drift = reducedMotion(win) ? 0 : (1 - fade) * -DRIFT;
4215
+ e.inner.style.transform = Math.abs(drift) > 0.05 ? `translateY(${drift.toFixed(2)}px)` : "";
4216
+ if (e.h.resting && e.o.resting) {
4217
+ e.moving = false;
4218
+ delete e.body.dataset.moving;
4219
+ e.body.style.height = "";
4220
+ e.inner.style.opacity = "";
4221
+ e.inner.style.transform = "";
4222
+ e.body.hidden = !e.open;
4223
+ } else
4224
+ moving = true;
4225
+ }
4226
+ if (e.leaving || !e.presence.resting) {
4227
+ e.presence.step(dt);
4228
+ const p = Math.max(0, Math.min(1, e.presence.x));
4229
+ e.el.style.opacity = p < 0.999 ? p.toFixed(3) : "";
4230
+ e.el.style.height = p < 0.999 ? `${(p * e.head.offsetHeight).toFixed(2)}px` : "";
4231
+ e.el.style.overflow = p < 0.999 ? "hidden" : "";
4232
+ if (e.presence.resting) {
4233
+ if (e.leaving) {
4234
+ e.cleanup?.();
4235
+ e.el.remove();
4236
+ entries.delete(e.option.id);
4237
+ }
4238
+ } else
4239
+ moving = true;
4240
+ }
4241
+ }
4242
+ const t = pending ? null : gliding ? finalCard() : liveCard();
4243
+ if (t) {
4244
+ if (gliding) {
4245
+ cardY.set(t.y);
4246
+ cardH.set(t.h);
4247
+ cardY.step(dt);
4248
+ cardH.step(dt);
4249
+ if (cardY.resting && cardH.resting && !moving)
4250
+ gliding = false;
4251
+ else
4252
+ moving = true;
4253
+ } else {
4254
+ cardY.snap(t.y);
4255
+ cardH.snap(t.h);
4256
+ }
4257
+ }
4258
+ placeCard();
4259
+ notifyLayout(doc);
4260
+ return moving;
4261
+ });
4262
+ const rebind = () => {
4263
+ list.forEach((o, i) => {
4264
+ const e = entries.get(o.id);
4265
+ if (e)
4266
+ bind(e, i);
4267
+ });
4268
+ };
4269
+ const pick = (id, animate = true) => {
4270
+ if (id === selected || !entries.has(id))
4271
+ return;
4272
+ let was = selected ? entries.get(selected) : undefined;
4273
+ if (pending) {
4274
+ was = pending.was;
4275
+ const waiting = pending.now;
4276
+ pending = null;
4277
+ if (waiting.option.id !== id)
4278
+ shelve(waiting);
4279
+ }
4280
+ selected = id;
4281
+ rebind();
4282
+ const now = entries.get(id);
4283
+ if (animate && !reducedMotion(win) && !now.mounted && options.body) {
4284
+ premount(now);
4285
+ pending = { was, now, last: natural(now), frames: 0 };
4286
+ loop.wake();
4287
+ return;
4288
+ }
4289
+ start(was, now, animate);
4290
+ };
4291
+ const select = (id, source) => {
4292
+ if (!id)
4293
+ return;
4294
+ wanted = null;
4295
+ if (id === selected)
4296
+ return;
4297
+ const inside = root.contains(doc.activeElement);
4298
+ pick(id);
4299
+ if (source !== "pointer" && inside)
4300
+ entries.get(id)?.head.focus({ preventScroll: true });
4301
+ options.onSelect?.(id, source);
4302
+ };
4303
+ const sync = (animate) => {
4304
+ flush();
4305
+ const ids = list.map((o) => o.id);
4306
+ const was = selected;
4307
+ selected = keepPick(ids, selected, wanted);
4308
+ wanted = stillWanted(ids, wanted);
4309
+ if (animate && was !== selected && !reducedMotion(win))
4310
+ gliding = true;
4311
+ const reduced = reducedMotion(win);
4312
+ const live = new Set(ids);
4313
+ for (const e of entries.values()) {
4314
+ if (live.has(e.option.id) || e.leaving)
4315
+ continue;
4316
+ if (!animate) {
4317
+ e.cleanup?.();
4318
+ e.el.remove();
4319
+ entries.delete(e.option.id);
4320
+ continue;
4321
+ }
4322
+ e.leaving = true;
4323
+ e.el.setAttribute("aria-hidden", "true");
4324
+ e.head.tabIndex = -1;
4325
+ if (e.open)
4326
+ setOpen(e, false, true);
4327
+ tune(e.presence, reduced ? FADE3 : CLOSE2).set(0);
4328
+ }
4329
+ let before = card;
4330
+ list.forEach((o, i) => {
4331
+ let e = entries.get(o.id);
4332
+ if (e?.leaving) {
4333
+ e.leaving = false;
4334
+ e.el.removeAttribute("aria-hidden");
4335
+ tune(e.presence, OPEN2).set(1);
4336
+ }
4337
+ if (!e) {
4338
+ e = make(o);
4339
+ ro.observe(e.el);
4340
+ if (animate)
4341
+ tune(e.presence, reduced ? FADE3 : OPEN2).snap(0).set(1);
4342
+ }
4343
+ if (before.nextElementSibling !== e.el)
4344
+ before.after(e.el);
4345
+ before = e.el;
4346
+ bind(e, i);
4347
+ });
4348
+ const open = selected ? entries.get(selected) : undefined;
4349
+ const ahead = animate && !reduced && open && !open.open && !open.mounted && !!options.body ? { was: was ? entries.get(was) : undefined, now: open } : null;
4350
+ for (const e of entries.values())
4351
+ if (e !== open && e !== ahead?.was && e.open && !e.leaving)
4352
+ setOpen(e, false, animate);
4353
+ if (ahead) {
4354
+ premount(ahead.now);
4355
+ pending = { ...ahead, last: natural(ahead.now), frames: 0 };
4356
+ } else if (open && !open.open)
4357
+ setOpen(open, true, animate);
4358
+ loop.wake();
4359
+ };
4360
+ const ids = () => list.map((o) => o.id).filter((id) => !entries.get(id)?.leaving);
4361
+ const onDigit = (e) => {
4362
+ if (!claimsDigit(e, isTypingTarget(e.target)))
4363
+ return;
4364
+ if (!root.getClientRects().length)
4365
+ return;
4366
+ const id = digitPick(ids(), e.key);
4367
+ if (!id)
4368
+ return;
4369
+ e.preventDefault();
4370
+ select(id, "digit");
4371
+ };
4372
+ root.addEventListener("keydown", (e) => {
4373
+ const head = e.target.closest?.(".knobs-picker-head");
4374
+ if (head && !e.metaKey && !e.ctrlKey && !e.altKey) {
4375
+ if (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Home" || e.key === "End") {
4376
+ e.preventDefault();
4377
+ select(stepPick(ids(), selected, e.key), "key");
4378
+ return;
4379
+ }
4380
+ if (e.key === "Enter" && selected) {
4381
+ e.preventDefault();
4382
+ options.onChoose?.(selected);
4383
+ return;
4384
+ }
4385
+ }
4386
+ if (digits === "focus")
4387
+ onDigit(e);
4388
+ });
4389
+ if (digits === "page")
4390
+ win.addEventListener("keydown", onDigit);
4391
+ root.addEventListener("click", (e) => {
4392
+ const el = e.target.closest(".knobs-picker-head");
4393
+ const id = el?.closest(".knobs-picker-option")?.dataset.id;
4394
+ if (!id || entries.get(id)?.leaving)
4395
+ return;
4396
+ select(id, e.detail === 0 ? "key" : "pointer");
4397
+ });
4398
+ const ro = new ResizeObserver(() => loop.wake());
4399
+ ro.observe(root);
4400
+ root.addEventListener("knobs-restyle", () => {
4401
+ syncFrame(root);
4402
+ loop.wake();
4403
+ });
4404
+ sync(false);
4405
+ const first = liveCard();
4406
+ if (first) {
4407
+ cardY.snap(first.y);
4408
+ cardH.snap(first.h);
4409
+ }
4410
+ placeCard();
4411
+ loop.wake();
4412
+ const handle = {
4413
+ element: root,
4414
+ set(next) {
4415
+ list = next;
4416
+ sync(true);
4417
+ },
4418
+ select(id) {
4419
+ const ask = askPick(ids(), id);
4420
+ wanted = ask.wanted;
4421
+ if (ask.pick)
4422
+ pick(ask.pick);
4423
+ },
4424
+ get selected() {
4425
+ return selected;
4426
+ },
4427
+ choose(id) {
4428
+ chosen = id;
4429
+ rebind();
4430
+ },
4431
+ get chosen() {
4432
+ return chosen;
4433
+ },
4434
+ focus() {
4435
+ (selected ? entries.get(selected)?.head : undefined)?.focus({ preventScroll: true });
4436
+ },
4437
+ destroy() {
4438
+ loop.stop();
4439
+ ro.disconnect();
4440
+ if (digits === "page")
4441
+ win.removeEventListener("keydown", onDigit);
4442
+ for (const e of entries.values())
4443
+ e.cleanup?.();
4444
+ entries.clear();
4445
+ root.remove();
4446
+ },
4447
+ dispose: () => handle.destroy()
4448
+ };
4449
+ return handle;
4450
+ }
4451
+
2055
4452
  // src/core/storage.ts
2056
4453
  function browserStorage() {
2057
4454
  return typeof window === "undefined" ? null : window.localStorage;
@@ -3283,6 +5680,43 @@ function edgeOf(panes, id) {
3283
5680
  const first = panes.findIndex((p) => flex.has(p.id));
3284
5681
  return panes.findIndex((p) => p.id === id) < first ? "start" : "end";
3285
5682
  }
5683
+ function foldProgress(size, open, rail) {
5684
+ const span = open - rail;
5685
+ if (span <= 0)
5686
+ return size > rail ? 0 : 1;
5687
+ return Math.min(1, Math.max(0, (open - size) / span));
5688
+ }
5689
+ var FACE_OUT = 0.4;
5690
+ var FACE_IN = 0.3;
5691
+ var clamp01 = (x) => Math.min(1, Math.max(0, x));
5692
+ function face(prev, away) {
5693
+ const lo = clamp01(1 - away / FACE_IN);
5694
+ const hi = clamp01(1 - away / FACE_OUT);
5695
+ return Math.min(hi, Math.max(lo, prev));
5696
+ }
5697
+ function foldFaces(prev, p) {
5698
+ return { content: face(prev.content, p), rail: face(prev.rail, 1 - p) };
5699
+ }
5700
+ function chevronTurn(p, edge) {
5701
+ return (edge === "start" ? 180 : -180) * (1 - clamp01(p));
5702
+ }
5703
+ function chevronFlip(f) {
5704
+ return 2 * clamp01(f) - 1;
5705
+ }
5706
+ var FOLD_STYLE = { chevron: "flip", title: "travel", duration: 0.3 };
5707
+ function foldStyle(options = {}) {
5708
+ const duration = options.duration;
5709
+ return {
5710
+ chevron: options.chevron === "turn" ? "turn" : "flip",
5711
+ title: options.title === "fade" ? "fade" : "travel",
5712
+ duration: duration !== undefined && Number.isFinite(duration) && duration > 0 ? Math.min(2, duration) : FOLD_STYLE.duration
5713
+ };
5714
+ }
5715
+ function titleTravel(at, open, rail, edge, p) {
5716
+ const k = 1 - clamp01(p);
5717
+ const dx = at.title - at.label - (edge === "start" ? open - rail : 0);
5718
+ return { x: dx * k, y: at.dy * k, turn: -90 * k };
5719
+ }
3286
5720
  function parseLayout(raw, panes) {
3287
5721
  if (!raw || typeof raw !== "object")
3288
5722
  return;
@@ -3310,8 +5744,24 @@ function serializeLayout(layout) {
3310
5744
  }
3311
5745
 
3312
5746
  // src/ui/split.ts
3313
- var COLLAPSE = springMotion({ duration: 0.42, bounce: 0.04 });
5747
+ var FLIP = springMotion({ duration: 0.18, bounce: 0 });
3314
5748
  var SETTLE = springMotion({ duration: 0.3, bounce: 0 });
5749
+ var CONTENT_BLUR = 3;
5750
+ var LABEL_BLUR = 2;
5751
+ var put = (el, prop, value) => {
5752
+ if (el.style.getPropertyValue(prop) !== value)
5753
+ el.style.setProperty(prop, value);
5754
+ };
5755
+ var hide = (el, hidden) => {
5756
+ if (hidden)
5757
+ el.setAttribute("aria-hidden", "true");
5758
+ else
5759
+ el.removeAttribute("aria-hidden");
5760
+ };
5761
+ var fade = (el, a, blur) => {
5762
+ put(el, "opacity", a >= 1 ? "" : a.toFixed(3));
5763
+ put(el, "filter", a >= 1 || a <= 0 || !blur ? "" : `blur(${(blur * (1 - a)).toFixed(2)}px)`);
5764
+ };
3315
5765
  var iconNode = (doc, icon, label) => {
3316
5766
  if (icon instanceof Node)
3317
5767
  return icon;
@@ -3330,6 +5780,8 @@ function split(container, options) {
3330
5780
  const row = direction === "row";
3331
5781
  const specs = options.panes;
3332
5782
  const flex = flexible(specs);
5783
+ const style = foldStyle(options.fold);
5784
+ const COLLAPSE = springMotion({ duration: style.duration, bounce: 0 });
3333
5785
  const store = options.storage === undefined ? browserStorage() : options.storage;
3334
5786
  const key = options.id ? storageKey(options.id, "split") : undefined;
3335
5787
  const saved = (() => {
@@ -3358,13 +5810,31 @@ function split(container, options) {
3358
5810
  const el = doc.createElement("section");
3359
5811
  el.className = "knobs-pane";
3360
5812
  el.dataset.pane = spec.id;
5813
+ const edge = edgeOf(specs, spec.id);
5814
+ el.dataset.edge = edge;
3361
5815
  const clip = doc.createElement("div");
3362
5816
  clip.className = "knobs-pane-clip";
3363
5817
  const inner = doc.createElement("div");
3364
5818
  inner.className = "knobs-pane-inner";
3365
5819
  const body = doc.createElement("div");
3366
5820
  body.className = "knobs-pane-body";
3367
- const view = { spec, el, clip, inner, body, open: 0, shown: 0 };
5821
+ const shut = Boolean(collapsed[spec.id]);
5822
+ const view = {
5823
+ spec,
5824
+ el,
5825
+ clip,
5826
+ inner,
5827
+ body,
5828
+ edge,
5829
+ open: 0,
5830
+ shown: 0,
5831
+ folding: false,
5832
+ faces: { content: shut ? 0 : 1, rail: shut ? 1 : 0 },
5833
+ headPx: 0,
5834
+ chev: { iconEnd: 0, chevEnd: 0, dy: 0, scale: 1 },
5835
+ places: { title: 0, label: 0, dy: 0 },
5836
+ flip: new Spring(shut ? 1 : 0, FLIP.omega, FLIP.zeta, 0.002)
5837
+ };
3368
5838
  if (spec.title !== undefined) {
3369
5839
  const head = doc.createElement("header");
3370
5840
  head.className = "knobs-pane-head";
@@ -3402,9 +5872,10 @@ function split(container, options) {
3402
5872
  tooltip(b, () => collapsed[spec.id] ? "Open" : row ? "Collapse" : "Fold");
3403
5873
  b.innerHTML = row ? ICONS.caret : ICONS.chevronDown;
3404
5874
  if (row)
3405
- b.firstElementChild?.setAttribute("style", edgeOf(specs, spec.id) === "start" ? "transform:scaleX(-1)" : "");
5875
+ b.firstElementChild?.setAttribute("style", edge === "start" ? "transform:scaleX(-1)" : "");
3406
5876
  else
3407
5877
  b.firstElementChild?.classList.add("knobs-pane-caret");
5878
+ view.toggleIcon = b.firstElementChild ?? undefined;
3408
5879
  b.addEventListener("click", (e) => {
3409
5880
  e.stopPropagation();
3410
5881
  setCollapsed(spec.id, !collapsed[spec.id]);
@@ -3423,8 +5894,10 @@ function split(container, options) {
3423
5894
  inner.append(body);
3424
5895
  clip.append(inner);
3425
5896
  el.append(clip);
3426
- if (spec.scroll ?? spec.title !== undefined)
5897
+ if (spec.scroll ?? spec.title !== undefined) {
3427
5898
  el.dataset.scroll = "";
5899
+ body.classList.add("knobs-scroll-fade");
5900
+ }
3428
5901
  if (spec.element)
3429
5902
  body.append(spec.element);
3430
5903
  if (spec.collapsible && (row || !view.head)) {
@@ -3436,13 +5909,22 @@ function split(container, options) {
3436
5909
  rail.setAttribute("aria-label", `Open ${label}`);
3437
5910
  tooltip(rail, `Open ${label}`, { placement: row ? "right" : "top" });
3438
5911
  const text = doc.createElement("span");
5912
+ text.className = "knobs-pane-rail-label";
3439
5913
  text.textContent = spec.rail ?? spec.title ?? "";
3440
- rail.innerHTML = row ? ICONS.caret.replace("<svg", `<svg style="transform:scaleX(${edgeOf(specs, spec.id) === "start" ? 1 : -1})"`) : ICONS.chevronDown;
3441
- rail.append(text);
5914
+ const chevron = doc.createElement("span");
5915
+ chevron.className = "knobs-pane-chevron";
5916
+ const turn = doc.createElement("span");
5917
+ turn.className = "knobs-pane-turn";
5918
+ turn.innerHTML = row ? ICONS.caret.replace("<svg", `<svg style="transform:scaleX(${edge === "start" ? 1 : -1})"`) : ICONS.chevronDown;
5919
+ chevron.append(turn);
5920
+ rail.append(chevron, text);
3442
5921
  rail.addEventListener("click", () => setCollapsed(spec.id, false));
3443
5922
  el.append(rail);
3444
5923
  el.dataset.rail = "";
3445
5924
  view.rail = rail;
5925
+ view.railLabel = text;
5926
+ view.chevron = chevron;
5927
+ view.turn = turn;
3446
5928
  }
3447
5929
  return view;
3448
5930
  };
@@ -3472,9 +5954,10 @@ function split(container, options) {
3472
5954
  const total = () => along(root);
3473
5955
  let paneM = 0;
3474
5956
  const marginOf = (v) => v.el.hasAttribute("data-nested") ? 0 : paneM;
3475
- const headerSize = (v) => v.head ? v.head.getBoundingClientRect().height + marginOf(v) * 2 : 0;
5957
+ const headerSize = (v) => v.head ? v.headPx + marginOf(v) * 2 : 0;
3476
5958
  let railPx = 0;
3477
- const readRail = () => {
5959
+ let measured = false;
5960
+ const measure = () => {
3478
5961
  syncFrame(root);
3479
5962
  const probe = doc.createElement("div");
3480
5963
  probe.style.cssText = "position:absolute;visibility:hidden;width:var(--ku-rail);height:var(--ku-pane-m)";
@@ -3483,8 +5966,44 @@ function split(container, options) {
3483
5966
  paneM = r.height;
3484
5967
  railPx = (r.width || 26) + paneM * 2;
3485
5968
  probe.remove();
5969
+ for (const v of views.values())
5970
+ if (v.head)
5971
+ v.headPx = v.head.getBoundingClientRect().height;
5972
+ measured = root.isConnected && r.width > 0;
5973
+ };
5974
+ const measureChevrons = (only) => {
5975
+ if (!row)
5976
+ return;
5977
+ for (const v of only ? [only] : views.values()) {
5978
+ if (!v.chevron || !v.toggleIcon || !v.rail || !v.railLabel)
5979
+ continue;
5980
+ const inner = v.inner.getBoundingClientRect();
5981
+ const icon = v.toggleIcon.getBoundingClientRect();
5982
+ const rail = v.rail.getBoundingClientRect();
5983
+ const chev = v.chevron.getBoundingClientRect();
5984
+ if (!inner.width || !chev.width)
5985
+ continue;
5986
+ v.chev = {
5987
+ iconEnd: inner.right - (icon.left + icon.width / 2),
5988
+ chevEnd: rail.right - (chev.left + chev.width / 2),
5989
+ dy: icon.top + icon.height / 2 - inner.top - (chev.top + chev.height / 2 - rail.top),
5990
+ scale: icon.width / chev.width
5991
+ };
5992
+ if (!v.titleEl)
5993
+ continue;
5994
+ const moved = v.railLabel.style.transform;
5995
+ v.railLabel.style.transform = "none";
5996
+ const label = v.railLabel.getBoundingClientRect();
5997
+ v.railLabel.style.transform = moved;
5998
+ const title = v.titleEl.getBoundingClientRect();
5999
+ v.places = {
6000
+ title: title.left + title.width / 2 - inner.left,
6001
+ label: label.left + label.width / 2 - rail.left,
6002
+ dy: title.top + title.height / 2 - inner.top - (label.top + label.height / 2 - rail.top)
6003
+ };
6004
+ }
3486
6005
  };
3487
- readRail();
6006
+ measure();
3488
6007
  const rail = (id) => {
3489
6008
  const v = views.get(id);
3490
6009
  return !row && v.head ? headerSize(v) : railPx;
@@ -3497,47 +6016,102 @@ function split(container, options) {
3497
6016
  };
3498
6017
  const target = () => solve({ panes: specs, sizes, collapsed, total: total(), rail });
3499
6018
  const current = () => solve({ panes: specs, sizes, collapsed, total: total(), rail, fixed: fixed() });
6019
+ const SIDES = { row: ["right", "left"], column: ["bottom", "top"] };
6020
+ const sides = (v) => {
6021
+ const [a, b] = SIDES[direction];
6022
+ return v.edge === "start" ? [a, b] : [b, a];
6023
+ };
6024
+ const renderFold = (v, size) => {
6025
+ const id = v.spec.id;
6026
+ const isCollapsed = collapsed[id];
6027
+ const folding = isCollapsed || v.folding;
6028
+ const r = rail(id);
6029
+ const open = folding ? Math.max(v.open, r + 1) : size;
6030
+ const p = folding ? foldProgress(size, open, r) : 0;
6031
+ v.faces = foldFaces(v.faces, p);
6032
+ const m = marginOf(v);
6033
+ const settled = !springs.has(id);
6034
+ const content = isCollapsed && settled ? 1 : v.faces.content;
6035
+ put(v.rail ? v.inner : v.body, "will-change", v.folding ? "opacity, filter" : "");
6036
+ if (!v.rail) {
6037
+ put(v.inner, "height", folding ? `${open - m * 2}px` : "");
6038
+ put(v.inner, "bottom", folding ? "auto" : "");
6039
+ fade(v.body, content, CONTENT_BLUR);
6040
+ if (v.toggleIcon)
6041
+ put(v.toggleIcon, "transform", p ? `rotate(${(-90 * p).toFixed(2)}deg)` : "");
6042
+ hide(v.body, isCollapsed && settled);
6043
+ return;
6044
+ }
6045
+ const [side, far] = sides(v);
6046
+ put(v.inner, row ? "width" : "height", folding ? `${open - m * 2}px` : "");
6047
+ put(v.inner, side, folding ? "0" : "");
6048
+ put(v.inner, far, folding ? "auto" : "");
6049
+ fade(v.inner, content, CONTENT_BLUR);
6050
+ hide(v.clip, isCollapsed && settled);
6051
+ const morph = row && Boolean(v.toggleIcon) && !v.el.hasAttribute("data-nested");
6052
+ v.el.toggleAttribute("data-morph", morph);
6053
+ const label = v.railLabel;
6054
+ const travel = morph && style.title === "travel" && Boolean(v.titleEl) && label.textContent === v.titleEl.textContent;
6055
+ v.el.toggleAttribute("data-travel", travel && folding);
6056
+ const k = 1 - p;
6057
+ if (travel && !folding) {
6058
+ put(label, "opacity", "0");
6059
+ put(label, "filter", "");
6060
+ put(label, "transform", "");
6061
+ put(label, "color", "");
6062
+ } else if (travel) {
6063
+ const t = titleTravel(v.places, open, r, v.edge, p);
6064
+ put(label, "opacity", "");
6065
+ put(label, "filter", "");
6066
+ put(label, "transform", k === 0 ? "" : `translate(${t.x.toFixed(2)}px,${t.y.toFixed(2)}px) rotate(${t.turn.toFixed(2)}deg)`);
6067
+ put(label, "color", k === 0 ? "" : `color-mix(in srgb,var(--ku-text) ${(k * 100).toFixed(1)}%,var(--ku-label))`);
6068
+ } else {
6069
+ put(label, "transform", "");
6070
+ put(label, "color", "");
6071
+ }
6072
+ if (morph) {
6073
+ put(v.rail, "opacity", "1");
6074
+ if (!travel)
6075
+ fade(label, v.faces.rail, LABEL_BLUR);
6076
+ const c = v.chev;
6077
+ const dx = c.chevEnd - c.iconEnd + (v.edge === "end" ? open - r : 0);
6078
+ const move = `translate(${(dx * k).toFixed(2)}px,${(c.dy * k).toFixed(2)}px)`;
6079
+ const size = `scale(${(1 + (c.scale - 1) * k).toFixed(4)})`;
6080
+ if (style.chevron === "turn")
6081
+ put(v.turn, "transform", k === 0 ? "" : `${move} rotate(${chevronTurn(p, v.edge).toFixed(2)}deg) ${size}`);
6082
+ else {
6083
+ const w = chevronFlip(v.flip.x);
6084
+ put(v.turn, "transform", k === 0 && w === 1 ? "" : `${move} ${size} scaleX(${w.toFixed(4)})`);
6085
+ }
6086
+ } else {
6087
+ const a = v.faces.rail;
6088
+ put(v.rail, "opacity", a >= 1 ? "1" : a.toFixed(3));
6089
+ put(label, "opacity", "");
6090
+ put(label, "filter", a >= 1 || a <= 0 ? "" : `blur(${(LABEL_BLUR * (1 - a)).toFixed(2)}px)`);
6091
+ put(v.turn, "transform", "");
6092
+ }
6093
+ };
3500
6094
  let lastReport = "";
3501
6095
  const render = (shown) => {
3502
6096
  for (const spec of specs) {
3503
6097
  const v = views.get(spec.id);
3504
6098
  const size = Math.max(0, shown[spec.id] ?? 0);
3505
6099
  v.shown = size;
3506
- v.el.style.flexBasis = `${size}px`;
3507
- v.el.style[row ? "width" : "height"] = `${size}px`;
3508
- const isCollapsed = collapsed[spec.id];
3509
- const moving = springs.has(spec.id);
3510
- v.el.toggleAttribute("data-collapsed", isCollapsed);
3511
- v.el.toggleAttribute("data-settled", !moving);
3512
- v.el.setAttribute("aria-hidden", String(isCollapsed && !moving));
3513
- const r = rail(spec.id);
3514
- const open = Math.max(v.open, r + 1);
3515
- const m = marginOf(v);
3516
- if (moving || isCollapsed) {
3517
- const p = Math.min(1, Math.max(0, (open - size) / (open - r)));
3518
- if (v.rail) {
3519
- const visible = Math.max(0, (size - r) / (open - r) * open);
3520
- const edge = edgeOf(specs, spec.id);
3521
- const side = row ? edge === "start" ? "right" : "left" : edge === "start" ? "bottom" : "top";
3522
- v.clip.style.cssText = `${row ? "width" : "height"}:${Math.max(0, visible - m * 2)}px;${side}:0;${row ? side === "right" ? "left:auto" : "right:auto" : side === "bottom" ? "top:auto" : "bottom:auto"}`;
3523
- v.inner.style.cssText = `${row ? "width" : "height"}:${open - m * 2}px;${side}:0;${row ? side === "right" ? "left:auto" : "right:auto" : side === "bottom" ? "top:auto" : "bottom:auto"}`;
3524
- v.rail.style.opacity = String(Math.max(0, (p - 0.35) / 0.65));
3525
- } else {
3526
- v.clip.style.cssText = "";
3527
- v.inner.style.cssText = `height:${open - m * 2}px;bottom:auto`;
3528
- }
3529
- } else {
3530
- v.clip.style.cssText = "";
3531
- v.inner.style.cssText = "";
3532
- if (v.rail)
3533
- v.rail.style.opacity = "0";
3534
- }
6100
+ put(v.el, "flex-basis", `${size}px`);
6101
+ put(v.el, row ? "width" : "height", `${size}px`);
6102
+ v.el.toggleAttribute("data-collapsed", collapsed[spec.id]);
6103
+ v.el.toggleAttribute("data-settled", !springs.has(spec.id));
6104
+ if (spec.collapsible)
6105
+ renderFold(v, size);
3535
6106
  }
3536
6107
  dividers.forEach((d, i) => {
3537
6108
  const a = specs[i];
3538
6109
  const b = specs[i + 1];
3539
- d.setAttribute("aria-valuenow", String(Math.round(views.get(a.id).shown)));
3540
- d.setAttribute("aria-controls", `${a.id} ${b.id}`);
6110
+ const now = String(Math.round(views.get(a.id).shown));
6111
+ if (d.getAttribute("aria-valuenow") !== now)
6112
+ d.setAttribute("aria-valuenow", now);
6113
+ if (!d.hasAttribute("aria-controls"))
6114
+ d.setAttribute("aria-controls", `${a.id} ${b.id}`);
3541
6115
  });
3542
6116
  const layout = snapshot();
3543
6117
  const report = JSON.stringify(layout);
@@ -3570,51 +6144,76 @@ function split(container, options) {
3570
6144
  const loop = frameLoop(win, (dt) => {
3571
6145
  for (const [id, s] of springs) {
3572
6146
  s.step(dt);
3573
- if (s.resting)
6147
+ if (s.resting) {
3574
6148
  springs.delete(id);
6149
+ views.get(id).folding = false;
6150
+ }
6151
+ }
6152
+ let flipping = false;
6153
+ for (const v of views.values()) {
6154
+ v.flip.step(dt);
6155
+ if (!v.flip.resting)
6156
+ flipping = true;
3575
6157
  }
3576
6158
  const shown = current();
3577
- render(shown);
3578
- if (springs.size === 0) {
6159
+ if (springs.size === 0)
3579
6160
  rememberOpen(shown);
3580
- render(shown);
3581
- }
3582
- return springs.size > 0;
6161
+ render(shown);
6162
+ return springs.size > 0 || flipping;
3583
6163
  });
3584
6164
  const animate = (motion = SETTLE) => {
3585
6165
  const to = target();
6166
+ for (const v of views.values())
6167
+ if (v.folding && !collapsed[v.spec.id])
6168
+ v.open = to[v.spec.id];
3586
6169
  if (reducedMotion(win)) {
3587
6170
  springs.clear();
3588
- render(to);
6171
+ const arriving = [...views.values()].filter((v) => v.folding).map((v) => collapsed[v.spec.id] ? v.rail : v.rail ? v.inner : v.body);
6172
+ for (const v of views.values()) {
6173
+ v.folding = false;
6174
+ v.flip.snap(collapsed[v.spec.id] ? 1 : 0);
6175
+ }
3589
6176
  rememberOpen(to);
6177
+ render(to);
6178
+ for (const el of arriving)
6179
+ el?.animate([{ opacity: 0 }, { opacity: 1 }], {
6180
+ duration: 160,
6181
+ easing: "cubic-bezier(.2,.8,.2,1)"
6182
+ });
3590
6183
  return;
3591
6184
  }
3592
6185
  for (const spec of specs) {
3593
- const from = views.get(spec.id).shown;
6186
+ const v = views.get(spec.id);
3594
6187
  const goal = to[spec.id];
3595
6188
  const s = springs.get(spec.id);
3596
6189
  if (s) {
3597
6190
  s.omega = motion.omega;
3598
6191
  s.zeta = motion.zeta;
3599
6192
  s.set(goal);
3600
- } else if (Math.abs(goal - from) > 0.5) {
3601
- springs.set(spec.id, new Spring(from, motion.omega, motion.zeta, 0.2).set(goal));
3602
- }
6193
+ } else if (Math.abs(goal - v.shown) > 0.5) {
6194
+ springs.set(spec.id, new Spring(v.shown, motion.omega, motion.zeta, 0.2).set(goal));
6195
+ } else
6196
+ v.folding = false;
3603
6197
  }
3604
6198
  loop.wake();
3605
6199
  };
6200
+ const fold = (id, value) => {
6201
+ const v = views.get(id);
6202
+ if (value && !v.folding)
6203
+ v.open = v.shown;
6204
+ collapsed[id] = value;
6205
+ v.folding = true;
6206
+ v.flip.set(value ? 1 : 0);
6207
+ };
6208
+ const release = (id) => {
6209
+ springs.delete(id);
6210
+ views.get(id).folding = false;
6211
+ };
3606
6212
  function setCollapsed(id, value, save = true) {
3607
6213
  const spec = specs.find((p) => p.id === id);
3608
6214
  if (!spec?.collapsible || collapsed[id] === value)
3609
6215
  return;
3610
- const v = views.get(id);
3611
- if (value && !springs.has(id))
3612
- v.open = v.shown;
3613
- if (!value) {
3614
- collapsed[id] = false;
3615
- v.open = target()[id];
3616
- }
3617
- collapsed[id] = value;
6216
+ fold(id, value);
3618
6217
  if (save)
3619
6218
  persist();
3620
6219
  animate(COLLAPSE);
@@ -3636,7 +6235,8 @@ function split(container, options) {
3636
6235
  d.focus({ preventScroll: true });
3637
6236
  const shown = current();
3638
6237
  for (const id of [a.id, b.id])
3639
- springs.delete(id);
6238
+ if (!collapsed[id])
6239
+ release(id);
3640
6240
  start = { ...shown };
3641
6241
  for (const id of [a.id, b.id])
3642
6242
  if (collapsed[id])
@@ -3670,7 +6270,7 @@ function split(container, options) {
3670
6270
  return;
3671
6271
  setSizes(result.sizes);
3672
6272
  for (const id of [a.id, b.id])
3673
- springs.delete(id);
6273
+ release(id);
3674
6274
  const shown = current();
3675
6275
  render(shown);
3676
6276
  rememberOpen(shown);
@@ -3733,17 +6333,21 @@ function split(container, options) {
3733
6333
  }
3734
6334
  function reset(...ids) {
3735
6335
  const which = ids.length ? ids : specs.map((p) => p.id);
6336
+ let folds = false;
3736
6337
  for (const id of which) {
3737
6338
  delete sizes[id];
3738
6339
  const spec = specs.find((p) => p.id === id);
3739
- if (spec?.collapsible)
3740
- collapsed[id] = Boolean(spec.collapsed);
6340
+ const shut = Boolean(spec?.collapsed);
6341
+ if (spec?.collapsible && collapsed[id] !== shut) {
6342
+ fold(id, shut);
6343
+ folds = true;
6344
+ }
3741
6345
  }
3742
6346
  if (which.some((id) => flex.has(id)))
3743
6347
  for (const id of flex)
3744
6348
  delete sizes[id];
3745
6349
  persist();
3746
- animate();
6350
+ animate(folds ? COLLAPSE : SETTLE);
3747
6351
  }
3748
6352
  {
3749
6353
  const first = target();
@@ -3752,18 +6356,29 @@ function split(container, options) {
3752
6356
  views.get(spec.id).open = collapsed[spec.id] ? solve({ panes: specs, sizes, collapsed: open, total: total(), rail })[spec.id] : first[spec.id];
3753
6357
  }
3754
6358
  render(first);
6359
+ measureChevrons();
6360
+ render(first);
3755
6361
  }
3756
- const relayout = () => {
3757
- readRail();
3758
- if (springs.size)
6362
+ const relayout = (remeasure) => {
6363
+ if (remeasure)
6364
+ measure();
6365
+ if (springs.size) {
6366
+ if (remeasure)
6367
+ measureChevrons();
3759
6368
  return loop.wake();
6369
+ }
3760
6370
  const shown = target();
3761
- render(shown);
3762
6371
  rememberOpen(shown);
6372
+ render(shown);
6373
+ if (remeasure) {
6374
+ measureChevrons();
6375
+ render(shown);
6376
+ }
3763
6377
  };
3764
- const ro = new ResizeObserver(relayout);
6378
+ const ro = new ResizeObserver(() => relayout(!measured));
3765
6379
  ro.observe(root);
3766
- root.addEventListener("knobs-restyle", relayout);
6380
+ const restyled = () => relayout(true);
6381
+ root.addEventListener("knobs-restyle", restyled);
3767
6382
  const paneApi = (id) => {
3768
6383
  const v = views.get(id);
3769
6384
  if (!v)
@@ -3786,9 +6401,9 @@ function split(container, options) {
3786
6401
  v.titleEl.textContent = text;
3787
6402
  if (v.countEl && count !== undefined)
3788
6403
  v.countEl.textContent = String(count);
3789
- const label = v.rail?.querySelector("span");
3790
- if (label)
3791
- label.textContent = text;
6404
+ if (v.railLabel)
6405
+ v.railLabel.textContent = text;
6406
+ measureChevrons(v);
3792
6407
  }
3793
6408
  };
3794
6409
  };
@@ -3823,7 +6438,7 @@ function split(container, options) {
3823
6438
  unregisterHints?.();
3824
6439
  loop.stop();
3825
6440
  ro.disconnect();
3826
- root.removeEventListener("knobs-restyle", relayout);
6441
+ root.removeEventListener("knobs-restyle", restyled);
3827
6442
  host?.removeAttribute("data-nested");
3828
6443
  root.remove();
3829
6444
  }
@@ -3894,7 +6509,7 @@ function menu(anchor, items, options = {}) {
3894
6509
  const lead = doc.createElement("span");
3895
6510
  lead.className = "knobs-menu-lead";
3896
6511
  if (e.checked)
3897
- lead.innerHTML = e.radio ? RADIO : CHECK;
6512
+ lead.innerHTML = e.radio ? RADIO : CHECK2;
3898
6513
  else if (e.icon) {
3899
6514
  if (e.icon.trimStart().startsWith("<"))
3900
6515
  lead.innerHTML = e.icon;
@@ -4232,7 +6847,7 @@ function contextMenu(target, items, options = {}) {
4232
6847
  }
4233
6848
  };
4234
6849
  }
4235
- var CHECK = '<svg viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 8.5 6.5 11.5 12.5 4.5"/></svg>';
6850
+ var CHECK2 = '<svg viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 8.5 6.5 11.5 12.5 4.5"/></svg>';
4236
6851
  var RADIO = '<svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="3" fill="currentColor"/></svg>';
4237
6852
 
4238
6853
  // src/ui/timeline-model.ts
@@ -4623,7 +7238,7 @@ function timeline(container, options) {
4623
7238
  renderHead();
4624
7239
  };
4625
7240
  const syncPlay = () => {
4626
- playBtn.innerHTML = playing ? PAUSE : PLAY;
7241
+ playBtn.innerHTML = playing ? PAUSE : PLAY2;
4627
7242
  playBtn.setAttribute("aria-label", playing ? "Pause" : "Play");
4628
7243
  root.toggleAttribute("data-playing", playing);
4629
7244
  };
@@ -5234,7 +7849,7 @@ function timeline(container, options) {
5234
7849
  };
5235
7850
  return api;
5236
7851
  }
5237
- var PLAY = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M5 3.6v8.8c0 .5.5.8.9.5l6.6-4.4c.4-.3.4-.8 0-1L5.9 3.1c-.4-.3-.9 0-.9.5Z" fill="currentColor"/></svg>';
7852
+ var PLAY2 = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M5 3.6v8.8c0 .5.5.8.9.5l6.6-4.4c.4-.3.4-.8 0-1L5.9 3.1c-.4-.3-.9 0-.9.5Z" fill="currentColor"/></svg>';
5238
7853
  var PAUSE = '<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="4" y="3.2" width="2.8" height="9.6" rx="1" fill="currentColor"/><rect x="9.2" y="3.2" width="2.8" height="9.6" rx="1" fill="currentColor"/></svg>';
5239
7854
 
5240
7855
  // src/ui/tree-model.ts
@@ -5493,7 +8108,7 @@ function tree(container, options) {
5493
8108
  let index = indexTree(items, access);
5494
8109
  const renamed = new Map;
5495
8110
  const root = doc.createElement("div");
5496
- root.className = "knobs-tree";
8111
+ root.className = "knobs-tree knobs-scroll-fade";
5497
8112
  root.tabIndex = 0;
5498
8113
  root.setAttribute("role", "tree");
5499
8114
  root.setAttribute("aria-multiselectable", "true");
@@ -5559,7 +8174,7 @@ function tree(container, options) {
5559
8174
  const icon = doc.createElement("span");
5560
8175
  icon.className = "knobs-tree-icon";
5561
8176
  const label = doc.createElement("span");
5562
- label.className = "knobs-tree-label";
8177
+ label.className = "knobs-tree-label knobs-scroll-fade-x";
5563
8178
  const text = doc.createElement("span");
5564
8179
  text.className = "knobs-tree-text";
5565
8180
  label.append(text);
@@ -6121,6 +8736,8 @@ function tree(container, options) {
6121
8736
  const rule = options.typeahead ?? true;
6122
8737
  if (rule === false)
6123
8738
  return false;
8739
+ if (!(running && typed !== "") && !/^[\p{L}\p{N}]$/u.test(key))
8740
+ return false;
6124
8741
  if (!(running && typed !== "") && options.hints !== false && claimsHintKey(doc, key))
6125
8742
  return false;
6126
8743
  return rule === true || running && typed !== "" || rule(key);
@@ -6370,6 +8987,14 @@ function builtinIcon(id, on) {
6370
8987
  }
6371
8988
 
6372
8989
  // src/ui/solid.ts
8990
+ var follow = (read, apply) => {
8991
+ const changes = propChanges(untrack(read));
8992
+ createEffect(() => {
8993
+ const changed = changes(read());
8994
+ if (anyChanged(changed))
8995
+ untrack(() => apply(changed));
8996
+ });
8997
+ };
6373
8998
  function Split(props) {
6374
8999
  if (typeof document === "undefined")
6375
9000
  return null;
@@ -6410,19 +9035,14 @@ function Tree(props) {
6410
9035
  }
6411
9036
  }));
6412
9037
  untrack(() => props.onReady?.(t));
6413
- const follow = (read, apply) => {
6414
- let first = true;
6415
- createEffect(() => {
6416
- const value = read();
6417
- if (first)
6418
- first = false;
6419
- else
6420
- untrack(() => apply(value));
6421
- });
6422
- };
6423
- follow(() => props.items, (items) => t.update(items));
6424
- follow(() => props.selection, (selection) => selection && t.select(selection));
6425
- follow(() => props.filter, (filter) => t.filter(filter));
9038
+ follow(() => ({ items: props.items, filter: props.filter, selection: props.selection }), (c) => {
9039
+ if ("items" in c)
9040
+ t.update(c.items);
9041
+ if ("filter" in c)
9042
+ t.filter(c.filter);
9043
+ if (c.selection)
9044
+ t.select(c.selection);
9045
+ });
6426
9046
  onCleanup(() => t.dispose());
6427
9047
  return host;
6428
9048
  }
@@ -6440,38 +9060,20 @@ function Timeline(props) {
6440
9060
  onCross: (m) => props.onCross?.(m)
6441
9061
  }));
6442
9062
  untrack(() => props.onReady?.(t));
6443
- const follow = (key) => {
6444
- let first = true;
6445
- createEffect(() => {
6446
- const value = props[key];
6447
- if (first) {
6448
- first = false;
6449
- return;
6450
- }
6451
- untrack(() => {
6452
- if (value === undefined)
6453
- return;
6454
- if (key === "time" && Math.abs(value - t.time) < 0.000001)
6455
- return;
6456
- if (key === "playing" && value === t.playing)
6457
- return;
6458
- if (key === "markers" && value === t.markers || key === "ranges" && value === t.ranges)
6459
- return;
6460
- t.update({ [key]: value });
6461
- });
6462
- });
6463
- };
6464
- for (const key of [
6465
- "time",
6466
- "playing",
6467
- "markers",
6468
- "ranges",
6469
- "duration",
6470
- "fps",
6471
- "units",
6472
- "speed"
6473
- ])
6474
- follow(key);
9063
+ follow(() => ({
9064
+ duration: props.duration,
9065
+ fps: props.fps,
9066
+ units: props.units,
9067
+ speed: props.speed,
9068
+ markers: props.markers,
9069
+ ranges: props.ranges,
9070
+ time: props.time,
9071
+ playing: props.playing
9072
+ }), (c) => {
9073
+ const next = timelineChanges(t, c);
9074
+ if (anyChanged(next))
9075
+ t.update(next);
9076
+ });
6475
9077
  onCleanup(() => t.dispose());
6476
9078
  return host;
6477
9079
  }
@@ -6488,23 +9090,72 @@ function Bento(props) {
6488
9090
  onActive: (id) => props.onActive?.(id)
6489
9091
  }));
6490
9092
  untrack(() => props.onReady?.(b));
6491
- const follow = (read, apply) => {
6492
- let first = true;
6493
- createEffect(() => {
6494
- const value = read();
6495
- if (first)
6496
- first = false;
6497
- else
6498
- untrack(() => apply(value));
6499
- });
6500
- };
6501
- follow(() => props.items, (items) => b.update(items));
6502
- follow(() => props.picks, (picks) => picks && b.pick(picks));
9093
+ follow(() => ({ items: props.items, picks: props.picks }), (c) => {
9094
+ if ("items" in c)
9095
+ b.update(c.items);
9096
+ if (c.picks)
9097
+ b.pick(c.picks);
9098
+ });
6503
9099
  onCleanup(() => b.dispose());
6504
9100
  return host;
6505
9101
  }
9102
+ function Activity(props) {
9103
+ if (typeof document === "undefined")
9104
+ return null;
9105
+ const host = document.createElement("div");
9106
+ host.style.height = "100%";
9107
+ const a = untrack(() => activity(host, {
9108
+ ...props,
9109
+ onSelect: (id, event, source) => props.onSelect?.(id, event, source),
9110
+ onActivate: (id, event) => props.onActivate?.(id, event),
9111
+ onToggle: (id, open) => props.onToggle?.(id, open),
9112
+ onPlay: props.onPlay && ((id, event) => props.onPlay?.(id, event))
9113
+ }));
9114
+ untrack(() => props.onReady?.(a));
9115
+ follow(() => ({ events: props.events, newest: props.newest, selected: props.selected }), (c) => {
9116
+ if (c.events || c.newest)
9117
+ a.update({ events: c.events, newest: c.newest });
9118
+ if (c.selected !== undefined && c.selected !== a.selected)
9119
+ a.select(c.selected);
9120
+ });
9121
+ onCleanup(() => a.destroy());
9122
+ return host;
9123
+ }
9124
+ function OptionPicker(props) {
9125
+ if (typeof document === "undefined")
9126
+ return null;
9127
+ const host = document.createElement("div");
9128
+ const owner = getOwner();
9129
+ const p = untrack(() => optionPicker(host, {
9130
+ ...props,
9131
+ onSelect: (id, source) => props.onSelect?.(id, source),
9132
+ onChoose: (id) => props.onChoose?.(id),
9133
+ body: (option, element) => {
9134
+ const render = props.children;
9135
+ if (!render)
9136
+ return;
9137
+ return createRoot((dispose) => {
9138
+ insert(element, untrack(() => render(option)));
9139
+ return dispose;
9140
+ }, owner);
9141
+ }
9142
+ }));
9143
+ untrack(() => props.onReady?.(p));
9144
+ follow(() => ({ options: props.options, selected: props.selected, chosen: props.chosen }), (c) => {
9145
+ if ("options" in c)
9146
+ p.set(c.options);
9147
+ if (c.selected != null)
9148
+ p.select(c.selected);
9149
+ if ("chosen" in c)
9150
+ p.choose(c.chosen ?? null);
9151
+ });
9152
+ onCleanup(() => p.destroy());
9153
+ return host;
9154
+ }
6506
9155
  export {
9156
+ Activity,
6507
9157
  Bento,
9158
+ OptionPicker,
6508
9159
  Split,
6509
9160
  Timeline,
6510
9161
  Tree