@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-react.js CHANGED
@@ -10,6 +10,701 @@ import {
10
10
  } from "react";
11
11
  import { createPortal } from "react-dom";
12
12
 
13
+ // node_modules/@kitlangton/rolling-number/dist/chunk-vm1v7ncb.js
14
+ var z = new Map;
15
+ var le = /[\u0590-\u08ff\u200e\u200f\u202a-\u202e\u2066-\u2069\ufb1d-\ufeff]/u;
16
+ var U = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:.-/&+'";
17
+ var V = new Map;
18
+ function Re(e) {
19
+ let t = V.get(e);
20
+ if (!t) {
21
+ if (t = [...new Set(ue(e))], V.size >= 16)
22
+ V.delete(V.keys().next().value);
23
+ V.set(e, t);
24
+ }
25
+ return t;
26
+ }
27
+ function ue(e) {
28
+ if (typeof Intl.Segmenter === "function")
29
+ return [...new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(e)].map((t) => t.segment);
30
+ return [...e];
31
+ }
32
+ function pe(e, t = {}) {
33
+ let n = t.charset ?? U, i = !le.test(e);
34
+ if (!i)
35
+ return { text: e, tokens: [], rollable: i, signature: "text", magnitude: "" };
36
+ let r = ue(e).map((s, o) => {
37
+ if (t.transition === "direct")
38
+ return { key: `char:${o}`, identity: `char:${o}`, text: s, wheel: [s], index: 0 };
39
+ let h = Re(typeof n === "string" ? n : n[o] ?? n.at(-1) ?? U), c = `char:${o}`, m = h.indexOf(s);
40
+ return m >= 0 ? { key: c, identity: c, text: s, wheel: h, index: m } : { key: `${c}:${s}`, identity: c, text: s };
41
+ });
42
+ return { text: e, tokens: r, rollable: i, signature: `text:${t.transition ?? "wheel"}`, magnitude: "" };
43
+ }
44
+ function me(e, t, n = 0.14) {
45
+ return { target: 0, duration: t, points: Array.from({ length: 49 }, (i, r) => {
46
+ if (r === 48)
47
+ return 0;
48
+ let s = Math.max(0, Math.min(1, (r / 48 - n) / (1 - n)));
49
+ return e * (1 + 10 * s) * Math.exp(-10 * s);
50
+ }) };
51
+ }
52
+ function W(e, t) {
53
+ if (t <= 0 || e.duration <= 0)
54
+ return e;
55
+ let n = e.duration + t, i = Math.round((e.points.length - 1) * n / e.duration) + 1, r = e.points[0] ?? e.target;
56
+ return { target: e.target, duration: n, points: Array.from({ length: i }, (s, o) => {
57
+ if (o === i - 1)
58
+ return e.target;
59
+ let h = o / (i - 1) * n - t;
60
+ return h <= 0 ? r : J(e, h).position;
61
+ }) };
62
+ }
63
+ function L(e, t, n, i) {
64
+ if (i <= 0)
65
+ return { points: [t, t], duration: 0, target: t };
66
+ 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;
67
+ return { points: Array.from({ length: 49 }, (m, f) => {
68
+ if (f === 48)
69
+ return t;
70
+ let y = f / 48;
71
+ return t + (s + (h + 10 * s) * y) * Math.exp(-10 * y);
72
+ }), duration: i, target: t };
73
+ }
74
+ function fe(e, t = 0, n = 24) {
75
+ if (e.duration <= 0)
76
+ return { points: [0, 0], duration: 0, target: 0 };
77
+ let i = e.duration / (e.points.length - 1) / 1000;
78
+ return { duration: e.duration, target: 0, points: e.points.map((r, s, o) => {
79
+ if (s === 0)
80
+ return Math.max(0, Math.min(1, t));
81
+ if (s === o.length - 1)
82
+ return 0;
83
+ let h = Math.abs((o[s + 1] - o[s - 1]) / (2 * i)), c = n / 6;
84
+ return Math.max(0, Math.min(1, (h - c) / (n - c)));
85
+ }) };
86
+ }
87
+ function J(e, t) {
88
+ if (t >= e.duration || e.duration === 0)
89
+ return { position: e.target, velocity: 0 };
90
+ 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;
91
+ return { position: r + (s - r) * (n - i), velocity: (s - r) * (e.points.length - 1) * 1000 / e.duration };
92
+ }
93
+ function Q(e, t, n, i = 10) {
94
+ let r = Math.floor(e / i) * i + t;
95
+ if (n > 0 && r < e - 0.001)
96
+ r += i;
97
+ else if (n < 0 && r > e + 0.001)
98
+ r -= i;
99
+ else if (n === 0)
100
+ r += Math.round((e - r) / i) * i;
101
+ return r;
102
+ }
103
+ var P = (e, t) => e[(t % e.length + e.length) % e.length];
104
+ function ge(e, t, n) {
105
+ let i = Math.floor(t), r = t - i, s = [P(e, i)];
106
+ if (r > 0.00001)
107
+ s.push(P(e, i + 1));
108
+ if (s.at(-1) !== n)
109
+ s.push(n);
110
+ return { wheel: s, from: r, target: s.length - 1 };
111
+ }
112
+ function ye(e, t = "outward") {
113
+ if (t !== "outward") {
114
+ let r = e.map((o, h) => o ? -1 : h).filter((o) => o >= 0);
115
+ if (t === "end")
116
+ r.reverse();
117
+ let s = e.map((o) => o ? 0 : 1);
118
+ if (t !== "none")
119
+ r.forEach((o, h) => {
120
+ s[o] = h + 1;
121
+ });
122
+ return s;
123
+ }
124
+ let n = e.map((r, s) => r ? 0 : s + 1);
125
+ if (!e.includes(true))
126
+ return n;
127
+ let i = -1 / 0;
128
+ for (let r = 0;r < e.length; r++)
129
+ if (e[r])
130
+ i = r;
131
+ else
132
+ n[r] = r - i;
133
+ i = 1 / 0;
134
+ for (let r = e.length - 1;r >= 0; r--)
135
+ if (e[r])
136
+ i = r;
137
+ else
138
+ n[r] = Math.min(n[r], i - r);
139
+ return n;
140
+ }
141
+ function Z(e, t) {
142
+ let n = new Map, i = [], r = 0;
143
+ for (let s of e) {
144
+ let o = t.get(s);
145
+ if (!o) {
146
+ i.push(s);
147
+ continue;
148
+ }
149
+ for (let h of i)
150
+ n.set(h, o.x);
151
+ i.length = 0, r = o.x + o.width;
152
+ }
153
+ for (let s of i)
154
+ n.set(s, r);
155
+ return n;
156
+ }
157
+ var ee = new WeakMap;
158
+
159
+ class Y {
160
+ view;
161
+ media;
162
+ members = new Set;
163
+ pending = new Set;
164
+ sizes = new WeakMap;
165
+ intersections = new WeakMap;
166
+ resize;
167
+ intersection;
168
+ frame = 0;
169
+ static for(e) {
170
+ let t = ee.get(e);
171
+ if (!t)
172
+ t = new Y(e), ee.set(e, t);
173
+ return t;
174
+ }
175
+ constructor(e) {
176
+ this.view = e;
177
+ 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)
178
+ this.resize = new e.ResizeObserver((t) => {
179
+ for (let n of t) {
180
+ let i = this.sizes.get(n.target);
181
+ if (i?.sizeChanged(n.target, n.contentRect.width, n.contentRect.height))
182
+ i.refresh();
183
+ }
184
+ });
185
+ if (e.IntersectionObserver)
186
+ this.intersection = new e.IntersectionObserver((t) => {
187
+ for (let n of t)
188
+ this.intersections.get(n.target)?.visibility(n.isIntersecting);
189
+ }, { rootMargin: "64px" });
190
+ }
191
+ refresh = () => {
192
+ for (let e of this.members)
193
+ e.refresh();
194
+ };
195
+ add(e, t) {
196
+ this.members.add(e), this.intersections.set(t, e), this.intersection?.observe(t);
197
+ }
198
+ watch(e, t) {
199
+ this.sizes.set(e, t), this.resize?.observe(e);
200
+ }
201
+ unwatch(e) {
202
+ this.resize?.unobserve(e), this.sizes.delete(e);
203
+ }
204
+ enqueue(e) {
205
+ if (this.pending.add(e), !this.frame)
206
+ this.frame = this.view.requestAnimationFrame(() => {
207
+ this.frame = 0;
208
+ let t = [...this.pending];
209
+ this.pending.clear();
210
+ let n = t.map((r) => r.stage());
211
+ for (let r of n)
212
+ r?.();
213
+ let i = t.map((r) => r.measure());
214
+ for (let r of i)
215
+ r?.();
216
+ });
217
+ }
218
+ remove(e, t) {
219
+ if (this.pending.delete(e), this.members.delete(e), this.intersection?.unobserve(t), this.intersections.delete(t), this.members.size)
220
+ return;
221
+ 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);
222
+ }
223
+ }
224
+ var be = new WeakMap;
225
+ function F(e, t, n) {
226
+ let i = e.animate(t, n), r = e.ownerDocument.timeline?.currentTime;
227
+ if (typeof r === "number" && i.playState === "running")
228
+ i.startTime = r;
229
+ return i;
230
+ }
231
+ function ve(e) {
232
+ let t = e.ownerDocument.defaultView;
233
+ if (!t)
234
+ return false;
235
+ let n = be.get(t);
236
+ if (n === undefined)
237
+ n = t.CSS?.supports("animation-timing-function", "linear(0, 1)") ?? false, be.set(t, n);
238
+ return n;
239
+ }
240
+
241
+ class N {
242
+ element;
243
+ property;
244
+ animation;
245
+ motion;
246
+ value = 0;
247
+ constructor(e, t) {
248
+ this.element = e;
249
+ this.property = t;
250
+ }
251
+ read() {
252
+ let e = this.animation?.currentTime;
253
+ return this.animation && this.motion ? J(this.motion, typeof e === "number" ? e : 0) : { position: this.value, velocity: 0 };
254
+ }
255
+ set(e, t) {
256
+ this.cancel(), this.value = e, this.element.style.setProperty(this.property, t(e));
257
+ }
258
+ play(e, t, n) {
259
+ 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)) {
260
+ n?.();
261
+ return;
262
+ }
263
+ 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 });
264
+ this.animation = m, this.motion = e, m.onfinish = () => {
265
+ if (this.animation !== m)
266
+ return;
267
+ this.animation = undefined, this.motion = undefined, m.onfinish = null, m.cancel(), n?.();
268
+ };
269
+ }
270
+ cancel() {
271
+ if (this.animation)
272
+ this.animation.onfinish = null, this.animation.cancel(), this.animation = undefined;
273
+ this.motion = undefined;
274
+ }
275
+ }
276
+ var te = "http://www.w3.org/2000/svg";
277
+ var $e = 0;
278
+
279
+ class G {
280
+ host;
281
+ layers = new Map;
282
+ filter;
283
+ intensity = 1;
284
+ constructor(e) {
285
+ this.host = e;
286
+ }
287
+ filterUrl(e) {
288
+ if (!this.filter) {
289
+ let n = this.host.ownerDocument, i = n.createElementNS(te, "svg");
290
+ i.classList.add("rn-blur-defs"), i.setAttribute("aria-hidden", "true"), i.setAttribute("focusable", "false");
291
+ let r = n.createElementNS(te, "filter"), s;
292
+ do
293
+ s = `rn-vertical-blur-${++$e}`;
294
+ while (n.getElementById(s));
295
+ r.id = s, r.setAttribute("x", "-15%"), r.setAttribute("width", "130%"), r.setAttribute("color-interpolation-filters", "sRGB");
296
+ let o = n.createElementNS(te, "feGaussianBlur");
297
+ r.append(o), i.append(r), this.host.append(i), this.filter = { svg: i, blur: o, id: s, height: 0 };
298
+ }
299
+ let t = e * 0.035 * this.intensity;
300
+ if (this.filter.height !== t)
301
+ this.filter.blur.setAttribute("stdDeviation", `0 ${t}`), this.filter.height = t;
302
+ return `url("#${this.filter.id}")`;
303
+ }
304
+ apply(e, t, n, i, r = "roll") {
305
+ let s = fe(t, i, r === "entry" ? 6 : 24);
306
+ if (s.points.every((f) => f === 0))
307
+ return false;
308
+ let o = this.host.ownerDocument.createElement("span");
309
+ o.className = "rn-sharp", o.append(...e.childNodes);
310
+ let h = o.cloneNode(true);
311
+ h.className = "rn-smear", h.style.filter = this.filterUrl(n), e.append(o, h);
312
+ let c = new N(o, "opacity"), m = new N(h, "opacity");
313
+ return this.layers.set(e, { sharp: o, sharpOpacity: c, smearOpacity: m }), c.play(s, (f) => String(1 - f)), m.play(s, String), true;
314
+ }
315
+ remove(e) {
316
+ let t = this.layers.get(e);
317
+ if (!t)
318
+ return 0;
319
+ let n = t.smearOpacity.read().position;
320
+ return t.sharpOpacity.cancel(), t.smearOpacity.cancel(), e.replaceChildren(...t.sharp.childNodes), this.layers.delete(e), n;
321
+ }
322
+ destroy() {
323
+ for (let e of this.layers.keys())
324
+ this.remove(e);
325
+ this.filter?.svg.remove(), this.filter = undefined;
326
+ }
327
+ }
328
+ function xe(e) {
329
+ return Math.max(45, Math.min(110, e / 7));
330
+ }
331
+ function we(e, t, n) {
332
+ let i = Math.abs(t - e);
333
+ return { points: [e, t], target: t, duration: i * n };
334
+ }
335
+ function Ae(e) {
336
+ let i = Array.from({ length: e + 1 }, (o, h) => ({ "--rn-flap-step": String(h), offset: h / e, easing: "steps(1, end)" })), r = [], s = [];
337
+ for (let o = 0;o < e; o++) {
338
+ let h = o / e, c = (o + 0.5) / e, m = (o + 1) / e;
339
+ 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 });
340
+ }
341
+ return { index: i, falls: r, lands: s };
342
+ }
343
+ function Me(e) {
344
+ for (let t of e.querySelectorAll(".rn-flap-smear")) {
345
+ for (let n of t.getAnimations())
346
+ n.cancel();
347
+ t.remove();
348
+ }
349
+ for (let t of e.querySelectorAll(".rn-flap-sharp")) {
350
+ for (let n of t.getAnimations())
351
+ n.cancel();
352
+ t.classList.remove("rn-flap-sharp");
353
+ }
354
+ }
355
+ function ke(e, t, n, i, r, s, o, h) {
356
+ let c = e.ownerDocument, m = Math.abs(i - n), f = i >= n ? 1 : -1;
357
+ if (!m) {
358
+ e.replaceChildren();
359
+ return;
360
+ }
361
+ 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");
362
+ g.style.cssText = `display:block;position:relative;height:${r}px`, F(g, y.index, { delay: o, duration: m * s, fill: "both" });
363
+ let T = (p, w) => {
364
+ let k = c.createElement("span");
365
+ k.className = `rn-face rn-flap rn-flap-${p}`, k.style.height = `${r}px`, k.style.overflow = "hidden";
366
+ let v = c.createElement("span");
367
+ v.style.cssText = `display:block;white-space:pre;line-height:${r}px`;
368
+ let l = w ? S : M;
369
+ if (u)
370
+ for (let b of l) {
371
+ let O = c.createElement("span");
372
+ O.style.cssText = `display:block;height:${r}px`, O.textContent = b, v.append(O);
373
+ }
374
+ else
375
+ v.textContent = l.join(`
376
+ `);
377
+ return k.append(v), v.style.transform = `translateY(calc(var(--rn-flap-step) * ${-r}px))`, k;
378
+ }, H = T("bottom", false), j = T("top", true), d = T("top", false), a = T("bottom", true);
379
+ if (h) {
380
+ let p = (w, k) => {
381
+ let v = w.firstElementChild, l = v.cloneNode(true);
382
+ v.classList.add("rn-flap-sharp");
383
+ let b = c.createElement("span");
384
+ 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);
385
+ 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" };
386
+ F(b, O, A), F(v, O.map((C) => ({ ...C, opacity: 1 - C.opacity })), A);
387
+ };
388
+ p(d, y.falls), p(a, y.lands);
389
+ }
390
+ 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);
391
+ }
392
+ var Ee = new WeakMap;
393
+ var _ = new WeakSet;
394
+ var ne = (e) => `translateX(${e}px)`;
395
+ var ie = (e) => `scale(${e})`;
396
+ var re = (e) => String(Math.max(0, Math.min(1, e)));
397
+ var se = (e) => ("transition" in e) && e.transition === "direct";
398
+ function Te(e) {
399
+ if (e.duration !== undefined && (!Number.isFinite(e.duration) || e.duration < 0 || e.duration > 1e4))
400
+ throw RangeError("duration must be between 0 and 10000 milliseconds");
401
+ if (e.flipDuration !== undefined && (!Number.isFinite(e.flipDuration) || e.flipDuration < 1 || e.flipDuration > 1e4))
402
+ throw RangeError("flipDuration must be between 1 and 10000 milliseconds");
403
+ }
404
+ var Fe = { validate(e) {
405
+ if (typeof e.text !== "string")
406
+ throw TypeError("text must be a string");
407
+ if (e.transition !== undefined && e.transition !== "direct" && e.transition !== "wheel")
408
+ throw RangeError("transition must be direct or wheel");
409
+ if (e.transition === "direct" && e.mode === "flap")
410
+ throw RangeError("Direct text transitions require roll mode");
411
+ Te(e);
412
+ }, model: (e) => pe(e.text, e), direction: () => 1 };
413
+
414
+ class oe {
415
+ host;
416
+ source;
417
+ options;
418
+ target;
419
+ displayed;
420
+ semantic;
421
+ measurement;
422
+ visual;
423
+ measures = new Map;
424
+ columns = new Map;
425
+ sizes = new Map;
426
+ scheduler;
427
+ enhanced = false;
428
+ destroyed = false;
429
+ visible = true;
430
+ reset = true;
431
+ measurementPending = false;
432
+ hadClass;
433
+ previousLeft;
434
+ blur;
435
+ blurIntensity = 1;
436
+ constructor(e, t, n) {
437
+ this.host = e;
438
+ this.source = n;
439
+ n.validate(t), this.options = { ...t }, this.target = this.displayed = n.model(t);
440
+ let i = e.ownerDocument, r = (o) => {
441
+ let h = i.createElement("span");
442
+ return h.className = o, h;
443
+ };
444
+ 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);
445
+ let s = i.defaultView;
446
+ if (s && typeof s.matchMedia === "function" && typeof s.requestAnimationFrame === "function" && typeof e.animate === "function")
447
+ this.scheduler = Y.for(s), this.scheduler.add(this, e), this.scheduler.watch(this.measurement, this);
448
+ this.prepare();
449
+ }
450
+ canAnimate() {
451
+ 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;
452
+ }
453
+ update(e) {
454
+ if (this.destroyed)
455
+ return;
456
+ let t = { ...this.options, ...e };
457
+ this.source.validate(t);
458
+ let n = this.source.model(t), i = n.text === this.target.text && n.signature === this.target.signature;
459
+ if (this.options.motionBlur && !t.motionBlur)
460
+ this.blur?.destroy(), this.blur = undefined, Me(this.visual);
461
+ if (se(this.options) !== se(t))
462
+ this.reset = true;
463
+ if (this.options = t, this.target = n, !this.canAnimate()) {
464
+ this.finish();
465
+ return;
466
+ }
467
+ if (i && this.enhanced && !this.reset)
468
+ return;
469
+ this.semantic.textContent = n.text, this.prepare();
470
+ }
471
+ prepare() {
472
+ if (!this.canAnimate()) {
473
+ this.finish();
474
+ return;
475
+ }
476
+ this.measurementPending = true, this.scheduler?.enqueue(this);
477
+ }
478
+ stage() {
479
+ if (this.destroyed)
480
+ return;
481
+ if (!this.canAnimate())
482
+ return () => this.finish();
483
+ return this.previousLeft = this.enhanced && !this.reset ? this.measurement.getBoundingClientRect().left : undefined, () => this.stageMeasurement();
484
+ }
485
+ stageMeasurement() {
486
+ let e = new Set(this.target.tokens.map((n) => n.key));
487
+ for (let [n, i] of this.measures) {
488
+ if (e.has(n))
489
+ continue;
490
+ this.scheduler?.unwatch(i), this.sizes.delete(i), i.remove(), this.measures.delete(n);
491
+ }
492
+ let t = null;
493
+ for (let n of this.target.tokens) {
494
+ let i = this.measures.get(n.key);
495
+ if (!i)
496
+ i = this.host.ownerDocument.createElement("span"), i.className = "rn-token", this.measures.set(n.key, i), this.scheduler?.watch(i, this);
497
+ if (i.textContent !== n.text)
498
+ i.textContent = n.text;
499
+ let r = t ? t.nextSibling : this.measurement.firstChild;
500
+ if (i !== r)
501
+ this.measurement.insertBefore(i, r);
502
+ t = i;
503
+ }
504
+ this.host.dataset.rnMeasuring = "";
505
+ }
506
+ measure() {
507
+ if (this.destroyed)
508
+ return;
509
+ if (!this.canAnimate())
510
+ return () => this.finish();
511
+ let e = this.measurement.getBoundingClientRect(), t = this.host.ownerDocument.defaultView;
512
+ if (!t)
513
+ return () => this.finish();
514
+ let n = t.getComputedStyle(this.measurement);
515
+ if (n.direction === "rtl")
516
+ return () => this.finish();
517
+ let i = parseFloat(n.width), r = parseFloat(n.height);
518
+ if (!i || !r || !e.width || !e.height)
519
+ return () => this.finish();
520
+ let s = e.width / i, o = e.height / r, h = parseFloat(n.getPropertyValue("--rn-blur"));
521
+ this.blurIntensity = Number.isFinite(h) ? Math.max(0, h) : 1, this.sizes.set(this.measurement, { width: i, height: r });
522
+ let c = new Map;
523
+ for (let [f, y] of this.measures) {
524
+ let M = y.getBoundingClientRect(), S = { width: M.width / s, height: M.height / o };
525
+ this.sizes.set(y, S), c.set(f, { ...S, x: (M.left - e.left) / s, y: (M.top - e.top) / o });
526
+ }
527
+ let m = this.previousLeft === undefined ? 0 : (this.previousLeft - e.left) / s;
528
+ return () => this.commit(c, m);
529
+ }
530
+ makeColumn(e) {
531
+ let t = this.host.ownerDocument.createElement("span");
532
+ if (t.className = "rn-slot", t.dataset.rnKey = e.key, e.index !== undefined)
533
+ t.dataset.rnWheel = "";
534
+ if (this.options.mode === "flap")
535
+ t.dataset.rnFlap = "";
536
+ let n = this.host.ownerDocument.createElement("span");
537
+ 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 };
538
+ }
539
+ face(e, t) {
540
+ let n = this.host.ownerDocument.createElement("span");
541
+ n.className = "rn-face", n.textContent = t, n.style.height = `${e.height}px`;
542
+ let i = e.reel.children.length;
543
+ 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);
544
+ }
545
+ rest(e) {
546
+ 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)
547
+ e.roll.set(1, ie);
548
+ else
549
+ e.roll.set(e.token.index, () => "translateY(0px)");
550
+ }
551
+ wrapInk(e) {
552
+ if (this.options.mode === "flap")
553
+ return;
554
+ let t = this.host.ownerDocument.createElement("span");
555
+ t.className = "rn-ink", t.append(...e.reel.childNodes), e.reel.append(t);
556
+ }
557
+ finishEntry(e) {
558
+ if (!e.entry)
559
+ return;
560
+ if (e.entry.blurred)
561
+ this.blur?.remove(e.reel);
562
+ e.entry.track.cancel(), e.entry.element.replaceWith(e.reel), e.entry = undefined;
563
+ }
564
+ enter(e, t, n, i) {
565
+ let r = this.host.ownerDocument.createElement("span");
566
+ r.className = "rn-enter", e.reel.replaceWith(r), r.append(e.reel);
567
+ let s = new N(r, "transform");
568
+ e.entry = { element: r, track: s, blurred: false };
569
+ let o = W(me(e.height * (i?.entryDistance ?? 1), i?.entryDuration ?? t, i?.entryHold), n);
570
+ if (this.options.motionBlur && e.token.text.trim()) {
571
+ this.blur ??= new G(this.host), this.blur.intensity = this.blurIntensity;
572
+ let h = { ...o, points: o.points.map((c) => c / e.height) };
573
+ e.entry.blurred = this.blur.apply(e.reel, h, e.height, 0, "entry");
574
+ }
575
+ s.play(o, (h) => `translateY(${h}px)`, () => this.finishEntry(e));
576
+ }
577
+ commit(e, t) {
578
+ if (this.destroyed)
579
+ return;
580
+ this.measurementPending = false;
581
+ 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);
582
+ if (this.target.text !== this.displayed.text)
583
+ this.host.dataset.rnTrend = h > 0 ? "up" : h < 0 ? "down" : "none";
584
+ let c = new Map([...this.columns].map(([d, a]) => {
585
+ let p = a.x.read();
586
+ return [d, { ...p, x: p.position, width: a.width }];
587
+ })), 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) => {
588
+ let a = this.columns.get(d.key);
589
+ return !a || a.exiting || u && d.index !== undefined && a.token.text !== d.text;
590
+ }), 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));
591
+ for (let [d, a] of this.target.tokens.entries()) {
592
+ let p = e.get(a.key);
593
+ if (!p)
594
+ continue;
595
+ 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;
596
+ if (!l) {
597
+ l = this.makeColumn(a), this.columns.set(a.key, l);
598
+ let x = (v?.x ?? m.get(a.key) ?? p.x) + t;
599
+ l.x.set(n ? x + (p.x - x) * (v ? 0 : r?.entryOrigin ?? 0) : p.x, ne), l.opacity.set(n ? 0 : 1, re);
600
+ }
601
+ let O = l.token.text !== a.text, A = Math.abs(l.height - p.height) > 0.1, C = l.exiting;
602
+ l.exiting = false, l.element.style.width = `${p.width}px`, l.element.style.height = `${p.height}px`, l.element.style.top = `${p.y}px`;
603
+ let q = c.get(a.key);
604
+ if (l.x.play(L(q ? q.position + t : l.x.read().position, p.x, q?.velocity ?? 0, s), ne), b || C || !n) {
605
+ 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;
606
+ l.opacity.play(b ? W(E, w + R) : E, re);
607
+ }
608
+ if (l.height = p.height, l.width = p.width, !n || A)
609
+ this.finishEntry(l);
610
+ if (b && o && a.wheel && i)
611
+ l.roll.set(Math.max(0, a.wheel.indexOf(" ")), () => "translateY(0px)");
612
+ if (o && !A && (O || b) && a.index !== undefined && a.wheel && i && l.roll.read().position !== a.index) {
613
+ 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);
614
+ this.blur?.remove(l.reel);
615
+ let I;
616
+ if (this.options.motionBlur && this.blurIntensity > 0)
617
+ this.blur ??= new G(this.host), this.blur.intensity = this.blurIntensity, I = this.blur.filterUrl(p.height);
618
+ ke(l.reel, a.wheel, E, R, p.height, D, w, I), l.token = a;
619
+ let X = l;
620
+ l.roll.play(W(we(E, R, D), w), () => "translateY(0px)", () => this.rest(X));
621
+ } else if (!b && !A && O && a.index !== undefined && a.wheel && l.token.index !== undefined && i) {
622
+ 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));
623
+ if (l.entry)
624
+ l.entry.blurred = false;
625
+ let Se = this.blur?.remove(l.reel) ?? 0;
626
+ l.reel.replaceChildren();
627
+ for (let B = ae;B <= Oe; B++)
628
+ this.face(l, P(I, B));
629
+ if (this.wrapInk(l), this.options.motionBlur)
630
+ this.blur ??= new G(this.host), this.blur.intensity = this.blurIntensity, this.blur.apply(l.reel, K, p.height, Se);
631
+ l.token = E ? { ...a, wheel: I, index: D } : a;
632
+ let Ce = l;
633
+ l.roll.play(K, (B) => `translateY(${(ae - B) * p.height}px)`, () => this.rest(Ce));
634
+ } else if (b || A || O || !n)
635
+ l.token = a, this.rest(l);
636
+ if (b && i && a.index !== undefined && !o)
637
+ this.enter(l, i, w, r);
638
+ if (v && i && (b || C)) {
639
+ let x = l.roll.read(), E = l;
640
+ l.roll.play(L(b ? 0.96 : x.position, 1, x.velocity, Math.min(i, 180)), ie, () => this.rest(E));
641
+ }
642
+ }
643
+ for (let [d, a] of this.columns) {
644
+ if (e.has(d))
645
+ continue;
646
+ let p = c.get(d), w = S.get(a.token.identity), k = w ? e.get(w) : undefined;
647
+ if (a.x.play(L(p.position + t, k?.x ?? y.get(d) ?? p.position, p.velocity, s), ne), a.exiting)
648
+ continue;
649
+ if (a.exiting = true, k && i) {
650
+ let l = a.roll.read();
651
+ a.roll.play(L(l.position, 1.04, l.velocity, Math.min(i, 180)), ie);
652
+ }
653
+ let v = a.opacity.read();
654
+ a.opacity.play(L(v.position, 0, v.velocity, a.token.index === undefined ? Math.min(i, 180) : i * 0.65), re, () => {
655
+ if (!a.exiting)
656
+ return;
657
+ this.removeColumn(a), this.columns.delete(d);
658
+ });
659
+ }
660
+ this.enhanced = true, this.reset = false, this.displayed = this.target, this.host.dataset.rnReady = "";
661
+ }
662
+ removeColumn(e) {
663
+ this.blur?.remove(e.reel), this.finishEntry(e), e.x.cancel(), e.roll.cancel(), e.opacity.cancel(), e.element.remove();
664
+ }
665
+ refresh() {
666
+ if (this.destroyed)
667
+ return;
668
+ this.reset = true, this.prepare();
669
+ }
670
+ sizeChanged(e, t, n) {
671
+ if (this.measurementPending || !this.host.hasAttribute("data-rn-measuring"))
672
+ return false;
673
+ let i = this.sizes.get(e);
674
+ return !i || Math.abs(i.width - t) > 0.2 || Math.abs(i.height - n) > 0.2;
675
+ }
676
+ visibility(e) {
677
+ if (this.visible === e)
678
+ return;
679
+ if (this.visible = e, e || this.options.pauseOffscreen !== false)
680
+ this.refresh();
681
+ }
682
+ finish() {
683
+ if (this.destroyed)
684
+ return;
685
+ this.measurementPending = false;
686
+ for (let e of this.columns.values())
687
+ this.removeColumn(e);
688
+ 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;
689
+ }
690
+ destroy() {
691
+ if (this.destroyed)
692
+ return;
693
+ this.finish(), this.destroyed = true;
694
+ for (let e of this.measures.values())
695
+ this.scheduler?.unwatch(e);
696
+ if (this.scheduler?.unwatch(this.measurement), this.scheduler?.remove(this, this.host), this.host.replaceChildren(this.host.ownerDocument.createTextNode(this.target.text)), !this.hadClass)
697
+ this.host.classList.remove("rn-root");
698
+ _.delete(this.host);
699
+ }
700
+ }
701
+ function rt(e, t) {
702
+ if (_.has(e))
703
+ throw Error("A rolling number is already mounted on this element");
704
+ let n = new oe(e, t, Fe);
705
+ return _.add(e), n;
706
+ }
707
+
13
708
  // src/core/spring.ts
