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