14
709
  class Spring {
15
710
  x;
@@ -92,323 +787,247 @@ function springCss(value) {
92
787
  return { easing, duration: ms };
93
788
  }
94
789
 
95
- // src/ui/bento-model.ts
96
- var FIT_MAX = 24;
97
- var clamp = (x, a, b) => Math.min(b, Math.max(a, x));
98
- var sum = (xs) => xs.reduce((s, x) => s + x, 0);
99
- var rowTarget = (width, height) => clamp(Math.round(Math.min(height / 3, width / 2.6)), 120, 420);
100
- function justify(aspects, start, end, width, target, gap, tol) {
790
+ // src/ui/activity-model.ts
791
+ function flattenActivity(events, expanded) {
101
792
  const rows = [];
102
- let i = start;
103
- while (i < end) {
104
- let total = 0;
105
- let j = i;
106
- let h = Number.POSITIVE_INFINITY;
107
- let closed = false;
108
- while (j < end) {
109
- const k = j - i + 1;
110
- const next = (width - (k - 1) * gap) / (total + aspects[j]);
111
- if (next <= target) {
112
- if (k === 1 || Math.abs(Math.log(next / target)) <= Math.abs(Math.log(h / target))) {
113
- total += aspects[j];
114
- j++;
115
- h = next;
116
- }
117
- closed = true;
118
- break;
119
- }
120
- total += aspects[j];
121
- h = next;
122
- j++;
123
- }
124
- const slice = aspects.slice(i, j);
125
- if (closed || h <= target * 1.25) {
126
- rows.push({ start: i, end: j, h, ws: slice.map((a) => a * h) });
127
- } else {
128
- const free = width - (j - i - 1) * gap;
129
- const f = free / (target * total);
130
- const ws = slice.map((a) => a * target * (f - 1 <= tol ? f : 1));
131
- rows.push({ start: i, end: j, h: target, ws });
132
- }
133
- i = j;
793
+ for (const event of events) {
794
+ const kids = event.children ?? [];
795
+ const group = kids.length > 0;
796
+ const open = group && expanded.has(event.id);
797
+ rows.push({ id: event.id, event, depth: 0, parent: null, group, expanded: open });
798
+ if (open)
799
+ for (const child of flatChildren(kids))
800
+ rows.push({
801
+ id: child.id,
802
+ event: child,
803
+ depth: 1,
804
+ parent: event.id,
805
+ group: false,
806
+ expanded: false
807
+ });
134
808
  }
135
809
  return rows;
136
810
  }
137
- function partition(xs, k) {
138
- const n = xs.length;
139
- k = clamp(k, 1, n);
140
- const prefix = [0];
141
- for (const x of xs)
142
- prefix.push(prefix.at(-1) + x);
143
- const cost = Array.from({ length: k + 1 }, () => new Float64Array(n + 1).fill(Infinity));
144
- const cut = Array.from({ length: k + 1 }, () => new Int32Array(n + 1));
145
- cost[0][0] = 0;
146
- for (let j = 1;j <= k; j++)
147
- for (let i = j;i <= n; i++)
148
- for (let m = j - 1;m < i; m++) {
149
- const c = Math.max(cost[j - 1][m], prefix[i] - prefix[m]);
150
- if (c < cost[j][i]) {
151
- cost[j][i] = c;
152
- cut[j][i] = m;
153
- }
154
- }
155
- const counts = [];
156
- for (let j = k, i = n;j > 0; j--) {
157
- const m = cut[j][i];
158
- counts.unshift(i - m);
159
- i = m;
811
+ function flatChildren(events) {
812
+ const out = [];
813
+ for (const e of events) {
814
+ out.push(e);
815
+ if (e.children?.length)
816
+ out.push(...flatChildren(e.children));
160
817
  }
161
- return counts;
818
+ return out;
162
819
  }
163
- function rowsBlock(aspects, w, h, gap, tol) {
164
- const n = aspects.length;
165
- if (!n || w <= 0 || h <= 0)
166
- return null;
167
- let best = null;
168
- const most = Math.min(n, Math.max(2, Math.ceil(Math.sqrt(n) * 2)));
169
- for (let r = 1;r <= most; r++) {
170
- const counts = partition(aspects, r);
171
- let at = 0;
172
- const groups = counts.map((c) => aspects.slice(at, at += c));
173
- const heights = groups.map((g) => (w - (g.length - 1) * gap) / sum(g));
174
- const budget = h - (r - 1) * gap;
175
- if (budget <= 0 || heights.some((x) => x <= 0))
176
- break;
177
- const s = Math.min(1, budget / sum(heights));
178
- const fill = s < 1 && 1 / s - 1 <= tol ? 1 / s : 1;
179
- const rects = [];
180
- let y = 0;
181
- let blockW = 0;
182
- groups.forEach((g, k) => {
183
- const rh = heights[k] * s;
184
- const rowW = sum(g.map((a) => a * rh * fill)) + (g.length - 1) * gap;
185
- blockW = Math.max(blockW, rowW);
186
- let x = 0;
187
- for (const a of g) {
188
- rects.push({ x, y, w: a * rh * fill, h: rh });
189
- x += a * rh * fill + gap;
190
- }
191
- y += rh + gap;
192
- });
193
- let i = 0;
194
- groups.forEach((g, k) => {
195
- const rowW = sum(g.map((a) => a * heights[k] * s * fill)) + (g.length - 1) * gap;
196
- const dx = (blockW - rowW) / 2;
197
- for (let m = 0;m < g.length; m++, i++) {
198
- const q = rects[i];
199
- rects[i] = { ...q, x: q.x + dx };
200
- }
201
- });
202
- const area = sum(rects.map((q) => q.w * q.h));
203
- const block = {
204
- rects,
205
- w: blockW,
206
- h: y - gap,
207
- area,
208
- crop: fill - 1,
209
- minH: Math.min(...rects.map((q) => q.h))
210
- };
211
- if (!best || block.area > best.area * 1.0001)
212
- best = block;
820
+ function indexActivity(events) {
821
+ const index = new Map;
822
+ for (const event of events) {
823
+ index.set(event.id, { event, parent: null });
824
+ for (const child of flatChildren(event.children ?? []))
825
+ index.set(child.id, { event: child, parent: event.id });
213
826
  }
214
- return best;
827
+ return index;
215
828
  }
216
- var offset = (rects, dx, dy) => rects.map((q) => ({ x: q.x + dx, y: q.y + dy, w: q.w, h: q.h }));
217
- function heroBlock(aspects, hero, w, h, gap, tol) {
218
- const a = aspects[hero];
219
- const rest = aspects.filter((_, i) => i !== hero);
220
- if (!rest.length)
221
- return null;
222
- let best = null;
223
- const place = (heroRect, restBlock, restAt, bw, bh) => {
224
- if (!restBlock)
225
- return;
226
- const [rx, ry] = restAt(restBlock);
227
- const others = offset(restBlock.rects, rx, ry);
228
- const rects = [...others.slice(0, hero), heroRect, ...others.slice(hero)];
229
- const area = heroRect.w * heroRect.h + restBlock.area;
230
- if (!best || area > best.area * 1.0001)
231
- best = { rects, w: bw, h: bh, area, crop: restBlock.crop, minH: restBlock.minH };
232
- };
233
- for (let f = 0.4;f <= 0.761; f += 0.04) {
234
- {
235
- const hw = Math.min(w * f, h * a);
236
- const hh = hw / a;
237
- const block = rowsBlock(rest, w - hw - gap, h, gap, tol);
238
- if (block) {
239
- const bw = hw + gap + block.w;
240
- const bh = Math.max(hh, block.h);
241
- place({ x: 0, y: (bh - hh) / 2, w: hw, h: hh }, block, (b) => [hw + gap, (bh - b.h) / 2], bw, bh);
242
- }
243
- }
244
- {
245
- const hh = Math.min(h * f, w / a);
246
- const hw = hh * a;
247
- const block = rowsBlock(rest, w, h - hh - gap, gap, tol);
248
- if (block) {
249
- const bw = Math.max(hw, block.w);
250
- place({ x: (bw - hw) / 2, y: 0, w: hw, h: hh }, block, (b) => [(bw - b.w) / 2, hh + gap], bw, hh + gap + block.h);
251
- }
252
- }
829
+ var isActivityKey = (key) => key === "ArrowUp" || key === "ArrowDown" || key === "ArrowLeft" || key === "ArrowRight" || key === "Home" || key === "End";
830
+ function navigateActivity(rows, selected, key) {
831
+ if (!rows.length)
832
+ return {};
833
+ const i = selected === null ? -1 : rows.findIndex((r) => r.id === selected);
834
+ const row = i >= 0 ? rows[i] : undefined;
835
+ switch (key) {
836
+ case "ArrowDown":
837
+ return { select: rows[i < 0 ? 0 : Math.min(rows.length - 1, i + 1)].id };
838
+ case "ArrowUp":
839
+ return { select: rows[i < 0 ? rows.length - 1 : Math.max(0, i - 1)].id };
840
+ case "Home":
841
+ return { select: rows[0].id };
842
+ case "End":
843
+ return { select: rows[rows.length - 1].id };
844
+ case "ArrowRight":
845
+ if (!row?.group)
846
+ return {};
847
+ if (!row.expanded)
848
+ return { toggle: { id: row.id, expanded: true } };
849
+ return rows[i + 1]?.parent === row.id ? { select: rows[i + 1].id } : {};
850
+ case "ArrowLeft":
851
+ if (!row)
852
+ return {};
853
+ if (row.group && row.expanded)
854
+ return { toggle: { id: row.id, expanded: false } };
855
+ return row.parent !== null ? { select: row.parent } : {};
253
856
  }
254
- return best;
255
857
  }
256
- function planBento(aspects, layout) {
257
- const gap = layout.gap ?? 2;
258
- const pad = layout.padding ?? gap;
259
- const tol = layout.tolerance ?? 0.08;
260
- const vw = Math.max(0, layout.width);
261
- const vh = Math.max(0, layout.height);
262
- const w = Math.max(1, vw - pad * 2);
263
- const h = Math.max(1, vh - pad * 2);
264
- const as = aspects.map((a) => Number.isFinite(a) && a > 0 ? clamp(a, 0.05, 20) : 1);
265
- const empty = {
266
- mode: "rows",
267
- rects: [],
268
- width: vw,
269
- height: 0,
270
- coverage: 0,
271
- crop: 0,
272
- headers: []
858
+ function takesActivityKey(key, result, rows) {
859
+ if (result.select !== undefined || result.toggle !== undefined)
860
+ return true;
861
+ return rows > 0 && key !== "ArrowLeft" && key !== "ArrowRight";
862
+ }
863
+ function keepSelection(rows, selected, parentOf) {
864
+ if (selected === null)
865
+ return null;
866
+ if (rows.some((r) => r.id === selected))
867
+ return selected;
868
+ const parent = parentOf(selected);
869
+ return parent && rows.some((r) => r.id === parent) ? parent : null;
870
+ }
871
+ function showsSelection(showing, input, keys = "focus") {
872
+ if (input === "key")
873
+ return true;
874
+ if (input === "pointer")
875
+ return false;
876
+ if (input === "blur")
877
+ return keys === "page" ? showing : false;
878
+ return showing;
879
+ }
880
+ function diffActivity(before, after) {
881
+ const was = new Set(before);
882
+ const now = new Set(after);
883
+ return {
884
+ entered: after.filter((id) => !was.has(id)),
885
+ left: before.filter((id) => !now.has(id)),
886
+ kept: after.filter((id) => was.has(id))
273
887
  };
274
- if (!as.length || vw <= 0)
275
- return empty;
276
- const grouped = layout.groups?.some((g) => g !== undefined) ?? false;
277
- const minTile = layout.minTile ?? Math.max(80, vh / 6);
278
- if ((layout.fit ?? true) && !grouped && as.length <= FIT_MAX && vh > 0) {
279
- const rows = rowsBlock(as, w, h, gap, tol);
280
- let pick = rows && rows.minH >= minTile ? { mode: "fit", block: rows } : null;
281
- const asked = layout.hero !== undefined && layout.hero >= 0 && layout.hero < as.length;
282
- const hero = as.length > 2 ? heroBlock(as, asked ? layout.hero : 0, w, h, gap, tol) : null;
283
- if (hero && hero.minH >= minTile && (asked || !pick || hero.area > pick.block.area * 1.04))
284
- pick = { mode: "hero", block: hero };
285
- if (pick) {
286
- const { block } = pick;
287
- const rects = offset(block.rects, pad + (w - block.w) / 2, pad + (h - block.h) / 2);
288
- return {
289
- mode: pick.mode,
290
- rects,
291
- width: vw,
292
- height: vh,
293
- coverage: block.area / (vw * vh),
294
- crop: block.crop,
295
- headers: []
296
- };
297
- }
298
- }
299
- const target = layout.rowHeight ?? rowTarget(vw, vh);
300
- const header = layout.header ?? 28;
301
- const rects = new Array(as.length);
302
- const headers = [];
303
- let y = pad;
304
- let crop = 0;
305
- let area = 0;
306
- let i = 0;
307
- while (i < as.length) {
308
- const group = layout.groups?.[i];
309
- let end = i + 1;
310
- while (end < as.length && layout.groups?.[end] === group)
311
- end++;
312
- if (group !== undefined) {
313
- if (i > 0)
314
- y += header / 2;
315
- headers.push({ group, y, index: i });
316
- y += header;
317
- }
318
- for (const row of justify(as, i, end, w, target, gap, tol)) {
319
- let x = pad;
320
- row.ws.forEach((rw, k) => {
321
- rects[row.start + k] = { x, y, w: rw, h: row.h };
322
- crop = Math.max(crop, rw / (as[row.start + k] * row.h) - 1);
323
- area += rw * row.h;
324
- x += rw + gap;
325
- });
326
- y += row.h + gap;
327
- }
328
- i = end;
329
- }
330
- const height = y - gap + pad;
331
- return { mode: "rows", rects, width: vw, height, coverage: area / (vw * height), crop, headers };
332
888
  }
333
- function windowOf(plan, top, bottom) {
334
- const { rects } = plan;
335
- if (plan.mode !== "rows")
336
- return rects.map((_, i) => i);
337
- let lo = 0;
338
- let hi = rects.length;
339
- while (lo < hi) {
340
- const mid = lo + hi >> 1;
341
- if (rects[mid].y + rects[mid].h < top)
342
- lo = mid + 1;
343
- else
344
- hi = mid;
345
- }
889
+ function stackRows(heights, top = 0, gap = 0) {
890
+ const y = [];
891
+ let at = top;
892
+ heights.forEach((h, i) => {
893
+ y.push(at);
894
+ at += h + (i < heights.length - 1 ? gap : 0);
895
+ });
896
+ return { y, bottom: at };
897
+ }
898
+ function railSegments(nodes, from, to) {
899
+ const sorted = [...nodes].sort((a, b) => a.y - b.y);
346
900
  const out = [];
347
- for (let i = lo;i < rects.length && rects[i].y <= bottom; i++)
348
- out.push(i);
901
+ for (let i = 0;i < sorted.length - 1; i++) {
902
+ const a = sorted[i];
903
+ const b = sorted[i + 1];
904
+ const top = Math.max(from, a.y + a.r);
905
+ const bottom = Math.min(to, b.y - b.r);
906
+ if (bottom - top > 0.25)
907
+ out.push([top, bottom]);
908
+ }
349
909
  return out;
350
910
  }
351
- 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;
352
- function neighbour(rects, from, dir) {
353
- const f = rects[from];
354
- if (!f)
355
- return from;
356
- const fx = f.x + f.w / 2;
357
- const fy = f.y + f.h / 2;
358
- if (dir === "left" || dir === "right") {
359
- const right = dir === "right";
360
- let best = -1;
361
- let bestD = Number.POSITIVE_INFINITY;
362
- rects.forEach((r, i) => {
363
- if (i === from || !overlapY(f, r))
364
- return;
365
- const d = (r.x + r.w / 2 - fx) * (right ? 1 : -1);
366
- if (d > 1 && d < bestD) {
367
- bestD = d;
368
- best = i;
369
- }
370
- });
371
- if (best >= 0)
372
- return best;
373
- return clamp(from + (right ? 1 : -1), 0, rects.length - 1);
374
- }
375
- const down = dir === "down";
376
- let edge = Number.POSITIVE_INFINITY;
377
- for (const r of rects) {
378
- const d = (r.y + r.h / 2 - fy) * (down ? 1 : -1);
379
- if (d > f.h * 0.25 && !overlapY(f, r))
380
- edge = Math.min(edge, down ? r.y : -(r.y + r.h));
381
- }
382
- if (!Number.isFinite(edge))
383
- return from;
384
- let best = from;
385
- let bestD = Number.POSITIVE_INFINITY;
386
- rects.forEach((r, i) => {
387
- const d = (r.y + r.h / 2 - fy) * (down ? 1 : -1);
388
- if (i === from || d <= f.h * 0.25 || overlapY(f, r))
389
- return;
390
- const near = down ? r.y - edge : -(r.y + r.h) - edge;
391
- if (near > Math.min(r.h, f.h) * 0.5)
392
- return;
393
- const dx = Math.abs(r.x + r.w / 2 - fx);
394
- if (dx < bestD) {
395
- bestD = dx;
396
- best = i;
397
- }
398
- });
399
- return best;
911
+ function holdScroll(hold, height, view) {
912
+ if (hold.from <= 0)
913
+ return { scroll: 0, space: height };
914
+ const end = Math.min(hold.from, Math.max(0, hold.h1 - view));
915
+ const span = hold.h1 - hold.h0;
916
+ const p = Math.abs(span) < 0.5 ? 1 : Math.min(1, Math.max(0, (height - hold.h0) / span));
917
+ const scroll = hold.from + (end - hold.from) * p;
918
+ return { scroll, space: Math.max(height, scroll + view) };
400
919
  }
401
- function fitRect(aspect, w, h, inset = 0) {
402
- const bw = Math.max(1, w - inset * 2);
403
- const bh = Math.max(1, h - inset * 2);
404
- const fw = Math.min(bw, bh * aspect);
405
- const fh = fw / aspect;
406
- return { x: (w - fw) / 2, y: (h - fh) / 2, w: fw, h: fh };
920
+ function formatDuration(duration) {
921
+ if (typeof duration === "string")
922
+ return duration;
923
+ const s = Math.max(0, Math.round(duration));
924
+ const h = Math.floor(s / 3600);
925
+ const m = Math.floor(s % 3600 / 60);
926
+ const sec = String(s % 60).padStart(2, "0");
927
+ return h ? `${h}:${String(m).padStart(2, "0")}:${sec}` : `${m}:${sec}`;
407
928
  }
408
- function motionBlur(speed, max = 3, per = 1600) {
409
- const b = Math.min(max, Math.abs(speed) / per);
410
- return b < 0.25 ? 0 : Math.round(b * 4) / 4;
929
+ function groupTitle(count) {
930
+ return `${count} update${count === 1 ? "" : "s"}`;
931
+ }
932
+
933
+ // src/panel/rolling-css.ts
934
+ var rolling_css_default = `.rn-root {
935
+ display: inline-block;
936
+ position: relative;
937
+ white-space: pre;
938
+ unicode-bidi: isolate;
939
+ vertical-align: baseline;
940
+ }
941
+ .rn-root > .rn-measure,
942
+ .rn-root > .rn-visual { display: none; }
943
+ .rn-root[data-rn-measuring] > .rn-measure {
944
+ display: inline-flex;
945
+ position: relative;
946
+ visibility: hidden;
947
+ white-space: pre;
948
+ padding: 0;
949
+ border: 0;
950
+ }
951
+ .rn-token { display: inline-block; padding: 0; border: 0; }
952
+ .rn-root[data-rn-measuring] > .rn-value,
953
+ .rn-react[data-rn-hydrated] > .rn-semantic,
954
+ .rn-solid[data-rn-hydrated] > .rn-semantic {
955
+ position: absolute;
956
+ inset: 0;
957
+ padding: 0;
958
+ margin: 0;
959
+ color: transparent;
960
+ z-index: 1;
961
+ }
962
+ /* A single native text layer is selectable; decorative reels never enter a copy. */
963
+ .rn-value::selection, .rn-semantic::selection {
964
+ color: HighlightText;
965
+ background: Highlight;
411
966
  }
967
+ .rn-root[data-rn-ready] > .rn-visual {
968
+ display: block;
969
+ position: absolute;
970
+ inset: 0;
971
+ pointer-events: none;
972
+ user-select: none;
973
+ }
974
+ .rn-slot {
975
+ position: absolute;
976
+ left: 0;
977
+ overflow: hidden;
978
+ /* Space for italic overhang without changing the measured advance width. */
979
+ box-sizing: content-box;
980
+ padding-inline: 0.15em;
981
+ margin-inline-start: -0.15em;
982
+ -webkit-mask-image: var(--rn-mask, linear-gradient(
983
+ to bottom,
984
+ transparent,
985
+ #000 var(--rn-edge-fade, 0.12em),
986
+ #000 calc(100% - var(--rn-edge-fade, 0.12em)),
987
+ transparent
988
+ ));
989
+ mask-image: var(--rn-mask, linear-gradient(
990
+ to bottom,
991
+ transparent,
992
+ #000 var(--rn-edge-fade, 0.12em),
993
+ #000 calc(100% - var(--rn-edge-fade, 0.12em)),
994
+ transparent
995
+ ));
996
+ -webkit-mask-repeat: no-repeat;
997
+ mask-repeat: no-repeat;
998
+ }
999
+ @supports (overflow: clip) {
1000
+ .rn-slot { overflow-x: visible; overflow-y: clip; }
1001
+ }
1002
+ .rn-slot:not([data-rn-wheel]) {
1003
+ overflow: visible;
1004
+ -webkit-mask-image: none;
1005
+ mask-image: none;
1006
+ }
1007
+ .rn-enter, .rn-reel, .rn-face { display: block; }
1008
+ .rn-reel { position: relative; }
1009
+ .rn-ink { display: block; position: absolute; inset: 0; will-change: transform; }
1010
+ /* Safari's SVG filter must rasterize the smear's descendants into its input. */
1011
+ .rn-smear .rn-ink { will-change: auto; }
1012
+ /* Each turning half carries its own perspective transform, not a shared 3D scene. */
1013
+ /* The slot acts as the bezel: the card stays vertically framed while it hinges. */
1014
+ .rn-slot[data-rn-flap] { -webkit-mask-image: none; mask-image: none; }
1015
+ /* Hinged faces must occlude the cards behind them, including with default CSS. */
1016
+ :where(.rn-slot[data-rn-flap]) .rn-face { background: var(--rn-flap-background, Canvas); }
1017
+ .rn-flap { position: absolute; inset: 0; transform-origin: 50% 50%; backface-visibility: hidden; }
1018
+ /* --rn-crease is the visible hinge gap between the two halves. */
1019
+ .rn-flap-top { clip-path: inset(0 0 calc(50% + var(--rn-crease, 1px) / 2) 0); }
1020
+ .rn-flap-bottom { clip-path: inset(calc(50% + var(--rn-crease, 1px) / 2) 0 0 0); }
1021
+ .rn-sharp, .rn-smear { display: block; }
1022
+ .rn-smear { position: absolute; inset: 0; pointer-events: none; }
1023
+ .rn-blur-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
1024
+ .rn-face { text-align: center; white-space: pre; }
1025
+ .rn-react, .rn-solid { display: inline-block; position: relative; white-space: pre; unicode-bidi: isolate; }
1026
+ .rn-mount { display: inline-block; user-select: none; }
1027
+ .rn-mount .rn-value { pointer-events: none; }
1028
+ .rn-react:not([data-rn-hydrated]) > .rn-mount,
1029
+ .rn-solid:not([data-rn-hydrated]) > .rn-mount { display: none; }
1030
+ `;
412
1031
 
413
1032
  // src/core/theme.ts
414
1033
  var block = (tokens) => Object.entries(tokens).map(([k, v]) => `${k.startsWith("--") || k === "color-scheme" ? k : `--k-${k}`}:${v}`).join(";");
@@ -502,6 +1121,88 @@ var problemsPreset = {
502
1121
  }
503
1122
  };
504
1123
 
1124
+ // src/ui/activity-css.ts
1125
+ var open = springEasing(2 * Math.PI / 0.22, 1);
1126
+ var activityCss = `
1127
+ :where(.knobs-activity,.knobs-activity *){box-sizing:border-box}
1128
+ :where(.knobs-activity){color-scheme:dark}
1129
+ .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;
1130
+ 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;
1131
+ --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));
1132
+ --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));
1133
+ --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));
1134
+ --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));
1135
+ --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}
1136
+ .knobs-activity [hidden]{display:none!important}
1137
+ /* Clipped below: the content grows on a spring, so a new last row shows as the rail makes room. */
1138
+ .knobs-activity-space{position:relative;width:100%;overflow-x:visible;overflow-y:clip;pointer-events:none}
1139
+ .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}
1140
+ .knobs-activity-line path{fill:none;stroke:var(--ka-line);stroke-width:1px}
1141
+ /* The selection's tint: a wash that glides between rows, shown only once keys move it. It leaves
1142
+ in 100 ms when the pointer is used, and comes back at once. */
1143
+ .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;
1144
+ transition:opacity 100ms ease-out;pointer-events:none;will-change:transform}
1145
+ .knobs-activity[data-keys] .knobs-activity-hl{opacity:1;transition:none}
1146
+ .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);
1147
+ 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)}
1148
+ .knobs-activity-row[data-leaving]{pointer-events:none}
1149
+ .knobs-activity-row[data-depth="1"]{height:var(--ka-row-child)}
1150
+ .knobs-activity-row>*{position:relative}
1151
+ /* Hover: in at once, out over 100 ms; not while keys drive the selection. */
1152
+ .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}
1153
+ .knobs-activity:not([data-keys]) .knobs-activity-row:not([data-leaving]):hover::before{opacity:1;transition-duration:calc(var(--k-hover-in,0) * 1ms)}
1154
+ .knobs-activity-row[data-kind="you"]{--ka-tint:var(--ka-you)}
1155
+ .knobs-activity-row[data-kind="agent"]{--ka-tint:var(--ka-agent)}
1156
+ .knobs-activity-row[data-kind="proposal"]{--ka-tint:var(--ka-proposal)}
1157
+ .knobs-activity-row[data-kind="review"]{--ka-tint:var(--ka-review)}
1158
+ .knobs-activity-row[data-kind="pr"]{--ka-tint:var(--ka-pr)}
1159
+ .knobs-activity-row[data-kind="build"]{--ka-tint:var(--ka-build)}
1160
+ .knobs-activity-row[data-kind="blocked"]{--ka-tint:var(--ka-blocked)}
1161
+ .knobs-activity-row[data-kind="shipped"]{--ka-tint:var(--ka-shipped)}
1162
+ /* The node: the kind's icon on the line (which stops clear of it), or a small dot inside a group. */
1163
+ .knobs-activity-node{flex:none;display:flex;align-items:center;justify-content:center;width:var(--ka-gutter);height:100%;color:var(--ka-tint)}
1164
+ .knobs-activity-node svg{display:block;width:14px;height:14px}
1165
+ .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}
1166
+ .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);
1167
+ opacity:0;scale:.8;transition:opacity .16s ease-out,scale var(--ka-open-ms) var(--ka-open-ease)}
1168
+ .knobs-activity-row[aria-selected="true"] .knobs-activity-node::before{opacity:1;scale:1}
1169
+ .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}
1170
+ .knobs-activity-row[data-current] .knobs-activity-node::after{opacity:1;animation:knobs-activity-breathe 2.6s ease-in-out infinite}
1171
+ @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)}
1172
+ 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)}}
1173
+ .knobs-activity-dot{display:block;width:5px;height:5px;border-radius:50%;background:currentColor}
1174
+ .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}
1175
+ /* The words: who in the text colour, then the title, one line that fades where it's cut off. */
1176
+ .knobs-activity-main{flex:1;min-width:0;overflow:hidden;white-space:nowrap}
1177
+ .knobs-activity-actor{margin-right:6px;font-weight:var(--ku-label-weight);color:var(--ku-text)}
1178
+ .knobs-activity-actor:empty{display:none}
1179
+ .knobs-activity-title{color:var(--ku-label)}
1180
+ .knobs-activity-row[data-depth="1"] .knobs-activity-main{font-size:var(--ku-text-meta)}
1181
+ .knobs-activity-row[data-depth="1"] .knobs-activity-actor{font-weight:var(--ku-font-weight);color:var(--ku-label)}
1182
+ .knobs-activity-row[data-depth="1"] .knobs-activity-title{color:var(--ku-dim)}
1183
+ .knobs-activity-row[aria-selected="true"] .knobs-activity-title{color:var(--ku-text)}
1184
+ .knobs-activity-row[data-dimmed] :is(.knobs-activity-main,.knobs-activity-meta){opacity:.55}
1185
+ .knobs-activity-row[data-dimmed] .knobs-activity-node{opacity:.6}
1186
+ /* Trailing: thumbnails, a duration chip, the group's chevron and the time. */
1187
+ .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}
1188
+ .knobs-activity-meta:empty{display:none}
1189
+ .knobs-activity-thumbs{display:flex;gap:3px}
1190
+ .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)}
1191
+ .knobs-activity-row[data-depth="1"] .knobs-activity-thumbs img{width:15px;height:15px}
1192
+ :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;
1193
+ 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}
1194
+ .knobs-activity-chip svg{width:8px;height:8px}
1195
+ button.knobs-activity-chip{cursor:pointer}
1196
+ button.knobs-activity-chip:hover{background:var(--ku-surface-hi);color:var(--ku-text);transition-duration:0s}
1197
+ .knobs-activity-caret{display:flex;width:12px;height:12px;color:var(--ku-dim);transition:rotate .18s var(--ku-ease)}
1198
+ .knobs-activity-caret svg{width:10px;height:10px;margin:1px}
1199
+ .knobs-activity-row[aria-expanded="true"] .knobs-activity-caret{rotate:90deg;transition:rotate var(--ka-open-ms) var(--ka-open-ease)}
1200
+ .knobs-activity-time{min-width:2ch;text-align:right;white-space:nowrap}
1201
+ .knobs-activity-time:empty{display:none}
1202
+ .knobs-activity-empty{padding:10px var(--ku-inset);color:var(--ku-dim)}
1203
+ @media(prefers-reduced-motion:reduce){.knobs-activity :where(*,::before,::after){transition:none!important;animation:none!important}}
1204
+ `;
1205
+
505
1206
  // src/ui/bento-css.ts
506
1207
  var bentoCss = `
507
1208
  .knobs-ui.knobs-bento{--ku-bento-radius:var(--k-ui-bento-radius,max(0px,calc(var(--ku-radius) - 5px)));
@@ -634,6 +1335,59 @@ var overlayCss = `
634
1335
  .knobs-toast .knobs-toast-action[hidden]{display:none}
635
1336
  `;
636
1337
 
1338
+ // src/ui/picker-css.ts
1339
+ var snappy = springEasing(2 * Math.PI / 0.25, 1);
1340
+ var pickerCss = `
1341
+ :where(.knobs-picker,.knobs-picker-option,.knobs-picker-head,.knobs-picker-head *,.knobs-picker-card,.knobs-picker-body){box-sizing:border-box}
1342
+ .knobs-ui.knobs-picker{position:relative;display:flex;flex-direction:column;gap:2px;padding:0;outline:none;
1343
+ 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;
1344
+ --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}
1345
+ .knobs-picker [hidden]{display:none!important}
1346
+ /* The selection: one card behind the selected option and its parameters, gliding between them. */
1347
+ .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}
1348
+ .knobs-picker[data-frame="flat"] .knobs-picker-card{box-shadow:none}
1349
+ .knobs-picker-option{position:relative;flex:none}
1350
+ :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);
1351
+ 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}
1352
+ /* Hover: in at once, out over 100 ms. Keyboard focus: the same wash, a little firmer. */
1353
+ .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}
1354
+ .knobs-picker-option:not([data-selected]) .knobs-picker-head:hover::before{opacity:1;transition-duration:calc(var(--k-hover-in,0) * 1ms)}
1355
+ .knobs-picker-head:focus-visible::before{opacity:1;background:var(--ku-wash-hi);transition-duration:0s}
1356
+ .knobs-picker-head>*{position:relative}
1357
+ .knobs-picker-option[data-selected] .knobs-picker-head{color:var(--ku-text)}
1358
+ /* Every row is numbered (only 1–9 are keys): tabular digits in a column two digits wide, so the
1359
+ names line up past the ninth. */
1360
+ .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);
1361
+ 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}
1362
+ .knobs-picker-key:empty{visibility:hidden}
1363
+ /* An option's picture: cover-fit, the rows' corner language and a hairline drawn over it. Its
1364
+ space is kept while it loads (nothing drawn), it fades in once loaded, and one that fails
1365
+ gives its space back on the same ease. */
1366
+ .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));
1367
+ object-fit:cover;background:none;outline:1px solid var(--ku-line);outline-offset:-1px;opacity:0;
1368
+ transition:opacity .2s ease-out,width .18s ease-out,margin .18s ease-out}
1369
+ .knobs-picker-thumb[data-state="loaded"]{opacity:1}
1370
+ .knobs-picker-thumb[data-instant]{transition:none}
1371
+ .knobs-picker-thumb[data-state="failed"]{width:0;margin-right:-8px;outline-color:transparent}
1372
+ .knobs-picker-option[data-selected] .knobs-picker-key{background:var(--ku-surface-hi);color:var(--ku-text)}
1373
+ .knobs-picker-name{flex:1;min-width:0;overflow:hidden;white-space:nowrap;font-weight:var(--ku-label-weight)}
1374
+ .knobs-picker-suggested{flex:none;color:var(--ku-dim);font-size:var(--ku-text-meta)}
1375
+ .knobs-picker-check{flex:none;display:flex;width:14px;height:14px;color:var(--ku-accent);opacity:0;scale:.6;filter:blur(2px);
1376
+ transition:opacity .11s ease-in,scale .11s ease-in,filter .11s ease-in}
1377
+ .knobs-picker-check svg{display:block;width:14px;height:14px}
1378
+ .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}
1379
+ .knobs-picker-body{position:relative;overflow:visible}
1380
+ .knobs-picker-body[data-moving]{overflow:hidden}
1381
+ /* The picker insets its parameters itself (--k-picker-pad), whatever a panel's own pad: a panel
1382
+ inside sheds its card and pad for the picker's, so nothing draws a card inside a card and no
1383
+ heading sits at the card's edge. */
1384
+ .knobs-picker-inner{padding:var(--k-picker-pad,0 8px 8px)}
1385
+ .knobs-picker-inner:empty{padding:0}
1386
+ .knobs-picker-inner .knobs-panel.knobs-inline{--k-chrome:0;padding:0}
1387
+ .knobs-picker-empty{padding:8px var(--ku-inset);color:var(--ku-dim)}
1388
+ @media(prefers-reduced-motion:reduce){.knobs-picker :where(*,::before,::after){transition-duration:.15s!important;scale:none!important;filter:none!important}}
1389
+ `;
1390
+
637
1391
  // src/ui/readout-css.ts
638
1392
  var readoutCss = `
639
1393
  /* Readouts: live values, sparklines, graphs, meters, tables and logs. */
@@ -818,6 +1572,28 @@ var css = `
818
1572
  :is(.knobs-tree,.knobs-split>.knobs-pane>.knobs-pane-clip>.knobs-pane-inner>.knobs-pane-head) [hidden]{display:none!important}
819
1573
  :is(.knobs-pane-head,.knobs-pane-rail,.knobs-tree) svg{display:block;flex:none}
820
1574
 
1575
+ /* Scroll fades: a scroller's content fades out over --knobs-fade at an edge with more beyond it,
1576
+ coming in over the first --knobs-fade-travel of scroll, so reaching an end never pops. They're
1577
+ scroll-driven (no script), so where those aren't supported there's no fade. --knobs-fade-inset
1578
+ starts the top one below a sticky header; .knobs-scroll-fade-x fades across instead (a single
1579
+ line that's cut off, like a tree's labels: no ellipsis). Pane bodies, trees and the palette's
1580
+ list carry the class; around a panel the GPU draws, the mask goes (it would trap the panel's
1581
+ overlays under it). */
1582
+ @property --knobs-fade-a{syntax:"<number>";inherits:false;initial-value:1}
1583
+ @property --knobs-fade-b{syntax:"<number>";inherits:false;initial-value:1}
1584
+ @keyframes knobs-fade-a{from{--knobs-fade-a:1}to{--knobs-fade-a:0}}
1585
+ @keyframes knobs-fade-b{from{--knobs-fade-b:0}to{--knobs-fade-b:1}}
1586
+ @supports (animation-timeline:scroll()){
1587
+ :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;
1588
+ animation-timeline:scroll(self block),scroll(self block);animation-range:0 var(--knobs-fade-travel),calc(100% - var(--knobs-fade-travel)) 100%}
1589
+ .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)}
1590
+ .knobs-scroll-fade-x{--knobs-fade:16px;--knobs-fade-travel:4px;animation-timeline:scroll(self inline),scroll(self inline);
1591
+ --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)}
1592
+ .knobs-scroll-fade:has(.knobs-panel[data-gpu-active="true"]){-webkit-mask-image:none;mask-image:none}
1593
+ .knobs-tree-row[data-editing] .knobs-tree-label{-webkit-mask-image:none;mask-image:none}
1594
+ }
1595
+ @supports not (animation-timeline:scroll()){.knobs-scroll-fade-x{text-overflow:ellipsis}}
1596
+
821
1597
  /* Split: panes along a row or column, hairline dividers with a wider grip. */
822
1598
  .knobs-split{position:relative;display:flex;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden}
823
1599
  .knobs-split[data-direction="column"]{flex-direction:column}
@@ -830,7 +1606,7 @@ var css = `
830
1606
  .knobs-pane-inner{position:absolute;inset:0;display:flex;flex-direction:column;min-width:0;min-height:0}
831
1607
  .knobs-pane-body{position:relative;flex:1;min-width:0;min-height:0;overflow:hidden}
832
1608
  .knobs-pane[data-scroll] .knobs-pane-body{overflow:auto;scrollbar-width:thin}
833
- .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}
1609
+ .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}
834
1610
  .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}
835
1611
  .knobs-pane-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:var(--ku-title-weight);color:var(--ku-text)}
836
1612
  .knobs-pane-count{flex:none;color:var(--ku-dim);font-variant-numeric:tabular-nums}
@@ -841,17 +1617,31 @@ var css = `
841
1617
  .knobs-ui .knobs-pane-btn[aria-pressed="true"]{color:var(--ku-accent)}
842
1618
  .knobs-pane-btn svg{width:13px;height:13px}
843
1619
  .knobs-pane-head[data-toggles]{cursor:pointer}
844
- .knobs-pane-caret{transition:transform var(--ku-ease-ms) var(--ku-ease)}
845
- .knobs-pane[data-collapsed] .knobs-pane-caret{transform:rotate(-90deg)}
846
- /* A collapsed pane's rail: its title along the edge; a click opens it. */
847
- .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;
1620
+ /* A collapsed pane's rail: its title along the edge; a click opens it. It holds on to the edge the
1621
+ pane folds toward, as wide as the rail, over the card (which folds to its size): only its label
1622
+ and chevron draw, and a nested split's slot, which has no card, gives it one of its own. */
1623
+ .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;
848
1624
  font-weight:var(--ku-title-weight);transition:background .15s,color .15s}
849
- .knobs-split[data-direction="row"]>.knobs-pane>.knobs-pane-rail{writing-mode:vertical-rl;padding:10px 0}
850
- .knobs-split[data-direction="column"]>.knobs-pane>.knobs-pane-rail{padding:0 var(--ku-inset)}
1625
+ .knobs-ui .knobs-pane[data-nested]>.knobs-pane-rail{background-color:var(--ku-pane-bg);box-shadow:var(--ku-pane-ring)}
1626
+ .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}
1627
+ .knobs-split[data-direction="row"]>.knobs-pane[data-edge="start"]>.knobs-pane-rail{right:0}
1628
+ .knobs-split[data-direction="row"]>.knobs-pane[data-edge="end"]>.knobs-pane-rail{left:0}
1629
+ .knobs-split[data-direction="column"]>.knobs-pane>.knobs-pane-rail{left:0;right:0;height:var(--ku-rail);padding:0 var(--ku-inset)}
1630
+ .knobs-split[data-direction="column"]>.knobs-pane[data-edge="start"]>.knobs-pane-rail{bottom:0}
1631
+ .knobs-split[data-direction="column"]>.knobs-pane[data-edge="end"]>.knobs-pane-rail{top:0}
851
1632
  .knobs-pane[data-collapsed]>.knobs-pane-rail{pointer-events:auto}
852
- .knobs-ui .knobs-pane-rail:is(:hover,:focus-visible){background-image:linear-gradient(var(--ku-wash),var(--ku-wash));color:var(--ku-text)}
853
- .knobs-pane-rail span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
854
- .knobs-pane-rail svg{width:11px;height:11px;color:var(--ku-dim);writing-mode:horizontal-tb}
1633
+ .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)}
1634
+ .knobs-pane-rail-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
1635
+ .knobs-pane-chevron{display:flex;flex:none;width:11px;height:11px;writing-mode:horizontal-tb;color:var(--ku-dim)}
1636
+ .knobs-pane-turn{display:flex;width:100%;height:100%;transform-origin:50% 50%}
1637
+ .knobs-pane-rail svg{width:11px;height:11px}
1638
+ /* The rail's chevron stands in for the header's collapse icon (it turns into the rail's), so the
1639
+ icon itself isn't drawn; hovering its button lights the chevron instead. */
1640
+ .knobs-pane[data-morph]>.knobs-pane-clip>.knobs-pane-inner>.knobs-pane-head>.knobs-pane-collapse>svg{visibility:hidden}
1641
+ /* So does the rail's label for the title, when it travels (split's fold: { title: "travel" }). */
1642
+ .knobs-pane[data-travel]>.knobs-pane-clip>.knobs-pane-inner>.knobs-pane-head>.knobs-pane-title{visibility:hidden}
1643
+ .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)}
1644
+ .knobs-pane[data-morph]:not([data-collapsed])>.knobs-pane-rail .knobs-pane-chevron{transition:color .15s}
855
1645
 
856
1646
  .knobs-divider{position:relative;z-index:2;flex:none;width:1px;background:var(--ku-divider);touch-action:none;outline:none}
857
1647
  .knobs-split[data-direction="column"]>.knobs-divider{width:auto;height:1px}
@@ -894,7 +1684,7 @@ var css = `
894
1684
  .knobs-tree-row[aria-expanded="true"]>.knobs-tree-caret{transform:rotate(90deg)}
895
1685
  .knobs-tree-row:not([aria-expanded])>.knobs-tree-caret{visibility:hidden}
896
1686
  .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)}
897
- .knobs-tree-label{position:relative;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;font-weight:var(--ku-font-weight)}
1687
+ .knobs-tree-label{position:relative;flex:1;min-width:0;overflow:hidden;font-weight:var(--ku-font-weight)}
898
1688
  .knobs-tree-row[data-ancestor] .knobs-tree-label{color:var(--ku-label)}
899
1689
  .knobs-tree-label .knobs-tree-mark{color:var(--ku-accent)}
900
1690
  .knobs-tree-row[data-off~="visible"] :is(.knobs-tree-label,.knobs-tree-icon){opacity:.45}
@@ -932,7 +1722,7 @@ var css = `
932
1722
  .knobs-tl-play svg{width:12px;height:12px}
933
1723
  .knobs-timeline[data-playing] .knobs-tl-play{background:var(--ku-accent);color:var(--ku-bg)}
934
1724
  .knobs-tl-time{display:flex;flex-direction:column;line-height:1.2;font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
935
- .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}
1725
+ .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}
936
1726
  .knobs-tl-sub{font-size:var(--ku-text-meta);color:var(--ku-dim);white-space:nowrap}
937
1727
  .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;
938
1728
  transition:background calc(var(--ku-hover-out) * 1ms)}
@@ -984,7 +1774,7 @@ function injectUiStyles(doc) {
984
1774
  return;
985
1775
  const style = doc.createElement("style");
986
1776
  style.dataset.knobsUi = "";
987
- style.textContent = css + readoutCss + overlayCss + bentoCss;
1777
+ style.textContent = css + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + rolling_css_default;
988
1778
  doc.head.append(style);
989
1779
  }
990
1780
  function injectUiPreset(doc, preset) {
@@ -1024,6 +1814,12 @@ function syncFrame(root) {
1024
1814
  }
1025
1815
  var lookDefaults = { current: {} };
1026
1816
  var reducedMotion = (win) => win?.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
1817
+ var tune = (s, m) => {
1818
+ s.omega = m.omega;
1819
+ s.zeta = m.zeta;
1820
+ return s;
1821
+ };
1822
+ 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));
1027
1823
  function notifyLayout(doc) {
1028
1824
  doc.dispatchEvent(new Event("knobs-layout"));
1029
1825
  }
@@ -1051,28 +1847,1037 @@ function frameLoop(win, step) {
1051
1847
  raf = 0;
1052
1848
  last = 0;
1053
1849
  }
1054
- };
1850
+ };
1851
+ }
1852
+ 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>`;
1853
+ var ICONS = {
1854
+ caret: svg('<path d="M6 3.5 10.5 8 6 12.5"/>'),
1855
+ chevronDown: svg('<path d="M3.5 6 8 10.5 12.5 6"/>'),
1856
+ 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"/>'),
1857
+ 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"/>'),
1858
+ 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"/>'),
1859
+ 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"/>')
1860
+ };
1861
+
1862
+ // src/ui/activity.ts
1863
+ var ACTIVITY_ICONS = {
1864
+ 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"/>'),
1865
+ 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"/>'),
1866
+ 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"/>'),
1867
+ review: svg('<rect x="2" y="3.5" width="12" height="9" rx="2.2"/><path d="M7 6.3v3.4L9.8 8Z"/>'),
1868
+ 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"/>'),
1869
+ 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"/>'),
1870
+ blocked: svg('<path d="M3.8 14V2.6M3.8 3h8l-1.9 3 1.9 3h-8"/>'),
1871
+ shipped: svg('<circle cx="8" cy="8" r="5.8"/><path d="m5.6 8.2 1.7 1.7 3.2-3.6"/>')
1872
+ };
1873
+ var PLAY = '<svg viewBox="0 0 8 8" aria-hidden="true"><path d="M1.5 1v6l5.2-3Z" fill="currentColor"/></svg>';
1874
+ var REFLOW = springMotion({ duration: 0.4, bounce: 0 });
1875
+ var OPEN = springMotion({ duration: 0.22, bounce: 0 });
1876
+ var CLOSE = springMotion({ duration: 0.18, bounce: 0 });
1877
+ var BLOOM = springMotion({ duration: 0.3, bounce: 0 });
1878
+ var EXIT = springMotion({ duration: 0.24, bounce: 0 });
1879
+ var FADE = springMotion({ duration: 0.16, bounce: 0 });
1880
+ var CLEAR = [11, 5];
1881
+ var PAD = 6;
1882
+ var SLIDE = 8;
1883
+ function activity(container, options) {
1884
+ const doc = container.ownerDocument;
1885
+ const win = doc.defaultView;
1886
+ let newest = options.newest ?? "bottom";
1887
+ const keysFrom = options.keys ?? "focus";
1888
+ let events = options.events;
1889
+ let index = indexActivity(events);
1890
+ const expanded = new Set(options.expanded ?? []);
1891
+ let rows = [];
1892
+ let selected = options.selected ?? null;
1893
+ let showing = false;
1894
+ const root = doc.createElement("div");
1895
+ root.className = "knobs-activity knobs-scroll-fade";
1896
+ root.tabIndex = 0;
1897
+ root.setAttribute("role", "tree");
1898
+ if (options.label)
1899
+ root.setAttribute("aria-label", options.label);
1900
+ applyLook(root, options);
1901
+ const space = doc.createElement("div");
1902
+ space.className = "knobs-activity-space";
1903
+ const hl = doc.createElement("div");
1904
+ hl.className = "knobs-activity-hl";
1905
+ hl.setAttribute("aria-hidden", "true");
1906
+ const line = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
1907
+ line.setAttribute("class", "knobs-activity-line");
1908
+ line.setAttribute("aria-hidden", "true");
1909
+ const path = doc.createElementNS("http://www.w3.org/2000/svg", "path");
1910
+ line.append(path);
1911
+ const empty = doc.createElement("div");
1912
+ empty.className = "knobs-activity-empty";
1913
+ empty.hidden = true;
1914
+ space.append(hl, line);
1915
+ root.append(empty, space);
1916
+ container.append(root);
1917
+ let heights = [32, 26];
1918
+ const measure = () => {
1919
+ syncFrame(root);
1920
+ const probe = (depth) => {
1921
+ const p = doc.createElement("div");
1922
+ p.className = "knobs-activity-row";
1923
+ p.dataset.depth = String(depth);
1924
+ p.style.visibility = "hidden";
1925
+ space.append(p);
1926
+ const h = p.getBoundingClientRect().height;
1927
+ p.remove();
1928
+ return h;
1929
+ };
1930
+ heights = [probe(0) || 32, probe(1) || 26];
1931
+ };
1932
+ measure();
1933
+ const uid = `ka${Math.random().toString(36).slice(2, 7)}`;
1934
+ const domIds = new Map;
1935
+ const domId = (id) => {
1936
+ let d = domIds.get(id);
1937
+ if (!d)
1938
+ domIds.set(id, d = `${uid}-${domIds.size}`);
1939
+ return d;
1940
+ };
1941
+ const views = new Map;
1942
+ const makeView = (row) => {
1943
+ const el = doc.createElement("div");
1944
+ el.className = "knobs-activity-row";
1945
+ el.setAttribute("role", "treeitem");
1946
+ el.id = domId(row.id);
1947
+ el.dataset.id = row.id;
1948
+ const node = doc.createElement("span");
1949
+ node.className = "knobs-activity-node";
1950
+ const main = doc.createElement("span");
1951
+ main.className = "knobs-activity-main knobs-scroll-fade-x";
1952
+ const actor = doc.createElement("span");
1953
+ actor.className = "knobs-activity-actor";
1954
+ const title = doc.createElement("span");
1955
+ title.className = "knobs-activity-title";
1956
+ main.append(actor, title);
1957
+ const meta = doc.createElement("span");
1958
+ meta.className = "knobs-activity-meta";
1959
+ const thumbs = doc.createElement("span");
1960
+ thumbs.className = "knobs-activity-thumbs";
1961
+ const caret = doc.createElement("span");
1962
+ caret.className = "knobs-activity-caret";
1963
+ caret.innerHTML = ICONS.caret;
1964
+ const time = doc.createElement("span");
1965
+ time.className = "knobs-activity-time";
1966
+ el.append(node, main, meta);
1967
+ space.append(el);
1968
+ const v = {
1969
+ id: row.id,
1970
+ el,
1971
+ node,
1972
+ actor,
1973
+ title,
1974
+ meta,
1975
+ thumbs,
1976
+ chip: null,
1977
+ caret,
1978
+ time,
1979
+ roll: null,
1980
+ chipRoll: null,
1981
+ y: new Spring(0, REFLOW.omega, REFLOW.zeta, 0.05),
1982
+ p: new Spring(1, BLOOM.omega, BLOOM.zeta, 0.002),
1983
+ depth: row.depth,
1984
+ under: null,
1985
+ from: 0,
1986
+ leaving: false,
1987
+ event: undefined,
1988
+ state: ""
1989
+ };
1990
+ views.set(row.id, v);
1991
+ return v;
1992
+ };
1993
+ const dropView = (v) => {
1994
+ v.roll?.destroy();
1995
+ v.chipRoll?.destroy();
1996
+ v.el.remove();
1997
+ views.delete(v.id);
1998
+ };
1999
+ const rollInto = (host, roll, text) => {
2000
+ if (!text) {
2001
+ roll?.destroy();
2002
+ host.textContent = "";
2003
+ return null;
2004
+ }
2005
+ if (roll) {
2006
+ roll.update({ text });
2007
+ return roll;
2008
+ }
2009
+ return rt(host, {
2010
+ text,
2011
+ charset: "0123456789",
2012
+ transition: "direct",
2013
+ animated: !reducedMotion(win)
2014
+ });
2015
+ };
2016
+ const bind = (v, row) => {
2017
+ const e = row.event;
2018
+ const state = `${row.depth}|${row.group}|${row.expanded}|${selected === row.id}`;
2019
+ if (v.event === e && v.state === state)
2020
+ return;
2021
+ const changed = v.event !== e;
2022
+ v.event = e;
2023
+ v.state = state;
2024
+ v.depth = row.depth;
2025
+ const el = v.el;
2026
+ el.dataset.depth = String(row.depth);
2027
+ el.setAttribute("aria-level", String(row.depth + 1));
2028
+ el.setAttribute("aria-selected", String(selected === row.id));
2029
+ if (row.group)
2030
+ el.setAttribute("aria-expanded", String(row.expanded));
2031
+ else
2032
+ el.removeAttribute("aria-expanded");
2033
+ if (!changed)
2034
+ return;
2035
+ if (e.kind)
2036
+ el.dataset.kind = e.kind;
2037
+ else
2038
+ delete el.dataset.kind;
2039
+ el.toggleAttribute("data-current", Boolean(e.current));
2040
+ el.toggleAttribute("data-dimmed", Boolean(e.dimmed));
2041
+ const tint = e.tint ?? (e.kind ? options.tints?.[e.kind] : undefined);
2042
+ if (tint)
2043
+ el.style.setProperty("--ka-tint", tint);
2044
+ else
2045
+ el.style.removeProperty("--ka-tint");
2046
+ const icon = e.icon ?? (row.depth === 1 ? undefined : (e.kind && (options.icons?.[e.kind] ?? ACTIVITY_ICONS[e.kind])) ?? undefined);
2047
+ if (icon instanceof Node)
2048
+ v.node.replaceChildren(icon);
2049
+ else if (icon === undefined) {
2050
+ if (!v.node.firstElementChild?.classList.contains("knobs-activity-dot"))
2051
+ v.node.innerHTML = '<i class="knobs-activity-dot"></i>';
2052
+ } else if (icon.trimStart().startsWith("<")) {
2053
+ if (v.node.innerHTML !== icon)
2054
+ v.node.innerHTML = icon;
2055
+ } else {
2056
+ const glyph = doc.createElement("span");
2057
+ glyph.className = "knobs-activity-glyph";
2058
+ glyph.textContent = icon;
2059
+ v.node.replaceChildren(glyph);
2060
+ }
2061
+ const kids = e.children?.length ?? 0;
2062
+ v.actor.textContent = e.actor ?? "";
2063
+ const title = e.title ?? (kids ? groupTitle(kids) : "");
2064
+ if (v.title.textContent !== title)
2065
+ v.title.textContent = title;
2066
+ el.setAttribute("aria-label", [e.actor, title, e.time].filter(Boolean).join(", "));
2067
+ const parts = [];
2068
+ const thumbs = e.thumbs?.slice(0, 4) ?? [];
2069
+ const shown = [...v.thumbs.querySelectorAll("img")].map((i) => i.getAttribute("src"));
2070
+ if (thumbs.join(`
2071
+ `) !== shown.join(`
2072
+ `))
2073
+ v.thumbs.replaceChildren(...thumbs.map((src) => {
2074
+ const img = doc.createElement("img");
2075
+ img.src = src;
2076
+ img.alt = "";
2077
+ img.loading = "lazy";
2078
+ img.draggable = false;
2079
+ return img;
2080
+ }));
2081
+ if (thumbs.length)
2082
+ parts.push(v.thumbs);
2083
+ if (e.duration !== undefined) {
2084
+ const button = Boolean(options.onPlay);
2085
+ if (!v.chip || v.chip.tagName === "BUTTON" !== button) {
2086
+ v.chipRoll?.destroy();
2087
+ v.chipRoll = null;
2088
+ v.chip = doc.createElement(button ? "button" : "span");
2089
+ v.chip.className = "knobs-activity-chip";
2090
+ if (button) {
2091
+ v.chip.type = "button";
2092
+ v.chip.tabIndex = -1;
2093
+ v.chip.setAttribute("aria-label", "Play");
2094
+ }
2095
+ v.chip.innerHTML = `${PLAY}<span></span>`;
2096
+ }
2097
+ v.chipRoll = rollInto(v.chip.lastElementChild, v.chipRoll, formatDuration(e.duration));
2098
+ parts.push(v.chip);
2099
+ }
2100
+ if (kids)
2101
+ parts.push(v.caret);
2102
+ v.roll = rollInto(v.time, v.roll, e.time ?? "");
2103
+ if (e.time)
2104
+ parts.push(v.time);
2105
+ const now = [...v.meta.children];
2106
+ if (now.length !== parts.length || now.some((n, i) => n !== parts[i]))
2107
+ v.meta.replaceChildren(...parts);
2108
+ };
2109
+ const nodeY = (v) => v.y.x + heights[v.depth] / 2;
2110
+ let targets = new Map;
2111
+ const lineTop = new Spring(0, REFLOW.omega, REFLOW.zeta, 0.05);
2112
+ const lineBottom = new Spring(0, REFLOW.omega, REFLOW.zeta, 0.05);
2113
+ const hlY = new Spring(0, REFLOW.omega, REFLOW.zeta, 0.05);
2114
+ const hlH = new Spring(0, REFLOW.omega, REFLOW.zeta, 0.05);
2115
+ const height = new Spring(0, REFLOW.omega, REFLOW.zeta, 0.05);
2116
+ let following = false;
2117
+ let scrollHold = null;
2118
+ const place = (v, reduced) => {
2119
+ const p = Math.max(0, Math.min(1, v.p.x));
2120
+ let y = v.y.x;
2121
+ let clip = 0;
2122
+ let transform = "";
2123
+ let filter = "";
2124
+ if (v.under !== null) {
2125
+ const parent = views.get(v.under);
2126
+ const bottom = (parent ? parent.y.x : targets.get(v.under) ?? 0) + heights[0];
2127
+ clip = Math.min(heights[v.depth], Math.max(0, bottom - y));
2128
+ } else if (p < 1 && !reduced) {
2129
+ y += (1 - p) * SLIDE * v.from;
2130
+ transform = ` scale(${(0.97 + 0.03 * p).toFixed(4)})`;
2131
+ const blur = (1 - p) * 3;
2132
+ if (blur > 0.05)
2133
+ filter = `blur(${blur.toFixed(2)}px)`;
2134
+ }
2135
+ v.el.style.transform = `translate3d(0,${Math.round(y * 2) / 2}px,0)${transform}`;
2136
+ v.el.style.clipPath = clip > 0 ? `inset(${clip}px 0 0 0)` : "";
2137
+ v.el.style.opacity = p < 0.999 ? p.toFixed(3) : "";
2138
+ v.el.style.filter = filter;
2139
+ };
2140
+ const drawLine = () => {
2141
+ const nodes = [];
2142
+ for (const v of views.values()) {
2143
+ const p = Math.max(0, Math.min(1, v.p.x));
2144
+ if (p <= 0.001)
2145
+ continue;
2146
+ const y = nodeY(v);
2147
+ if (v.under !== null) {
2148
+ const parent = views.get(v.under);
2149
+ if (parent && y < parent.y.x + heights[0])
2150
+ continue;
2151
+ }
2152
+ nodes.push({ y, r: CLEAR[v.depth] * Math.min(1, p * 1.5) });
2153
+ }
2154
+ const segs = railSegments(nodes, lineTop.x, lineBottom.x);
2155
+ path.setAttribute("d", segs.map(([a, b]) => `M1 ${a.toFixed(2)}V${b.toFixed(2)}`).join(""));
2156
+ };
2157
+ const placeHighlight = () => {
2158
+ hl.style.transform = `translate3d(0,${Math.round(hlY.x * 2) / 2}px,0)`;
2159
+ hl.style.height = `${Math.max(0, hlH.x).toFixed(2)}px`;
2160
+ };
2161
+ const loop = frameLoop(win, (dt) => {
2162
+ const reduced = reducedMotion(win);
2163
+ let moving = false;
2164
+ for (const s of [lineTop, lineBottom, hlY, hlH, height]) {
2165
+ s.step(dt);
2166
+ if (!s.resting)
2167
+ moving = true;
2168
+ }
2169
+ for (const v of [...views.values()]) {
2170
+ v.y.step(dt);
2171
+ v.p.step(dt);
2172
+ if (v.y.resting && v.p.resting) {
2173
+ if (v.leaving) {
2174
+ dropView(v);
2175
+ continue;
2176
+ }
2177
+ v.under = null;
2178
+ v.from = 0;
2179
+ } else
2180
+ moving = true;
2181
+ place(v, reduced);
2182
+ }
2183
+ drawLine();
2184
+ placeHighlight();
2185
+ if (scrollHold && !following) {
2186
+ const { scroll, space: reserved } = holdScroll(scrollHold, height.x, root.clientHeight);
2187
+ space.style.height = `${Math.max(0, reserved).toFixed(1)}px`;
2188
+ root.scrollTop = scroll;
2189
+ } else
2190
+ space.style.height = `${Math.max(0, height.x).toFixed(1)}px`;
2191
+ if (following)
2192
+ root.scrollTop = newest === "bottom" ? root.scrollHeight : 0;
2193
+ if (!moving) {
2194
+ following = false;
2195
+ scrollHold = null;
2196
+ }
2197
+ return moving;
2198
+ });
2199
+ const atNewestEnd = () => newest === "bottom" ? root.scrollTop + root.clientHeight >= root.scrollHeight - 4 : root.scrollTop <= 4;
2200
+ function sync(cause, toggled) {
2201
+ const reduced = reducedMotion(win);
2202
+ const animate = cause !== "snap";
2203
+ const glide = cause === "open" ? OPEN : cause === "close" ? CLOSE : REFLOW;
2204
+ const before = [...views.values()].filter((v) => !v.leaving).map((v) => v.id);
2205
+ rows = flattenActivity(events, expanded);
2206
+ selected = keepSelection(rows, selected, (id) => index.get(id)?.parent);
2207
+ const stack = stackRows(rows.map((r) => heights[r.depth]), PAD);
2208
+ targets = new Map(rows.map((r, i) => [r.id, stack.y[i]]));
2209
+ const { entered } = diffActivity(before, rows.map((r) => r.id));
2210
+ const fresh = new Set(entered);
2211
+ let opened = 0;
2212
+ if (cause === "open") {
2213
+ for (const r of rows)
2214
+ if (r.parent === toggled && fresh.has(r.id))
2215
+ opened += heights[r.depth];
2216
+ }
2217
+ let shut = 0;
2218
+ const live = new Set(rows.map((r) => r.id));
2219
+ if (cause === "close") {
2220
+ for (const v of views.values())
2221
+ if (!live.has(v.id) && !v.leaving && index.get(v.id)?.parent === toggled)
2222
+ shut += heights[v.depth];
2223
+ }
2224
+ const following0 = animate && options.follow !== false && atNewestEnd();
2225
+ const lastIndex = rows.length - 1;
2226
+ rows.forEach((row, i) => {
2227
+ const goal = stack.y[i];
2228
+ let v = views.get(row.id);
2229
+ if (v && v.leaving) {
2230
+ v.leaving = false;
2231
+ delete v.el.dataset.leaving;
2232
+ tune(v.p, cause === "open" ? OPEN : BLOOM).set(1);
2233
+ }
2234
+ if (!v) {
2235
+ v = makeView(row);
2236
+ if (!animate) {
2237
+ v.y.snap(goal);
2238
+ v.p.snap(1);
2239
+ } else if (reduced) {
2240
+ v.y.snap(goal);
2241
+ tune(v.p, FADE).snap(0).set(1);
2242
+ } else {
2243
+ const parent = row.parent !== null ? views.get(row.parent) : undefined;
2244
+ if (parent && !parent.leaving) {
2245
+ v.under = row.parent;
2246
+ const drawer = cause === "open" && row.parent === toggled;
2247
+ v.y.snap(drawer ? goal - opened + parent.y.x - parent.y.target : parent.y.x);
2248
+ tune(v.y, drawer ? OPEN : glide).set(goal);
2249
+ tune(v.p, drawer ? OPEN : BLOOM).snap(0).set(1);
2250
+ } else {
2251
+ v.y.snap(goal);
2252
+ v.from = newest === "bottom" ? 1 : -1;
2253
+ tune(v.p, BLOOM).snap(0).set(1);
2254
+ }
2255
+ }
2256
+ } else if (!animate || reduced)
2257
+ v.y.snap(goal);
2258
+ else
2259
+ tune(v.y, glide).set(goal);
2260
+ bind(v, row);
2261
+ });
2262
+ for (const v of [...views.values()]) {
2263
+ if (live.has(v.id) || v.leaving)
2264
+ continue;
2265
+ if (!animate) {
2266
+ dropView(v);
2267
+ continue;
2268
+ }
2269
+ v.leaving = true;
2270
+ v.el.dataset.leaving = "";
2271
+ v.el.setAttribute("aria-selected", "false");
2272
+ if (reduced) {
2273
+ tune(v.p, FADE).set(0);
2274
+ continue;
2275
+ }
2276
+ const group = index.get(v.id)?.parent ?? null;
2277
+ const parent = group !== null ? views.get(group) : undefined;
2278
+ if (parent && !parent.leaving && v.depth === 1) {
2279
+ v.under = group;
2280
+ const drawer = cause === "close" && group === toggled;
2281
+ tune(v.y, drawer ? CLOSE : glide).set(drawer ? v.y.target - shut : targets.get(group) ?? parent.y.target);
2282
+ tune(v.p, drawer ? CLOSE : EXIT).set(0);
2283
+ } else {
2284
+ v.from = 0;
2285
+ tune(v.p, EXIT).set(0);
2286
+ }
2287
+ }
2288
+ const first = rows[0];
2289
+ const last = rows[lastIndex];
2290
+ const firstY = first ? targets.get(first.id) + heights[first.depth] / 2 : PAD;
2291
+ const lastY = last ? targets.get(last.id) + heights[last.depth] / 2 : PAD;
2292
+ const bottom = stack.bottom + PAD;
2293
+ if (!animate || reduced) {
2294
+ lineTop.snap(firstY);
2295
+ lineBottom.snap(lastY);
2296
+ height.snap(bottom);
2297
+ } else {
2298
+ if (!before.length) {
2299
+ lineTop.snap(firstY);
2300
+ lineBottom.snap(firstY);
2301
+ }
2302
+ tune(lineTop, glide).set(firstY);
2303
+ tune(lineBottom, cause === "set" ? BLOOM : glide).set(lastY);
2304
+ tune(height, glide).set(bottom);
2305
+ }
2306
+ moveHighlight(animate && !reduced ? glide : null);
2307
+ hl.hidden = !selected;
2308
+ empty.hidden = rows.length > 0;
2309
+ empty.textContent = options.empty ?? "Nothing yet";
2310
+ if (selected)
2311
+ root.setAttribute("aria-activedescendant", domId(selected));
2312
+ else
2313
+ root.removeAttribute("aria-activedescendant");
2314
+ following = following || following0 && cause === "set" && fresh.size > 0;
2315
+ scrollHold = animate && !following && !reduced ? { from: root.scrollTop, h0: height.x, h1: height.target } : null;
2316
+ for (const v of views.values())
2317
+ place(v, reduced);
2318
+ drawLine();
2319
+ placeHighlight();
2320
+ space.style.height = `${height.x.toFixed(1)}px`;
2321
+ loop.wake();
2322
+ }
2323
+ const moveHighlight = (motion) => {
2324
+ if (!showing)
2325
+ return;
2326
+ const y = selected ? targets.get(selected) : undefined;
2327
+ if (y === undefined)
2328
+ return;
2329
+ const h = heights[rows.find((r) => r.id === selected)?.depth ?? 0];
2330
+ if (!motion) {
2331
+ hlY.snap(y);
2332
+ hlH.snap(h);
2333
+ return;
2334
+ }
2335
+ tune(hlY, motion).set(y);
2336
+ tune(hlH, motion).set(h);
2337
+ };
2338
+ const setShowing = (input) => {
2339
+ const next = showsSelection(showing, input, keysFrom);
2340
+ if (next === showing)
2341
+ return;
2342
+ showing = next;
2343
+ root.toggleAttribute("data-keys", next);
2344
+ };
2345
+ const rebind = () => {
2346
+ for (const row of rows) {
2347
+ const v = views.get(row.id);
2348
+ if (v && !v.leaving)
2349
+ bind(v, row);
2350
+ }
2351
+ };
2352
+ const reveal = (id) => {
2353
+ const y = targets.get(id);
2354
+ if (y === undefined)
2355
+ return;
2356
+ const h = heights[rows.find((r) => r.id === id)?.depth ?? 0];
2357
+ const top = root.scrollTop;
2358
+ const view = root.clientHeight;
2359
+ if (view <= 0 || root.scrollHeight <= view)
2360
+ return;
2361
+ const behavior = reducedMotion(win) ? "auto" : "smooth";
2362
+ if (y - PAD < top)
2363
+ root.scrollTo({ top: y - PAD, behavior });
2364
+ else if (y + h + PAD > top + view)
2365
+ root.scrollTo({ top: y + h + PAD - view, behavior });
2366
+ };
2367
+ const choose = (id, source) => {
2368
+ if (source === "key") {
2369
+ const wasShowing = showing;
2370
+ setShowing("key");
2371
+ if (!wasShowing)
2372
+ moveHighlight(null);
2373
+ } else if (source === "pointer")
2374
+ setShowing("pointer");
2375
+ if (id === selected)
2376
+ return;
2377
+ const from = selected ? targets.get(selected) : undefined;
2378
+ selected = id;
2379
+ rebind();
2380
+ hl.hidden = !id;
2381
+ if (id) {
2382
+ const to = targets.get(id);
2383
+ const far = from !== undefined && to !== undefined && Math.abs(to - from) > 400;
2384
+ const reduced = reducedMotion(win);
2385
+ moveHighlight(reduced || from === undefined ? null : springMotion({ duration: far ? 0.42 : 0.3, bounce: 0 }));
2386
+ root.setAttribute("aria-activedescendant", domId(id));
2387
+ if (source === "key") {
2388
+ scrollHold = null;
2389
+ reveal(id);
2390
+ }
2391
+ const event = index.get(id)?.event;
2392
+ if (event && source !== "code")
2393
+ options.onSelect?.(id, event, source);
2394
+ } else
2395
+ root.removeAttribute("aria-activedescendant");
2396
+ loop.wake();
2397
+ };
2398
+ const toggle = (id, open) => {
2399
+ const row = rows.find((r) => r.id === id);
2400
+ if (!row?.group || row.expanded === open)
2401
+ return;
2402
+ if (open)
2403
+ expanded.add(id);
2404
+ else
2405
+ expanded.delete(id);
2406
+ sync(open ? "open" : "close", id);
2407
+ options.onToggle?.(id, open);
2408
+ };
2409
+ root.addEventListener("pointerdown", (e) => {
2410
+ if (e.button !== 0)
2411
+ return;
2412
+ setShowing("pointer");
2413
+ const el = e.target.closest(".knobs-activity-row");
2414
+ const id = el?.dataset.id;
2415
+ if (!el || !id || el.dataset.leaving !== undefined)
2416
+ return;
2417
+ e.preventDefault();
2418
+ root.focus({ preventScroll: true });
2419
+ if (e.target.closest(".knobs-activity-chip"))
2420
+ return;
2421
+ const row = rows.find((r) => r.id === id);
2422
+ choose(id, "pointer");
2423
+ if (row?.group)
2424
+ toggle(id, !row.expanded);
2425
+ });
2426
+ root.addEventListener("click", (e) => {
2427
+ const chip = e.target.closest(".knobs-activity-chip");
2428
+ const id = chip?.closest(".knobs-activity-row")?.dataset.id;
2429
+ const event = id ? index.get(id)?.event : undefined;
2430
+ if (chip && id && event && options.onPlay) {
2431
+ e.stopPropagation();
2432
+ options.onPlay(id, event);
2433
+ }
2434
+ });
2435
+ const release = () => {
2436
+ scrollHold = null;
2437
+ following = false;
2438
+ };
2439
+ root.addEventListener("wheel", release, { passive: true });
2440
+ root.addEventListener("touchstart", release, { passive: true });
2441
+ const onPointer = () => setShowing("pointer");
2442
+ if (keysFrom === "page")
2443
+ win.addEventListener("pointerdown", onPointer, true);
2444
+ const onKey = (e) => {
2445
+ if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.altKey)
2446
+ return;
2447
+ if (keysFrom === "page") {
2448
+ if (e.target !== root && (isTypingTarget(e.target) || !root.getClientRects().length))
2449
+ return;
2450
+ const t = e.target;
2451
+ if (t !== root && t?.closest?.(".knobs-panel,[role=tree],[role=listbox],[role=grid],[role=slider]"))
2452
+ return;
2453
+ }
2454
+ let handled = true;
2455
+ if (isActivityKey(e.key) && !e.shiftKey) {
2456
+ const r = navigateActivity(rows, selected, e.key);
2457
+ if (!takesActivityKey(e.key, r, rows.length))
2458
+ return;
2459
+ if (r.toggle) {
2460
+ setShowing("key");
2461
+ toggle(r.toggle.id, r.toggle.expanded);
2462
+ } else if (r.select)
2463
+ choose(r.select, "key");
2464
+ else
2465
+ setShowing("key");
2466
+ } else if (e.key === "Enter" && selected) {
2467
+ setShowing("key");
2468
+ const row = rows.find((r) => r.id === selected);
2469
+ const event = index.get(selected)?.event;
2470
+ if (options.onActivate && event)
2471
+ options.onActivate(selected, event);
2472
+ else if (row?.group)
2473
+ toggle(row.id, !row.expanded);
2474
+ else
2475
+ handled = false;
2476
+ } else
2477
+ handled = false;
2478
+ if (handled) {
2479
+ e.preventDefault();
2480
+ e.stopPropagation();
2481
+ }
2482
+ };
2483
+ if (keysFrom === "page")
2484
+ win.addEventListener("keydown", onKey);
2485
+ else
2486
+ root.addEventListener("keydown", onKey);
2487
+ root.addEventListener("blur", () => setShowing("blur"));
2488
+ const remeasure = () => {
2489
+ const was = heights.join();
2490
+ measure();
2491
+ if (heights.join() !== was)
2492
+ sync("snap");
2493
+ };
2494
+ const ro = new ResizeObserver(remeasure);
2495
+ ro.observe(root);
2496
+ root.addEventListener("knobs-restyle", () => {
2497
+ measure();
2498
+ sync("snap");
2499
+ });
2500
+ sync("snap");
2501
+ const handle = {
2502
+ element: root,
2503
+ set(next) {
2504
+ handle.update({ events: next });
2505
+ },
2506
+ add(event) {
2507
+ handle.set(newest === "bottom" ? [...events, event] : [event, ...events]);
2508
+ },
2509
+ update(next) {
2510
+ if (next.newest)
2511
+ newest = next.newest;
2512
+ if (next.events) {
2513
+ events = next.events;
2514
+ index = indexActivity(events);
2515
+ for (const id of [...expanded])
2516
+ if (!index.has(id))
2517
+ expanded.delete(id);
2518
+ }
2519
+ sync("set");
2520
+ },
2521
+ select(id) {
2522
+ if (id !== null) {
2523
+ const parent = index.get(id)?.parent;
2524
+ if (parent && !expanded.has(parent)) {
2525
+ expanded.add(parent);
2526
+ sync("open", parent);
2527
+ }
2528
+ }
2529
+ choose(id, "code");
2530
+ },
2531
+ get selected() {
2532
+ return selected;
2533
+ },
2534
+ expand: (id, open = !expanded.has(id)) => toggle(id, open),
2535
+ isExpanded: (id) => expanded.has(id),
2536
+ focus: () => root.focus({ preventScroll: true }),
2537
+ destroy() {
2538
+ loop.stop();
2539
+ ro.disconnect();
2540
+ if (keysFrom === "page") {
2541
+ win.removeEventListener("keydown", onKey);
2542
+ win.removeEventListener("pointerdown", onPointer, true);
2543
+ }
2544
+ for (const v of [...views.values()])
2545
+ dropView(v);
2546
+ root.remove();
2547
+ },
2548
+ dispose: () => handle.destroy()
2549
+ };
2550
+ return handle;
2551
+ }
2552
+
2553
+ // src/ui/bento-model.ts
2554
+ var FIT_MAX = 24;
2555
+ var clamp = (x, a, b) => Math.min(b, Math.max(a, x));
2556
+ var sum = (xs) => xs.reduce((s, x) => s + x, 0);
2557
+ var rowTarget = (width, height) => clamp(Math.round(Math.min(height / 3, width / 2.6)), 120, 420);
2558
+ function justify(aspects, start, end, width, target, gap, tol) {
2559
+ const rows = [];
2560
+ let i = start;
2561
+ while (i < end) {
2562
+ let total = 0;
2563
+ let j = i;
2564
+ let h = Number.POSITIVE_INFINITY;
2565
+ let closed = false;
2566
+ while (j < end) {
2567
+ const k = j - i + 1;
2568
+ const next = (width - (k - 1) * gap) / (total + aspects[j]);
2569
+ if (next <= target) {
2570
+ if (k === 1 || Math.abs(Math.log(next / target)) <= Math.abs(Math.log(h / target))) {
2571
+ total += aspects[j];
2572
+ j++;
2573
+ h = next;
2574
+ }
2575
+ closed = true;
2576
+ break;
2577
+ }
2578
+ total += aspects[j];
2579
+ h = next;
2580
+ j++;
2581
+ }
2582
+ const slice = aspects.slice(i, j);
2583
+ if (closed || h <= target * 1.25) {
2584
+ rows.push({ start: i, end: j, h, ws: slice.map((a) => a * h) });
2585
+ } else {
2586
+ const free = width - (j - i - 1) * gap;
2587
+ const f = free / (target * total);
2588
+ const ws = slice.map((a) => a * target * (f - 1 <= tol ? f : 1));
2589
+ rows.push({ start: i, end: j, h: target, ws });
2590
+ }
2591
+ i = j;
2592
+ }
2593
+ return rows;
2594
+ }
2595
+ function partition(xs, k) {
2596
+ const n = xs.length;
2597
+ k = clamp(k, 1, n);
2598
+ const prefix = [0];
2599
+ for (const x of xs)
2600
+ prefix.push(prefix.at(-1) + x);
2601
+ const cost = Array.from({ length: k + 1 }, () => new Float64Array(n + 1).fill(Infinity));
2602
+ const cut = Array.from({ length: k + 1 }, () => new Int32Array(n + 1));
2603
+ cost[0][0] = 0;
2604
+ for (let j = 1;j <= k; j++)
2605
+ for (let i = j;i <= n; i++)
2606
+ for (let m = j - 1;m < i; m++) {
2607
+ const c = Math.max(cost[j - 1][m], prefix[i] - prefix[m]);
2608
+ if (c < cost[j][i]) {
2609
+ cost[j][i] = c;
2610
+ cut[j][i] = m;
2611
+ }
2612
+ }
2613
+ const counts = [];
2614
+ for (let j = k, i = n;j > 0; j--) {
2615
+ const m = cut[j][i];
2616
+ counts.unshift(i - m);
2617
+ i = m;
2618
+ }
2619
+ return counts;
2620
+ }
2621
+ function rowsBlock(aspects, w, h, gap, tol) {
2622
+ const n = aspects.length;
2623
+ if (!n || w <= 0 || h <= 0)
2624
+ return null;
2625
+ let best = null;
2626
+ const most = Math.min(n, Math.max(2, Math.ceil(Math.sqrt(n) * 2)));
2627
+ for (let r = 1;r <= most; r++) {
2628
+ const counts = partition(aspects, r);
2629
+ let at = 0;
2630
+ const groups = counts.map((c) => aspects.slice(at, at += c));
2631
+ const heights = groups.map((g) => (w - (g.length - 1) * gap) / sum(g));
2632
+ const budget = h - (r - 1) * gap;
2633
+ if (budget <= 0 || heights.some((x) => x <= 0))
2634
+ break;
2635
+ const s = Math.min(1, budget / sum(heights));
2636
+ const fill = s < 1 && 1 / s - 1 <= tol ? 1 / s : 1;
2637
+ const rects = [];
2638
+ let y = 0;
2639
+ let blockW = 0;
2640
+ groups.forEach((g, k) => {
2641
+ const rh = heights[k] * s;
2642
+ const rowW = sum(g.map((a) => a * rh * fill)) + (g.length - 1) * gap;
2643
+ blockW = Math.max(blockW, rowW);
2644
+ let x = 0;
2645
+ for (const a of g) {
2646
+ rects.push({ x, y, w: a * rh * fill, h: rh });
2647
+ x += a * rh * fill + gap;
2648
+ }
2649
+ y += rh + gap;
2650
+ });
2651
+ let i = 0;
2652
+ groups.forEach((g, k) => {
2653
+ const rowW = sum(g.map((a) => a * heights[k] * s * fill)) + (g.length - 1) * gap;
2654
+ const dx = (blockW - rowW) / 2;
2655
+ for (let m = 0;m < g.length; m++, i++) {
2656
+ const q = rects[i];
2657
+ rects[i] = { ...q, x: q.x + dx };
2658
+ }
2659
+ });
2660
+ const area = sum(rects.map((q) => q.w * q.h));
2661
+ const block = {
2662
+ rects,
2663
+ w: blockW,
2664
+ h: y - gap,
2665
+ area,
2666
+ crop: fill - 1,
2667
+ minH: Math.min(...rects.map((q) => q.h))
2668
+ };
2669
+ if (!best || block.area > best.area * 1.0001)
2670
+ best = block;
2671
+ }
2672
+ return best;
2673
+ }
2674
+ var offset = (rects, dx, dy) => rects.map((q) => ({ x: q.x + dx, y: q.y + dy, w: q.w, h: q.h }));
2675
+ function heroBlock(aspects, hero, w, h, gap, tol) {
2676
+ const a = aspects[hero];
2677
+ const rest = aspects.filter((_, i) => i !== hero);
2678
+ if (!rest.length)
2679
+ return null;
2680
+ let best = null;
2681
+ const place = (heroRect, restBlock, restAt, bw, bh) => {
2682
+ if (!restBlock)
2683
+ return;
2684
+ const [rx, ry] = restAt(restBlock);
2685
+ const others = offset(restBlock.rects, rx, ry);
2686
+ const rects = [...others.slice(0, hero), heroRect, ...others.slice(hero)];
2687
+ const area = heroRect.w * heroRect.h + restBlock.area;
2688
+ if (!best || area > best.area * 1.0001)
2689
+ best = { rects, w: bw, h: bh, area, crop: restBlock.crop, minH: restBlock.minH };
2690
+ };
2691
+ for (let f = 0.4;f <= 0.761; f += 0.04) {
2692
+ {
2693
+ const hw = Math.min(w * f, h * a);
2694
+ const hh = hw / a;
2695
+ const block = rowsBlock(rest, w - hw - gap, h, gap, tol);
2696
+ if (block) {
2697
+ const bw = hw + gap + block.w;
2698
+ const bh = Math.max(hh, block.h);
2699
+ place({ x: 0, y: (bh - hh) / 2, w: hw, h: hh }, block, (b) => [hw + gap, (bh - b.h) / 2], bw, bh);
2700
+ }
2701
+ }
2702
+ {
2703
+ const hh = Math.min(h * f, w / a);
2704
+ const hw = hh * a;
2705
+ const block = rowsBlock(rest, w, h - hh - gap, gap, tol);
2706
+ if (block) {
2707
+ const bw = Math.max(hw, block.w);
2708
+ place({ x: (bw - hw) / 2, y: 0, w: hw, h: hh }, block, (b) => [(bw - b.w) / 2, hh + gap], bw, hh + gap + block.h);
2709
+ }
2710
+ }
2711
+ }
2712
+ return best;
2713
+ }
2714
+ function planBento(aspects, layout) {
2715
+ const gap = layout.gap ?? 2;
2716
+ const pad = layout.padding ?? gap;
2717
+ const tol = layout.tolerance ?? 0.08;
2718
+ const vw = Math.max(0, layout.width);
2719
+ const vh = Math.max(0, layout.height);
2720
+ const w = Math.max(1, vw - pad * 2);
2721
+ const h = Math.max(1, vh - pad * 2);
2722
+ const as = aspects.map((a) => Number.isFinite(a) && a > 0 ? clamp(a, 0.05, 20) : 1);
2723
+ const empty = {
2724
+ mode: "rows",
2725
+ rects: [],
2726
+ width: vw,
2727
+ height: 0,
2728
+ coverage: 0,
2729
+ crop: 0,
2730
+ headers: []
2731
+ };
2732
+ if (!as.length || vw <= 0)
2733
+ return empty;
2734
+ const grouped = layout.groups?.some((g) => g !== undefined) ?? false;
2735
+ const minTile = layout.minTile ?? Math.max(80, vh / 6);
2736
+ if ((layout.fit ?? true) && !grouped && as.length <= FIT_MAX && vh > 0) {
2737
+ const rows = rowsBlock(as, w, h, gap, tol);
2738
+ let pick = rows && rows.minH >= minTile ? { mode: "fit", block: rows } : null;
2739
+ const asked = layout.hero !== undefined && layout.hero >= 0 && layout.hero < as.length;
2740
+ const hero = as.length > 2 ? heroBlock(as, asked ? layout.hero : 0, w, h, gap, tol) : null;
2741
+ if (hero && hero.minH >= minTile && (asked || !pick || hero.area > pick.block.area * 1.04))
2742
+ pick = { mode: "hero", block: hero };
2743
+ if (pick) {
2744
+ const { block } = pick;
2745
+ const rects = offset(block.rects, pad + (w - block.w) / 2, pad + (h - block.h) / 2);
2746
+ return {
2747
+ mode: pick.mode,
2748
+ rects,
2749
+ width: vw,
2750
+ height: vh,
2751
+ coverage: block.area / (vw * vh),
2752
+ crop: block.crop,
2753
+ headers: []
2754
+ };
2755
+ }
2756
+ }
2757
+ const target = layout.rowHeight ?? rowTarget(vw, vh);
2758
+ const header = layout.header ?? 28;
2759
+ const rects = new Array(as.length);
2760
+ const headers = [];
2761
+ let y = pad;
2762
+ let crop = 0;
2763
+ let area = 0;
2764
+ let i = 0;
2765
+ while (i < as.length) {
2766
+ const group = layout.groups?.[i];
2767
+ let end = i + 1;
2768
+ while (end < as.length && layout.groups?.[end] === group)
2769
+ end++;
2770
+ if (group !== undefined) {
2771
+ if (i > 0)
2772
+ y += header / 2;
2773
+ headers.push({ group, y, index: i });
2774
+ y += header;
2775
+ }
2776
+ for (const row of justify(as, i, end, w, target, gap, tol)) {
2777
+ let x = pad;
2778
+ row.ws.forEach((rw, k) => {
2779
+ rects[row.start + k] = { x, y, w: rw, h: row.h };
2780
+ crop = Math.max(crop, rw / (as[row.start + k] * row.h) - 1);
2781
+ area += rw * row.h;
2782
+ x += rw + gap;
2783
+ });
2784
+ y += row.h + gap;
2785
+ }
2786
+ i = end;
2787
+ }
2788
+ const height = y - gap + pad;
2789
+ return { mode: "rows", rects, width: vw, height, coverage: area / (vw * height), crop, headers };
2790
+ }
2791
+ function windowOf(plan, top, bottom) {
2792
+ const { rects } = plan;
2793
+ if (plan.mode !== "rows")
2794
+ return rects.map((_, i) => i);
2795
+ let lo = 0;
2796
+ let hi = rects.length;
2797
+ while (lo < hi) {
2798
+ const mid = lo + hi >> 1;
2799
+ if (rects[mid].y + rects[mid].h < top)
2800
+ lo = mid + 1;
2801
+ else
2802
+ hi = mid;
2803
+ }
2804
+ const out = [];
2805
+ for (let i = lo;i < rects.length && rects[i].y <= bottom; i++)
2806
+ out.push(i);
2807
+ return out;
2808
+ }
2809
+ 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;
2810
+ function neighbour(rects, from, dir) {
2811
+ const f = rects[from];
2812
+ if (!f)
2813
+ return from;
2814
+ const fx = f.x + f.w / 2;
2815
+ const fy = f.y + f.h / 2;
2816
+ if (dir === "left" || dir === "right") {
2817
+ const right = dir === "right";
2818
+ let best = -1;
2819
+ let bestD = Number.POSITIVE_INFINITY;
2820
+ rects.forEach((r, i) => {
2821
+ if (i === from || !overlapY(f, r))
2822
+ return;
2823
+ const d = (r.x + r.w / 2 - fx) * (right ? 1 : -1);
2824
+ if (d > 1 && d < bestD) {
2825
+ bestD = d;
2826
+ best = i;
2827
+ }
2828
+ });
2829
+ if (best >= 0)
2830
+ return best;
2831
+ return clamp(from + (right ? 1 : -1), 0, rects.length - 1);
2832
+ }
2833
+ const down = dir === "down";
2834
+ let edge = Number.POSITIVE_INFINITY;
2835
+ for (const r of rects) {
2836
+ const d = (r.y + r.h / 2 - fy) * (down ? 1 : -1);
2837
+ if (d > f.h * 0.25 && !overlapY(f, r))
2838
+ edge = Math.min(edge, down ? r.y : -(r.y + r.h));
2839
+ }
2840
+ if (!Number.isFinite(edge))
2841
+ return from;
2842
+ let best = from;
2843
+ let bestD = Number.POSITIVE_INFINITY;
2844
+ rects.forEach((r, i) => {
2845
+ const d = (r.y + r.h / 2 - fy) * (down ? 1 : -1);
2846
+ if (i === from || d <= f.h * 0.25 || overlapY(f, r))
2847
+ return;
2848
+ const near = down ? r.y - edge : -(r.y + r.h) - edge;
2849
+ if (near > Math.min(r.h, f.h) * 0.5)
2850
+ return;
2851
+ const dx = Math.abs(r.x + r.w / 2 - fx);
2852
+ if (dx < bestD) {
2853
+ bestD = dx;
2854
+ best = i;
2855
+ }
2856
+ });
2857
+ return best;
2858
+ }
2859
+ function fitRect(aspect, w, h, inset = 0) {
2860
+ const bw = Math.max(1, w - inset * 2);
2861
+ const bh = Math.max(1, h - inset * 2);
2862
+ const fw = Math.min(bw, bh * aspect);
2863
+ const fh = fw / aspect;
2864
+ return { x: (w - fw) / 2, y: (h - fh) / 2, w: fw, h: fh };
2865
+ }
2866
+ function motionBlur(speed, max = 3, per = 1600) {
2867
+ const b = Math.min(max, Math.abs(speed) / per);
2868
+ return b < 0.25 ? 0 : Math.round(b * 4) / 4;
1055
2869
  }
1056
- 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>`;
1057
- var ICONS = {
1058
- caret: svg('<path d="M6 3.5 10.5 8 6 12.5"/>'),
1059
- chevronDown: svg('<path d="M3.5 6 8 10.5 12.5 6"/>'),
1060
- 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"/>'),
1061
- 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"/>'),
1062
- 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"/>'),
1063
- 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"/>')
1064
- };
1065
2870
 
1066
2871
  // src/ui/bento.ts
1067
2872
  var LAYOUT = springMotion({ duration: 0.4, bounce: 0 });
1068
2873
  var ZOOM_IN = springMotion({ duration: 0.45, bounce: 0 });
1069
2874
  var ZOOM_OUT = springMotion({ duration: 0.38, bounce: 0 });
1070
- var SLIDE = springMotion({ duration: 0.3, bounce: 0 });
2875
+ var SLIDE2 = springMotion({ duration: 0.3, bounce: 0 });
1071
2876
  var SLIDE_OUT = springMotion({ duration: 0.25, bounce: 0 });
1072
2877
  var ENTER = springMotion({ duration: 0.3, bounce: 0 });
1073
- var EXIT = springMotion({ duration: 0.25, bounce: 0 });
2878
+ var EXIT2 = springMotion({ duration: 0.25, bounce: 0 });
1074
2879
  var WASH = springMotion({ duration: 0.25, bounce: 0 });
1075
- var FADE = springMotion({ duration: 0.18, bounce: 0 });
2880
+ var FADE2 = springMotion({ duration: 0.18, bounce: 0 });
1076
2881
  var INSTANT = springMotion({ duration: 0.1, bounce: 0 });
1077
2882
  var SLIDE_BY = 0.12;
1078
2883
  var EXIT_BLUR = 3;
@@ -1242,7 +3047,7 @@ function bento(container, options) {
1242
3047
  if (enter && !reduced())
1243
3048
  drive(t.life.snap(0), ENTER, 1);
1244
3049
  else if (enter)
1245
- drive(t.life.snap(0), FADE, 1);
3050
+ drive(t.life.snap(0), FADE2, 1);
1246
3051
  if (reduced() || !from)
1247
3052
  for (const s of [t.x, t.y, t.w, t.h])
1248
3053
  s.snap(s.target);
@@ -1572,7 +3377,7 @@ function bento(container, options) {
1572
3377
  if (r)
1573
3378
  setBox(t, r.w, r.h);
1574
3379
  if (f.out || f.veil)
1575
- drive(t.life.snap(0), reduced() ? FADE : ENTER, 1);
3380
+ drive(t.life.snap(0), reduced() ? FADE2 : ENTER, 1);
1576
3381
  t.drawn = "";
1577
3382
  draw(t);
1578
3383
  syncZoomed();
@@ -1605,11 +3410,11 @@ function bento(container, options) {
1605
3410
  f.z.snap(1);
1606
3411
  f.dx.snap(0);
1607
3412
  f.veil = true;
1608
- drive(f.alpha.snap(f.alpha.target === 0 ? f.alpha.x : 0), FADE, 1);
3413
+ drive(f.alpha.snap(f.alpha.target === 0 ? f.alpha.x : 0), FADE2, 1);
1609
3414
  } else {
1610
3415
  f.veil = false;
1611
3416
  drive(f.z, ZOOM_IN, 1);
1612
- drive(f.dx, SLIDE, 0);
3417
+ drive(f.dx, SLIDE2, 0);
1613
3418
  drive(f.alpha, ENTER, 1);
1614
3419
  }
1615
3420
  syncZoomed();
@@ -1640,7 +3445,7 @@ function bento(container, options) {
1640
3445
  revealUnder(f.tile.id);
1641
3446
  if (reduced()) {
1642
3447
  f.veil = true;
1643
- drive(f.alpha, FADE, 0);
3448
+ drive(f.alpha, FADE2, 0);
1644
3449
  } else
1645
3450
  drive(f.z, ZOOM_OUT, 0);
1646
3451
  syncZoomed();
@@ -1661,7 +3466,7 @@ function bento(container, options) {
1661
3466
  cur.out = true;
1662
3467
  cur.veil = false;
1663
3468
  drive(cur.dx, SLIDE_OUT, -dir * by);
1664
- drive(cur.alpha, reduced() ? FADE : SLIDE_OUT, 0);
3469
+ drive(cur.alpha, reduced() ? FADE2 : SLIDE_OUT, 0);
1665
3470
  let f = focusOf(t);
1666
3471
  if (!f) {
1667
3472
  f = beginFocus(t);
@@ -1672,8 +3477,8 @@ function bento(container, options) {
1672
3477
  f.out = false;
1673
3478
  f.veil = false;
1674
3479
  drive(f.z, ZOOM_IN, 1);
1675
- drive(f.dx, SLIDE, 0);
1676
- drive(f.alpha, reduced() ? FADE : SLIDE, 1);
3480
+ drive(f.dx, SLIDE2, 0);
3481
+ drive(f.alpha, reduced() ? FADE2 : SLIDE2, 1);
1677
3482
  setActive(id);
1678
3483
  syncZoomed();
1679
3484
  loop.wake();
@@ -1947,118 +3752,702 @@ function bento(container, options) {
1947
3752
  tiles.get(id)?.el.setAttribute("aria-selected", String(picks.has(id)));
1948
3753
  options.onPick?.(orderedPicks());
1949
3754
  };
1950
- const orderedPicks = () => items.filter((it) => picks.has(it.id)).map((it) => it.id);
1951
- root.addEventListener("wheel", onWheel, { passive: false });
1952
- root.addEventListener("gesturestart", onGesture);
1953
- root.addEventListener("gesturechange", onGesture);
1954
- root.addEventListener("gestureend", onGesture);
1955
- root.addEventListener("pointerdown", onPointerDown);
1956
- root.addEventListener("pointermove", onPointerMove);
1957
- root.addEventListener("pointerup", onPointerUp);
1958
- root.addEventListener("pointercancel", onPointerUp);
1959
- root.addEventListener("mousemove", onPointerMoveAny);
1960
- root.addEventListener("click", onClick);
1961
- root.addEventListener("keydown", onKey);
1962
- root.addEventListener("scroll", onScroll, { passive: true });
1963
- root.addEventListener("knobs-restyle", () => relayout("snap"));
1964
- let lastSize = "";
1965
- const ro = new win.ResizeObserver(() => {
1966
- const size = `${root.clientWidth}x${root.clientHeight}`;
1967
- if (size === lastSize || !ready)
3755
+ const orderedPicks = () => items.filter((it) => picks.has(it.id)).map((it) => it.id);
3756
+ root.addEventListener("wheel", onWheel, { passive: false });
3757
+ root.addEventListener("gesturestart", onGesture);
3758
+ root.addEventListener("gesturechange", onGesture);
3759
+ root.addEventListener("gestureend", onGesture);
3760
+ root.addEventListener("pointerdown", onPointerDown);
3761
+ root.addEventListener("pointermove", onPointerMove);
3762
+ root.addEventListener("pointerup", onPointerUp);
3763
+ root.addEventListener("pointercancel", onPointerUp);
3764
+ root.addEventListener("mousemove", onPointerMoveAny);
3765
+ root.addEventListener("click", onClick);
3766
+ root.addEventListener("keydown", onKey);
3767
+ root.addEventListener("scroll", onScroll, { passive: true });
3768
+ root.addEventListener("knobs-restyle", () => relayout("snap"));
3769
+ let lastSize = "";
3770
+ const ro = new win.ResizeObserver(() => {
3771
+ const size = `${root.clientWidth}x${root.clientHeight}`;
3772
+ if (size === lastSize || !ready)
3773
+ return;
3774
+ lastSize = size;
3775
+ relayout("glide");
3776
+ });
3777
+ ro.observe(root);
3778
+ if (pendingProbes) {
3779
+ root.dataset.pending = "";
3780
+ win.setTimeout(start, 700);
3781
+ } else
3782
+ start();
3783
+ lastSize = `${root.clientWidth}x${root.clientHeight}`;
3784
+ return {
3785
+ element: root,
3786
+ update(next) {
3787
+ const before = { plan, index };
3788
+ const ids = new Set(next.map((it) => it.id));
3789
+ const entering = new Set(next.filter((it) => !index.has(it.id)).map((it) => it.id));
3790
+ for (const t of tiles.values()) {
3791
+ if (ids.has(t.id)) {
3792
+ if (t.leaving) {
3793
+ t.leaving = false;
3794
+ drive(t.life, ENTER, 1);
3795
+ }
3796
+ t.item = next.find((it) => it.id === t.id) ?? t.item;
3797
+ continue;
3798
+ }
3799
+ if (t.leaving)
3800
+ continue;
3801
+ const f = focusOf(t);
3802
+ if (f)
3803
+ endFocus(f);
3804
+ t.leaving = true;
3805
+ drive(t.life, reduced() ? FADE2 : EXIT2, 0);
3806
+ }
3807
+ items = next;
3808
+ reindex();
3809
+ for (const it of items)
3810
+ probe(it);
3811
+ for (const id of picks)
3812
+ if (!index.has(id))
3813
+ picks.delete(id);
3814
+ if (active && !index.has(active))
3815
+ setActive(null);
3816
+ if (ready)
3817
+ relayout("glide", before, entering);
3818
+ loop.wake();
3819
+ },
3820
+ zoom(id) {
3821
+ if (id === null)
3822
+ zoomOut();
3823
+ else if (index.has(id))
3824
+ zoomIn(id);
3825
+ },
3826
+ get zoomed() {
3827
+ return current()?.tile.id ?? null;
3828
+ },
3829
+ get active() {
3830
+ return active;
3831
+ },
3832
+ focus(id) {
3833
+ root.focus({ preventScroll: true });
3834
+ if (id && index.has(id))
3835
+ move(id);
3836
+ },
3837
+ get picks() {
3838
+ return orderedPicks();
3839
+ },
3840
+ pick(ids) {
3841
+ picks = new Set(ids);
3842
+ for (const t of tiles.values())
3843
+ t.el.setAttribute("aria-selected", String(picks.has(t.id)));
3844
+ },
3845
+ get plan() {
3846
+ return plan;
3847
+ },
3848
+ get moving() {
3849
+ return loop.running;
3850
+ },
3851
+ stats() {
3852
+ const s = [...times].sort((a, b) => a - b);
3853
+ const q = (p) => Math.round((s[Math.floor(p * (s.length - 1))] ?? 0) * 1000) / 1000;
3854
+ return { frames, p50: q(0.5), p95: q(0.95), max: q(1) };
3855
+ },
3856
+ dispose() {
3857
+ loop.stop();
3858
+ ro.disconnect();
3859
+ win.clearTimeout(swipe.timer);
3860
+ if (hand)
3861
+ win.clearTimeout(hand.timer);
3862
+ for (const t of [...tiles.values()])
3863
+ unmount(t);
3864
+ root.remove();
3865
+ }
3866
+ };
3867
+ }
3868
+
3869
+ // src/ui/follow.ts
3870
+ function propChanges(initial, same = Object.is) {
3871
+ let last = { ...initial };
3872
+ return (next) => {
3873
+ const changed = {};
3874
+ for (const key of Object.keys(next))
3875
+ if (!same(next[key], last[key]))
3876
+ changed[key] = next[key];
3877
+ last = { ...next };
3878
+ return changed;
3879
+ };
3880
+ }
3881
+ var anyChanged = (changed) => Object.keys(changed).length > 0;
3882
+ function timelineChanges(now, changed) {
3883
+ const next = {};
3884
+ for (const key of Object.keys(changed)) {
3885
+ const value = changed[key];
3886
+ if (value === undefined)
3887
+ continue;
3888
+ if (key === "time" && Math.abs(value - now.time) < 0.000001)
3889
+ continue;
3890
+ if (key === "playing" && value === now.playing)
3891
+ continue;
3892
+ if ((key === "markers" || key === "ranges") && value === now[key])
3893
+ continue;
3894
+ next[key] = value;
3895
+ }
3896
+ return next;
3897
+ }
3898
+
3899
+ // src/ui/picker-model.ts
3900
+ function digitPick(ids, key) {
3901
+ if (!/^[1-9]$/.test(key))
3902
+ return;
3903
+ return ids[Number(key) - 1];
3904
+ }
3905
+ function stepPick(ids, current, key) {
3906
+ if (!ids.length)
3907
+ return;
3908
+ if (key === "Home")
3909
+ return ids[0];
3910
+ if (key === "End")
3911
+ return ids[ids.length - 1];
3912
+ const i = current === null ? -1 : ids.indexOf(current);
3913
+ if (i < 0)
3914
+ return key === "ArrowDown" ? ids[0] : ids[ids.length - 1];
3915
+ return ids[Math.max(0, Math.min(ids.length - 1, i + (key === "ArrowDown" ? 1 : -1)))];
3916
+ }
3917
+ function keepPick(ids, selected, wanted = null) {
3918
+ if (wanted !== null && ids.includes(wanted))
3919
+ return wanted;
3920
+ if (selected !== null && ids.includes(selected))
3921
+ return selected;
3922
+ return ids[0] ?? null;
3923
+ }
3924
+ function askPick(ids, id) {
3925
+ return ids.includes(id) ? { pick: id, wanted: null } : { pick: undefined, wanted: id };
3926
+ }
3927
+ var stillWanted = (ids, wanted) => wanted !== null && ids.includes(wanted) ? null : wanted;
3928
+ var pickDigit = (index) => String(index + 1);
3929
+ function claimsDigit(event, typing = false) {
3930
+ return /^[1-9]$/.test(event.key) && !event.defaultPrevented && !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey && !event.repeat && !event.typing && !typing;
3931
+ }
3932
+ function cardTarget(heads, index, body, gap = 0, top = 0) {
3933
+ if (body === undefined)
3934
+ return null;
3935
+ let y = top;
3936
+ for (let i = 0;i < index; i++)
3937
+ y += heads[i] + gap;
3938
+ return { y, h: (heads[index] ?? 0) + body };
3939
+ }
3940
+ function settleHeight(previous, now, frames, limit = 6) {
3941
+ if (previous !== undefined && Math.abs(now - previous) < 0.5)
3942
+ return now;
3943
+ return frames >= limit ? now : undefined;
3944
+ }
3945
+
3946
+ // src/ui/picker.ts
3947
+ var OPEN2 = springMotion({ duration: 0.22, bounce: 0 });
3948
+ var CLOSE2 = springMotion({ duration: 0.18, bounce: 0 });
3949
+ var CARD = springMotion({ duration: 0.3, bounce: 0 });
3950
+ var FADE3 = springMotion({ duration: 0.16, bounce: 0 });
3951
+ var DRIFT = 6;
3952
+ var CHECK = svg('<path d="m3.5 8.4 3 3 6-6.6"/>');
3953
+ function optionPicker(container, options) {
3954
+ const doc = container.ownerDocument;
3955
+ const win = doc.defaultView;
3956
+ let list = options.options;
3957
+ let wanted = null;
3958
+ let selected = null;
3959
+ {
3960
+ const ids = list.map((o) => o.id);
3961
+ if (options.selected != null)
3962
+ wanted = askPick(ids, options.selected).wanted;
3963
+ selected = keepPick(ids, options.selected ?? null);
3964
+ }
3965
+ let chosen = options.chosen ?? null;
3966
+ const digits = options.digits ?? "focus";
3967
+ const root = doc.createElement("div");
3968
+ root.className = "knobs-picker";
3969
+ root.setAttribute("role", "radiogroup");
3970
+ if (options.label)
3971
+ root.setAttribute("aria-label", options.label);
3972
+ applyLook(root, options);
3973
+ const card = doc.createElement("div");
3974
+ card.className = "knobs-picker-card";
3975
+ card.setAttribute("aria-hidden", "true");
3976
+ root.append(card);
3977
+ container.append(root);
3978
+ const entries = new Map;
3979
+ const uid = `kp${Math.random().toString(36).slice(2, 7)}`;
3980
+ let serial = 0;
3981
+ const make = (option) => {
3982
+ const el = doc.createElement("div");
3983
+ el.className = "knobs-picker-option";
3984
+ el.dataset.id = option.id;
3985
+ const head = doc.createElement("button");
3986
+ head.type = "button";
3987
+ head.className = "knobs-picker-head";
3988
+ head.setAttribute("role", "radio");
3989
+ head.id = `${uid}-${serial++}`;
3990
+ const key = doc.createElement("span");
3991
+ key.className = "knobs-picker-key";
3992
+ key.setAttribute("aria-hidden", "true");
3993
+ const name = doc.createElement("span");
3994
+ name.className = "knobs-picker-name knobs-scroll-fade-x";
3995
+ const suggested = doc.createElement("span");
3996
+ suggested.className = "knobs-picker-suggested";
3997
+ const check = doc.createElement("span");
3998
+ check.className = "knobs-picker-check";
3999
+ check.innerHTML = CHECK;
4000
+ check.setAttribute("aria-hidden", "true");
4001
+ head.append(key, name, suggested, check);
4002
+ const body = doc.createElement("div");
4003
+ body.className = "knobs-picker-body";
4004
+ body.hidden = true;
4005
+ body.setAttribute("role", "group");
4006
+ body.setAttribute("aria-labelledby", head.id);
4007
+ const inner = doc.createElement("div");
4008
+ inner.className = "knobs-picker-inner";
4009
+ body.append(inner);
4010
+ el.append(head, body);
4011
+ const entry = {
4012
+ option,
4013
+ el,
4014
+ head,
4015
+ key,
4016
+ thumb: undefined,
4017
+ name,
4018
+ suggested,
4019
+ body,
4020
+ inner,
4021
+ mounted: false,
4022
+ measured: true,
4023
+ cleanup: undefined,
4024
+ h: new Spring(0, OPEN2.omega, OPEN2.zeta, 0.05),
4025
+ o: new Spring(0, OPEN2.omega, OPEN2.zeta, 0.002),
4026
+ moving: false,
4027
+ open: false,
4028
+ presence: new Spring(1, OPEN2.omega, OPEN2.zeta, 0.002),
4029
+ leaving: false
4030
+ };
4031
+ entries.set(option.id, entry);
4032
+ return entry;
4033
+ };
4034
+ const setThumb = (e, url) => {
4035
+ if ((e.thumb?.getAttribute("src") ?? undefined) === (url || undefined))
4036
+ return;
4037
+ if (!url) {
4038
+ e.thumb?.remove();
4039
+ e.thumb = undefined;
4040
+ return;
4041
+ }
4042
+ let img = e.thumb;
4043
+ if (!img) {
4044
+ const el = doc.createElement("img");
4045
+ el.className = "knobs-picker-thumb";
4046
+ el.alt = "";
4047
+ el.loading = "lazy";
4048
+ el.decoding = "async";
4049
+ el.draggable = false;
4050
+ el.addEventListener("load", () => el.dataset.state = "loaded");
4051
+ el.addEventListener("error", () => el.dataset.state = "failed");
4052
+ e.key.after(el);
4053
+ e.thumb = img = el;
4054
+ }
4055
+ const shown = img;
4056
+ shown.dataset.state = "loading";
4057
+ shown.src = url;
4058
+ if (shown.complete && shown.naturalWidth > 0) {
4059
+ shown.dataset.state = "loaded";
4060
+ shown.dataset.instant = "";
4061
+ win.requestAnimationFrame(() => delete shown.dataset.instant);
4062
+ }
4063
+ };
4064
+ const bind = (e, i) => {
4065
+ e.option = list[i];
4066
+ e.key.textContent = pickDigit(i);
4067
+ setThumb(e, e.option.thumb);
4068
+ if (e.name.textContent !== e.option.label)
4069
+ e.name.textContent = e.option.label;
4070
+ e.suggested.textContent = e.option.suggested ? options.suggestedLabel ?? "Suggested" : "";
4071
+ e.suggested.hidden = !e.option.suggested;
4072
+ const on = e.option.id === selected;
4073
+ e.el.toggleAttribute("data-selected", on);
4074
+ e.head.setAttribute("aria-checked", String(on));
4075
+ e.head.tabIndex = on ? 0 : -1;
4076
+ e.el.toggleAttribute("data-chosen", e.option.id === chosen);
4077
+ e.head.setAttribute("aria-label", [
4078
+ e.option.label,
4079
+ e.option.suggested ? options.suggestedLabel ?? "Suggested" : "",
4080
+ e.option.id === chosen ? "chosen" : ""
4081
+ ].filter(Boolean).join(", "));
4082
+ };
4083
+ const mount = (e) => {
4084
+ if (e.mounted || !options.body)
4085
+ return;
4086
+ e.mounted = true;
4087
+ const out = options.body(e.option, e.inner);
4088
+ if (typeof out === "function")
4089
+ e.cleanup = out;
4090
+ };
4091
+ const cardY = new Spring(0, CARD.omega, CARD.zeta, 0.05);
4092
+ const cardH = new Spring(0, CARD.omega, CARD.zeta, 0.05);
4093
+ let gliding = false;
4094
+ const liveCard = () => {
4095
+ const e = selected ? entries.get(selected) : undefined;
4096
+ return e && !e.leaving ? { y: e.el.offsetTop, h: e.el.offsetHeight } : null;
4097
+ };
4098
+ const finalCard = () => {
4099
+ const live = list.map((o) => entries.get(o.id)).filter((e) => !!e && !e.leaving);
4100
+ const index = live.findIndex((e) => e.option.id === selected);
4101
+ if (index < 0)
4102
+ return null;
4103
+ const gap = parseFloat(win.getComputedStyle(root).rowGap) || 0;
4104
+ const e = live[index];
4105
+ return cardTarget(live.map((x) => x.head.offsetHeight), index, e.measured ? natural(e) : undefined, gap);
4106
+ };
4107
+ const placeCard = () => {
4108
+ card.style.transform = `translate3d(0,${Math.round(cardY.x * 2) / 2}px,0)`;
4109
+ card.style.height = `${Math.max(0, cardH.x).toFixed(2)}px`;
4110
+ card.hidden = cardH.x < 0.5;
4111
+ };
4112
+ const natural = (e) => e.inner.offsetHeight;
4113
+ const setOpen = (e, open, animate) => {
4114
+ if (e.open === open && (e.moving || e.body.hidden === !open))
4115
+ return;
4116
+ e.open = open;
4117
+ const reduced = reducedMotion(win);
4118
+ if (open && !e.mounted) {
4119
+ if (animate && e.body.hidden) {
4120
+ e.body.hidden = false;
4121
+ e.body.style.height = "0px";
4122
+ e.body.dataset.moving = "";
4123
+ e.h.snap(0);
4124
+ e.o.snap(0);
4125
+ e.moving = true;
4126
+ }
4127
+ mount(e);
4128
+ }
4129
+ if (!animate) {
4130
+ e.body.hidden = !open;
4131
+ e.moving = false;
4132
+ e.body.style.height = "";
4133
+ delete e.body.dataset.moving;
4134
+ e.inner.style.opacity = "";
4135
+ e.inner.style.transform = "";
4136
+ return;
4137
+ }
4138
+ if (!e.moving) {
4139
+ const from = e.body.hidden ? 0 : e.body.offsetHeight;
4140
+ e.h.snap(from);
4141
+ e.o.snap(from > 0.5 ? 1 : 0);
4142
+ }
4143
+ e.body.hidden = false;
4144
+ e.moving = true;
4145
+ e.body.dataset.moving = "";
4146
+ if (reduced) {
4147
+ e.h.snap(open ? natural(e) : 0);
4148
+ tune(e.o, FADE3).set(open ? 1 : 0);
4149
+ } else {
4150
+ tune(e.h, open ? OPEN2 : CLOSE2).set(open ? natural(e) : 0);
4151
+ tune(e.o, open ? OPEN2 : CLOSE2).set(open ? 1 : 0);
4152
+ }
4153
+ loop.wake();
4154
+ };
4155
+ let pending = null;
4156
+ const premount = (e) => {
4157
+ e.body.hidden = false;
4158
+ e.body.style.height = "0px";
4159
+ e.body.dataset.moving = "";
4160
+ e.inner.style.opacity = "0";
4161
+ e.measured = false;
4162
+ mount(e);
4163
+ };
4164
+ const shelve = (e) => {
4165
+ if (e.open || e.moving)
4166
+ return;
4167
+ e.body.hidden = true;
4168
+ e.body.style.height = "";
4169
+ delete e.body.dataset.moving;
4170
+ e.inner.style.opacity = "";
4171
+ e.measured = true;
4172
+ };
4173
+ const start = (was, now, animate) => {
4174
+ now.measured = true;
4175
+ if (was && was !== now)
4176
+ setOpen(was, false, animate);
4177
+ setOpen(now, true, animate);
4178
+ if (animate && !reducedMotion(win)) {
4179
+ tune(cardY, CARD);
4180
+ tune(cardH, CARD);
4181
+ gliding = true;
4182
+ }
4183
+ loop.wake();
4184
+ };
4185
+ const flush = () => {
4186
+ const p = pending;
4187
+ if (!p)
4188
+ return;
4189
+ pending = null;
4190
+ start(p.was, p.now, true);
4191
+ };
4192
+ const loop = frameLoop(win, (dt) => {
4193
+ let moving = false;
4194
+ if (pending) {
4195
+ const p = pending;
4196
+ const h = natural(p.now);
4197
+ p.frames++;
4198
+ if (settleHeight(p.last, h, p.frames) !== undefined)
4199
+ flush();
4200
+ else {
4201
+ p.last = h;
4202
+ moving = true;
4203
+ }
4204
+ }
4205
+ for (const e of [...entries.values()]) {
4206
+ if (e.moving) {
4207
+ if (e.open && !reducedMotion(win))
4208
+ e.h.set(natural(e));
4209
+ e.h.step(dt);
4210
+ e.o.step(dt);
4211
+ const fade = Math.max(0, Math.min(1, e.o.x));
4212
+ e.body.style.height = `${Math.max(0, e.h.x).toFixed(2)}px`;
4213
+ const shown = e.open ? fade : Math.max(0, (fade - 0.4) / 0.6);
4214
+ e.inner.style.opacity = shown < 0.999 ? shown.toFixed(3) : "";
4215
+ const drift = reducedMotion(win) ? 0 : (1 - fade) * -DRIFT;
4216
+ e.inner.style.transform = Math.abs(drift) > 0.05 ? `translateY(${drift.toFixed(2)}px)` : "";
4217
+ if (e.h.resting && e.o.resting) {
4218
+ e.moving = false;
4219
+ delete e.body.dataset.moving;
4220
+ e.body.style.height = "";
4221
+ e.inner.style.opacity = "";
4222
+ e.inner.style.transform = "";
4223
+ e.body.hidden = !e.open;
4224
+ } else
4225
+ moving = true;
4226
+ }
4227
+ if (e.leaving || !e.presence.resting) {
4228
+ e.presence.step(dt);
4229
+ const p = Math.max(0, Math.min(1, e.presence.x));
4230
+ e.el.style.opacity = p < 0.999 ? p.toFixed(3) : "";
4231
+ e.el.style.height = p < 0.999 ? `${(p * e.head.offsetHeight).toFixed(2)}px` : "";
4232
+ e.el.style.overflow = p < 0.999 ? "hidden" : "";
4233
+ if (e.presence.resting) {
4234
+ if (e.leaving) {
4235
+ e.cleanup?.();
4236
+ e.el.remove();
4237
+ entries.delete(e.option.id);
4238
+ }
4239
+ } else
4240
+ moving = true;
4241
+ }
4242
+ }
4243
+ const t = pending ? null : gliding ? finalCard() : liveCard();
4244
+ if (t) {
4245
+ if (gliding) {
4246
+ cardY.set(t.y);
4247
+ cardH.set(t.h);
4248
+ cardY.step(dt);
4249
+ cardH.step(dt);
4250
+ if (cardY.resting && cardH.resting && !moving)
4251
+ gliding = false;
4252
+ else
4253
+ moving = true;
4254
+ } else {
4255
+ cardY.snap(t.y);
4256
+ cardH.snap(t.h);
4257
+ }
4258
+ }
4259
+ placeCard();
4260
+ notifyLayout(doc);
4261
+ return moving;
4262
+ });
4263
+ const rebind = () => {
4264
+ list.forEach((o, i) => {
4265
+ const e = entries.get(o.id);
4266
+ if (e)
4267
+ bind(e, i);
4268
+ });
4269
+ };
4270
+ const pick = (id, animate = true) => {
4271
+ if (id === selected || !entries.has(id))
4272
+ return;
4273
+ let was = selected ? entries.get(selected) : undefined;
4274
+ if (pending) {
4275
+ was = pending.was;
4276
+ const waiting = pending.now;
4277
+ pending = null;
4278
+ if (waiting.option.id !== id)
4279
+ shelve(waiting);
4280
+ }
4281
+ selected = id;
4282
+ rebind();
4283
+ const now = entries.get(id);
4284
+ if (animate && !reducedMotion(win) && !now.mounted && options.body) {
4285
+ premount(now);
4286
+ pending = { was, now, last: natural(now), frames: 0 };
4287
+ loop.wake();
4288
+ return;
4289
+ }
4290
+ start(was, now, animate);
4291
+ };
4292
+ const select = (id, source) => {
4293
+ if (!id)
4294
+ return;
4295
+ wanted = null;
4296
+ if (id === selected)
4297
+ return;
4298
+ const inside = root.contains(doc.activeElement);
4299
+ pick(id);
4300
+ if (source !== "pointer" && inside)
4301
+ entries.get(id)?.head.focus({ preventScroll: true });
4302
+ options.onSelect?.(id, source);
4303
+ };
4304
+ const sync = (animate) => {
4305
+ flush();
4306
+ const ids = list.map((o) => o.id);
4307
+ const was = selected;
4308
+ selected = keepPick(ids, selected, wanted);
4309
+ wanted = stillWanted(ids, wanted);
4310
+ if (animate && was !== selected && !reducedMotion(win))
4311
+ gliding = true;
4312
+ const reduced = reducedMotion(win);
4313
+ const live = new Set(ids);
4314
+ for (const e of entries.values()) {
4315
+ if (live.has(e.option.id) || e.leaving)
4316
+ continue;
4317
+ if (!animate) {
4318
+ e.cleanup?.();
4319
+ e.el.remove();
4320
+ entries.delete(e.option.id);
4321
+ continue;
4322
+ }
4323
+ e.leaving = true;
4324
+ e.el.setAttribute("aria-hidden", "true");
4325
+ e.head.tabIndex = -1;
4326
+ if (e.open)
4327
+ setOpen(e, false, true);
4328
+ tune(e.presence, reduced ? FADE3 : CLOSE2).set(0);
4329
+ }
4330
+ let before = card;
4331
+ list.forEach((o, i) => {
4332
+ let e = entries.get(o.id);
4333
+ if (e?.leaving) {
4334
+ e.leaving = false;
4335
+ e.el.removeAttribute("aria-hidden");
4336
+ tune(e.presence, OPEN2).set(1);
4337
+ }
4338
+ if (!e) {
4339
+ e = make(o);
4340
+ ro.observe(e.el);
4341
+ if (animate)
4342
+ tune(e.presence, reduced ? FADE3 : OPEN2).snap(0).set(1);
4343
+ }
4344
+ if (before.nextElementSibling !== e.el)
4345
+ before.after(e.el);
4346
+ before = e.el;
4347
+ bind(e, i);
4348
+ });
4349
+ const open = selected ? entries.get(selected) : undefined;
4350
+ const ahead = animate && !reduced && open && !open.open && !open.mounted && !!options.body ? { was: was ? entries.get(was) : undefined, now: open } : null;
4351
+ for (const e of entries.values())
4352
+ if (e !== open && e !== ahead?.was && e.open && !e.leaving)
4353
+ setOpen(e, false, animate);
4354
+ if (ahead) {
4355
+ premount(ahead.now);
4356
+ pending = { ...ahead, last: natural(ahead.now), frames: 0 };
4357
+ } else if (open && !open.open)
4358
+ setOpen(open, true, animate);
4359
+ loop.wake();
4360
+ };
4361
+ const ids = () => list.map((o) => o.id).filter((id) => !entries.get(id)?.leaving);
4362
+ const onDigit = (e) => {
4363
+ if (!claimsDigit(e, isTypingTarget(e.target)))
1968
4364
  return;
1969
- lastSize = size;
1970
- relayout("glide");
4365
+ if (!root.getClientRects().length)
4366
+ return;
4367
+ const id = digitPick(ids(), e.key);
4368
+ if (!id)
4369
+ return;
4370
+ e.preventDefault();
4371
+ select(id, "digit");
4372
+ };
4373
+ root.addEventListener("keydown", (e) => {
4374
+ const head = e.target.closest?.(".knobs-picker-head");
4375
+ if (head && !e.metaKey && !e.ctrlKey && !e.altKey) {
4376
+ if (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Home" || e.key === "End") {
4377
+ e.preventDefault();
4378
+ select(stepPick(ids(), selected, e.key), "key");
4379
+ return;
4380
+ }
4381
+ if (e.key === "Enter" && selected) {
4382
+ e.preventDefault();
4383
+ options.onChoose?.(selected);
4384
+ return;
4385
+ }
4386
+ }
4387
+ if (digits === "focus")
4388
+ onDigit(e);
4389
+ });
4390
+ if (digits === "page")
4391
+ win.addEventListener("keydown", onDigit);
4392
+ root.addEventListener("click", (e) => {
4393
+ const el = e.target.closest(".knobs-picker-head");
4394
+ const id = el?.closest(".knobs-picker-option")?.dataset.id;
4395
+ if (!id || entries.get(id)?.leaving)
4396
+ return;
4397
+ select(id, e.detail === 0 ? "key" : "pointer");
1971
4398
  });
4399
+ const ro = new ResizeObserver(() => loop.wake());
1972
4400
  ro.observe(root);
1973
- if (pendingProbes) {
1974
- root.dataset.pending = "";
1975
- win.setTimeout(start, 700);
1976
- } else
1977
- start();
1978
- lastSize = `${root.clientWidth}x${root.clientHeight}`;
1979
- return {
4401
+ root.addEventListener("knobs-restyle", () => {
4402
+ syncFrame(root);
4403
+ loop.wake();
4404
+ });
4405
+ sync(false);
4406
+ const first = liveCard();
4407
+ if (first) {
4408
+ cardY.snap(first.y);
4409
+ cardH.snap(first.h);
4410
+ }
4411
+ placeCard();
4412
+ loop.wake();
4413
+ const handle = {
1980
4414
  element: root,
1981
- update(next) {
1982
- const before = { plan, index };
1983
- const ids = new Set(next.map((it) => it.id));
1984
- const entering = new Set(next.filter((it) => !index.has(it.id)).map((it) => it.id));
1985
- for (const t of tiles.values()) {
1986
- if (ids.has(t.id)) {
1987
- if (t.leaving) {
1988
- t.leaving = false;
1989
- drive(t.life, ENTER, 1);
1990
- }
1991
- t.item = next.find((it) => it.id === t.id) ?? t.item;
1992
- continue;
1993
- }
1994
- if (t.leaving)
1995
- continue;
1996
- const f = focusOf(t);
1997
- if (f)
1998
- endFocus(f);
1999
- t.leaving = true;
2000
- drive(t.life, reduced() ? FADE : EXIT, 0);
2001
- }
2002
- items = next;
2003
- reindex();
2004
- for (const it of items)
2005
- probe(it);
2006
- for (const id of picks)
2007
- if (!index.has(id))
2008
- picks.delete(id);
2009
- if (active && !index.has(active))
2010
- setActive(null);
2011
- if (ready)
2012
- relayout("glide", before, entering);
2013
- loop.wake();
2014
- },
2015
- zoom(id) {
2016
- if (id === null)
2017
- zoomOut();
2018
- else if (index.has(id))
2019
- zoomIn(id);
2020
- },
2021
- get zoomed() {
2022
- return current()?.tile.id ?? null;
2023
- },
2024
- get active() {
2025
- return active;
2026
- },
2027
- focus(id) {
2028
- root.focus({ preventScroll: true });
2029
- if (id && index.has(id))
2030
- move(id);
4415
+ set(next) {
4416
+ list = next;
4417
+ sync(true);
2031
4418
  },
2032
- get picks() {
2033
- return orderedPicks();
4419
+ select(id) {
4420
+ const ask = askPick(ids(), id);
4421
+ wanted = ask.wanted;
4422
+ if (ask.pick)
4423
+ pick(ask.pick);
2034
4424
  },
2035
- pick(ids) {
2036
- picks = new Set(ids);
2037
- for (const t of tiles.values())
2038
- t.el.setAttribute("aria-selected", String(picks.has(t.id)));
4425
+ get selected() {
4426
+ return selected;
2039
4427
  },
2040
- get plan() {
2041
- return plan;
4428
+ choose(id) {
4429
+ chosen = id;
4430
+ rebind();
2042
4431
  },
2043
- get moving() {
2044
- return loop.running;
4432
+ get chosen() {
4433
+ return chosen;
2045
4434
  },
2046
- stats() {
2047
- const s = [...times].sort((a, b) => a - b);
2048
- const q = (p) => Math.round((s[Math.floor(p * (s.length - 1))] ?? 0) * 1000) / 1000;
2049
- return { frames, p50: q(0.5), p95: q(0.95), max: q(1) };
4435
+ focus() {
4436
+ (selected ? entries.get(selected)?.head : undefined)?.focus({ preventScroll: true });
2050
4437
  },
2051
- dispose() {
4438
+ destroy() {
2052
4439
  loop.stop();
2053
4440
  ro.disconnect();
2054
- win.clearTimeout(swipe.timer);
2055
- if (hand)
2056
- win.clearTimeout(hand.timer);
2057
- for (const t of [...tiles.values()])
2058
- unmount(t);
4441
+ if (digits === "page")
4442
+ win.removeEventListener("keydown", onDigit);
4443
+ for (const e of entries.values())
4444
+ e.cleanup?.();
4445
+ entries.clear();
2059
4446
  root.remove();
2060
- }
4447
+ },
4448
+ dispose: () => handle.destroy()
2061
4449
  };
4450
+ return handle;
2062
4451
  }
2063
4452
 
2064
4453
  // src/core/storage.ts
@@ -3292,6 +5681,43 @@ function edgeOf(panes, id) {
3292
5681
  const first = panes.findIndex((p) => flex.has(p.id));
3293
5682
  return panes.findIndex((p) => p.id === id) < first ? "start" : "end";
3294
5683
  }
5684
+ function foldProgress(size, open, rail) {
5685
+ const span = open - rail;
5686
+ if (span <= 0)
5687
+ return size > rail ? 0 : 1;
5688
+ return Math.min(1, Math.max(0, (open - size) / span));
5689
+ }
5690
+ var FACE_OUT = 0.4;
5691
+ var FACE_IN = 0.3;
5692
+ var clamp01 = (x) => Math.min(1, Math.max(0, x));
5693
+ function face(prev, away) {
5694
+ const lo = clamp01(1 - away / FACE_IN);
5695
+ const hi = clamp01(1 - away / FACE_OUT);
5696
+ return Math.min(hi, Math.max(lo, prev));
5697
+ }
5698
+ function foldFaces(prev, p) {
5699
+ return { content: face(prev.content, p), rail: face(prev.rail, 1 - p) };
5700
+ }
5701
+ function chevronTurn(p, edge) {
5702
+ return (edge === "start" ? 180 : -180) * (1 - clamp01(p));
5703
+ }
5704
+ function chevronFlip(f) {
5705
+ return 2 * clamp01(f) - 1;
5706
+ }
5707
+ var FOLD_STYLE = { chevron: "flip", title: "travel", duration: 0.3 };
5708
+ function foldStyle(options = {}) {
5709
+ const duration = options.duration;
5710
+ return {
5711
+ chevron: options.chevron === "turn" ? "turn" : "flip",
5712
+ title: options.title === "fade" ? "fade" : "travel",
5713
+ duration: duration !== undefined && Number.isFinite(duration) && duration > 0 ? Math.min(2, duration) : FOLD_STYLE.duration
5714
+ };
5715
+ }
5716
+ function titleTravel(at, open, rail, edge, p) {
5717
+ const k = 1 - clamp01(p);
5718
+ const dx = at.title - at.label - (edge === "start" ? open - rail : 0);
5719
+ return { x: dx * k, y: at.dy * k, turn: -90 * k };
5720
+ }
3295
5721
  function parseLayout(raw, panes) {
3296
5722
  if (!raw || typeof raw !== "object")
3297
5723
  return;
@@ -3319,8 +5745,24 @@ function serializeLayout(layout) {
3319
5745
  }
3320
5746
 
3321
5747
  // src/ui/split.ts
3322
- var COLLAPSE = springMotion({ duration: 0.42, bounce: 0.04 });
5748
+ var FLIP = springMotion({ duration: 0.18, bounce: 0 });
3323
5749
  var SETTLE = springMotion({ duration: 0.3, bounce: 0 });
5750
+ var CONTENT_BLUR = 3;
5751
+ var LABEL_BLUR = 2;
5752
+ var put = (el, prop, value) => {
5753
+ if (el.style.getPropertyValue(prop) !== value)
5754
+ el.style.setProperty(prop, value);
5755
+ };
5756
+ var hide = (el, hidden) => {
5757
+ if (hidden)
5758
+ el.setAttribute("aria-hidden", "true");
5759
+ else
5760
+ el.removeAttribute("aria-hidden");
5761
+ };
5762
+ var fade = (el, a, blur) => {
5763
+ put(el, "opacity", a >= 1 ? "" : a.toFixed(3));
5764
+ put(el, "filter", a >= 1 || a <= 0 || !blur ? "" : `blur(${(blur * (1 - a)).toFixed(2)}px)`);
5765
+ };
3324
5766
  var iconNode = (doc, icon, label) => {
3325
5767
  if (icon instanceof Node)
3326
5768
  return icon;
@@ -3339,6 +5781,8 @@ function split(container, options) {
3339
5781
  const row = direction === "row";
3340
5782
  const specs = options.panes;
3341
5783
  const flex = flexible(specs);
5784
+ const style = foldStyle(options.fold);
5785
+ const COLLAPSE = springMotion({ duration: style.duration, bounce: 0 });
3342
5786
  const store = options.storage === undefined ? browserStorage() : options.storage;
3343
5787
  const key = options.id ? storageKey(options.id, "split") : undefined;
3344
5788
  const saved = (() => {
@@ -3367,13 +5811,31 @@ function split(container, options) {
3367
5811
  const el = doc.createElement("section");
3368
5812
  el.className = "knobs-pane";
3369
5813
  el.dataset.pane = spec.id;
5814
+ const edge = edgeOf(specs, spec.id);
5815
+ el.dataset.edge = edge;
3370
5816
  const clip = doc.createElement("div");
3371
5817
  clip.className = "knobs-pane-clip";
3372
5818
  const inner = doc.createElement("div");
3373
5819
  inner.className = "knobs-pane-inner";
3374
5820
  const body = doc.createElement("div");
3375
5821
  body.className = "knobs-pane-body";
3376
- const view = { spec, el, clip, inner, body, open: 0, shown: 0 };
5822
+ const shut = Boolean(collapsed[spec.id]);
5823
+ const view = {
5824
+ spec,
5825
+ el,
5826
+ clip,
5827
+ inner,
5828
+ body,
5829
+ edge,
5830
+ open: 0,
5831
+ shown: 0,
5832
+ folding: false,
5833
+ faces: { content: shut ? 0 : 1, rail: shut ? 1 : 0 },
5834
+ headPx: 0,
5835
+ chev: { iconEnd: 0, chevEnd: 0, dy: 0, scale: 1 },
5836
+ places: { title: 0, label: 0, dy: 0 },
5837
+ flip: new Spring(shut ? 1 : 0, FLIP.omega, FLIP.zeta, 0.002)
5838
+ };
3377
5839
  if (spec.title !== undefined) {
3378
5840
  const head = doc.createElement("header");
3379
5841
  head.className = "knobs-pane-head";
@@ -3411,9 +5873,10 @@ function split(container, options) {
3411
5873
  tooltip(b, () => collapsed[spec.id] ? "Open" : row ? "Collapse" : "Fold");
3412
5874
  b.innerHTML = row ? ICONS.caret : ICONS.chevronDown;
3413
5875
  if (row)
3414
- b.firstElementChild?.setAttribute("style", edgeOf(specs, spec.id) === "start" ? "transform:scaleX(-1)" : "");
5876
+ b.firstElementChild?.setAttribute("style", edge === "start" ? "transform:scaleX(-1)" : "");
3415
5877
  else
3416
5878
  b.firstElementChild?.classList.add("knobs-pane-caret");
5879
+ view.toggleIcon = b.firstElementChild ?? undefined;
3417
5880
  b.addEventListener("click", (e) => {
3418
5881
  e.stopPropagation();
3419
5882
  setCollapsed(spec.id, !collapsed[spec.id]);
@@ -3432,8 +5895,10 @@ function split(container, options) {
3432
5895
  inner.append(body);
3433
5896
  clip.append(inner);
3434
5897
  el.append(clip);
3435
- if (spec.scroll ?? spec.title !== undefined)
5898
+ if (spec.scroll ?? spec.title !== undefined) {
3436
5899
  el.dataset.scroll = "";
5900
+ body.classList.add("knobs-scroll-fade");
5901
+ }
3437
5902
  if (spec.element)
3438
5903
  body.append(spec.element);
3439
5904
  if (spec.collapsible && (row || !view.head)) {
@@ -3445,13 +5910,22 @@ function split(container, options) {
3445
5910
  rail.setAttribute("aria-label", `Open ${label}`);
3446
5911
  tooltip(rail, `Open ${label}`, { placement: row ? "right" : "top" });
3447
5912
  const text = doc.createElement("span");
5913
+ text.className = "knobs-pane-rail-label";
3448
5914
  text.textContent = spec.rail ?? spec.title ?? "";
3449
- rail.innerHTML = row ? ICONS.caret.replace("<svg", `<svg style="transform:scaleX(${edgeOf(specs, spec.id) === "start" ? 1 : -1})"`) : ICONS.chevronDown;
3450
- rail.append(text);
5915
+ const chevron = doc.createElement("span");
5916
+ chevron.className = "knobs-pane-chevron";
5917
+ const turn = doc.createElement("span");
5918
+ turn.className = "knobs-pane-turn";
5919
+ turn.innerHTML = row ? ICONS.caret.replace("<svg", `<svg style="transform:scaleX(${edge === "start" ? 1 : -1})"`) : ICONS.chevronDown;
5920
+ chevron.append(turn);
5921
+ rail.append(chevron, text);
3451
5922
  rail.addEventListener("click", () => setCollapsed(spec.id, false));
3452
5923
  el.append(rail);
3453
5924
  el.dataset.rail = "";
3454
5925
  view.rail = rail;
5926
+ view.railLabel = text;
5927
+ view.chevron = chevron;
5928
+ view.turn = turn;
3455
5929
  }
3456
5930
  return view;
3457
5931
  };
@@ -3481,9 +5955,10 @@ function split(container, options) {
3481
5955
  const total = () => along(root);
3482
5956
  let paneM = 0;
3483
5957
  const marginOf = (v) => v.el.hasAttribute("data-nested") ? 0 : paneM;
3484
- const headerSize = (v) => v.head ? v.head.getBoundingClientRect().height + marginOf(v) * 2 : 0;
5958
+ const headerSize = (v) => v.head ? v.headPx + marginOf(v) * 2 : 0;
3485
5959
  let railPx = 0;
3486
- const readRail = () => {
5960
+ let measured = false;
5961
+ const measure = () => {
3487
5962
  syncFrame(root);
3488
5963
  const probe = doc.createElement("div");
3489
5964
  probe.style.cssText = "position:absolute;visibility:hidden;width:var(--ku-rail);height:var(--ku-pane-m)";
@@ -3492,8 +5967,44 @@ function split(container, options) {
3492
5967
  paneM = r.height;
3493
5968
  railPx = (r.width || 26) + paneM * 2;
3494
5969
  probe.remove();
5970
+ for (const v of views.values())
5971
+ if (v.head)
5972
+ v.headPx = v.head.getBoundingClientRect().height;
5973
+ measured = root.isConnected && r.width > 0;
5974
+ };
5975
+ const measureChevrons = (only) => {
5976
+ if (!row)
5977
+ return;
5978
+ for (const v of only ? [only] : views.values()) {
5979
+ if (!v.chevron || !v.toggleIcon || !v.rail || !v.railLabel)
5980
+ continue;
5981
+ const inner = v.inner.getBoundingClientRect();
5982
+ const icon = v.toggleIcon.getBoundingClientRect();
5983
+ const rail = v.rail.getBoundingClientRect();
5984
+ const chev = v.chevron.getBoundingClientRect();
5985
+ if (!inner.width || !chev.width)
5986
+ continue;
5987
+ v.chev = {
5988
+ iconEnd: inner.right - (icon.left + icon.width / 2),
5989
+ chevEnd: rail.right - (chev.left + chev.width / 2),
5990
+ dy: icon.top + icon.height / 2 - inner.top - (chev.top + chev.height / 2 - rail.top),
5991
+ scale: icon.width / chev.width
5992
+ };
5993
+ if (!v.titleEl)
5994
+ continue;
5995
+ const moved = v.railLabel.style.transform;
5996
+ v.railLabel.style.transform = "none";
5997
+ const label = v.railLabel.getBoundingClientRect();
5998
+ v.railLabel.style.transform = moved;
5999
+ const title = v.titleEl.getBoundingClientRect();
6000
+ v.places = {
6001
+ title: title.left + title.width / 2 - inner.left,
6002
+ label: label.left + label.width / 2 - rail.left,
6003
+ dy: title.top + title.height / 2 - inner.top - (label.top + label.height / 2 - rail.top)
6004
+ };
6005
+ }
3495
6006
  };
3496
- readRail();
6007
+ measure();
3497
6008
  const rail = (id) => {
3498
6009
  const v = views.get(id);
3499
6010
  return !row && v.head ? headerSize(v) : railPx;
@@ -3506,47 +6017,102 @@ function split(container, options) {
3506
6017
  };
3507
6018
  const target = () => solve({ panes: specs, sizes, collapsed, total: total(), rail });
3508
6019
  const current = () => solve({ panes: specs, sizes, collapsed, total: total(), rail, fixed: fixed() });
6020
+ const SIDES = { row: ["right", "left"], column: ["bottom", "top"] };
6021
+ const sides = (v) => {
6022
+ const [a, b] = SIDES[direction];
6023
+ return v.edge === "start" ? [a, b] : [b, a];
6024
+ };
6025
+ const renderFold = (v, size) => {
6026
+ const id = v.spec.id;
6027
+ const isCollapsed = collapsed[id];
6028
+ const folding = isCollapsed || v.folding;
6029
+ const r = rail(id);
6030
+ const open = folding ? Math.max(v.open, r + 1) : size;
6031
+ const p = folding ? foldProgress(size, open, r) : 0;
6032
+ v.faces = foldFaces(v.faces, p);
6033
+ const m = marginOf(v);
6034
+ const settled = !springs.has(id);
6035
+ const content = isCollapsed && settled ? 1 : v.faces.content;
6036
+ put(v.rail ? v.inner : v.body, "will-change", v.folding ? "opacity, filter" : "");
6037
+ if (!v.rail) {
6038
+ put(v.inner, "height", folding ? `${open - m * 2}px` : "");
6039
+ put(v.inner, "bottom", folding ? "auto" : "");
6040
+ fade(v.body, content, CONTENT_BLUR);
6041
+ if (v.toggleIcon)
6042
+ put(v.toggleIcon, "transform", p ? `rotate(${(-90 * p).toFixed(2)}deg)` : "");
6043
+ hide(v.body, isCollapsed && settled);
6044
+ return;
6045
+ }
6046
+ const [side, far] = sides(v);
6047
+ put(v.inner, row ? "width" : "height", folding ? `${open - m * 2}px` : "");
6048
+ put(v.inner, side, folding ? "0" : "");
6049
+ put(v.inner, far, folding ? "auto" : "");
6050
+ fade(v.inner, content, CONTENT_BLUR);
6051
+ hide(v.clip, isCollapsed && settled);
6052
+ const morph = row && Boolean(v.toggleIcon) && !v.el.hasAttribute("data-nested");
6053
+ v.el.toggleAttribute("data-morph", morph);
6054
+ const label = v.railLabel;
6055
+ const travel = morph && style.title === "travel" && Boolean(v.titleEl) && label.textContent === v.titleEl.textContent;
6056
+ v.el.toggleAttribute("data-travel", travel && folding);
6057
+ const k = 1 - p;
6058
+ if (travel && !folding) {
6059
+ put(label, "opacity", "0");
6060
+ put(label, "filter", "");
6061
+ put(label, "transform", "");
6062
+ put(label, "color", "");
6063
+ } else if (travel) {
6064
+ const t = titleTravel(v.places, open, r, v.edge, p);
6065
+ put(label, "opacity", "");
6066
+ put(label, "filter", "");
6067
+ put(label, "transform", k === 0 ? "" : `translate(${t.x.toFixed(2)}px,${t.y.toFixed(2)}px) rotate(${t.turn.toFixed(2)}deg)`);
6068
+ put(label, "color", k === 0 ? "" : `color-mix(in srgb,var(--ku-text) ${(k * 100).toFixed(1)}%,var(--ku-label))`);
6069
+ } else {
6070
+ put(label, "transform", "");
6071
+ put(label, "color", "");
6072
+ }
6073
+ if (morph) {
6074
+ put(v.rail, "opacity", "1");
6075
+ if (!travel)
6076
+ fade(label, v.faces.rail, LABEL_BLUR);
6077
+ const c = v.chev;
6078
+ const dx = c.chevEnd - c.iconEnd + (v.edge === "end" ? open - r : 0);
6079
+ const move = `translate(${(dx * k).toFixed(2)}px,${(c.dy * k).toFixed(2)}px)`;
6080
+ const size = `scale(${(1 + (c.scale - 1) * k).toFixed(4)})`;
6081
+ if (style.chevron === "turn")
6082
+ put(v.turn, "transform", k === 0 ? "" : `${move} rotate(${chevronTurn(p, v.edge).toFixed(2)}deg) ${size}`);
6083
+ else {
6084
+ const w = chevronFlip(v.flip.x);
6085
+ put(v.turn, "transform", k === 0 && w === 1 ? "" : `${move} ${size} scaleX(${w.toFixed(4)})`);
6086
+ }
6087
+ } else {
6088
+ const a = v.faces.rail;
6089
+ put(v.rail, "opacity", a >= 1 ? "1" : a.toFixed(3));
6090
+ put(label, "opacity", "");
6091
+ put(label, "filter", a >= 1 || a <= 0 ? "" : `blur(${(LABEL_BLUR * (1 - a)).toFixed(2)}px)`);
6092
+ put(v.turn, "transform", "");
6093
+ }
6094
+ };
3509
6095
  let lastReport = "";
3510
6096
  const render = (shown) => {
3511
6097
  for (const spec of specs) {
3512
6098
  const v = views.get(spec.id);
3513
6099
  const size = Math.max(0, shown[spec.id] ?? 0);
3514
6100
  v.shown = size;
3515
- v.el.style.flexBasis = `${size}px`;
3516
- v.el.style[row ? "width" : "height"] = `${size}px`;
3517
- const isCollapsed = collapsed[spec.id];
3518
- const moving = springs.has(spec.id);
3519
- v.el.toggleAttribute("data-collapsed", isCollapsed);
3520
- v.el.toggleAttribute("data-settled", !moving);
3521
- v.el.setAttribute("aria-hidden", String(isCollapsed && !moving));
3522
- const r = rail(spec.id);
3523
- const open = Math.max(v.open, r + 1);
3524
- const m = marginOf(v);
3525
- if (moving || isCollapsed) {
3526
- const p = Math.min(1, Math.max(0, (open - size) / (open - r)));
3527
- if (v.rail) {
3528
- const visible = Math.max(0, (size - r) / (open - r) * open);
3529
- const edge = edgeOf(specs, spec.id);
3530
- const side = row ? edge === "start" ? "right" : "left" : edge === "start" ? "bottom" : "top";
3531
- 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"}`;
3532
- 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"}`;
3533
- v.rail.style.opacity = String(Math.max(0, (p - 0.35) / 0.65));
3534
- } else {
3535
- v.clip.style.cssText = "";
3536
- v.inner.style.cssText = `height:${open - m * 2}px;bottom:auto`;
3537
- }
3538
- } else {
3539
- v.clip.style.cssText = "";
3540
- v.inner.style.cssText = "";
3541
- if (v.rail)
3542
- v.rail.style.opacity = "0";
3543
- }
6101
+ put(v.el, "flex-basis", `${size}px`);
6102
+ put(v.el, row ? "width" : "height", `${size}px`);
6103
+ v.el.toggleAttribute("data-collapsed", collapsed[spec.id]);
6104
+ v.el.toggleAttribute("data-settled", !springs.has(spec.id));
6105
+ if (spec.collapsible)
6106
+ renderFold(v, size);
3544
6107
  }
3545
6108
  dividers.forEach((d, i) => {
3546
6109
  const a = specs[i];
3547
6110
  const b = specs[i + 1];
3548
- d.setAttribute("aria-valuenow", String(Math.round(views.get(a.id).shown)));
3549
- d.setAttribute("aria-controls", `${a.id} ${b.id}`);
6111
+ const now = String(Math.round(views.get(a.id).shown));
6112
+ if (d.getAttribute("aria-valuenow") !== now)
6113
+ d.setAttribute("aria-valuenow", now);
6114
+ if (!d.hasAttribute("aria-controls"))
6115
+ d.setAttribute("aria-controls", `${a.id} ${b.id}`);
3550
6116
  });
3551
6117
  const layout = snapshot();
3552
6118
  const report = JSON.stringify(layout);
@@ -3579,51 +6145,76 @@ function split(container, options) {
3579
6145
  const loop = frameLoop(win, (dt) => {
3580
6146
  for (const [id, s] of springs) {
3581
6147
  s.step(dt);
3582
- if (s.resting)
6148
+ if (s.resting) {
3583
6149
  springs.delete(id);
6150
+ views.get(id).folding = false;
6151
+ }
6152
+ }
6153
+ let flipping = false;
6154
+ for (const v of views.values()) {
6155
+ v.flip.step(dt);
6156
+ if (!v.flip.resting)
6157
+ flipping = true;
3584
6158
  }
3585
6159
  const shown = current();
3586
- render(shown);
3587
- if (springs.size === 0) {
6160
+ if (springs.size === 0)
3588
6161
  rememberOpen(shown);
3589
- render(shown);
3590
- }
3591
- return springs.size > 0;
6162
+ render(shown);
6163
+ return springs.size > 0 || flipping;
3592
6164
  });
3593
6165
  const animate = (motion = SETTLE) => {
3594
6166
  const to = target();
6167
+ for (const v of views.values())
6168
+ if (v.folding && !collapsed[v.spec.id])
6169
+ v.open = to[v.spec.id];
3595
6170
  if (reducedMotion(win)) {
3596
6171
  springs.clear();
3597
- render(to);
6172
+ const arriving = [...views.values()].filter((v) => v.folding).map((v) => collapsed[v.spec.id] ? v.rail : v.rail ? v.inner : v.body);
6173
+ for (const v of views.values()) {
6174
+ v.folding = false;
6175
+ v.flip.snap(collapsed[v.spec.id] ? 1 : 0);
6176
+ }
3598
6177
  rememberOpen(to);
6178
+ render(to);
6179
+ for (const el of arriving)
6180
+ el?.animate([{ opacity: 0 }, { opacity: 1 }], {
6181
+ duration: 160,
6182
+ easing: "cubic-bezier(.2,.8,.2,1)"
6183
+ });
3599
6184
  return;
3600
6185
  }
3601
6186
  for (const spec of specs) {
3602
- const from = views.get(spec.id).shown;
6187
+ const v = views.get(spec.id);
3603
6188
  const goal = to[spec.id];
3604
6189
  const s = springs.get(spec.id);
3605
6190
  if (s) {
3606
6191
  s.omega = motion.omega;
3607
6192
  s.zeta = motion.zeta;
3608
6193
  s.set(goal);
3609
- } else if (Math.abs(goal - from) > 0.5) {
3610
- springs.set(spec.id, new Spring(from, motion.omega, motion.zeta, 0.2).set(goal));
3611
- }
6194
+ } else if (Math.abs(goal - v.shown) > 0.5) {
6195
+ springs.set(spec.id, new Spring(v.shown, motion.omega, motion.zeta, 0.2).set(goal));
6196
+ } else
6197
+ v.folding = false;
3612
6198
  }
3613
6199
  loop.wake();
3614
6200
  };
6201
+ const fold = (id, value) => {
6202
+ const v = views.get(id);
6203
+ if (value && !v.folding)
6204
+ v.open = v.shown;
6205
+ collapsed[id] = value;
6206
+ v.folding = true;
6207
+ v.flip.set(value ? 1 : 0);
6208
+ };
6209
+ const release = (id) => {
6210
+ springs.delete(id);
6211
+ views.get(id).folding = false;
6212
+ };
3615
6213
  function setCollapsed(id, value, save = true) {
3616
6214
  const spec = specs.find((p) => p.id === id);
3617
6215
  if (!spec?.collapsible || collapsed[id] === value)
3618
6216
  return;
3619
- const v = views.get(id);
3620
- if (value && !springs.has(id))
3621
- v.open = v.shown;
3622
- if (!value) {
3623
- collapsed[id] = false;
3624
- v.open = target()[id];
3625
- }
3626
- collapsed[id] = value;
6217
+ fold(id, value);
3627
6218
  if (save)
3628
6219
  persist();
3629
6220
  animate(COLLAPSE);
@@ -3645,7 +6236,8 @@ function split(container, options) {
3645
6236
  d.focus({ preventScroll: true });
3646
6237
  const shown = current();
3647
6238
  for (const id of [a.id, b.id])
3648
- springs.delete(id);
6239
+ if (!collapsed[id])
6240
+ release(id);
3649
6241
  start = { ...shown };
3650
6242
  for (const id of [a.id, b.id])
3651
6243
  if (collapsed[id])
@@ -3679,7 +6271,7 @@ function split(container, options) {
3679
6271
  return;
3680
6272
  setSizes(result.sizes);
3681
6273
  for (const id of [a.id, b.id])
3682
- springs.delete(id);
6274
+ release(id);
3683
6275
  const shown = current();
3684
6276
  render(shown);
3685
6277
  rememberOpen(shown);
@@ -3742,17 +6334,21 @@ function split(container, options) {
3742
6334
  }
3743
6335
  function reset(...ids) {
3744
6336
  const which = ids.length ? ids : specs.map((p) => p.id);
6337
+ let folds = false;
3745
6338
  for (const id of which) {
3746
6339
  delete sizes[id];
3747
6340
  const spec = specs.find((p) => p.id === id);
3748
- if (spec?.collapsible)
3749
- collapsed[id] = Boolean(spec.collapsed);
6341
+ const shut = Boolean(spec?.collapsed);
6342
+ if (spec?.collapsible && collapsed[id] !== shut) {
6343
+ fold(id, shut);
6344
+ folds = true;
6345
+ }
3750
6346
  }
3751
6347
  if (which.some((id) => flex.has(id)))
3752
6348
  for (const id of flex)
3753
6349
  delete sizes[id];
3754
6350
  persist();
3755
- animate();
6351
+ animate(folds ? COLLAPSE : SETTLE);
3756
6352
  }
3757
6353
  {
3758
6354
  const first = target();
@@ -3761,18 +6357,29 @@ function split(container, options) {
3761
6357
  views.get(spec.id).open = collapsed[spec.id] ? solve({ panes: specs, sizes, collapsed: open, total: total(), rail })[spec.id] : first[spec.id];
3762
6358
  }
3763
6359
  render(first);
6360
+ measureChevrons();
6361
+ render(first);
3764
6362
  }
3765
- const relayout = () => {
3766
- readRail();
3767
- if (springs.size)
6363
+ const relayout = (remeasure) => {
6364
+ if (remeasure)
6365
+ measure();
6366
+ if (springs.size) {
6367
+ if (remeasure)
6368
+ measureChevrons();
3768
6369
  return loop.wake();
6370
+ }
3769
6371
  const shown = target();
3770
- render(shown);
3771
6372
  rememberOpen(shown);
6373
+ render(shown);
6374
+ if (remeasure) {
6375
+ measureChevrons();
6376
+ render(shown);
6377
+ }
3772
6378
  };
3773
- const ro = new ResizeObserver(relayout);
6379
+ const ro = new ResizeObserver(() => relayout(!measured));
3774
6380
  ro.observe(root);
3775
- root.addEventListener("knobs-restyle", relayout);
6381
+ const restyled = () => relayout(true);
6382
+ root.addEventListener("knobs-restyle", restyled);
3776
6383
  const paneApi = (id) => {
3777
6384
  const v = views.get(id);
3778
6385
  if (!v)
@@ -3795,9 +6402,9 @@ function split(container, options) {
3795
6402
  v.titleEl.textContent = text;
3796
6403
  if (v.countEl && count !== undefined)
3797
6404
  v.countEl.textContent = String(count);
3798
- const label = v.rail?.querySelector("span");
3799
- if (label)
3800
- label.textContent = text;
6405
+ if (v.railLabel)
6406
+ v.railLabel.textContent = text;
6407
+ measureChevrons(v);
3801
6408
  }
3802
6409
  };
3803
6410
  };
@@ -3832,7 +6439,7 @@ function split(container, options) {
3832
6439
  unregisterHints?.();
3833
6440
  loop.stop();
3834
6441
  ro.disconnect();
3835
- root.removeEventListener("knobs-restyle", relayout);
6442
+ root.removeEventListener("knobs-restyle", restyled);
3836
6443
  host?.removeAttribute("data-nested");
3837
6444
  root.remove();
3838
6445
  }
@@ -3903,7 +6510,7 @@ function menu(anchor, items, options = {}) {
3903
6510
  const lead = doc.createElement("span");
3904
6511
  lead.className = "knobs-menu-lead";
3905
6512
  if (e.checked)
3906
- lead.innerHTML = e.radio ? RADIO : CHECK;
6513
+ lead.innerHTML = e.radio ? RADIO : CHECK2;
3907
6514
  else if (e.icon) {
3908
6515
  if (e.icon.trimStart().startsWith("<"))
3909
6516
  lead.innerHTML = e.icon;
@@ -4241,7 +6848,7 @@ function contextMenu(target, items, options = {}) {
4241
6848
  }
4242
6849
  };
4243
6850
  }
4244
- 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>';
6851
+ 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>';
4245
6852
  var RADIO = '<svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="3" fill="currentColor"/></svg>';
4246
6853
 
4247
6854
  // src/ui/timeline-model.ts
@@ -4632,7 +7239,7 @@ function timeline(container, options) {
4632
7239
  renderHead();
4633
7240
  };
4634
7241
  const syncPlay = () => {
4635
- playBtn.innerHTML = playing ? PAUSE : PLAY;
7242
+ playBtn.innerHTML = playing ? PAUSE : PLAY2;
4636
7243
  playBtn.setAttribute("aria-label", playing ? "Pause" : "Play");
4637
7244
  root.toggleAttribute("data-playing", playing);
4638
7245
  };
@@ -5243,7 +7850,7 @@ function timeline(container, options) {
5243
7850
  };
5244
7851
  return api;
5245
7852
  }
5246
- 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>';
7853
+ 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>';
5247
7854
  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>';
5248
7855
 
5249
7856
  // src/ui/tree-model.ts
@@ -5502,7 +8109,7 @@ function tree(container, options) {
5502
8109
  let index = indexTree(items, access);
5503
8110
  const renamed = new Map;
5504
8111
  const root = doc.createElement("div");
5505
- root.className = "knobs-tree";
8112
+ root.className = "knobs-tree knobs-scroll-fade";
5506
8113
  root.tabIndex = 0;
5507
8114
  root.setAttribute("role", "tree");
5508
8115
  root.setAttribute("aria-multiselectable", "true");
@@ -5568,7 +8175,7 @@ function tree(container, options) {
5568
8175
  const icon = doc.createElement("span");
5569
8176
  icon.className = "knobs-tree-icon";
5570
8177
  const label = doc.createElement("span");
5571
- label.className = "knobs-tree-label";
8178
+ label.className = "knobs-tree-label knobs-scroll-fade-x";
5572
8179
  const text = doc.createElement("span");
5573
8180
  text.className = "knobs-tree-text";
5574
8181
  label.append(text);
@@ -6130,6 +8737,8 @@ function tree(container, options) {
6130
8737
  const rule = options.typeahead ?? true;
6131
8738
  if (rule === false)
6132
8739
  return false;
8740
+ if (!(running && typed !== "") && !/^[\p{L}\p{N}]$/u.test(key))
8741
+ return false;
6133
8742
  if (!(running && typed !== "") && options.hints !== false && claimsHintKey(doc, key))
6134
8743
  return false;
6135
8744
  return rule === true || running && typed !== "" || rule(key);
@@ -6381,6 +8990,15 @@ function builtinIcon(id, on) {
6381
8990
  // src/ui/react.tsx
6382
8991
  import { jsx } from "react/jsx-runtime";
6383
8992
  "use client";
8993
+ function useFollow(values, apply) {
8994
+ const changes = useRef(null);
8995
+ changes.current ??= propChanges(values);
8996
+ useEffect(() => {
8997
+ const changed = changes.current(values);
8998
+ if (anyChanged(changed))
8999
+ apply(changed);
9000
+ });
9001
+ }
6384
9002
  function Split(props) {
6385
9003
  const ref = useRef(null);
6386
9004
  const [handle, setHandle] = useState(null);
@@ -6432,25 +9050,17 @@ function Tree(props) {
6432
9050
  handle.current = null;
6433
9051
  };
6434
9052
  }, []);
6435
- const first = useRef(true);
6436
- useEffect(() => {
6437
- if (first.current)
6438
- return;
6439
- handle.current?.update(props.items);
6440
- }, [props.items]);
6441
- useEffect(() => {
6442
- if (first.current || !props.selection)
6443
- return;
6444
- handle.current?.select(props.selection);
6445
- }, [props.selection]);
6446
- useEffect(() => {
6447
- if (first.current)
9053
+ useFollow({ items: props.items, filter: props.filter, selection: props.selection }, (c) => {
9054
+ const t = handle.current;
9055
+ if (!t)
6448
9056
  return;
6449
- handle.current?.filter(props.filter);
6450
- }, [props.filter]);
6451
- useEffect(() => {
6452
- first.current = false;
6453
- }, []);
9057
+ if ("items" in c)
9058
+ t.update(c.items);
9059
+ if ("filter" in c)
9060
+ t.filter(c.filter);
9061
+ if (c.selection)
9062
+ t.select(c.selection);
9063
+ });
6454
9064
  return /* @__PURE__ */ jsx("div", {
6455
9065
  ref,
6456
9066
  className: props.className,
@@ -6478,33 +9088,21 @@ function Timeline(props) {
6478
9088
  handle.current = null;
6479
9089
  };
6480
9090
  }, []);
6481
- const first = useRef(true);
6482
- const follow = (key) => useEffect(() => {
9091
+ useFollow({
9092
+ duration: props.duration,
9093
+ fps: props.fps,
9094
+ units: props.units,
9095
+ speed: props.speed,
9096
+ markers: props.markers,
9097
+ ranges: props.ranges,
9098
+ time: props.time,
9099
+ playing: props.playing
9100
+ }, (c) => {
6483
9101
  const t = handle.current;
6484
- const value = props[key];
6485
- if (first.current || !t || value === undefined)
6486
- return;
6487
- if (key === "time" && Math.abs(value - t.time) < 0.000001)
6488
- return;
6489
- if (key === "playing" && value === t.playing)
6490
- return;
6491
- if (key === "markers" && value === t.markers)
6492
- return;
6493
- if (key === "ranges" && value === t.ranges)
6494
- return;
6495
- t.update({ [key]: value });
6496
- }, [props[key]]);
6497
- follow("time");
6498
- follow("playing");
6499
- follow("markers");
6500
- follow("ranges");
6501
- follow("duration");
6502
- follow("fps");
6503
- follow("units");
6504
- follow("speed");
6505
- useEffect(() => {
6506
- first.current = false;
6507
- }, []);
9102
+ const next = t && timelineChanges(t, c);
9103
+ if (t && next && anyChanged(next))
9104
+ t.update(next);
9105
+ });
6508
9106
  return /* @__PURE__ */ jsx("div", {
6509
9107
  ref,
6510
9108
  className: props.className,
@@ -6530,28 +9128,102 @@ function Bento(props) {
6530
9128
  handle.current = null;
6531
9129
  };
6532
9130
  }, []);
6533
- const first = useRef(true);
6534
- useEffect(() => {
6535
- if (first.current)
6536
- return;
6537
- handle.current?.update(props.items);
6538
- }, [props.items]);
6539
- useEffect(() => {
6540
- if (first.current || !props.picks)
9131
+ useFollow({ items: props.items, picks: props.picks }, (c) => {
9132
+ const b = handle.current;
9133
+ if (!b)
6541
9134
  return;
6542
- handle.current?.pick(props.picks);
6543
- }, [props.picks]);
6544
- useEffect(() => {
6545
- first.current = false;
9135
+ if ("items" in c)
9136
+ b.update(c.items);
9137
+ if (c.picks)
9138
+ b.pick(c.picks);
9139
+ });
9140
+ return /* @__PURE__ */ jsx("div", {
9141
+ ref,
9142
+ className: props.className,
9143
+ style: { height: "100%", ...props.style }
9144
+ });
9145
+ }
9146
+ function Activity(props) {
9147
+ const ref = useRef(null);
9148
+ const handle = useRef(null);
9149
+ const latest = useRef(props);
9150
+ latest.current = props;
9151
+ useLayoutEffect(() => {
9152
+ const p = latest.current;
9153
+ const a = activity(ref.current, {
9154
+ ...p,
9155
+ onSelect: (id, event, source) => latest.current.onSelect?.(id, event, source),
9156
+ onActivate: (id, event) => latest.current.onActivate?.(id, event),
9157
+ onToggle: (id, open) => latest.current.onToggle?.(id, open),
9158
+ onPlay: p.onPlay && ((id, event) => latest.current.onPlay?.(id, event))
9159
+ });
9160
+ handle.current = a;
9161
+ p.onReady?.(a);
9162
+ return () => {
9163
+ a.destroy();
9164
+ handle.current = null;
9165
+ };
6546
9166
  }, []);
9167
+ useFollow({ events: props.events, newest: props.newest, selected: props.selected }, (c) => {
9168
+ const a = handle.current;
9169
+ if (!a)
9170
+ return;
9171
+ if (c.events || c.newest)
9172
+ a.update({ events: c.events, newest: c.newest });
9173
+ if (c.selected !== undefined && c.selected !== a.selected)
9174
+ a.select(c.selected);
9175
+ });
6547
9176
  return /* @__PURE__ */ jsx("div", {
6548
9177
  ref,
6549
9178
  className: props.className,
6550
9179
  style: { height: "100%", ...props.style }
6551
9180
  });
6552
9181
  }
9182
+ function OptionPicker(props) {
9183
+ const ref = useRef(null);
9184
+ const handle = useRef(null);
9185
+ const latest = useRef(props);
9186
+ latest.current = props;
9187
+ const [bodies, setBodies] = useState([]);
9188
+ useLayoutEffect(() => {
9189
+ const p = optionPicker(ref.current, {
9190
+ ...latest.current,
9191
+ onSelect: (id, source) => latest.current.onSelect?.(id, source),
9192
+ onChoose: (id) => latest.current.onChoose?.(id),
9193
+ body: (option, el) => {
9194
+ setBodies((all) => [...all, { option, el }]);
9195
+ return () => setBodies((all) => all.filter((b) => b.el !== el));
9196
+ }
9197
+ });
9198
+ handle.current = p;
9199
+ latest.current.onReady?.(p);
9200
+ return () => {
9201
+ p.destroy();
9202
+ handle.current = null;
9203
+ };
9204
+ }, []);
9205
+ useFollow({ options: props.options, selected: props.selected, chosen: props.chosen }, (c) => {
9206
+ const p = handle.current;
9207
+ if (!p)
9208
+ return;
9209
+ if ("options" in c)
9210
+ p.set(c.options);
9211
+ if (c.selected != null)
9212
+ p.select(c.selected);
9213
+ if ("chosen" in c)
9214
+ p.choose(c.chosen ?? null);
9215
+ });
9216
+ return /* @__PURE__ */ jsx("div", {
9217
+ ref,
9218
+ className: props.className,
9219
+ style: props.style,
9220
+ children: props.children && bodies.map(({ option, el }) => createPortal(props.children(option), el, option.id))
9221
+ });
9222
+ }
6553
9223
  export {
9224
+ Activity,
6554
9225
  Bento,
9226
+ OptionPicker,
6555
9227
  Split,
6556
9228
  Timeline,
6557
9229
  Tree