@kitlangton/knobs 0.4.0-next.11 → 0.4.0-next.12

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.
@@ -13,6 +13,7 @@ import {
13
13
  comboFromEvent2,
14
14
  heldMods,
15
15
  matchesCombo2,
16
+ comboParts2,
16
17
  comboLabel2,
17
18
  isTypingTarget,
18
19
  GRADIENT_SAMPLES,
@@ -25,6 +26,7 @@ import {
25
26
  sameRamp,
26
27
  isReadout2,
27
28
  hasValue2,
29
+ variantsOf,
28
30
  SPRING2,
29
31
  toolbarShortcut2,
30
32
  actionShortcut2,
@@ -44,7 +46,7 @@ import {
44
46
  pushReadout,
45
47
  READOUT,
46
48
  storageKey
47
- } from "./chunk-mjdgzad5.js";
49
+ } from "./chunk-7kns3qe6.js";
48
50
  import {
49
51
  resolveLook,
50
52
  themeOf,
@@ -99,6 +101,7 @@ import {
99
101
  crumbTrail,
100
102
  escTarget,
101
103
  digitOption,
104
+ digitRoute,
102
105
  digitHints,
103
106
  createCrumbs,
104
107
  TOKENS,
@@ -116,7 +119,7 @@ import {
116
119
  createHintController,
117
120
  pageHints,
118
121
  toggleLayer
119
- } from "./chunk-j57rhjtm.js";
122
+ } from "./chunk-nwfm1bfj.js";
120
123
 
121
124
  // src/panel/die.ts
122
125
  var ROUND = 16 * Math.sqrt(5), DIE = { half: 64 + ROUND, round: ROUND, pip: 12 }, PIPS = {
@@ -994,6 +997,11 @@ function resolveActivePresetEntry(entries, currentValues, lastAppliedId, baseId
994
997
  isCustom: !0
995
998
  };
996
999
  }
1000
+ function presetLines(count, min, avail, gap) {
1001
+ if (count <= 0 || min <= 0 || avail <= 0)
1002
+ return 1;
1003
+ return Math.ceil(count / Math.max(1, Math.floor((avail + gap) / (min + gap))));
1004
+ }
997
1005
  function presetsTouch(entries, keys, defaults) {
998
1006
  return entries.some((e) => !(e.kind === "preset" && e.rawId === "default") && keys.some((k) => !same(e.values[k], defaults[k])));
999
1007
  }
@@ -1021,7 +1029,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1021
1029
  saveButton.type = "button", saveButton.tabIndex = -1, saveButton.setAttribute("aria-label", "Save as variant"), saveButton.setAttribute("aria-keyshortcuts", "Shift+S"), saveButton.dataset.hint = "Save", saveButton.onclick = () => run(save);
1022
1030
  if (track.append(pill), root.append(track), saveButton)
1023
1031
  root.append(saveButton);
1024
- let pillEdges = new Edges, pillY = new Spring(0, 28, 0.86), pillAlpha = new Spring(1, 26, 0.9), entries = [], buttons = [], signature = "", lastVariantsRef = null, lastAppliedId = null, activeState = { activeId: "p:default", activeIndex: 0, isCustom: !1 }, prevActiveId = "", prevActiveIndex = -2, prevIsCustom = !1, prevDrifted = !1, initializedPill = !1, slideUntil = 0, unpinned = (values) => {
1032
+ let pillEdges = new Edges, pillY = new Spring(0, 28, 0.86), pillAlpha = new Spring(1, 26, 0.9), entries = [], buttons = [], signature = "", lastVariantsRef = null, lastAppliedId = null, activeState = { activeId: "p:default", activeIndex: 0, isCustom: !1 }, prevActiveId = "", prevActiveIndex = -2, prevIsCustom = !1, prevDrifted = !1, initializedPill = !1, slideUntil = 0, display = "auto", chipMin = 0, unpinned = (values) => {
1025
1033
  let out;
1026
1034
  for (let k of Object.keys(values))
1027
1035
  if (controls.isPinned(k) && !same(values[k], controls.locked[k]))
@@ -1152,7 +1160,18 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1152
1160
  range.selectNodeContents(b), widest = Math.max(widest, range.getBoundingClientRect().width);
1153
1161
  }
1154
1162
  if (widest > 0)
1155
- track.style.setProperty("--k-preset-min", `${Math.ceil(widest * 1.06 + 14)}px`);
1163
+ chipMin = Math.ceil(widest * 1.06 + 14), track.style.setProperty("--k-preset-min", `${chipMin}px`);
1164
+ placeDisplay();
1165
+ }
1166
+ function chipLines() {
1167
+ let pad = parseFloat(getComputedStyle(root).getPropertyValue("--_k-tray-pad")) || 0, save = saveButton ? saveButton.offsetWidth + pad : 0;
1168
+ return presetLines(buttons.length, chipMin, root.clientWidth - pad * 2 - save, pad);
1169
+ }
1170
+ function placeDisplay() {
1171
+ let want = display === "list" || display === "auto" && chipLines() > 2 ? "list" : "chips";
1172
+ if (root.dataset.display === want)
1173
+ return !1;
1174
+ return root.dataset.display = want, !0;
1156
1175
  }
1157
1176
  function save() {
1158
1177
  finishName(!0);
@@ -1288,8 +1307,8 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1288
1307
  minWidth: 20,
1289
1308
  rows: !0
1290
1309
  }), doc.fonts?.ready.then(fitColumns);
1291
- let ro = new ResizeObserver(() => measurePill(performance.now() >= slideUntil));
1292
- return ro.observe(track), {
1310
+ let ro = new ResizeObserver(() => measurePill(placeDisplay() || performance.now() >= slideUntil));
1311
+ return ro.observe(track), ro.observe(root), {
1293
1312
  element: root,
1294
1313
  track,
1295
1314
  pill,
@@ -1302,7 +1321,15 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1302
1321
  getActiveState: () => activeState,
1303
1322
  shows: () => wanted(entries),
1304
1323
  cycle,
1324
+ select: (index) => {
1325
+ if (index !== activeState.activeIndex || activeState.drifted)
1326
+ selectAt(index);
1327
+ },
1305
1328
  selectById,
1329
+ display(mode) {
1330
+ if (display = mode, placeDisplay())
1331
+ measurePill(!0);
1332
+ },
1306
1333
  save: () => {
1307
1334
  let message = "";
1308
1335
  return run(() => message = save()), message;
@@ -5642,7 +5669,9 @@ class GpuOverlayManager {
5642
5669
  this.drawIcon(item.icon, t.iconSm, at, ink, cardClip, k);
5643
5670
  }
5644
5671
  let lr = this.rectOf(item.label), px = t.buttonFontSize ?? t.textControl, words = this.fit(item.label.textContent ?? "", lr.width + 1, px, t.labelWeight);
5645
- if (this.drawText(words, lr.left, cy, ink, { px, weight: t.labelWeight, ...zoom }), item.chip) {
5672
+ if (lr.width > 0)
5673
+ this.drawText(words, lr.left, cy, ink, { px, weight: t.labelWeight, ...zoom });
5674
+ if (item.chip && item.chip.getClientRects().length) {
5646
5675
  let kr = this.rectOf(item.chip);
5647
5676
  this.drawText(item.chip.textContent ?? "", kr.left, cy, withAlpha(t.dim, alpha), {
5648
5677
  px: t.textCap,
@@ -6447,7 +6476,10 @@ class GpuOverlayManager {
6447
6476
  this.drawRoundWash(dr, rowAlpha, this.wash(button, on)), this.drawDie(die, dr, withAlpha(this.hot(button, on, tokens.dim), rowAlpha)), this.drawRowOverlay(row.row, rRect, cardClip);
6448
6477
  } else if (row.kind === "keybind")
6449
6478
  this.drawKeybind(row, rRect, cardClip, rowAlpha, hovered);
6450
- else if (row.kind === "toolbar")
6479
+ else if (row.kind === "text") {
6480
+ let labelY = row.row.hasAttribute("data-multiline") ? this.rectOf(row.labelEl).top + this.rectOf(row.labelEl).height / 2 : rowCenterY;
6481
+ this.drawRowHover(row.row, rRect, hovered || row.row.classList.contains("knobs-active")), this.drawRowLabel(row.labelEl, row.display, row.isModified(), labelY, rowAlpha), this.drawRowOverlay(row.row, rRect, cardClip);
6482
+ } else if (row.kind === "toolbar")
6451
6483
  this.drawToolbar(row, rRect, cardClip, rowAlpha, hinting);
6452
6484
  else if (row.kind === "readout")
6453
6485
  this.drawReadout(row, rRect, cardClip, rowAlpha, hovered);
@@ -10392,954 +10424,6 @@ function toolbarColumns(count, widest, avail, gap) {
10392
10424
  return Math.ceil(count / Math.ceil(count / cols));
10393
10425
  }
10394
10426
 
10395
- // src/panel/inspector.ts
10396
- function make(doc) {
10397
- return (tag, cls, text) => {
10398
- let node = doc.createElement(tag);
10399
- if (cls)
10400
- node.className = cls;
10401
- if (text !== void 0)
10402
- node.textContent = text;
10403
- return node;
10404
- };
10405
- }
10406
- function shell(c, kind) {
10407
- let el = make(c.doc), row = el("div", "knobs-row knobs-insp");
10408
- row.dataset.field = c.key, row.dataset.kind = kind;
10409
- let label = el("span", void 0, c.display);
10410
- return row.append(label), { row, label, el };
10411
- }
10412
- var fmt = (v, places) => (Object.is(v, -0) ? 0 : v).toFixed(places);
10413
- function inlineEdit(doc, host, label, commit, back) {
10414
- let input = doc.createElement("input");
10415
- input.className = "knobs-cell-edit", input.type = "text", input.inputMode = "decimal", input.spellcheck = !1, input.hidden = !0, input.setAttribute("aria-label", label), host.append(input);
10416
- let open = !1, done = (keep) => {
10417
- if (!open)
10418
- return;
10419
- open = !1;
10420
- let next = Number(input.value.trim().replace(/[^\d.eE+-]/g, ""));
10421
- if (input.hidden = !0, host.classList.remove("knobs-editing"), keep && input.value.trim() !== "" && Number.isFinite(next))
10422
- commit(next);
10423
- let target = back();
10424
- if (doc.activeElement === input || doc.activeElement === doc.body)
10425
- target.focus({ preventScroll: !0 });
10426
- };
10427
- return input.addEventListener("keydown", (event) => {
10428
- if (event.key === "Enter" || event.key === "Escape")
10429
- event.preventDefault(), event.stopPropagation(), done(event.key === "Enter");
10430
- else
10431
- event.stopPropagation();
10432
- }), input.addEventListener("blur", () => done(!0)), {
10433
- input,
10434
- get open() {
10435
- return open;
10436
- },
10437
- start(text, select = !0) {
10438
- if (open = !0, input.value = text, input.hidden = !1, host.classList.add("knobs-editing"), input.focus({ preventScroll: !0 }), select)
10439
- input.select();
10440
- else
10441
- input.setSelectionRange(text.length, text.length);
10442
- },
10443
- done
10444
- };
10445
- }
10446
- var typed = (event) => plainKey(event) && !event.altKey && /^[0-9.\-]$/.test(event.key) ? event.key : void 0;
10447
- function scrub(target, row, o) {
10448
- let at, moved = !1, last = 0, carried = 0, rect;
10449
- target.addEventListener("pointerdown", (event) => {
10450
- if (event.button !== 0 || event.shiftKey || event.metaKey)
10451
- return;
10452
- event.preventDefault(), target.setPointerCapture(event.pointerId), target.focus({ preventScroll: !0, focusVisible: !1 }), at = last = event.clientX, carried = 0, moved = !1;
10453
- }), target.addEventListener("pointermove", (event) => {
10454
- if (at === void 0)
10455
- return;
10456
- if (!moved && Math.abs(event.clientX - at) < 3)
10457
- return;
10458
- if (!moved)
10459
- moved = !0, last = event.clientX, rect = row.getBoundingClientRect(), row.classList.add("knobs-active"), o.start();
10460
- let r = rect, rate = precisionRate(offRow(event.clientY, r.top, r.bottom)) * (event.altKey ? 0.1 : 1);
10461
- carried += (event.clientX - last) * rate, last = event.clientX, o.move(carried, event.altKey, event.shiftKey);
10462
- });
10463
- let release = (event) => {
10464
- if (at === void 0)
10465
- return;
10466
- at = void 0, rect = void 0, row.classList.remove("knobs-active"), o.end(moved, event);
10467
- };
10468
- return target.addEventListener("pointerup", release), target.addEventListener("pointercancel", release), { scrubbing: () => at !== void 0 && moved };
10469
- }
10470
- var rate = (min, max, step) => Math.max(step, (max - min) / 200), onGrid = (v, min, step) => Number((min + Math.round((v - min) / step) * step).toPrecision(12)), AXES = ["x", "y", "z"];
10471
- function vecRow(c) {
10472
- let { row, label, el } = shell(c, "vec"), f = () => c.field, places = () => f().decimals ?? decimals(f().step), axes = AXES.slice(0, c.field.axes), cells = el("span", "knobs-cells");
10473
- if (axes.length > 2)
10474
- cells.dataset.wide = "";
10475
- let linked = Boolean(c.field.linked), link;
10476
- if (c.field.link) {
10477
- link = el("button", "knobs-link"), link.type = "button", link.tabIndex = -1;
10478
- let paint = () => {
10479
- link.replaceChildren(iconSvg(c.doc, linked ? "link" : "unlink")), link.setAttribute("aria-pressed", String(linked)), link.setAttribute("aria-label", linked ? "Unlink axes" : "Link axes");
10480
- };
10481
- paint(), link.onclick = () => {
10482
- linked = !linked, paint(), c.wake();
10483
- }, row.append(link);
10484
- }
10485
- row.append(cells);
10486
- let shown = c.get(), mixed = !1, g = gesture(c), base = shown, withAxis = (from, axis, v) => {
10487
- let { min, max, step } = f(), next = clamp(onGrid(v, min, step), min, max);
10488
- if (!linked)
10489
- return { ...from, [axis]: next };
10490
- let was = from[axis] ?? 0, k = Math.abs(was) > 0.000000001 ? next / was : void 0, out = {};
10491
- for (let a of axes) {
10492
- let own = from[a] ?? 0;
10493
- out[a] = a === axis ? next : clamp(Number((k !== void 0 ? own * k : own + (next - was)).toPrecision(12)), min, max);
10494
- }
10495
- return out;
10496
- }, gpuCells = [], reads = [], rolls = [], stops = [];
10497
- for (let axis of axes) {
10498
- let cell = el("span", "knobs-cell");
10499
- cell.tabIndex = 0, cell.dataset.axis = axis, cell.setAttribute("role", "spinbutton");
10500
- let name = c.field.axisLabels?.[axes.indexOf(axis)] ?? axis;
10501
- cell.setAttribute("aria-label", `${c.display} ${name}`);
10502
- let letter = el("i", "knobs-cell-axis", name.toUpperCase());
10503
- letter.setAttribute("aria-hidden", "true");
10504
- let read = el("span", "knobs-cell-read");
10505
- cell.append(letter, read), cells.append(cell), reads.push(read), rolls.push(rollingReadout(read)), stops.push(cell);
10506
- let editor = inlineEdit(c.doc, cell, `${c.display} ${name} value`, (v) => {
10507
- g.start(), g.set(withAxis(c.get(), axis, v));
10508
- }, () => cell), drag = scrub(cell, row, {
10509
- perPx: () => rate(f().min, f().max, f().step),
10510
- start() {
10511
- g.start(), base = c.get();
10512
- },
10513
- move(dx, _fine, coarse) {
10514
- let by = dx * rate(f().min, f().max, f().step) * (coarse ? 10 : 1);
10515
- g.set(withAxis(base, axis, (base[axis] ?? 0) + by));
10516
- },
10517
- end(moved) {
10518
- if (!moved)
10519
- editor.start(mixed ? "" : fmt(shown[axis] ?? 0, places()));
10520
- c.wake();
10521
- }
10522
- }), panAt, give = textGive(read, () => c.wake());
10523
- onPan(cell, {
10524
- accept: () => !drag.scrubbing() && !editor.open,
10525
- move(dx) {
10526
- let perPx = rate(f().min, f().max, f().step);
10527
- if (panAt === void 0)
10528
- g.start(), base = c.get(), panAt = { at: 0, give: 0 }, row.classList.add("knobs-active"), cell.setAttribute("data-panned", "");
10529
- let from = base[axis] ?? 0;
10530
- if (panAt = band(panAt, dx, (f().min - from) / perPx, (f().max - from) / perPx), g.set(withAxis(base, axis, from + panAt.at * perPx)), panAt.give !== give.give)
10531
- give.hold(panAt.give);
10532
- },
10533
- lift: () => give.release(),
10534
- end() {
10535
- panAt = void 0, give.release(), row.classList.remove("knobs-active"), cell.removeAttribute("data-panned"), c.wake();
10536
- },
10537
- step(direction) {
10538
- g.start(), g.set(withAxis(c.get(), axis, (shown[axis] ?? 0) + direction * f().step));
10539
- }
10540
- }), cell.addEventListener("keydown", (event) => {
10541
- if (event.target !== cell || !plainKey(event))
10542
- return;
10543
- let dir = arrowOf(event), char = typed(event);
10544
- if (dir) {
10545
- event.preventDefault(), event.stopPropagation();
10546
- let by = f().step * (event.shiftKey ? bigOf(event) : event.altKey ? 0.1 : 1);
10547
- g.start(), g.set(withAxis(c.get(), axis, (shown[axis] ?? 0) + dir * by));
10548
- } else if (event.key === "Home" || event.key === "End")
10549
- event.preventDefault(), g.start(), g.set(withAxis(c.get(), axis, event.key === "Home" ? f().min : f().max));
10550
- else if (event.key === "Enter" && keyPrefs(cell).edit)
10551
- event.preventDefault(), event.stopPropagation(), editor.start(mixed ? "" : fmt(shown[axis] ?? 0, places()));
10552
- else if (char && keyPrefs(cell).edit)
10553
- event.preventDefault(), event.stopPropagation(), editor.start(char, !1);
10554
- }), gpuCells.push({
10555
- el: cell,
10556
- axis: letter,
10557
- read,
10558
- editing: () => editor.open,
10559
- active: () => drag.scrubbing() || panAt !== void 0
10560
- });
10561
- }
10562
- let paint = (ms = 0, delay = 0) => {
10563
- axes.forEach((axis, i) => {
10564
- let text = mixed ? "—" : fmt(shown[axis] ?? 0, places()), unit = f().unit && !mixed ? ` ${f().unit}` : "";
10565
- rolls[i].show(mixed ? ["—"] : [{ value: shown[axis] ?? 0, decimals: places() }], ms, delay), stops[i].setAttribute("aria-valuetext", `${text}${unit}`);
10566
- });
10567
- };
10568
- paint();
10569
- let placed = !1, gpu = {
10570
- kind: "vec",
10571
- key: c.key,
10572
- display: c.display,
10573
- row,
10574
- labelEl: label,
10575
- cells: gpuCells,
10576
- ...link ? { link } : {},
10577
- linked: () => linked,
10578
- wave: c.wave,
10579
- isModified: c.isModified
10580
- };
10581
- return {
10582
- row,
10583
- labelEl: label,
10584
- input: stops[0],
10585
- stops: () => stops,
10586
- update(value, delay, instant, isMixed = !1) {
10587
- shown = value, mixed = isMixed;
10588
- let hand = row.classList.contains("knobs-active");
10589
- paint(!placed || instant || hand ? 0 : rollMs(row), delay), placed = !0;
10590
- },
10591
- dispose() {
10592
- for (let r of rolls)
10593
- r.dispose();
10594
- },
10595
- tracks: [],
10596
- gpu: [gpu]
10597
- };
10598
- }
10599
- function rangeRow(c) {
10600
- let el = make(c.doc), f = () => c.field, places = () => f().decimals ?? decimals(f().step), row = el("div", "knobs-row knobs-range");
10601
- row.dataset.field = c.key;
10602
- let track = el("div", "knobs-range-track"), fill = el("i", "knobs-range-fill"), label = el("span", "knobs-label", c.display), read = el("span", "knobs-range-read"), thumbs = ["lo", "hi"].map((end) => {
10603
- let t = el("span", "knobs-range-thumb");
10604
- return t.dataset.end = end, t.tabIndex = 0, t.setAttribute("role", "slider"), t.setAttribute("aria-label", `${c.display} ${end === "lo" ? "low" : "high"}`), t;
10605
- }), words = el("span", "knobs-range-text");
10606
- words.append(label, read);
10607
- let band2 = el("i", "knobs-range-smear");
10608
- track.append(fill, band2, ...thumbs, words), row.append(track);
10609
- let shown = c.get(), mixed = !1, dragging = !1, smearFrame = 0, smearAt = 0, smearStrength = 0, lastEnds = [0, 0], smears = [0, 0], ends = () => {
10610
- let cs = getComputedStyle(track);
10611
- return [
10612
- parseFloat(cs.getPropertyValue("--kr-lo")) || 0,
10613
- parseFloat(cs.getPropertyValue("--kr-hi")) || 0
10614
- ];
10615
- }, setSmears = (lo, hi) => {
10616
- smears[0] = lo === 0 ? 0 : (smears[0] + lo) / 2, smears[1] = hi === 0 ? 0 : (smears[1] + hi) / 2, track.classList.toggle("knobs-range-smearing", smears[0] > 0 || smears[1] > 0), track.style.setProperty("--kr-smear-lo", smears[0].toFixed(2)), track.style.setProperty("--kr-smear-hi", smears[1].toFixed(2));
10617
- }, runSmear = (now) => {
10618
- let at = ends(), dt = (now - smearAt) / 1000, w = track.clientWidth, speeds = [0, 1].map((i) => dt > 0 ? Math.abs(at[i] - lastEnds[i]) * w / dt : 0);
10619
- lastEnds = at, smearAt = now;
10620
- let gliding = !dragging && track.getAnimations().length > 0;
10621
- setSmears(gliding ? rangeSmear(speeds[0], smearStrength) : 0, gliding ? rangeSmear(speeds[1], smearStrength) : 0), smearFrame = gliding ? requestAnimationFrame(runSmear) : 0;
10622
- };
10623
- track.addEventListener("transitionrun", (event) => {
10624
- if (event.target !== track || smearFrame || dragging)
10625
- return;
10626
- if (track.closest('[data-gpu-active="true"]'))
10627
- return;
10628
- smearStrength = clamp(parseFloat(getComputedStyle(track).getPropertyValue("--k-range-blur")) || 0, 0, 1);
10629
- let reduced = c.doc.defaultView?.matchMedia("(prefers-reduced-motion: reduce)").matches;
10630
- if (smearStrength <= 0 || reduced)
10631
- return;
10632
- lastEnds = ends(), smearAt = performance.now(), smearFrame = requestAnimationFrame(runSmear);
10633
- });
10634
- let lastEnd = 0, g = gesture(c), span = () => f().max - f().min || 1, ratio = (v) => clamp((v - f().min) / span(), 0, 1), quant = (v, fine) => clamp(onGrid(v, f().min, fine ? f().step / 10 : f().step), f().min, f().max), withEnd = (from, end, v, fine = !1) => {
10635
- let q = quant(v, fine);
10636
- return end === 0 ? [Math.min(q, from[1]), from[1]] : [from[0], Math.max(q, from[0])];
10637
- }, rolling = rollingReadout(read), showText = (ms, delay) => rolling.show(mixed ? ["—"] : [
10638
- { value: shown[0], decimals: places() },
10639
- "–",
10640
- { value: shown[1], decimals: places() },
10641
- f().unit ? ` ${f().unit}` : ""
10642
- ], ms, delay), place = (instant) => {
10643
- let [a, b] = [ratio(shown[0]), ratio(shown[1])], nodes = [fill, track];
10644
- if (instant)
10645
- for (let n of nodes)
10646
- n.style.transition = "none";
10647
- if (fill.style.transform = `translateX(${(a * 100).toFixed(4)}%) scaleX(${Math.max(0, b - a).toFixed(5)})`, track.style.setProperty("--kr-lo", a.toFixed(6)), track.style.setProperty("--kr-hi", b.toFixed(6)), instant) {
10648
- fill.offsetWidth;
10649
- for (let n of nodes)
10650
- n.style.transition = "";
10651
- }
10652
- }, paint = (instant, delay = 0) => {
10653
- showText(instant ? 0 : rollMs(row), delay), thumbs.forEach((th, i) => {
10654
- th.setAttribute("aria-valuenow", String(shown[i])), th.setAttribute("aria-valuemin", String(i === 0 ? f().min : shown[0])), th.setAttribute("aria-valuemax", String(i === 0 ? shown[1] : f().max));
10655
- }), place(instant);
10656
- }, grab = 0, startValue = shown, rect, lastX = 0, hand = { at: 0, give: 0 }, moved = 0, card = () => row.parentElement?.classList.contains("knobs-card") ? row.parentElement : null, give = textGive(row, () => c.wake(), {
10657
- home: STRETCH_HOME_S,
10658
- draw: (px) => drawStretch(row, px, 0, { w: track.clientWidth, h: 0 }, { thin: !0, unclip: card() })
10659
- }), most = GIVE, fit = () => {
10660
- let room = roomOf(row, card());
10661
- most = giveRoom(Math.min(room.left, room.right) - 1, GIVE);
10662
- }, valueAt = (x) => f().min + clamp((x - rect.left) / rect.width, 0, 1) * span();
10663
- track.addEventListener("pointerdown", (event) => {
10664
- if (event.button !== 0 || event.shiftKey || event.metaKey)
10665
- return;
10666
- if (event.target.closest(".knobs-cell-edit,.knobs-accessory"))
10667
- return;
10668
- event.preventDefault(), track.setPointerCapture(event.pointerId), rect = track.getBoundingClientRect();
10669
- let x = event.clientX - rect.left, [ax, bx] = [ratio(shown[0]) * rect.width, ratio(shown[1]) * rect.width];
10670
- if (grab = x > ax + 8 && x < bx - 8 && bx - ax > 24 ? 2 : Math.abs(x - ax) <= Math.abs(x - bx) && !(x > bx) ? 0 : 1, ax === bx)
10671
- grab = x < ax ? 0 : 1;
10672
- if (grab !== 2)
10673
- lastEnd = grab;
10674
- if (thumbs[grab === 2 ? lastEnd : grab].focus({
10675
- preventScroll: !0,
10676
- focusVisible: !1
10677
- }), lastX = event.clientX, startValue = shown, moved = 0, fit(), hand = {
10678
- at: grab === 2 || event.altKey ? ratio(shown[grab === 2 ? 0 : grab]) * rect.width : clamp(x, 0, rect.width),
10679
- give: 0
10680
- }, dragging = !0, row.classList.add("knobs-active"), g.start(), grab !== 2 && !event.altKey)
10681
- g.set(withEnd(shown, grab, valueAt(event.clientX)));
10682
- c.wake();
10683
- }), track.addEventListener("pointermove", (event) => {
10684
- if (!dragging || !rect)
10685
- return;
10686
- let rate = precisionRate(offRow(event.clientY, rect.top, rect.bottom)) * (event.altKey ? 0.1 : 1), dx = (event.clientX - lastX) * rate;
10687
- if (lastX = event.clientX, grab === 2) {
10688
- moved += dx;
10689
- let width = startValue[1] - startValue[0], by = moved / rect.width * span(), lo = clamp(quant(startValue[0] + by, event.altKey), f().min, f().max - width);
10690
- g.set([lo, Number((lo + width).toPrecision(12))]);
10691
- return;
10692
- }
10693
- if (hand = band(hand, dx, 0, rect.width, most), g.set(withEnd(c.get(), grab, f().min + hand.at / rect.width * span(), event.altKey)), hand.give !== give.give)
10694
- give.hold(hand.give);
10695
- });
10696
- let release = () => {
10697
- if (!dragging)
10698
- return;
10699
- dragging = !1, rect = void 0, give.release(), row.classList.remove("knobs-active"), c.wake();
10700
- };
10701
- track.addEventListener("pointerup", release), track.addEventListener("pointercancel", release);
10702
- let panAt;
10703
- onPan(track, {
10704
- accept: () => !dragging,
10705
- move(dx) {
10706
- let w = track.clientWidth;
10707
- if (!w)
10708
- return;
10709
- if (panAt === void 0)
10710
- g.start(), panAt = { at: (shown[lastEnd] - f().min) / span() * w, give: 0 }, fit(), dragging = !0, row.classList.add("knobs-active");
10711
- if (panAt = band(panAt, dx, 0, w, most), g.set(withEnd(c.get(), lastEnd, f().min + panAt.at / w * span())), panAt.give !== give.give)
10712
- give.hold(panAt.give);
10713
- },
10714
- lift: () => give.release(),
10715
- end() {
10716
- panAt = void 0, give.release(), dragging = !1, row.classList.remove("knobs-active"), c.wake();
10717
- },
10718
- step(direction) {
10719
- g.start(), g.set(withEnd(shown, lastEnd, shown[lastEnd] + direction * f().step));
10720
- }
10721
- });
10722
- let editors = thumbs.map((th, i) => inlineEdit(c.doc, row, `${c.display} ${i === 0 ? "low" : "high"} value`, (v) => {
10723
- g.start(), g.set(withEnd(c.get(), i, v));
10724
- }, () => th));
10725
- thumbs.forEach((th, i) => {
10726
- let end = i;
10727
- th.addEventListener("focus", () => {
10728
- lastEnd = end, row.dataset.endFocus = th.dataset.end, c.wake();
10729
- }), th.addEventListener("blur", () => {
10730
- if (row.dataset.endFocus === th.dataset.end)
10731
- delete row.dataset.endFocus;
10732
- }), th.addEventListener("keydown", (event) => {
10733
- if (!plainKey(event))
10734
- return;
10735
- let dir = arrowOf(event), char = typed(event);
10736
- if (dir) {
10737
- event.preventDefault(), event.stopPropagation();
10738
- let by = f().step * (event.shiftKey ? bigOf(event) : event.altKey ? 0.1 : 1);
10739
- g.start(), g.set(withEnd(shown, end, shown[end] + dir * by, event.altKey));
10740
- } else if (event.key === "Home" || event.key === "End")
10741
- event.preventDefault(), g.start(), g.set(withEnd(shown, end, event.key === "Home" ? f().min : f().max));
10742
- else if (event.key === "Enter" && keyPrefs(th).edit)
10743
- event.preventDefault(), event.stopPropagation(), editors[end].start(mixed ? "" : fmt(shown[end], places()));
10744
- else if (char && keyPrefs(th).edit)
10745
- event.preventDefault(), event.stopPropagation(), editors[end].start(char, !1);
10746
- });
10747
- });
10748
- let first = !0, gpu = {
10749
- kind: "range",
10750
- key: c.key,
10751
- display: c.display,
10752
- row,
10753
- labelEl: label,
10754
- track,
10755
- read,
10756
- thumbs,
10757
- ratios: () => [ratio(shown[0]), ratio(shown[1])],
10758
- dragging: () => dragging,
10759
- editing: () => editors.some((e) => e.open),
10760
- wave: c.wave,
10761
- isModified: c.isModified
10762
- };
10763
- return {
10764
- row,
10765
- labelEl: label,
10766
- input: thumbs[0],
10767
- stops: () => thumbs,
10768
- update(value, delay, instant, isMixed = !1) {
10769
- shown = value, mixed = isMixed, paint(first || Boolean(instant) || dragging, delay), first = !1;
10770
- },
10771
- dispose() {
10772
- cancelAnimationFrame(smearFrame), rolling.dispose();
10773
- },
10774
- tracks: [],
10775
- gpu: [gpu]
10776
- };
10777
- }
10778
- function angleRow(c) {
10779
- let { row, label, el } = shell(c, "angle"), f = () => c.field, control = el("span", "knobs-angle");
10780
- control.tabIndex = 0, control.setAttribute("role", "slider"), control.setAttribute("aria-label", c.display);
10781
- let read = el("span", "knobs-angle-read"), dial = el("span", "knobs-dial"), hand = el("i", "knobs-dial-hand");
10782
- dial.append(hand), control.append(read, dial), row.append(control);
10783
- let value = c.get(), turn = value, mixed = !1, dragging = !1, g = gesture(c), places = () => decimals(f().step), snapTo = (v, coarse) => {
10784
- let step = coarse ? Math.max(15, f().step) : f().step, q = Number((Math.round(v / step) * step).toPrecision(12));
10785
- return f().wrap ? wrapAngle2(q, f().min, f().max) : clamp(q, f().min, f().max);
10786
- }, rolling = rollingReadout(read), paint = (instant, delay = 0) => {
10787
- let t = mixed ? "—" : `${fmt(value, places())}°`;
10788
- if (rolling.show(mixed ? ["—"] : [{ value, decimals: places() }, "°"], instant || row.classList.contains("knobs-active") ? 0 : rollMs(row), delay), control.setAttribute("aria-valuenow", String(value)), control.setAttribute("aria-valuetext", t), instant || reducedMotion4(c.doc))
10789
- hand.style.transition = "none";
10790
- if (hand.style.rotate = `${turn.toFixed(3)}deg`, instant)
10791
- hand.offsetWidth, hand.style.transition = "";
10792
- }, follow = (next) => {
10793
- let d = next - value;
10794
- if (f().wrap) {
10795
- let s = f().max - f().min;
10796
- d = ((d + s / 2) % s + s) % s - s / 2;
10797
- }
10798
- turn += d, value = next;
10799
- }, mode = "scrub", startX = 0, startValue = 0, carried = 0, rowRect, centre = [0, 0], moved = !1, angleAt = (x, y) => Math.atan2(x - centre[0], -(y - centre[1])) * 180 / Math.PI;
10800
- control.addEventListener("pointerdown", (event) => {
10801
- if (event.button !== 0 || event.shiftKey || event.metaKey)
10802
- return;
10803
- if (event.target.closest(".knobs-cell-edit"))
10804
- return;
10805
- event.preventDefault(), control.setPointerCapture(event.pointerId), control.focus({ preventScroll: !0, focusVisible: !1 });
10806
- let r = dial.getBoundingClientRect();
10807
- if (centre = [r.left + r.width / 2, r.top + r.height / 2], mode = event.target.closest(".knobs-dial") ? "dial" : "scrub", startX = event.clientX, startValue = value, carried = 0, moved = !1, dragging = !0, g.start(), mode === "dial")
10808
- moved = !0, row.classList.add("knobs-active"), g.set(snapTo(angleAt(event.clientX, event.clientY), !1));
10809
- c.wake();
10810
- }), control.addEventListener("pointermove", (event) => {
10811
- if (!dragging)
10812
- return;
10813
- if (mode === "dial") {
10814
- let a = angleAt(event.clientX, event.clientY);
10815
- g.set(snapTo(f().wrap ? a : clamp(a, f().min, f().max), event.shiftKey));
10816
- return;
10817
- }
10818
- if (!moved && Math.abs(event.clientX - startX) < 3)
10819
- return;
10820
- if (!moved)
10821
- moved = !0, startX = event.clientX, rowRect = row.getBoundingClientRect(), row.classList.add("knobs-active");
10822
- let r = rowRect, rate = precisionRate(offRow(event.clientY, r.top, r.bottom)) * (event.altKey ? 0.1 : 1);
10823
- carried += (event.clientX - startX) * rate, startX = event.clientX, g.set(snapTo(startValue + carried * f().step, event.shiftKey));
10824
- });
10825
- let editor = inlineEdit(c.doc, row, `${c.display} value`, (v) => {
10826
- g.start(), g.set(snapTo(v, !1));
10827
- }, () => control), release = () => {
10828
- if (!dragging)
10829
- return;
10830
- if (dragging = !1, row.classList.remove("knobs-active"), !moved)
10831
- editor.start(mixed ? "" : fmt(value, places()));
10832
- c.wake();
10833
- };
10834
- control.addEventListener("pointerup", release), control.addEventListener("pointercancel", release);
10835
- let panAt, give = textGive(read, () => c.wake());
10836
- onPan(control, {
10837
- accept: () => !dragging,
10838
- move(dx) {
10839
- let step = f().step;
10840
- if (panAt === void 0)
10841
- g.start(), panAt = { at: value / step, give: 0 }, row.classList.add("knobs-active");
10842
- if (panAt = f().wrap ? { at: panAt.at + dx, give: 0 } : band(panAt, dx, f().min / step, f().max / step), g.set(snapTo(panAt.at * step, !1)), panAt.give !== give.give)
10843
- give.hold(panAt.give);
10844
- },
10845
- lift: () => give.release(),
10846
- end() {
10847
- panAt = void 0, give.release(), row.classList.remove("knobs-active"), c.wake();
10848
- },
10849
- step(direction) {
10850
- g.start(), g.set(snapTo(value + direction * f().step, !1));
10851
- }
10852
- }), control.addEventListener("keydown", (event) => {
10853
- if (event.target !== control || !plainKey(event))
10854
- return;
10855
- let dir = arrowOf(event), char = typed(event);
10856
- if (dir)
10857
- if (event.preventDefault(), event.stopPropagation(), g.start(), event.shiftKey) {
10858
- let s = Math.max(15, f().step), k = dir > 0 ? Math.floor(value / s + 0.000000001) + 1 : Math.ceil(value / s - 0.000000001) - 1;
10859
- g.set(snapTo(k * s, !1));
10860
- } else
10861
- g.set(snapTo(value + dir * f().step * (event.altKey ? 0.1 : 1), !1));
10862
- else if (event.key === "Home" || event.key === "End")
10863
- event.preventDefault(), g.start(), g.set(snapTo(event.key === "Home" ? f().min : f().max - (f().wrap ? f().step : 0), !1));
10864
- else if (event.key === "Enter" && keyPrefs(control).edit)
10865
- event.preventDefault(), event.stopPropagation(), editor.start(mixed ? "" : fmt(value, places()));
10866
- else if (char && keyPrefs(control).edit)
10867
- event.preventDefault(), event.stopPropagation(), editor.start(char, !1);
10868
- });
10869
- let first = !0, gpu = {
10870
- kind: "angle",
10871
- key: c.key,
10872
- display: c.display,
10873
- row,
10874
- labelEl: label,
10875
- control,
10876
- read,
10877
- dial,
10878
- turn: () => mixed ? 0 : turn,
10879
- dragging: () => dragging,
10880
- editing: () => editor.open,
10881
- wave: c.wave,
10882
- isModified: c.isModified
10883
- };
10884
- return {
10885
- row,
10886
- labelEl: label,
10887
- input: control,
10888
- stops: () => [control],
10889
- update(next, delay, instant, isMixed = !1) {
10890
- if (mixed = isMixed, first || instant)
10891
- value = turn = next;
10892
- else
10893
- follow(next);
10894
- paint(first || Boolean(instant) || dragging, delay), first = !1;
10895
- },
10896
- dispose() {
10897
- rolling.dispose();
10898
- },
10899
- tracks: [],
10900
- gpu: [gpu]
10901
- };
10902
- }
10903
- var SEED_STEP = 12, DICE_ROLL_MS = 620;
10904
- function seedRow(c) {
10905
- let { row, label, el } = shell(c, "seed"), f = () => c.field, control = el("span", "knobs-seed");
10906
- control.tabIndex = 0, control.setAttribute("role", "spinbutton"), control.setAttribute("aria-label", c.display);
10907
- let read = el("span", "knobs-seed-read");
10908
- control.append(read);
10909
- let reel = el("span", "knobs-seed-digits");
10910
- read.append(reel);
10911
- let roller = it(reel, {
10912
- value: c.get(),
10913
- format: { useGrouping: !1, maximumFractionDigits: 0 },
10914
- duration: 1,
10915
- motionBlur: !0
10916
- }), places = [], initialized = !1, diced = !1, roll = el("button", "knobs-seed-roll");
10917
- roll.type = "button", roll.tabIndex = -1, roll.setAttribute("aria-label", `Roll ${c.display}`);
10918
- let die = createHeaderDie(c.doc, () => c.wake(), GROUP_DIE);
10919
- roll.append(die.node), row.append(control, roll);
10920
- let value = c.get(), mixed = !1, g = gesture(c), set = (v) => {
10921
- stopCarry(), g.start(), g.set(Math.round(clamp(v, f().min, f().max)));
10922
- }, reroll = () => {
10923
- let { min, max } = f(), next = value;
10924
- for (let i = 0;i < 8 && next === value; i++)
10925
- next = min + Math.floor(Math.random() * (max - min + 1));
10926
- die.roll(), diced = !0, set(next), diced = !1;
10927
- };
10928
- roll.onclick = () => reroll();
10929
- let editor = inlineEdit(c.doc, row, `${c.display} value`, set, () => control), give = textGive(read, () => c.wake()), recent = moves(), panned, carry = [], stopCarry = () => {
10930
- for (let t of carry)
10931
- clearTimeout(t);
10932
- carry = [];
10933
- }, scrubBy = (steps) => {
10934
- let { min, max } = f(), next = clamp(value + steps, min, max);
10935
- if (next === value)
10936
- return !1;
10937
- let dir = next > value ? 1 : -1;
10938
- for (let i = 0;i < Math.min(3, Math.abs(next - value)); i++)
10939
- die.turn(dir);
10940
- return g.set(next), tick(control), !0;
10941
- }, letGo = () => {
10942
- panned = void 0, give.release(), row.classList.remove("knobs-active"), c.wake();
10943
- };
10944
- onPan(row, {
10945
- accept: () => !editor.open,
10946
- move(dx) {
10947
- if (!panned)
10948
- stopCarry(), g.start(), recent.reset(), panned = { travel: 0, give: 0 }, row.classList.add("knobs-active"), c.wake();
10949
- recent.add(dx);
10950
- let { min, max } = f(), { next, steps } = stepBand(panned, dx, SEED_STEP, [value - min, max - value]);
10951
- if (panned = next, steps)
10952
- scrubBy(steps);
10953
- if (next.give !== give.give)
10954
- give.hold(next.give);
10955
- },
10956
- lift() {
10957
- if (!panned)
10958
- return;
10959
- let n = carrySteps(recent.speed()), dir = Math.sign(recent.speed());
10960
- letGo();
10961
- for (let i = 0;i < n; i++)
10962
- carry.push(setTimeout(() => {
10963
- if (!scrubBy(dir))
10964
- stopCarry();
10965
- }, carryAt(i)));
10966
- },
10967
- end: letGo,
10968
- step(direction) {
10969
- stopCarry(), set(value + direction);
10970
- }
10971
- }), control.addEventListener("click", () => {
10972
- if (!editor.open)
10973
- editor.start(mixed ? "" : String(value));
10974
- }), control.addEventListener("keydown", (event) => {
10975
- if (event.target !== control || !plainKey(event))
10976
- return;
10977
- let dir = arrowOf(event), char = typed(event);
10978
- if (dir)
10979
- event.preventDefault(), event.stopPropagation(), set(value + dir * bigOf(event));
10980
- else if (event.key === " ")
10981
- event.preventDefault(), event.stopPropagation(), reroll();
10982
- else if (event.key === "Enter" && keyPrefs(control).edit)
10983
- event.preventDefault(), event.stopPropagation(), editor.start(mixed ? "" : String(value));
10984
- else if (char && keyPrefs(control).edit)
10985
- event.preventDefault(), event.stopPropagation(), editor.start(char, !1);
10986
- });
10987
- let paint = (previous, instant) => {
10988
- read.toggleAttribute("data-mixed", mixed), control.setAttribute("aria-valuenow", String(value));
10989
- let ms = !initialized || instant ? 0 : diced ? DICE_ROLL_MS : rollMs(row);
10990
- initialized = !0, syncPlaces(places, String(value), diced ? 1 : Math.sign(value - previous), ms, 0);
10991
- let dom = ms > 0 && row.closest('[data-gpu-active="true"]') === null;
10992
- if (roller.update({
10993
- value,
10994
- animated: !0,
10995
- duration: dom ? ms : 1,
10996
- motionBlur: dom,
10997
- direction: diced ? "up" : "auto"
10998
- }), ms > 0)
10999
- c.wake();
11000
- }, gpu = {
11001
- kind: "seed",
11002
- key: c.key,
11003
- display: c.display,
11004
- row,
11005
- labelEl: label,
11006
- control,
11007
- read,
11008
- places,
11009
- mixed: () => mixed,
11010
- die: { button: roll, die },
11011
- editing: () => editor.open,
11012
- wave: c.wave,
11013
- isModified: c.isModified
11014
- };
11015
- return {
11016
- row,
11017
- labelEl: label,
11018
- input: control,
11019
- stops: () => [control],
11020
- update(next, _delay, instant, isMixed = !1) {
11021
- let previous = value;
11022
- value = next, mixed = isMixed, paint(previous, Boolean(instant));
11023
- },
11024
- dispose: () => {
11025
- stopCarry(), die.dispose(), roller.destroy();
11026
- },
11027
- tracks: [],
11028
- gpu: [gpu]
11029
- };
11030
- }
11031
- var CAP_MORPH = springLinear(SPRINGS2.snappy);
11032
- function keyCap(o) {
11033
- let chip = o.doc.createElement("button");
11034
- chip.className = "knobs-keys", chip.type = "button";
11035
- let text = o.doc.createElement("span");
11036
- text.className = "knobs-keys-text", chip.append(text);
11037
- let value = "", capturing = !1, preview = "", morph, write = (shown) => {
11038
- let glide = chip.isConnected && chip.offsetWidth > 0 && !chip.closest('[data-gpu-active="true"]') && !reducedMotion4(o.doc), from = glide ? chip.getBoundingClientRect().width : 0;
11039
- if (morph?.cancel(), text.textContent = shown, !glide)
11040
- return;
11041
- let to = chip.getBoundingClientRect().width;
11042
- if (text.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 150, easing: "ease-out" }), Math.abs(to - from) < 0.5)
11043
- return;
11044
- chip.dataset.morph = "", morph = chip.animate([{ width: `${from}px` }, { width: `${to}px` }], {
11045
- duration: CAP_MORPH.ms,
11046
- easing: CAP_MORPH.easing
11047
- }), morph.onfinish = () => delete chip.dataset.morph, morph.oncancel = () => delete chip.dataset.morph;
11048
- }, paint = () => {
11049
- let shown = capturing ? preview || "Type keys…" : value === void 0 ? "—" : value ? comboLabel2(value) : o.empty;
11050
- if (text.textContent !== shown)
11051
- write(shown);
11052
- chip.toggleAttribute("data-empty", !capturing && value === ""), chip.setAttribute("aria-pressed", String(capturing)), chip.setAttribute("aria-label", `${o.name()}: ${capturing ? "press a key combination" : value ? comboLabel2(value) : "none"}`), o.wake();
11053
- }, capture = (on) => {
11054
- if (capturing === on)
11055
- return;
11056
- if (on && o.row.closest("[data-disabled],[data-readonly]"))
11057
- return;
11058
- capturing = on, preview = "", o.row.classList.toggle("knobs-active", on), paint();
11059
- }, held = (event) => {
11060
- let mods = heldMods(event);
11061
- preview = mods.length ? comboLabel2(`${mods.join("+")}+…`) : "", paint();
11062
- };
11063
- return chip.onclick = () => capture(!capturing), chip.addEventListener("keydown", (event) => {
11064
- if (event.isComposing)
11065
- return;
11066
- if (!capturing) {
11067
- if ((event.key === "Enter" || event.key === " ") && plainKey(event) && !event.altKey)
11068
- event.preventDefault(), event.stopPropagation(), capture(!0);
11069
- return;
11070
- }
11071
- if (event.key === "Tab")
11072
- return capture(!1);
11073
- event.preventDefault(), event.stopPropagation();
11074
- let mods = heldMods(event);
11075
- if (event.key === "Escape" && !mods.length)
11076
- return capture(!1);
11077
- let take = (combo) => {
11078
- value = combo, capture(!1), o.set(combo);
11079
- };
11080
- if ((event.key === "Backspace" || event.key === "Delete") && !mods.length)
11081
- return take("");
11082
- let combo = comboFromEvent2(event);
11083
- if (!combo)
11084
- return held(event);
11085
- take(combo);
11086
- }), chip.addEventListener("keyup", (event) => {
11087
- if (capturing)
11088
- held(event);
11089
- }), chip.addEventListener("blur", () => capture(!1)), paint(), {
11090
- chip,
11091
- text,
11092
- capturing: () => capturing,
11093
- show(combo) {
11094
- value = combo, paint();
11095
- }
11096
- };
11097
- }
11098
- function keybindRow(c) {
11099
- let { row, label } = shell(c, "keybind"), cap = keyCap({
11100
- doc: c.doc,
11101
- row,
11102
- empty: "None",
11103
- name: () => c.display,
11104
- set: (combo) => c.set(combo),
11105
- wake: c.wake
11106
- }), { chip, text } = cap;
11107
- row.append(chip), cap.show(c.get());
11108
- let gpu = {
11109
- kind: "keybind",
11110
- key: c.key,
11111
- display: c.display,
11112
- row,
11113
- labelEl: label,
11114
- chip,
11115
- text,
11116
- capturing: cap.capturing,
11117
- wave: c.wave,
11118
- isModified: c.isModified
11119
- };
11120
- return {
11121
- row,
11122
- labelEl: label,
11123
- input: chip,
11124
- stops: () => [chip],
11125
- update(next, _delay, _instant, isMixed = !1) {
11126
- cap.show(isMixed ? void 0 : next);
11127
- },
11128
- dispose() {},
11129
- tracks: [],
11130
- gpu: [gpu]
11131
- };
11132
- }
11133
- function naturalWidth(b) {
11134
- let cs = getComputedStyle(b), w = (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0), parts = [...b.children];
11135
- for (let part of parts) {
11136
- let ps = getComputedStyle(part), margin = parseFloat(ps.marginRight) || 0;
11137
- w += part instanceof HTMLElement ? part.scrollWidth + margin : parseFloat(ps.width) || 0;
11138
- }
11139
- return Math.ceil(w + (parseFloat(cs.columnGap) || 0) * Math.max(0, parts.length - 1));
11140
- }
11141
- function toolbarRow(c, press, keyOf = toolbarShortcut2) {
11142
- let el = make(c.doc), row = el("div", "knobs-row knobs-toolbar");
11143
- row.dataset.field = c.key, row.setAttribute("role", "toolbar"), row.setAttribute("aria-label", c.display);
11144
- let field = c.field, buttons = [], last = 0, keys = "", keysNow = () => field.items.map((item) => keyOf(item)?.key ?? "").join("\x00"), layout = () => {
11145
- let cs = getComputedStyle(row), avail = row.clientWidth - (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0);
11146
- if (avail <= 0 || !buttons.length)
11147
- return;
11148
- let widest = Math.max(...buttons.map(naturalWidth)), cols = String(toolbarColumns(buttons.length, widest, avail, parseFloat(cs.columnGap) || 0));
11149
- if (row.style.getPropertyValue("--k-tool-cols") !== cols)
11150
- row.style.setProperty("--k-tool-cols", cols), c.wake();
11151
- }, sizes = new ResizeObserver(layout);
11152
- sizes.observe(row), c.doc.fonts?.ready.then(() => row.isConnected && layout());
11153
- let gpu = {
11154
- kind: "toolbar",
11155
- key: c.key,
11156
- display: c.display,
11157
- row,
11158
- items: [],
11159
- wave: c.wave,
11160
- isModified: () => !1
11161
- }, build = () => {
11162
- let items = [];
11163
- if (buttons = field.items.map((item, i) => {
11164
- let b = el("button", "knobs-tool");
11165
- b.type = "button", b.tabIndex = -1, b.disabled = Boolean(item.disabled), b.dataset.item = item.id ?? item.label;
11166
- let iconEl;
11167
- if (item.icon)
11168
- iconEl = iconSvg(c.doc, item.icon), b.append(iconEl);
11169
- let name = el("span", "knobs-tool-label", item.label);
11170
- b.append(name);
11171
- let chip, key = keyOf(item);
11172
- if (key) {
11173
- if (chip = el("kbd", "knobs-tool-key", comboLabel2(key.key)), b.append(chip), key.scope !== "none")
11174
- b.setAttribute("aria-keyshortcuts", key.key);
11175
- }
11176
- return b.onclick = () => {
11177
- last = i, press(item);
11178
- }, b.addEventListener("focus", () => last = i), items.push({
11179
- button: b,
11180
- ...item.icon ? { icon: item.icon } : {},
11181
- ...iconEl ? { iconEl } : {},
11182
- label: name,
11183
- ...chip ? { chip } : {}
11184
- }), b;
11185
- }), row.replaceChildren(...buttons), gpu.items = items, keys = keysNow(), row.isConnected)
11186
- layout();
11187
- };
11188
- build(), row.addEventListener("keydown", (event) => {
11189
- let at = buttons.indexOf(event.target), dir = arrowOf(event);
11190
- if (at < 0 || !dir || !plainKey(event))
11191
- return;
11192
- event.preventDefault(), event.stopPropagation();
11193
- let live = buttons.filter((b) => !b.disabled), i = live.indexOf(buttons[at]);
11194
- live[(i + dir + live.length) % live.length]?.focus();
11195
- });
11196
- let first = () => buttons.find((b) => !b.disabled) ?? buttons[0];
11197
- return {
11198
- row,
11199
- labelEl: row,
11200
- get input() {
11201
- let b = buttons[last];
11202
- return b && !b.disabled ? b : first();
11203
- },
11204
- get field() {
11205
- return field;
11206
- },
11207
- stops() {
11208
- let b = buttons[last];
11209
- return [b && !b.disabled ? b : first()];
11210
- },
11211
- update() {},
11212
- refresh() {
11213
- if (keysNow() === keys)
11214
- return;
11215
- let focused = buttons.indexOf(c.doc.activeElement);
11216
- if (build(), focused >= 0)
11217
- buttons[focused]?.focus({ preventScroll: !0 });
11218
- c.wake();
11219
- },
11220
- match(event, global) {
11221
- return field.items.find((item) => {
11222
- let key = keyOf(item);
11223
- return key !== void 0 && !item.disabled && key.scope === (global ? "page" : "panel") && matchesCombo2(key.key, event);
11224
- });
11225
- },
11226
- press(item) {
11227
- let i = field.items.indexOf(item), b = buttons[i];
11228
- if (b && !reducedMotion4(c.doc))
11229
- b.classList.add("knobs-pressed"), setTimeout(() => b.classList.remove("knobs-pressed"), 140), c.wake();
11230
- press(item);
11231
- },
11232
- swap(next) {
11233
- field = next, build(), c.wake();
11234
- },
11235
- dispose() {
11236
- sizes.disconnect();
11237
- },
11238
- tracks: [],
11239
- gpu: [gpu]
11240
- };
11241
- }
11242
-
11243
- // src/panel/search.ts
11244
- function createSearch(doc, options) {
11245
- let element = doc.createElement("div");
11246
- element.className = "knobs-search";
11247
- let field = doc.createElement("label");
11248
- field.className = "knobs-search-field", field.append(iconSvg(doc, "search"));
11249
- let input = doc.createElement("input");
11250
- input.type = "search", input.placeholder = options.placeholder, input.spellcheck = !1, input.autocomplete = "off", input.setAttribute("aria-label", "Search controls"), input.setAttribute("aria-keyshortcuts", "/"), field.append(input), element.append(field);
11251
- let on = /* @__PURE__ */ new Set, chips = options.chips.map((spec) => {
11252
- let b = doc.createElement("button");
11253
- b.type = "button", b.className = "knobs-search-chip", b.tabIndex = -1, b.dataset.chip = spec.id, b.setAttribute("aria-pressed", "false");
11254
- let name = doc.createElement("span");
11255
- if (name.textContent = spec.label, b.append(name), spec.count) {
11256
- let n = doc.createElement("span");
11257
- n.className = "knobs-search-count", b.append(n);
11258
- }
11259
- return b.onclick = () => {
11260
- if (on.has(spec.id))
11261
- on.delete(spec.id);
11262
- else
11263
- on.add(spec.id);
11264
- b.setAttribute("aria-pressed", String(on.has(spec.id))), options.change();
11265
- }, element.append(b), b;
11266
- });
11267
- return input.addEventListener("input", () => options.change()), {
11268
- element,
11269
- input,
11270
- chips,
11271
- query: () => input.value.trim().toLowerCase(),
11272
- active: () => on,
11273
- filtering: () => input.value.trim() !== "" || on.size > 0,
11274
- counts(counts) {
11275
- for (let b of chips) {
11276
- let n = b.querySelector(".knobs-search-count");
11277
- if (!n)
11278
- continue;
11279
- let text = String(counts.get(b.dataset.chip) ?? 0);
11280
- if (n.textContent !== text)
11281
- n.textContent = text;
11282
- let none = text === "0" && !on.has(b.dataset.chip);
11283
- if (b.hasAttribute("data-none") !== none)
11284
- b.toggleAttribute("data-none", none);
11285
- }
11286
- },
11287
- set(query, next) {
11288
- let changed = !1;
11289
- if (query !== void 0 && input.value !== query)
11290
- input.value = query, changed = !0;
11291
- if (next) {
11292
- let want = new Set(next.filter((id) => chips.some((b) => b.dataset.chip === id)));
11293
- if (want.size !== on.size || [...want].some((id) => !on.has(id))) {
11294
- on.clear();
11295
- for (let id of want)
11296
- on.add(id);
11297
- for (let b of chips)
11298
- b.setAttribute("aria-pressed", String(on.has(b.dataset.chip)));
11299
- changed = !0;
11300
- }
11301
- }
11302
- if (changed)
11303
- options.change();
11304
- },
11305
- clear() {
11306
- let had = input.value !== "" || on.size > 0;
11307
- input.value = "", on.clear();
11308
- for (let b of chips)
11309
- b.setAttribute("aria-pressed", "false");
11310
- if (had)
11311
- options.change();
11312
- },
11313
- focus() {
11314
- input.focus({ preventScroll: !0 }), input.select();
11315
- }
11316
- };
11317
- }
11318
- function matchSpan(text, query) {
11319
- if (!query)
11320
- return;
11321
- let at = text.toLowerCase().indexOf(query);
11322
- return at < 0 ? void 0 : [at, at + query.length];
11323
- }
11324
- function markLabel(label, text, span) {
11325
- let doc = label.ownerDocument, parts = [];
11326
- if (!span)
11327
- parts.push(doc.createTextNode(text));
11328
- else {
11329
- if (span[0] > 0)
11330
- parts.push(doc.createTextNode(text.slice(0, span[0])));
11331
- let mark = doc.createElement("mark");
11332
- if (mark.className = "knobs-match", mark.textContent = text.slice(span[0], span[1]), parts.push(mark), span[1] < text.length)
11333
- parts.push(doc.createTextNode(text.slice(span[1])));
11334
- }
11335
- let current = [...label.childNodes].filter((c) => c.nodeType === 3 || c instanceof HTMLElement && c.classList.contains("knobs-match"));
11336
- if (current.length === parts.length && current.every((c, i) => c.nodeName === parts[i].nodeName && c.textContent === parts[i].textContent))
11337
- return;
11338
- for (let c of current)
11339
- c.remove();
11340
- label.prepend(...parts);
11341
- }
11342
-
11343
10427
  // src/ui/activity-css.ts
11344
10428
  var open = springLinear(SPRINGS2.snappy), activityCss = `
11345
10429
  :where(.knobs-activity,.knobs-activity *){box-sizing:border-box}
@@ -12014,13 +11098,17 @@ var css = `
12014
11098
  .knobs-tree-row[aria-expanded="true"]>.knobs-tree-caret{transform:rotate(90deg)}
12015
11099
  .knobs-tree-row:not([aria-expanded])>.knobs-tree-caret{visibility:hidden}
12016
11100
  .knobs-tree-icon{flex:none;display:flex;align-items:center;justify-content:center;width:14px;margin-right:6px;color:var(--_k-fg-3);font-size:calc(var(--_k-font-size) - 1px)}
12017
- .knobs-tree-label{position:relative;flex:1;min-width:0;overflow:hidden;font-weight:var(--_k-weight)}
11101
+ /* The label wins the room: it keeps its width while the detail (which only takes what's left)
11102
+ fades out at its end, and gives way only once the detail is gone. Each fades where it's cut. */
11103
+ .knobs-tree-label{position:relative;flex:0 1 auto;min-width:0;overflow:hidden;font-weight:var(--_k-weight)}
11104
+ .knobs-tree-row[data-editing]>.knobs-tree-label{flex-grow:1}
12018
11105
  .knobs-tree-row[data-ancestor] .knobs-tree-label{color:var(--_k-fg-2)}
12019
11106
  .knobs-tree-label .knobs-tree-mark{color:var(--_k-accent)}
12020
11107
  .knobs-tree-row[data-off~="visible"] :is(.knobs-tree-label,.knobs-tree-icon){opacity:.45}
12021
11108
  .knobs-tree-row[data-off~="locked"] .knobs-tree-label{color:var(--_k-fg-2)}
12022
- .knobs-tree-detail{flex:none;margin-left:6px;color:var(--_k-fg-3);font-variant-numeric:tabular-nums}
12023
- .knobs-tree-tools{flex:none;display:flex;align-items:center;gap:1px;margin-left:4px}
11109
+ .knobs-tree-detail{flex:1 1 0;min-width:0;overflow:hidden;padding-left:6px;text-align:right;color:var(--_k-fg-3);font-variant-numeric:tabular-nums}
11110
+ .knobs-tree-row[data-editing]>.knobs-tree-detail{flex-grow:0;visibility:hidden}
11111
+ .knobs-tree-tools{flex:none;display:flex;align-items:center;gap:1px;margin-left:auto;padding-left:4px}
12024
11112
  .knobs-ui .knobs-tree-toggle{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:calc(var(--_k-radius) * 999);color:var(--_k-fg-3);cursor:pointer;opacity:0;transition:opacity .12s,color .12s,background .12s}
12025
11113
  .knobs-ui .knobs-tree-toggle svg{width:12px;height:12px}
12026
11114
  .knobs-tree-row:is(:hover,[data-active],[aria-selected="true"]) .knobs-tree-toggle,.knobs-ui .knobs-tree-toggle[aria-pressed="false"]{opacity:1}
@@ -12137,47 +11225,1277 @@ function injectUiStyles(doc) {
12137
11225
  let style = doc.createElement("style");
12138
11226
  style.dataset.knobsUi = "", style.textContent = uiCss(), doc.head.append(style);
12139
11227
  }
12140
- function applyLook2(root, look) {
12141
- injectUiStyles(root.ownerDocument), root.classList.add("knobs-root", "knobs-ui"), applyLook(root, resolveLook(look)), syncFrame(root);
11228
+ function applyLook2(root, look) {
11229
+ injectUiStyles(root.ownerDocument), root.classList.add("knobs-root", "knobs-ui"), applyLook(root, resolveLook(look)), syncFrame(root);
11230
+ }
11231
+ function syncFrame(root) {
11232
+ if (!root.isConnected)
11233
+ return;
11234
+ if (root.ownerDocument.defaultView?.getComputedStyle(root).getPropertyValue("--k-frame").trim() === "flat")
11235
+ root.dataset.frame = "flat";
11236
+ else
11237
+ delete root.dataset.frame;
11238
+ }
11239
+ var lookDefaults = { current: {} };
11240
+ function frameLoop(win, step) {
11241
+ let raf = 0, last = 0, tick = (now) => {
11242
+ let dt = last ? Math.min(0.064, (now - last) / 1000) : 0.016666666666666666;
11243
+ if (last = now, raf = step(dt) ? win.requestAnimationFrame(tick) : 0, !raf)
11244
+ last = 0;
11245
+ };
11246
+ return {
11247
+ wake() {
11248
+ if (!raf)
11249
+ raf = win.requestAnimationFrame(tick);
11250
+ },
11251
+ get running() {
11252
+ return raf !== 0;
11253
+ },
11254
+ stop() {
11255
+ if (raf)
11256
+ win.cancelAnimationFrame(raf);
11257
+ raf = 0, last = 0;
11258
+ }
11259
+ };
11260
+ }
11261
+ 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>`, ICONS = {
11262
+ caret: svg('<path d="M6 3.5 10.5 8 6 12.5"/>'),
11263
+ chevronDown: svg('<path d="M3.5 6 8 10.5 12.5 6"/>'),
11264
+ 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"/>'),
11265
+ 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"/>'),
11266
+ 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"/>'),
11267
+ 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"/>')
11268
+ };
11269
+
11270
+ // src/ui/layer.ts
11271
+ var lookOf = (look) => ({ ...lookDefaults.current, ...look }), roots = /* @__PURE__ */ new WeakMap;
11272
+ function layerRoot(doc) {
11273
+ let root = roots.get(doc);
11274
+ if (root?.isConnected)
11275
+ return root;
11276
+ return root = doc.createElement("div"), root.className = "knobs-layers", doc.body.append(root), roots.set(doc, root), root;
11277
+ }
11278
+ function layerElement(doc, cls, look) {
11279
+ let el = doc.createElement("div");
11280
+ return el.className = cls, layerRoot(doc).append(el), applyLook2(el, lookOf(look)), el;
11281
+ }
11282
+ function presence(el, o = {}) {
11283
+ let win = el.ownerDocument.defaultView, spring = new Spring(0, 1, 1, 0.002), dx = o.dx ?? 0, dy = o.dy ?? 0, gone = [], shown = !1, render = () => {
11284
+ let reduced = reducedMotion(win), p = spring.x, q = Math.max(0, Math.min(1, p)), scale = reduced ? 1 : (o.scale ?? 0.94) + (1 - (o.scale ?? 0.94)) * p, blur = reduced ? 0 : (1 - q) * (o.blur ?? 6), t = reduced ? 0 : 1 - p;
11285
+ el.style.opacity = q.toFixed(3), el.style.transform = p === 1 ? "" : `translate(${(dx * t).toFixed(2)}px,${(dy * t).toFixed(2)}px) scale(${scale.toFixed(4)})`, el.style.filter = blur > 0.05 ? `blur(${blur.toFixed(2)}px)` : "", el.style.pointerEvents = shown ? "" : "none", o.onFrame?.(p);
11286
+ }, loop = frameLoop(win, (dt) => {
11287
+ if (spring.step(dt), render(), !spring.resting)
11288
+ return !0;
11289
+ if (!shown) {
11290
+ let done = gone;
11291
+ gone = [];
11292
+ for (let f of done)
11293
+ f();
11294
+ o.onGone?.();
11295
+ }
11296
+ return !1;
11297
+ }), tune = (seconds, zeta) => {
11298
+ let reduced = reducedMotion(win);
11299
+ spring.omega = 2 * Math.PI / (reduced ? FADE.duration : seconds), spring.zeta = reduced ? 1 : zeta;
11300
+ };
11301
+ return render(), {
11302
+ show() {
11303
+ shown = !0, gone = [], el.style.pointerEvents = "", tune(o.duration ?? 0.32, 1 - (o.bounce ?? 0.2)), spring.set(1), loop.wake();
11304
+ },
11305
+ hide() {
11306
+ return shown = !1, el.style.pointerEvents = "none", tune(o.exit ?? 0.2, 1), spring.set(0), loop.wake(), new Promise((resolve) => gone.push(resolve));
11307
+ },
11308
+ snap(to = 1) {
11309
+ if (shown = to === 1, spring.snap(to), loop.stop(), render(), !shown) {
11310
+ let done = gone;
11311
+ gone = [];
11312
+ for (let f of done)
11313
+ f();
11314
+ o.onGone?.();
11315
+ } else
11316
+ gone = [];
11317
+ },
11318
+ get shown() {
11319
+ return shown;
11320
+ },
11321
+ from(x, y) {
11322
+ dx = x, dy = y;
11323
+ },
11324
+ dispose() {
11325
+ loop.stop();
11326
+ for (let f of gone)
11327
+ f();
11328
+ gone = [];
11329
+ }
11330
+ };
11331
+ }
11332
+ var anchorBox = (a) => {
11333
+ if (typeof a.getBoundingClientRect === "function") {
11334
+ let r = a.getBoundingClientRect();
11335
+ return { x: r.left, y: r.top, w: r.width, h: r.height };
11336
+ }
11337
+ let r = a;
11338
+ return { x: r.x, y: r.y, w: r.width ?? 0, h: r.height ?? 0 };
11339
+ }, viewBox = (doc) => ({
11340
+ x: 0,
11341
+ y: 0,
11342
+ w: doc.documentElement.clientWidth,
11343
+ h: doc.documentElement.clientHeight
11344
+ });
11345
+
11346
+ // src/ui/overlay-model.ts
11347
+ var opposite = { top: "bottom", bottom: "top", left: "right", right: "left" };
11348
+ function place2(o) {
11349
+ let { anchor: a, size, view: v } = o, offset = o.offset ?? 6, margin = o.margin ?? 8, [want, align = "center"] = (o.placement ?? "bottom").split("-"), room = (s) => s === "top" ? a.y - v.y : s === "bottom" ? v.y + v.h - (a.y + a.h) : s === "left" ? a.x - v.x : v.x + v.w - (a.x + a.w), need = (s) => (s === "top" || s === "bottom" ? size.h : size.w) + offset + margin, side = want;
11350
+ if (room(want) < need(want) && room(opposite[want]) > room(want))
11351
+ side = opposite[want];
11352
+ let vertical = side === "top" || side === "bottom", x, y;
11353
+ if (vertical)
11354
+ y = side === "bottom" ? a.y + a.h + offset : a.y - offset - size.h, x = align === "start" ? a.x : align === "end" ? a.x + a.w - size.w : a.x + (a.w - size.w) / 2, x = clamp2(x, v.x + margin, v.x + v.w - margin - size.w);
11355
+ else
11356
+ x = side === "right" ? a.x + a.w + offset : a.x - offset - size.w, y = align === "start" ? a.y : align === "end" ? a.y + a.h - size.h : a.y + (a.h - size.h) / 2, y = clamp2(y, v.y + margin, v.y + v.h - margin - size.h);
11357
+ let origin = vertical ? { x: clamp2(a.x + a.w / 2 - x, 0, size.w), y: side === "bottom" ? 0 : size.h } : { x: side === "right" ? 0 : size.w, y: clamp2(a.y + a.h / 2 - y, 0, size.h) };
11358
+ return {
11359
+ x: Math.round(x),
11360
+ y: Math.round(y),
11361
+ placement: align === "center" ? side : `${side}-${align}`,
11362
+ origin
11363
+ };
11364
+ }
11365
+ var clamp2 = (v, lo, hi) => hi < lo ? lo : Math.max(lo, Math.min(hi, v));
11366
+
11367
+ // src/ui/tooltip.ts
11368
+ var tips = /* @__PURE__ */ new WeakMap, HANDOFF_MS = 400, controllers = /* @__PURE__ */ new WeakMap;
11369
+ function tipOf(start) {
11370
+ let node = start instanceof Element ? start : null;
11371
+ while (node) {
11372
+ let own = tips.get(node);
11373
+ if (own)
11374
+ return { el: node, tip: own };
11375
+ let data = node.getAttribute("data-tip");
11376
+ if (data) {
11377
+ let shortcut = node.getAttribute("data-tip-shortcut") ?? void 0, placement = node.getAttribute("data-tip-placement") ?? void 0;
11378
+ return {
11379
+ el: node,
11380
+ tip: {
11381
+ text: data,
11382
+ opts: { ...shortcut ? { shortcut } : {}, ...placement ? { placement } : {} }
11383
+ }
11384
+ };
11385
+ }
11386
+ node = node.parentElement;
11387
+ }
11388
+ return null;
11389
+ }
11390
+ function controllerOf(doc) {
11391
+ let existing = controllers.get(doc);
11392
+ if (existing)
11393
+ return existing;
11394
+ let win = doc.defaultView, layer, motion, text, keys, target = null, pending = null, timer = 0, hiddenAt = -1 / 0, quiet = null, keyboard = !1, build = (look) => {
11395
+ layer = layerElement(doc, "knobs-layer knobs-tooltip", look), layer.id = `knobs-tooltip-${Math.random().toString(36).slice(2, 8)}`, layer.setAttribute("role", "tooltip"), text = doc.createElement("span"), text.className = "knobs-tooltip-text", keys = doc.createElement("span"), keys.className = "knobs-tooltip-keys", layer.append(text, keys), motion = presence(layer, { scale: 0.96, blur: 3, duration: 0.24 });
11396
+ }, show = (el, tip, instant) => {
11397
+ if (!layer?.isConnected)
11398
+ build(tip.opts.look);
11399
+ target?.removeAttribute("aria-describedby"), target = el, text.textContent = typeof tip.text === "function" ? tip.text() : tip.text, keys.replaceChildren(...(tip.opts.shortcut ? comboParts2(tip.opts.shortcut) : []).map((k) => {
11400
+ let kbd = doc.createElement("kbd");
11401
+ return kbd.className = "knobs-kbd", kbd.textContent = k, kbd;
11402
+ })), keys.hidden = !tip.opts.shortcut;
11403
+ let p = place2({
11404
+ anchor: anchorBox(el),
11405
+ size: { w: layer.offsetWidth, h: layer.offsetHeight },
11406
+ view: viewBox(doc),
11407
+ placement: tip.opts.placement ?? "top",
11408
+ offset: 6
11409
+ });
11410
+ layer.style.left = `${p.x}px`, layer.style.top = `${p.y}px`, layer.style.transformOrigin = `${p.origin.x}px ${p.origin.y}px`;
11411
+ let side = p.placement.split("-")[0];
11412
+ if (motion.from(0, side === "top" ? 3 : side === "bottom" ? -3 : 0), el.setAttribute("aria-describedby", layer.id), instant)
11413
+ motion.snap(1);
11414
+ else
11415
+ motion.show();
11416
+ }, hide = (now = !1) => {
11417
+ if (win.clearTimeout(timer), pending = null, !target)
11418
+ return;
11419
+ if (target.removeAttribute("aria-describedby"), target = null, hiddenAt = performance.now(), now)
11420
+ motion?.snap(0);
11421
+ else
11422
+ motion?.hide();
11423
+ }, want = (found) => {
11424
+ if (!found)
11425
+ return hide();
11426
+ if (found.el === target || found.el === pending?.el)
11427
+ return;
11428
+ if (found.el === quiet)
11429
+ return;
11430
+ if (win.clearTimeout(timer), target !== null || performance.now() - hiddenAt < HANDOFF_MS) {
11431
+ pending = null, show(found.el, found.tip, !0);
11432
+ return;
11433
+ }
11434
+ if (target)
11435
+ hide();
11436
+ pending = found, timer = win.setTimeout(() => {
11437
+ if (pending = null, found.el.isConnected)
11438
+ show(found.el, found.tip, !1);
11439
+ }, found.tip.opts.delay ?? 500);
11440
+ };
11441
+ doc.addEventListener("pointerover", (e) => {
11442
+ if (e.pointerType === "touch")
11443
+ return;
11444
+ keyboard = !1, want(tipOf(e.target));
11445
+ }), doc.addEventListener("pointerout", (e) => {
11446
+ let to = e.relatedTarget, from = tipOf(e.target);
11447
+ if (!from)
11448
+ return;
11449
+ if (to && from.el.contains(to))
11450
+ return;
11451
+ if (quiet === from.el)
11452
+ quiet = null;
11453
+ if (!tipOf(to))
11454
+ hide();
11455
+ }), doc.addEventListener("pointerdown", (e) => {
11456
+ let found = tipOf(e.target);
11457
+ if (found)
11458
+ quiet = found.el;
11459
+ hide(!0);
11460
+ }, !0), doc.addEventListener("keydown", (e) => {
11461
+ if (keyboard = !0, e.key === "Escape" && target)
11462
+ hide();
11463
+ }, !0), doc.addEventListener("focusin", (e) => {
11464
+ let found = tipOf(e.target);
11465
+ if (found && keyboard && e.target.matches(":focus-visible"))
11466
+ want(found);
11467
+ }), doc.addEventListener("focusout", (e) => {
11468
+ if (target && target.contains(e.target))
11469
+ hide();
11470
+ }), doc.addEventListener("scroll", () => target && hide(!0), !0);
11471
+ let c = { hide };
11472
+ return controllers.set(doc, c), c;
11473
+ }
11474
+ function tooltip(el, content, options = {}) {
11475
+ let { text, ...rest } = typeof content === "string" || typeof content === "function" ? { text: content } : content;
11476
+ tips.set(el, { text, opts: { ...options, ...rest } });
11477
+ let c = controllerOf(el.ownerDocument), shortcut = rest.shortcut ?? options.shortcut;
11478
+ if (shortcut && !el.hasAttribute("aria-keyshortcuts"))
11479
+ el.setAttribute("aria-keyshortcuts", shortcut);
11480
+ return () => {
11481
+ tips.delete(el), c.hide(!0);
11482
+ };
11483
+ }
11484
+
11485
+ // src/panel/inspector.ts
11486
+ function make(doc) {
11487
+ return (tag, cls, text) => {
11488
+ let node = doc.createElement(tag);
11489
+ if (cls)
11490
+ node.className = cls;
11491
+ if (text !== void 0)
11492
+ node.textContent = text;
11493
+ return node;
11494
+ };
11495
+ }
11496
+ function shell(c, kind) {
11497
+ let el = make(c.doc), row = el("div", "knobs-row knobs-insp");
11498
+ row.dataset.field = c.key, row.dataset.kind = kind;
11499
+ let label = el("span", void 0, c.display);
11500
+ return row.append(label), { row, label, el };
11501
+ }
11502
+ var fmt = (v, places) => (Object.is(v, -0) ? 0 : v).toFixed(places);
11503
+ function inlineEdit(doc, host, label, commit, back) {
11504
+ let input = doc.createElement("input");
11505
+ input.className = "knobs-cell-edit", input.type = "text", input.inputMode = "decimal", input.spellcheck = !1, input.hidden = !0, input.setAttribute("aria-label", label), host.append(input);
11506
+ let open = !1, done = (keep) => {
11507
+ if (!open)
11508
+ return;
11509
+ open = !1;
11510
+ let next = Number(input.value.trim().replace(/[^\d.eE+-]/g, ""));
11511
+ if (input.hidden = !0, host.classList.remove("knobs-editing"), keep && input.value.trim() !== "" && Number.isFinite(next))
11512
+ commit(next);
11513
+ let target = back();
11514
+ if (doc.activeElement === input || doc.activeElement === doc.body)
11515
+ target.focus({ preventScroll: !0 });
11516
+ };
11517
+ return input.addEventListener("keydown", (event) => {
11518
+ if (event.key === "Enter" || event.key === "Escape")
11519
+ event.preventDefault(), event.stopPropagation(), done(event.key === "Enter");
11520
+ else
11521
+ event.stopPropagation();
11522
+ }), input.addEventListener("blur", () => done(!0)), {
11523
+ input,
11524
+ get open() {
11525
+ return open;
11526
+ },
11527
+ start(text, select = !0) {
11528
+ if (open = !0, input.value = text, input.hidden = !1, host.classList.add("knobs-editing"), input.focus({ preventScroll: !0 }), select)
11529
+ input.select();
11530
+ else
11531
+ input.setSelectionRange(text.length, text.length);
11532
+ },
11533
+ done
11534
+ };
11535
+ }
11536
+ var typed = (event) => plainKey(event) && !event.altKey && /^[0-9.\-]$/.test(event.key) ? event.key : void 0;
11537
+ function scrub(target, row, o) {
11538
+ let at, moved = !1, last = 0, carried = 0, rect;
11539
+ target.addEventListener("pointerdown", (event) => {
11540
+ if (event.button !== 0 || event.shiftKey || event.metaKey)
11541
+ return;
11542
+ event.preventDefault(), target.setPointerCapture(event.pointerId), target.focus({ preventScroll: !0, focusVisible: !1 }), at = last = event.clientX, carried = 0, moved = !1;
11543
+ }), target.addEventListener("pointermove", (event) => {
11544
+ if (at === void 0)
11545
+ return;
11546
+ if (!moved && Math.abs(event.clientX - at) < 3)
11547
+ return;
11548
+ if (!moved)
11549
+ moved = !0, last = event.clientX, rect = row.getBoundingClientRect(), row.classList.add("knobs-active"), o.start();
11550
+ let r = rect, rate = precisionRate(offRow(event.clientY, r.top, r.bottom)) * (event.altKey ? 0.1 : 1);
11551
+ carried += (event.clientX - last) * rate, last = event.clientX, o.move(carried, event.altKey, event.shiftKey);
11552
+ });
11553
+ let release = (event) => {
11554
+ if (at === void 0)
11555
+ return;
11556
+ at = void 0, rect = void 0, row.classList.remove("knobs-active"), o.end(moved, event);
11557
+ };
11558
+ return target.addEventListener("pointerup", release), target.addEventListener("pointercancel", release), { scrubbing: () => at !== void 0 && moved };
11559
+ }
11560
+ var rate = (min, max, step) => Math.max(step, (max - min) / 200), onGrid = (v, min, step) => Number((min + Math.round((v - min) / step) * step).toPrecision(12)), AXES = ["x", "y", "z"];
11561
+ function vecRow(c) {
11562
+ let { row, label, el } = shell(c, "vec"), f = () => c.field, places = () => f().decimals ?? decimals(f().step), axes = AXES.slice(0, c.field.axes), cells = el("span", "knobs-cells");
11563
+ if (axes.length > 2)
11564
+ cells.dataset.wide = "";
11565
+ let linked = Boolean(c.field.linked), link;
11566
+ if (c.field.link) {
11567
+ link = el("button", "knobs-link"), link.type = "button", link.tabIndex = -1;
11568
+ let paint = () => {
11569
+ link.replaceChildren(iconSvg(c.doc, linked ? "link" : "unlink")), link.setAttribute("aria-pressed", String(linked)), link.setAttribute("aria-label", linked ? "Unlink axes" : "Link axes");
11570
+ };
11571
+ paint(), link.onclick = () => {
11572
+ linked = !linked, paint(), c.wake();
11573
+ }, row.append(link);
11574
+ }
11575
+ row.append(cells);
11576
+ let shown = c.get(), mixed = !1, g = gesture(c), base = shown, withAxis = (from, axis, v) => {
11577
+ let { min, max, step } = f(), next = clamp(onGrid(v, min, step), min, max);
11578
+ if (!linked)
11579
+ return { ...from, [axis]: next };
11580
+ let was = from[axis] ?? 0, k = Math.abs(was) > 0.000000001 ? next / was : void 0, out = {};
11581
+ for (let a of axes) {
11582
+ let own = from[a] ?? 0;
11583
+ out[a] = a === axis ? next : clamp(Number((k !== void 0 ? own * k : own + (next - was)).toPrecision(12)), min, max);
11584
+ }
11585
+ return out;
11586
+ }, gpuCells = [], reads = [], rolls = [], stops = [];
11587
+ for (let axis of axes) {
11588
+ let cell = el("span", "knobs-cell");
11589
+ cell.tabIndex = 0, cell.dataset.axis = axis, cell.setAttribute("role", "spinbutton");
11590
+ let name = c.field.axisLabels?.[axes.indexOf(axis)] ?? axis;
11591
+ cell.setAttribute("aria-label", `${c.display} ${name}`);
11592
+ let letter = el("i", "knobs-cell-axis", name.toUpperCase());
11593
+ letter.setAttribute("aria-hidden", "true");
11594
+ let read = el("span", "knobs-cell-read");
11595
+ cell.append(letter, read), cells.append(cell), reads.push(read), rolls.push(rollingReadout(read)), stops.push(cell);
11596
+ let editor = inlineEdit(c.doc, cell, `${c.display} ${name} value`, (v) => {
11597
+ g.start(), g.set(withAxis(c.get(), axis, v));
11598
+ }, () => cell), drag = scrub(cell, row, {
11599
+ perPx: () => rate(f().min, f().max, f().step),
11600
+ start() {
11601
+ g.start(), base = c.get();
11602
+ },
11603
+ move(dx, _fine, coarse) {
11604
+ let by = dx * rate(f().min, f().max, f().step) * (coarse ? 10 : 1);
11605
+ g.set(withAxis(base, axis, (base[axis] ?? 0) + by));
11606
+ },
11607
+ end(moved) {
11608
+ if (!moved)
11609
+ editor.start(mixed ? "" : fmt(shown[axis] ?? 0, places()));
11610
+ c.wake();
11611
+ }
11612
+ }), panAt, give = textGive(read, () => c.wake());
11613
+ onPan(cell, {
11614
+ accept: () => !drag.scrubbing() && !editor.open,
11615
+ move(dx) {
11616
+ let perPx = rate(f().min, f().max, f().step);
11617
+ if (panAt === void 0)
11618
+ g.start(), base = c.get(), panAt = { at: 0, give: 0 }, row.classList.add("knobs-active"), cell.setAttribute("data-panned", "");
11619
+ let from = base[axis] ?? 0;
11620
+ if (panAt = band(panAt, dx, (f().min - from) / perPx, (f().max - from) / perPx), g.set(withAxis(base, axis, from + panAt.at * perPx)), panAt.give !== give.give)
11621
+ give.hold(panAt.give);
11622
+ },
11623
+ lift: () => give.release(),
11624
+ end() {
11625
+ panAt = void 0, give.release(), row.classList.remove("knobs-active"), cell.removeAttribute("data-panned"), c.wake();
11626
+ },
11627
+ step(direction) {
11628
+ g.start(), g.set(withAxis(c.get(), axis, (shown[axis] ?? 0) + direction * f().step));
11629
+ }
11630
+ }), cell.addEventListener("keydown", (event) => {
11631
+ if (event.target !== cell || !plainKey(event))
11632
+ return;
11633
+ let dir = arrowOf(event), char = typed(event);
11634
+ if (dir) {
11635
+ event.preventDefault(), event.stopPropagation();
11636
+ let by = f().step * (event.shiftKey ? bigOf(event) : event.altKey ? 0.1 : 1);
11637
+ g.start(), g.set(withAxis(c.get(), axis, (shown[axis] ?? 0) + dir * by));
11638
+ } else if (event.key === "Home" || event.key === "End")
11639
+ event.preventDefault(), g.start(), g.set(withAxis(c.get(), axis, event.key === "Home" ? f().min : f().max));
11640
+ else if (event.key === "Enter" && keyPrefs(cell).edit)
11641
+ event.preventDefault(), event.stopPropagation(), editor.start(mixed ? "" : fmt(shown[axis] ?? 0, places()));
11642
+ else if (char && keyPrefs(cell).edit)
11643
+ event.preventDefault(), event.stopPropagation(), editor.start(char, !1);
11644
+ }), gpuCells.push({
11645
+ el: cell,
11646
+ axis: letter,
11647
+ read,
11648
+ editing: () => editor.open,
11649
+ active: () => drag.scrubbing() || panAt !== void 0
11650
+ });
11651
+ }
11652
+ let paint = (ms = 0, delay = 0) => {
11653
+ axes.forEach((axis, i) => {
11654
+ let text = mixed ? "—" : fmt(shown[axis] ?? 0, places()), unit = f().unit && !mixed ? ` ${f().unit}` : "";
11655
+ rolls[i].show(mixed ? ["—"] : [{ value: shown[axis] ?? 0, decimals: places() }], ms, delay), stops[i].setAttribute("aria-valuetext", `${text}${unit}`);
11656
+ });
11657
+ };
11658
+ paint();
11659
+ let placed = !1, gpu = {
11660
+ kind: "vec",
11661
+ key: c.key,
11662
+ display: c.display,
11663
+ row,
11664
+ labelEl: label,
11665
+ cells: gpuCells,
11666
+ ...link ? { link } : {},
11667
+ linked: () => linked,
11668
+ wave: c.wave,
11669
+ isModified: c.isModified
11670
+ };
11671
+ return {
11672
+ row,
11673
+ labelEl: label,
11674
+ input: stops[0],
11675
+ stops: () => stops,
11676
+ update(value, delay, instant, isMixed = !1) {
11677
+ shown = value, mixed = isMixed;
11678
+ let hand = row.classList.contains("knobs-active");
11679
+ paint(!placed || instant || hand ? 0 : rollMs(row), delay), placed = !0;
11680
+ },
11681
+ dispose() {
11682
+ for (let r of rolls)
11683
+ r.dispose();
11684
+ },
11685
+ tracks: [],
11686
+ gpu: [gpu]
11687
+ };
11688
+ }
11689
+ function rangeRow(c) {
11690
+ let el = make(c.doc), f = () => c.field, places = () => f().decimals ?? decimals(f().step), row = el("div", "knobs-row knobs-range");
11691
+ row.dataset.field = c.key;
11692
+ let track = el("div", "knobs-range-track"), fill = el("i", "knobs-range-fill"), label = el("span", "knobs-label", c.display), read = el("span", "knobs-range-read"), thumbs = ["lo", "hi"].map((end) => {
11693
+ let t = el("span", "knobs-range-thumb");
11694
+ return t.dataset.end = end, t.tabIndex = 0, t.setAttribute("role", "slider"), t.setAttribute("aria-label", `${c.display} ${end === "lo" ? "low" : "high"}`), t;
11695
+ }), words = el("span", "knobs-range-text");
11696
+ words.append(label, read);
11697
+ let band2 = el("i", "knobs-range-smear");
11698
+ track.append(fill, band2, ...thumbs, words), row.append(track);
11699
+ let shown = c.get(), mixed = !1, dragging = !1, smearFrame = 0, smearAt = 0, smearStrength = 0, lastEnds = [0, 0], smears = [0, 0], ends = () => {
11700
+ let cs = getComputedStyle(track);
11701
+ return [
11702
+ parseFloat(cs.getPropertyValue("--kr-lo")) || 0,
11703
+ parseFloat(cs.getPropertyValue("--kr-hi")) || 0
11704
+ ];
11705
+ }, setSmears = (lo, hi) => {
11706
+ smears[0] = lo === 0 ? 0 : (smears[0] + lo) / 2, smears[1] = hi === 0 ? 0 : (smears[1] + hi) / 2, track.classList.toggle("knobs-range-smearing", smears[0] > 0 || smears[1] > 0), track.style.setProperty("--kr-smear-lo", smears[0].toFixed(2)), track.style.setProperty("--kr-smear-hi", smears[1].toFixed(2));
11707
+ }, runSmear = (now) => {
11708
+ let at = ends(), dt = (now - smearAt) / 1000, w = track.clientWidth, speeds = [0, 1].map((i) => dt > 0 ? Math.abs(at[i] - lastEnds[i]) * w / dt : 0);
11709
+ lastEnds = at, smearAt = now;
11710
+ let gliding = !dragging && track.getAnimations().length > 0;
11711
+ setSmears(gliding ? rangeSmear(speeds[0], smearStrength) : 0, gliding ? rangeSmear(speeds[1], smearStrength) : 0), smearFrame = gliding ? requestAnimationFrame(runSmear) : 0;
11712
+ };
11713
+ track.addEventListener("transitionrun", (event) => {
11714
+ if (event.target !== track || smearFrame || dragging)
11715
+ return;
11716
+ if (track.closest('[data-gpu-active="true"]'))
11717
+ return;
11718
+ smearStrength = clamp(parseFloat(getComputedStyle(track).getPropertyValue("--k-range-blur")) || 0, 0, 1);
11719
+ let reduced = c.doc.defaultView?.matchMedia("(prefers-reduced-motion: reduce)").matches;
11720
+ if (smearStrength <= 0 || reduced)
11721
+ return;
11722
+ lastEnds = ends(), smearAt = performance.now(), smearFrame = requestAnimationFrame(runSmear);
11723
+ });
11724
+ let lastEnd = 0, g = gesture(c), span = () => f().max - f().min || 1, ratio = (v) => clamp((v - f().min) / span(), 0, 1), quant = (v, fine) => clamp(onGrid(v, f().min, fine ? f().step / 10 : f().step), f().min, f().max), withEnd = (from, end, v, fine = !1) => {
11725
+ let q = quant(v, fine);
11726
+ return end === 0 ? [Math.min(q, from[1]), from[1]] : [from[0], Math.max(q, from[0])];
11727
+ }, rolling = rollingReadout(read), showText = (ms, delay) => rolling.show(mixed ? ["—"] : [
11728
+ { value: shown[0], decimals: places() },
11729
+ "–",
11730
+ { value: shown[1], decimals: places() },
11731
+ f().unit ? ` ${f().unit}` : ""
11732
+ ], ms, delay), place = (instant) => {
11733
+ let [a, b] = [ratio(shown[0]), ratio(shown[1])], nodes = [fill, track];
11734
+ if (instant)
11735
+ for (let n of nodes)
11736
+ n.style.transition = "none";
11737
+ if (fill.style.transform = `translateX(${(a * 100).toFixed(4)}%) scaleX(${Math.max(0, b - a).toFixed(5)})`, track.style.setProperty("--kr-lo", a.toFixed(6)), track.style.setProperty("--kr-hi", b.toFixed(6)), instant) {
11738
+ fill.offsetWidth;
11739
+ for (let n of nodes)
11740
+ n.style.transition = "";
11741
+ }
11742
+ }, paint = (instant, delay = 0) => {
11743
+ showText(instant ? 0 : rollMs(row), delay), thumbs.forEach((th, i) => {
11744
+ th.setAttribute("aria-valuenow", String(shown[i])), th.setAttribute("aria-valuemin", String(i === 0 ? f().min : shown[0])), th.setAttribute("aria-valuemax", String(i === 0 ? shown[1] : f().max));
11745
+ }), place(instant);
11746
+ }, grab = 0, startValue = shown, rect, lastX = 0, hand = { at: 0, give: 0 }, moved = 0, card = () => row.parentElement?.classList.contains("knobs-card") ? row.parentElement : null, give = textGive(row, () => c.wake(), {
11747
+ home: STRETCH_HOME_S,
11748
+ draw: (px) => drawStretch(row, px, 0, { w: track.clientWidth, h: 0 }, { thin: !0, unclip: card() })
11749
+ }), most = GIVE, fit = () => {
11750
+ let room = roomOf(row, card());
11751
+ most = giveRoom(Math.min(room.left, room.right) - 1, GIVE);
11752
+ }, valueAt = (x) => f().min + clamp((x - rect.left) / rect.width, 0, 1) * span();
11753
+ track.addEventListener("pointerdown", (event) => {
11754
+ if (event.button !== 0 || event.shiftKey || event.metaKey)
11755
+ return;
11756
+ if (event.target.closest(".knobs-cell-edit,.knobs-accessory"))
11757
+ return;
11758
+ event.preventDefault(), track.setPointerCapture(event.pointerId), rect = track.getBoundingClientRect();
11759
+ let x = event.clientX - rect.left, [ax, bx] = [ratio(shown[0]) * rect.width, ratio(shown[1]) * rect.width];
11760
+ if (grab = x > ax + 8 && x < bx - 8 && bx - ax > 24 ? 2 : Math.abs(x - ax) <= Math.abs(x - bx) && !(x > bx) ? 0 : 1, ax === bx)
11761
+ grab = x < ax ? 0 : 1;
11762
+ if (grab !== 2)
11763
+ lastEnd = grab;
11764
+ if (thumbs[grab === 2 ? lastEnd : grab].focus({
11765
+ preventScroll: !0,
11766
+ focusVisible: !1
11767
+ }), lastX = event.clientX, startValue = shown, moved = 0, fit(), hand = {
11768
+ at: grab === 2 || event.altKey ? ratio(shown[grab === 2 ? 0 : grab]) * rect.width : clamp(x, 0, rect.width),
11769
+ give: 0
11770
+ }, dragging = !0, row.classList.add("knobs-active"), g.start(), grab !== 2 && !event.altKey)
11771
+ g.set(withEnd(shown, grab, valueAt(event.clientX)));
11772
+ c.wake();
11773
+ }), track.addEventListener("pointermove", (event) => {
11774
+ if (!dragging || !rect)
11775
+ return;
11776
+ let rate = precisionRate(offRow(event.clientY, rect.top, rect.bottom)) * (event.altKey ? 0.1 : 1), dx = (event.clientX - lastX) * rate;
11777
+ if (lastX = event.clientX, grab === 2) {
11778
+ moved += dx;
11779
+ let width = startValue[1] - startValue[0], by = moved / rect.width * span(), lo = clamp(quant(startValue[0] + by, event.altKey), f().min, f().max - width);
11780
+ g.set([lo, Number((lo + width).toPrecision(12))]);
11781
+ return;
11782
+ }
11783
+ if (hand = band(hand, dx, 0, rect.width, most), g.set(withEnd(c.get(), grab, f().min + hand.at / rect.width * span(), event.altKey)), hand.give !== give.give)
11784
+ give.hold(hand.give);
11785
+ });
11786
+ let release = () => {
11787
+ if (!dragging)
11788
+ return;
11789
+ dragging = !1, rect = void 0, give.release(), row.classList.remove("knobs-active"), c.wake();
11790
+ };
11791
+ track.addEventListener("pointerup", release), track.addEventListener("pointercancel", release);
11792
+ let panAt;
11793
+ onPan(track, {
11794
+ accept: () => !dragging,
11795
+ move(dx) {
11796
+ let w = track.clientWidth;
11797
+ if (!w)
11798
+ return;
11799
+ if (panAt === void 0)
11800
+ g.start(), panAt = { at: (shown[lastEnd] - f().min) / span() * w, give: 0 }, fit(), dragging = !0, row.classList.add("knobs-active");
11801
+ if (panAt = band(panAt, dx, 0, w, most), g.set(withEnd(c.get(), lastEnd, f().min + panAt.at / w * span())), panAt.give !== give.give)
11802
+ give.hold(panAt.give);
11803
+ },
11804
+ lift: () => give.release(),
11805
+ end() {
11806
+ panAt = void 0, give.release(), dragging = !1, row.classList.remove("knobs-active"), c.wake();
11807
+ },
11808
+ step(direction) {
11809
+ g.start(), g.set(withEnd(shown, lastEnd, shown[lastEnd] + direction * f().step));
11810
+ }
11811
+ });
11812
+ let editors = thumbs.map((th, i) => inlineEdit(c.doc, row, `${c.display} ${i === 0 ? "low" : "high"} value`, (v) => {
11813
+ g.start(), g.set(withEnd(c.get(), i, v));
11814
+ }, () => th));
11815
+ thumbs.forEach((th, i) => {
11816
+ let end = i;
11817
+ th.addEventListener("focus", () => {
11818
+ lastEnd = end, row.dataset.endFocus = th.dataset.end, c.wake();
11819
+ }), th.addEventListener("blur", () => {
11820
+ if (row.dataset.endFocus === th.dataset.end)
11821
+ delete row.dataset.endFocus;
11822
+ }), th.addEventListener("keydown", (event) => {
11823
+ if (!plainKey(event))
11824
+ return;
11825
+ let dir = arrowOf(event), char = typed(event);
11826
+ if (dir) {
11827
+ event.preventDefault(), event.stopPropagation();
11828
+ let by = f().step * (event.shiftKey ? bigOf(event) : event.altKey ? 0.1 : 1);
11829
+ g.start(), g.set(withEnd(shown, end, shown[end] + dir * by, event.altKey));
11830
+ } else if (event.key === "Home" || event.key === "End")
11831
+ event.preventDefault(), g.start(), g.set(withEnd(shown, end, event.key === "Home" ? f().min : f().max));
11832
+ else if (event.key === "Enter" && keyPrefs(th).edit)
11833
+ event.preventDefault(), event.stopPropagation(), editors[end].start(mixed ? "" : fmt(shown[end], places()));
11834
+ else if (char && keyPrefs(th).edit)
11835
+ event.preventDefault(), event.stopPropagation(), editors[end].start(char, !1);
11836
+ });
11837
+ });
11838
+ let first = !0, gpu = {
11839
+ kind: "range",
11840
+ key: c.key,
11841
+ display: c.display,
11842
+ row,
11843
+ labelEl: label,
11844
+ track,
11845
+ read,
11846
+ thumbs,
11847
+ ratios: () => [ratio(shown[0]), ratio(shown[1])],
11848
+ dragging: () => dragging,
11849
+ editing: () => editors.some((e) => e.open),
11850
+ wave: c.wave,
11851
+ isModified: c.isModified
11852
+ };
11853
+ return {
11854
+ row,
11855
+ labelEl: label,
11856
+ input: thumbs[0],
11857
+ stops: () => thumbs,
11858
+ update(value, delay, instant, isMixed = !1) {
11859
+ shown = value, mixed = isMixed, paint(first || Boolean(instant) || dragging, delay), first = !1;
11860
+ },
11861
+ dispose() {
11862
+ cancelAnimationFrame(smearFrame), rolling.dispose();
11863
+ },
11864
+ tracks: [],
11865
+ gpu: [gpu]
11866
+ };
11867
+ }
11868
+ function angleRow(c) {
11869
+ let { row, label, el } = shell(c, "angle"), f = () => c.field, control = el("span", "knobs-angle");
11870
+ control.tabIndex = 0, control.setAttribute("role", "slider"), control.setAttribute("aria-label", c.display);
11871
+ let read = el("span", "knobs-angle-read"), dial = el("span", "knobs-dial"), hand = el("i", "knobs-dial-hand");
11872
+ dial.append(hand), control.append(read, dial), row.append(control);
11873
+ let value = c.get(), turn = value, mixed = !1, dragging = !1, g = gesture(c), places = () => decimals(f().step), snapTo = (v, coarse) => {
11874
+ let step = coarse ? Math.max(15, f().step) : f().step, q = Number((Math.round(v / step) * step).toPrecision(12));
11875
+ return f().wrap ? wrapAngle2(q, f().min, f().max) : clamp(q, f().min, f().max);
11876
+ }, rolling = rollingReadout(read), paint = (instant, delay = 0) => {
11877
+ let t = mixed ? "—" : `${fmt(value, places())}°`;
11878
+ if (rolling.show(mixed ? ["—"] : [{ value, decimals: places() }, "°"], instant || row.classList.contains("knobs-active") ? 0 : rollMs(row), delay), control.setAttribute("aria-valuenow", String(value)), control.setAttribute("aria-valuetext", t), instant || reducedMotion4(c.doc))
11879
+ hand.style.transition = "none";
11880
+ if (hand.style.rotate = `${turn.toFixed(3)}deg`, instant)
11881
+ hand.offsetWidth, hand.style.transition = "";
11882
+ }, follow = (next) => {
11883
+ let d = next - value;
11884
+ if (f().wrap) {
11885
+ let s = f().max - f().min;
11886
+ d = ((d + s / 2) % s + s) % s - s / 2;
11887
+ }
11888
+ turn += d, value = next;
11889
+ }, mode = "scrub", startX = 0, startValue = 0, carried = 0, rowRect, centre = [0, 0], moved = !1, angleAt = (x, y) => Math.atan2(x - centre[0], -(y - centre[1])) * 180 / Math.PI;
11890
+ control.addEventListener("pointerdown", (event) => {
11891
+ if (event.button !== 0 || event.shiftKey || event.metaKey)
11892
+ return;
11893
+ if (event.target.closest(".knobs-cell-edit"))
11894
+ return;
11895
+ event.preventDefault(), control.setPointerCapture(event.pointerId), control.focus({ preventScroll: !0, focusVisible: !1 });
11896
+ let r = dial.getBoundingClientRect();
11897
+ if (centre = [r.left + r.width / 2, r.top + r.height / 2], mode = event.target.closest(".knobs-dial") ? "dial" : "scrub", startX = event.clientX, startValue = value, carried = 0, moved = !1, dragging = !0, g.start(), mode === "dial")
11898
+ moved = !0, row.classList.add("knobs-active"), g.set(snapTo(angleAt(event.clientX, event.clientY), !1));
11899
+ c.wake();
11900
+ }), control.addEventListener("pointermove", (event) => {
11901
+ if (!dragging)
11902
+ return;
11903
+ if (mode === "dial") {
11904
+ let a = angleAt(event.clientX, event.clientY);
11905
+ g.set(snapTo(f().wrap ? a : clamp(a, f().min, f().max), event.shiftKey));
11906
+ return;
11907
+ }
11908
+ if (!moved && Math.abs(event.clientX - startX) < 3)
11909
+ return;
11910
+ if (!moved)
11911
+ moved = !0, startX = event.clientX, rowRect = row.getBoundingClientRect(), row.classList.add("knobs-active");
11912
+ let r = rowRect, rate = precisionRate(offRow(event.clientY, r.top, r.bottom)) * (event.altKey ? 0.1 : 1);
11913
+ carried += (event.clientX - startX) * rate, startX = event.clientX, g.set(snapTo(startValue + carried * f().step, event.shiftKey));
11914
+ });
11915
+ let editor = inlineEdit(c.doc, row, `${c.display} value`, (v) => {
11916
+ g.start(), g.set(snapTo(v, !1));
11917
+ }, () => control), release = () => {
11918
+ if (!dragging)
11919
+ return;
11920
+ if (dragging = !1, row.classList.remove("knobs-active"), !moved)
11921
+ editor.start(mixed ? "" : fmt(value, places()));
11922
+ c.wake();
11923
+ };
11924
+ control.addEventListener("pointerup", release), control.addEventListener("pointercancel", release);
11925
+ let panAt, give = textGive(read, () => c.wake());
11926
+ onPan(control, {
11927
+ accept: () => !dragging,
11928
+ move(dx) {
11929
+ let step = f().step;
11930
+ if (panAt === void 0)
11931
+ g.start(), panAt = { at: value / step, give: 0 }, row.classList.add("knobs-active");
11932
+ if (panAt = f().wrap ? { at: panAt.at + dx, give: 0 } : band(panAt, dx, f().min / step, f().max / step), g.set(snapTo(panAt.at * step, !1)), panAt.give !== give.give)
11933
+ give.hold(panAt.give);
11934
+ },
11935
+ lift: () => give.release(),
11936
+ end() {
11937
+ panAt = void 0, give.release(), row.classList.remove("knobs-active"), c.wake();
11938
+ },
11939
+ step(direction) {
11940
+ g.start(), g.set(snapTo(value + direction * f().step, !1));
11941
+ }
11942
+ }), control.addEventListener("keydown", (event) => {
11943
+ if (event.target !== control || !plainKey(event))
11944
+ return;
11945
+ let dir = arrowOf(event), char = typed(event);
11946
+ if (dir)
11947
+ if (event.preventDefault(), event.stopPropagation(), g.start(), event.shiftKey) {
11948
+ let s = Math.max(15, f().step), k = dir > 0 ? Math.floor(value / s + 0.000000001) + 1 : Math.ceil(value / s - 0.000000001) - 1;
11949
+ g.set(snapTo(k * s, !1));
11950
+ } else
11951
+ g.set(snapTo(value + dir * f().step * (event.altKey ? 0.1 : 1), !1));
11952
+ else if (event.key === "Home" || event.key === "End")
11953
+ event.preventDefault(), g.start(), g.set(snapTo(event.key === "Home" ? f().min : f().max - (f().wrap ? f().step : 0), !1));
11954
+ else if (event.key === "Enter" && keyPrefs(control).edit)
11955
+ event.preventDefault(), event.stopPropagation(), editor.start(mixed ? "" : fmt(value, places()));
11956
+ else if (char && keyPrefs(control).edit)
11957
+ event.preventDefault(), event.stopPropagation(), editor.start(char, !1);
11958
+ });
11959
+ let first = !0, gpu = {
11960
+ kind: "angle",
11961
+ key: c.key,
11962
+ display: c.display,
11963
+ row,
11964
+ labelEl: label,
11965
+ control,
11966
+ read,
11967
+ dial,
11968
+ turn: () => mixed ? 0 : turn,
11969
+ dragging: () => dragging,
11970
+ editing: () => editor.open,
11971
+ wave: c.wave,
11972
+ isModified: c.isModified
11973
+ };
11974
+ return {
11975
+ row,
11976
+ labelEl: label,
11977
+ input: control,
11978
+ stops: () => [control],
11979
+ update(next, delay, instant, isMixed = !1) {
11980
+ if (mixed = isMixed, first || instant)
11981
+ value = turn = next;
11982
+ else
11983
+ follow(next);
11984
+ paint(first || Boolean(instant) || dragging, delay), first = !1;
11985
+ },
11986
+ dispose() {
11987
+ rolling.dispose();
11988
+ },
11989
+ tracks: [],
11990
+ gpu: [gpu]
11991
+ };
11992
+ }
11993
+ var SEED_STEP = 12, DICE_ROLL_MS = 620;
11994
+ function seedRow(c) {
11995
+ let { row, label, el } = shell(c, "seed"), f = () => c.field, control = el("span", "knobs-seed");
11996
+ control.tabIndex = 0, control.setAttribute("role", "spinbutton"), control.setAttribute("aria-label", c.display);
11997
+ let read = el("span", "knobs-seed-read");
11998
+ control.append(read);
11999
+ let reel = el("span", "knobs-seed-digits");
12000
+ read.append(reel);
12001
+ let roller = it(reel, {
12002
+ value: c.get(),
12003
+ format: { useGrouping: !1, maximumFractionDigits: 0 },
12004
+ duration: 1,
12005
+ motionBlur: !0
12006
+ }), places = [], initialized = !1, diced = !1, roll = el("button", "knobs-seed-roll");
12007
+ roll.type = "button", roll.tabIndex = -1, roll.setAttribute("aria-label", `Roll ${c.display}`);
12008
+ let die = createHeaderDie(c.doc, () => c.wake(), GROUP_DIE);
12009
+ roll.append(die.node), row.append(control, roll);
12010
+ let value = c.get(), mixed = !1, g = gesture(c), set = (v) => {
12011
+ stopCarry(), g.start(), g.set(Math.round(clamp(v, f().min, f().max)));
12012
+ }, reroll = () => {
12013
+ let { min, max } = f(), next = value;
12014
+ for (let i = 0;i < 8 && next === value; i++)
12015
+ next = min + Math.floor(Math.random() * (max - min + 1));
12016
+ die.roll(), diced = !0, set(next), diced = !1;
12017
+ };
12018
+ roll.onclick = () => reroll();
12019
+ let editor = inlineEdit(c.doc, row, `${c.display} value`, set, () => control), give = textGive(read, () => c.wake()), recent = moves(), panned, carry = [], stopCarry = () => {
12020
+ for (let t of carry)
12021
+ clearTimeout(t);
12022
+ carry = [];
12023
+ }, scrubBy = (steps) => {
12024
+ let { min, max } = f(), next = clamp(value + steps, min, max);
12025
+ if (next === value)
12026
+ return !1;
12027
+ let dir = next > value ? 1 : -1;
12028
+ for (let i = 0;i < Math.min(3, Math.abs(next - value)); i++)
12029
+ die.turn(dir);
12030
+ return g.set(next), tick(control), !0;
12031
+ }, letGo = () => {
12032
+ panned = void 0, give.release(), row.classList.remove("knobs-active"), c.wake();
12033
+ };
12034
+ onPan(row, {
12035
+ accept: () => !editor.open,
12036
+ move(dx) {
12037
+ if (!panned)
12038
+ stopCarry(), g.start(), recent.reset(), panned = { travel: 0, give: 0 }, row.classList.add("knobs-active"), c.wake();
12039
+ recent.add(dx);
12040
+ let { min, max } = f(), { next, steps } = stepBand(panned, dx, SEED_STEP, [value - min, max - value]);
12041
+ if (panned = next, steps)
12042
+ scrubBy(steps);
12043
+ if (next.give !== give.give)
12044
+ give.hold(next.give);
12045
+ },
12046
+ lift() {
12047
+ if (!panned)
12048
+ return;
12049
+ let n = carrySteps(recent.speed()), dir = Math.sign(recent.speed());
12050
+ letGo();
12051
+ for (let i = 0;i < n; i++)
12052
+ carry.push(setTimeout(() => {
12053
+ if (!scrubBy(dir))
12054
+ stopCarry();
12055
+ }, carryAt(i)));
12056
+ },
12057
+ end: letGo,
12058
+ step(direction) {
12059
+ stopCarry(), set(value + direction);
12060
+ }
12061
+ }), control.addEventListener("click", () => {
12062
+ if (!editor.open)
12063
+ editor.start(mixed ? "" : String(value));
12064
+ }), control.addEventListener("keydown", (event) => {
12065
+ if (event.target !== control || !plainKey(event))
12066
+ return;
12067
+ let dir = arrowOf(event), char = typed(event);
12068
+ if (dir)
12069
+ event.preventDefault(), event.stopPropagation(), set(value + dir * bigOf(event));
12070
+ else if (event.key === " ")
12071
+ event.preventDefault(), event.stopPropagation(), reroll();
12072
+ else if (event.key === "Enter" && keyPrefs(control).edit)
12073
+ event.preventDefault(), event.stopPropagation(), editor.start(mixed ? "" : String(value));
12074
+ else if (char && keyPrefs(control).edit)
12075
+ event.preventDefault(), event.stopPropagation(), editor.start(char, !1);
12076
+ });
12077
+ let paint = (previous, instant) => {
12078
+ read.toggleAttribute("data-mixed", mixed), control.setAttribute("aria-valuenow", String(value));
12079
+ let ms = !initialized || instant ? 0 : diced ? DICE_ROLL_MS : rollMs(row);
12080
+ initialized = !0, syncPlaces(places, String(value), diced ? 1 : Math.sign(value - previous), ms, 0);
12081
+ let dom = ms > 0 && row.closest('[data-gpu-active="true"]') === null;
12082
+ if (roller.update({
12083
+ value,
12084
+ animated: !0,
12085
+ duration: dom ? ms : 1,
12086
+ motionBlur: dom,
12087
+ direction: diced ? "up" : "auto"
12088
+ }), ms > 0)
12089
+ c.wake();
12090
+ }, gpu = {
12091
+ kind: "seed",
12092
+ key: c.key,
12093
+ display: c.display,
12094
+ row,
12095
+ labelEl: label,
12096
+ control,
12097
+ read,
12098
+ places,
12099
+ mixed: () => mixed,
12100
+ die: { button: roll, die },
12101
+ editing: () => editor.open,
12102
+ wave: c.wave,
12103
+ isModified: c.isModified
12104
+ };
12105
+ return {
12106
+ row,
12107
+ labelEl: label,
12108
+ input: control,
12109
+ stops: () => [control],
12110
+ update(next, _delay, instant, isMixed = !1) {
12111
+ let previous = value;
12112
+ value = next, mixed = isMixed, paint(previous, Boolean(instant));
12113
+ },
12114
+ dispose: () => {
12115
+ stopCarry(), die.dispose(), roller.destroy();
12116
+ },
12117
+ tracks: [],
12118
+ gpu: [gpu]
12119
+ };
12120
+ }
12121
+ var CAP_MORPH = springLinear(SPRINGS2.snappy);
12122
+ function keyCap(o) {
12123
+ let chip = o.doc.createElement("button");
12124
+ chip.className = "knobs-keys", chip.type = "button";
12125
+ let text = o.doc.createElement("span");
12126
+ text.className = "knobs-keys-text", chip.append(text);
12127
+ let value = "", capturing = !1, preview = "", morph, write = (shown) => {
12128
+ let glide = chip.isConnected && chip.offsetWidth > 0 && !chip.closest('[data-gpu-active="true"]') && !reducedMotion4(o.doc), from = glide ? chip.getBoundingClientRect().width : 0;
12129
+ if (morph?.cancel(), text.textContent = shown, !glide)
12130
+ return;
12131
+ let to = chip.getBoundingClientRect().width;
12132
+ if (text.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 150, easing: "ease-out" }), Math.abs(to - from) < 0.5)
12133
+ return;
12134
+ chip.dataset.morph = "", morph = chip.animate([{ width: `${from}px` }, { width: `${to}px` }], {
12135
+ duration: CAP_MORPH.ms,
12136
+ easing: CAP_MORPH.easing
12137
+ }), morph.onfinish = () => delete chip.dataset.morph, morph.oncancel = () => delete chip.dataset.morph;
12138
+ }, paint = () => {
12139
+ let shown = capturing ? preview || "Type keys…" : value === void 0 ? "—" : value ? comboLabel2(value) : o.empty;
12140
+ if (text.textContent !== shown)
12141
+ write(shown);
12142
+ chip.toggleAttribute("data-empty", !capturing && value === ""), chip.setAttribute("aria-pressed", String(capturing)), chip.setAttribute("aria-label", `${o.name()}: ${capturing ? "press a key combination" : value ? comboLabel2(value) : "none"}`), o.wake();
12143
+ }, capture = (on) => {
12144
+ if (capturing === on)
12145
+ return;
12146
+ if (on && o.row.closest("[data-disabled],[data-readonly]"))
12147
+ return;
12148
+ capturing = on, preview = "", o.row.classList.toggle("knobs-active", on), paint();
12149
+ }, held = (event) => {
12150
+ let mods = heldMods(event);
12151
+ preview = mods.length ? comboLabel2(`${mods.join("+")}+…`) : "", paint();
12152
+ };
12153
+ return chip.onclick = () => capture(!capturing), chip.addEventListener("keydown", (event) => {
12154
+ if (event.isComposing)
12155
+ return;
12156
+ if (!capturing) {
12157
+ if ((event.key === "Enter" || event.key === " ") && plainKey(event) && !event.altKey)
12158
+ event.preventDefault(), event.stopPropagation(), capture(!0);
12159
+ return;
12160
+ }
12161
+ if (event.key === "Tab")
12162
+ return capture(!1);
12163
+ event.preventDefault(), event.stopPropagation();
12164
+ let mods = heldMods(event);
12165
+ if (event.key === "Escape" && !mods.length)
12166
+ return capture(!1);
12167
+ let take = (combo) => {
12168
+ value = combo, capture(!1), o.set(combo);
12169
+ };
12170
+ if ((event.key === "Backspace" || event.key === "Delete") && !mods.length)
12171
+ return take("");
12172
+ let combo = comboFromEvent2(event);
12173
+ if (!combo)
12174
+ return held(event);
12175
+ take(combo);
12176
+ }), chip.addEventListener("keyup", (event) => {
12177
+ if (capturing)
12178
+ held(event);
12179
+ }), chip.addEventListener("blur", () => capture(!1)), paint(), {
12180
+ chip,
12181
+ text,
12182
+ capturing: () => capturing,
12183
+ show(combo) {
12184
+ value = combo, paint();
12185
+ }
12186
+ };
12142
12187
  }
12143
- function syncFrame(root) {
12144
- if (!root.isConnected)
12145
- return;
12146
- if (root.ownerDocument.defaultView?.getComputedStyle(root).getPropertyValue("--k-frame").trim() === "flat")
12147
- root.dataset.frame = "flat";
12148
- else
12149
- delete root.dataset.frame;
12188
+ function keybindRow(c) {
12189
+ let { row, label } = shell(c, "keybind"), cap = keyCap({
12190
+ doc: c.doc,
12191
+ row,
12192
+ empty: "None",
12193
+ name: () => c.display,
12194
+ set: (combo) => c.set(combo),
12195
+ wake: c.wake
12196
+ }), { chip, text } = cap;
12197
+ row.append(chip), cap.show(c.get());
12198
+ let gpu = {
12199
+ kind: "keybind",
12200
+ key: c.key,
12201
+ display: c.display,
12202
+ row,
12203
+ labelEl: label,
12204
+ chip,
12205
+ text,
12206
+ capturing: cap.capturing,
12207
+ wave: c.wave,
12208
+ isModified: c.isModified
12209
+ };
12210
+ return {
12211
+ row,
12212
+ labelEl: label,
12213
+ input: chip,
12214
+ stops: () => [chip],
12215
+ update(next, _delay, _instant, isMixed = !1) {
12216
+ cap.show(isMixed ? void 0 : next);
12217
+ },
12218
+ dispose() {},
12219
+ tracks: [],
12220
+ gpu: [gpu]
12221
+ };
12150
12222
  }
12151
- var lookDefaults = { current: {} };
12152
- function frameLoop(win, step) {
12153
- let raf = 0, last = 0, tick = (now) => {
12154
- let dt = last ? Math.min(0.064, (now - last) / 1000) : 0.016666666666666666;
12155
- if (last = now, raf = step(dt) ? win.requestAnimationFrame(tick) : 0, !raf)
12156
- last = 0;
12223
+ function textRow(c) {
12224
+ let { row, label, el } = shell(c, "text"), multiline = Boolean(c.field.multiline);
12225
+ row.toggleAttribute("data-multiline", multiline);
12226
+ let input = multiline ? Object.assign(el("textarea", "knobs-text"), { rows: 2 }) : Object.assign(el("input", "knobs-text"), { type: "text" });
12227
+ input.spellcheck = !1, input.autocomplete = "off", input.placeholder = c.field.placeholder ?? "", input.setAttribute("aria-label", c.display), input.value = c.get(), row.append(input);
12228
+ let typed = !1;
12229
+ input.addEventListener("focus", () => typed = !1), input.addEventListener("input", () => {
12230
+ c.set(input.value, { coalesce: typed }), typed = !0;
12231
+ }), input.addEventListener("keydown", (e) => {
12232
+ let event = e;
12233
+ if (event.isComposing)
12234
+ return;
12235
+ if (!(event.key === "Escape" || event.key === "Enter" && (!multiline || event.metaKey || event.ctrlKey)))
12236
+ return;
12237
+ event.preventDefault(), event.stopPropagation(), input.blur();
12238
+ });
12239
+ let gpu = {
12240
+ kind: "text",
12241
+ key: c.key,
12242
+ display: c.display,
12243
+ row,
12244
+ labelEl: label,
12245
+ wave: c.wave,
12246
+ isModified: c.isModified
12247
+ };
12248
+ return {
12249
+ row,
12250
+ labelEl: label,
12251
+ input,
12252
+ stops: () => [input],
12253
+ update(next, _delay, _instant, isMixed = !1) {
12254
+ input.placeholder = isMixed ? "—" : c.field.placeholder ?? "";
12255
+ let shown = isMixed ? "" : next;
12256
+ if (input.value !== shown)
12257
+ input.value = shown;
12258
+ },
12259
+ dispose() {},
12260
+ tracks: [],
12261
+ gpu: [gpu]
12262
+ };
12263
+ }
12264
+ function naturalWidth(b) {
12265
+ let cs = getComputedStyle(b), w = (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0), parts = [...b.children];
12266
+ for (let part of parts) {
12267
+ let ps = getComputedStyle(part), margin = parseFloat(ps.marginRight) || 0;
12268
+ w += part instanceof HTMLElement ? part.scrollWidth + margin : parseFloat(ps.width) || 0;
12269
+ }
12270
+ return Math.ceil(w + (parseFloat(cs.columnGap) || 0) * Math.max(0, parts.length - 1));
12271
+ }
12272
+ function toolbarRow(c, press, keyOf = toolbarShortcut2) {
12273
+ let el = make(c.doc), row = el("div", "knobs-row knobs-toolbar");
12274
+ row.dataset.field = c.key, row.setAttribute("role", "toolbar"), row.setAttribute("aria-label", c.display);
12275
+ let field = c.field, buttons = [], last = 0, keys = "", untips = [], keysNow = () => field.items.map((item) => keyOf(item)?.key ?? "").join("\x00"), showIcons = (on) => {
12276
+ if (row.hasAttribute("data-icons") === on && untips.length === (on ? buttons.length : 0))
12277
+ return;
12278
+ row.toggleAttribute("data-icons", on);
12279
+ for (let off of untips)
12280
+ off();
12281
+ untips = on ? field.items.map((item, i) => {
12282
+ let key = keyOf(item);
12283
+ return tooltip(buttons[i], item.label, key ? { shortcut: key.key } : {});
12284
+ }) : [];
12285
+ }, layout = () => {
12286
+ let cs = getComputedStyle(row), avail = row.clientWidth - (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0);
12287
+ if (avail <= 0 || !buttons.length)
12288
+ return;
12289
+ let gap = parseFloat(cs.columnGap) || 0, iconed = field.items.every((item) => item.icon), icons = iconed && field.labels === !1;
12290
+ if (iconed && field.labels === void 0) {
12291
+ let was = row.hasAttribute("data-icons");
12292
+ if (was)
12293
+ row.removeAttribute("data-icons");
12294
+ let widest = Math.max(...buttons.map(naturalWidth));
12295
+ if (was)
12296
+ row.setAttribute("data-icons", "");
12297
+ icons = toolbarColumns(buttons.length, widest, avail, gap) < buttons.length;
12298
+ }
12299
+ showIcons(icons);
12300
+ let cols = String(icons ? buttons.length : toolbarColumns(buttons.length, Math.max(...buttons.map(naturalWidth)), avail, gap));
12301
+ if (row.style.getPropertyValue("--k-tool-cols") !== cols)
12302
+ row.style.setProperty("--k-tool-cols", cols), c.wake();
12303
+ }, sizes = new ResizeObserver(layout);
12304
+ sizes.observe(row), c.doc.fonts?.ready.then(() => row.isConnected && layout());
12305
+ let gpu = {
12306
+ kind: "toolbar",
12307
+ key: c.key,
12308
+ display: c.display,
12309
+ row,
12310
+ items: [],
12311
+ wave: c.wave,
12312
+ isModified: () => !1
12313
+ }, build = () => {
12314
+ let items = [];
12315
+ buttons = field.items.map((item, i) => {
12316
+ let b = el("button", "knobs-tool");
12317
+ b.type = "button", b.tabIndex = -1, b.disabled = Boolean(item.disabled), b.dataset.item = item.id ?? item.label, b.setAttribute("aria-label", item.label);
12318
+ let iconEl;
12319
+ if (item.icon)
12320
+ iconEl = iconSvg(c.doc, item.icon), b.append(iconEl);
12321
+ let name = el("span", "knobs-tool-label", item.label);
12322
+ b.append(name);
12323
+ let chip, key = keyOf(item);
12324
+ if (key) {
12325
+ if (chip = el("kbd", "knobs-tool-key", comboLabel2(key.key)), b.append(chip), key.scope !== "none")
12326
+ b.setAttribute("aria-keyshortcuts", key.key);
12327
+ }
12328
+ return b.onclick = () => {
12329
+ last = i, press(item);
12330
+ }, b.addEventListener("focus", () => last = i), items.push({
12331
+ button: b,
12332
+ ...item.icon ? { icon: item.icon } : {},
12333
+ ...iconEl ? { iconEl } : {},
12334
+ label: name,
12335
+ ...chip ? { chip } : {}
12336
+ }), b;
12337
+ }), row.replaceChildren(...buttons), gpu.items = items, keys = keysNow();
12338
+ for (let off of untips)
12339
+ off();
12340
+ if (untips = [], row.isConnected)
12341
+ layout();
12157
12342
  };
12343
+ build(), row.addEventListener("keydown", (event) => {
12344
+ let at = buttons.indexOf(event.target), dir = arrowOf(event);
12345
+ if (at < 0 || !dir || !plainKey(event))
12346
+ return;
12347
+ event.preventDefault(), event.stopPropagation();
12348
+ let live = buttons.filter((b) => !b.disabled), i = live.indexOf(buttons[at]);
12349
+ live[(i + dir + live.length) % live.length]?.focus();
12350
+ });
12351
+ let first = () => buttons.find((b) => !b.disabled) ?? buttons[0];
12158
12352
  return {
12159
- wake() {
12160
- if (!raf)
12161
- raf = win.requestAnimationFrame(tick);
12353
+ row,
12354
+ labelEl: row,
12355
+ get input() {
12356
+ let b = buttons[last];
12357
+ return b && !b.disabled ? b : first();
12162
12358
  },
12163
- get running() {
12164
- return raf !== 0;
12359
+ get field() {
12360
+ return field;
12165
12361
  },
12166
- stop() {
12167
- if (raf)
12168
- win.cancelAnimationFrame(raf);
12169
- raf = 0, last = 0;
12362
+ stops() {
12363
+ let b = buttons[last];
12364
+ return [b && !b.disabled ? b : first()];
12365
+ },
12366
+ update() {},
12367
+ refresh() {
12368
+ if (keysNow() === keys)
12369
+ return;
12370
+ let focused = buttons.indexOf(c.doc.activeElement);
12371
+ if (build(), focused >= 0)
12372
+ buttons[focused]?.focus({ preventScroll: !0 });
12373
+ c.wake();
12374
+ },
12375
+ match(event, global) {
12376
+ return field.items.find((item) => {
12377
+ let key = keyOf(item);
12378
+ return key !== void 0 && !item.disabled && key.scope === (global ? "page" : "panel") && matchesCombo2(key.key, event);
12379
+ });
12380
+ },
12381
+ press(item) {
12382
+ let i = field.items.indexOf(item), b = buttons[i];
12383
+ if (b && !reducedMotion4(c.doc))
12384
+ b.classList.add("knobs-pressed"), setTimeout(() => b.classList.remove("knobs-pressed"), 140), c.wake();
12385
+ press(item);
12386
+ },
12387
+ swap(next) {
12388
+ field = next, build(), c.wake();
12389
+ },
12390
+ dispose() {
12391
+ sizes.disconnect();
12392
+ for (let off of untips)
12393
+ off();
12394
+ },
12395
+ tracks: [],
12396
+ gpu: [gpu]
12397
+ };
12398
+ }
12399
+
12400
+ // src/panel/search.ts
12401
+ function createSearch(doc, options) {
12402
+ let element = doc.createElement("div");
12403
+ element.className = "knobs-search";
12404
+ let field = doc.createElement("label");
12405
+ field.className = "knobs-search-field", field.append(iconSvg(doc, "search"));
12406
+ let input = doc.createElement("input");
12407
+ input.type = "search", input.placeholder = options.placeholder, input.spellcheck = !1, input.autocomplete = "off", input.setAttribute("aria-label", "Search controls"), input.setAttribute("aria-keyshortcuts", "/"), field.append(input), element.append(field);
12408
+ let on = /* @__PURE__ */ new Set, chips = options.chips.map((spec) => {
12409
+ let b = doc.createElement("button");
12410
+ b.type = "button", b.className = "knobs-search-chip", b.tabIndex = -1, b.dataset.chip = spec.id, b.setAttribute("aria-pressed", "false");
12411
+ let name = doc.createElement("span");
12412
+ if (name.textContent = spec.label, b.append(name), spec.count) {
12413
+ let n = doc.createElement("span");
12414
+ n.className = "knobs-search-count", b.append(n);
12415
+ }
12416
+ return b.onclick = () => {
12417
+ if (on.has(spec.id))
12418
+ on.delete(spec.id);
12419
+ else
12420
+ on.add(spec.id);
12421
+ b.setAttribute("aria-pressed", String(on.has(spec.id))), options.change();
12422
+ }, element.append(b), b;
12423
+ });
12424
+ return input.addEventListener("input", () => options.change()), {
12425
+ element,
12426
+ input,
12427
+ chips,
12428
+ query: () => input.value.trim().toLowerCase(),
12429
+ active: () => on,
12430
+ filtering: () => input.value.trim() !== "" || on.size > 0,
12431
+ counts(counts) {
12432
+ for (let b of chips) {
12433
+ let n = b.querySelector(".knobs-search-count");
12434
+ if (!n)
12435
+ continue;
12436
+ let text = String(counts.get(b.dataset.chip) ?? 0);
12437
+ if (n.textContent !== text)
12438
+ n.textContent = text;
12439
+ let none = text === "0" && !on.has(b.dataset.chip);
12440
+ if (b.hasAttribute("data-none") !== none)
12441
+ b.toggleAttribute("data-none", none);
12442
+ }
12443
+ },
12444
+ set(query, next) {
12445
+ let changed = !1;
12446
+ if (query !== void 0 && input.value !== query)
12447
+ input.value = query, changed = !0;
12448
+ if (next) {
12449
+ let want = new Set(next.filter((id) => chips.some((b) => b.dataset.chip === id)));
12450
+ if (want.size !== on.size || [...want].some((id) => !on.has(id))) {
12451
+ on.clear();
12452
+ for (let id of want)
12453
+ on.add(id);
12454
+ for (let b of chips)
12455
+ b.setAttribute("aria-pressed", String(on.has(b.dataset.chip)));
12456
+ changed = !0;
12457
+ }
12458
+ }
12459
+ if (changed)
12460
+ options.change();
12461
+ },
12462
+ clear() {
12463
+ let had = input.value !== "" || on.size > 0;
12464
+ input.value = "", on.clear();
12465
+ for (let b of chips)
12466
+ b.setAttribute("aria-pressed", "false");
12467
+ if (had)
12468
+ options.change();
12469
+ },
12470
+ focus() {
12471
+ input.focus({ preventScroll: !0 }), input.select();
12170
12472
  }
12171
12473
  };
12172
12474
  }
12173
- 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>`, ICONS = {
12174
- caret: svg('<path d="M6 3.5 10.5 8 6 12.5"/>'),
12175
- chevronDown: svg('<path d="M3.5 6 8 10.5 12.5 6"/>'),
12176
- 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"/>'),
12177
- 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"/>'),
12178
- 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"/>'),
12179
- 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"/>')
12180
- };
12475
+ function matchSpan(text, query) {
12476
+ if (!query)
12477
+ return;
12478
+ let at = text.toLowerCase().indexOf(query);
12479
+ return at < 0 ? void 0 : [at, at + query.length];
12480
+ }
12481
+ function markLabel(label, text, span) {
12482
+ let doc = label.ownerDocument, parts = [];
12483
+ if (!span)
12484
+ parts.push(doc.createTextNode(text));
12485
+ else {
12486
+ if (span[0] > 0)
12487
+ parts.push(doc.createTextNode(text.slice(0, span[0])));
12488
+ let mark = doc.createElement("mark");
12489
+ if (mark.className = "knobs-match", mark.textContent = text.slice(span[0], span[1]), parts.push(mark), span[1] < text.length)
12490
+ parts.push(doc.createTextNode(text.slice(span[1])));
12491
+ }
12492
+ let current = [...label.childNodes].filter((c) => c.nodeType === 3 || c instanceof HTMLElement && c.classList.contains("knobs-match"));
12493
+ if (current.length === parts.length && current.every((c, i) => c.nodeName === parts[i].nodeName && c.textContent === parts[i].textContent))
12494
+ return;
12495
+ for (let c of current)
12496
+ c.remove();
12497
+ label.prepend(...parts);
12498
+ }
12181
12499
 
12182
12500
  // src/ui/readout-model.ts
12183
12501
  var SERIES_TOKENS = [
@@ -13454,69 +13772,6 @@ function readoutRow(c) {
13454
13772
  };
13455
13773
  }
13456
13774
 
13457
- // src/ui/layer.ts
13458
- var lookOf = (look) => ({ ...lookDefaults.current, ...look }), roots = /* @__PURE__ */ new WeakMap;
13459
- function layerRoot(doc) {
13460
- let root = roots.get(doc);
13461
- if (root?.isConnected)
13462
- return root;
13463
- return root = doc.createElement("div"), root.className = "knobs-layers", doc.body.append(root), roots.set(doc, root), root;
13464
- }
13465
- function layerElement(doc, cls, look) {
13466
- let el = doc.createElement("div");
13467
- return el.className = cls, layerRoot(doc).append(el), applyLook2(el, lookOf(look)), el;
13468
- }
13469
- function presence(el, o = {}) {
13470
- let win = el.ownerDocument.defaultView, spring = new Spring(0, 1, 1, 0.002), dx = o.dx ?? 0, dy = o.dy ?? 0, gone = [], shown = !1, render = () => {
13471
- let reduced = reducedMotion(win), p = spring.x, q = Math.max(0, Math.min(1, p)), scale = reduced ? 1 : (o.scale ?? 0.94) + (1 - (o.scale ?? 0.94)) * p, blur = reduced ? 0 : (1 - q) * (o.blur ?? 6), t = reduced ? 0 : 1 - p;
13472
- el.style.opacity = q.toFixed(3), el.style.transform = p === 1 ? "" : `translate(${(dx * t).toFixed(2)}px,${(dy * t).toFixed(2)}px) scale(${scale.toFixed(4)})`, el.style.filter = blur > 0.05 ? `blur(${blur.toFixed(2)}px)` : "", el.style.pointerEvents = shown ? "" : "none", o.onFrame?.(p);
13473
- }, loop = frameLoop(win, (dt) => {
13474
- if (spring.step(dt), render(), !spring.resting)
13475
- return !0;
13476
- if (!shown) {
13477
- let done = gone;
13478
- gone = [];
13479
- for (let f of done)
13480
- f();
13481
- o.onGone?.();
13482
- }
13483
- return !1;
13484
- }), tune = (seconds, zeta) => {
13485
- let reduced = reducedMotion(win);
13486
- spring.omega = 2 * Math.PI / (reduced ? FADE.duration : seconds), spring.zeta = reduced ? 1 : zeta;
13487
- };
13488
- return render(), {
13489
- show() {
13490
- shown = !0, gone = [], el.style.pointerEvents = "", tune(o.duration ?? 0.32, 1 - (o.bounce ?? 0.2)), spring.set(1), loop.wake();
13491
- },
13492
- hide() {
13493
- return shown = !1, el.style.pointerEvents = "none", tune(o.exit ?? 0.2, 1), spring.set(0), loop.wake(), new Promise((resolve) => gone.push(resolve));
13494
- },
13495
- snap(to = 1) {
13496
- if (shown = to === 1, spring.snap(to), loop.stop(), render(), !shown) {
13497
- let done = gone;
13498
- gone = [];
13499
- for (let f of done)
13500
- f();
13501
- o.onGone?.();
13502
- } else
13503
- gone = [];
13504
- },
13505
- get shown() {
13506
- return shown;
13507
- },
13508
- from(x, y) {
13509
- dx = x, dy = y;
13510
- },
13511
- dispose() {
13512
- loop.stop();
13513
- for (let f of gone)
13514
- f();
13515
- gone = [];
13516
- }
13517
- };
13518
- }
13519
-
13520
13775
  // src/ui/toast.ts
13521
13776
  var regions = /* @__PURE__ */ new WeakMap;
13522
13777
  function toast(message, options = {}) {
@@ -14496,6 +14751,17 @@ function positionPanel(panel, header, anchor, id, storage) {
14496
14751
  };
14497
14752
  }
14498
14753
 
14754
+ // src/panel/dock.ts
14755
+ function paneHeadOf(host) {
14756
+ let head = host.closest(".knobs-pane-body")?.parentElement?.querySelector(":scope > .knobs-pane-head");
14757
+ return head && !head.querySelector(":scope > [data-docked]") ? head : void 0;
14758
+ }
14759
+ function dock(actions, head) {
14760
+ return actions.dataset.docked = "", head.insertBefore(actions, head.querySelector(":scope > .knobs-pane-collapse")), () => {
14761
+ actions.remove(), delete actions.dataset.docked;
14762
+ };
14763
+ }
14764
+
14499
14765
  // src/panel/presence.ts
14500
14766
  var EXIT = springMotion2(SPRINGS2.snappy), ENTER = springMotion2(SPRINGS2.default), FADE2 = springMotion2(FADE), BLUR2 = 4, SCALE = 0.96, LEAD2 = 0.4, FOLDED = [
14501
14767
  "height",
@@ -14821,7 +15087,7 @@ function buildPanel(controls, host, options) {
14821
15087
  randomButton.hidden = !0;
14822
15088
  randomButton.onclick = (event) => run(() => reroll(void 0, event.altKey)), randomButton.querySelector(".knobs-icon")?.replaceWith(die.node);
14823
15089
  let resetAll = () => {
14824
- if (!resetBtn.hidden && resetBtn.dataset.shown === "true" && !header.hidden)
15090
+ if (!resetBtn.hidden && resetBtn.dataset.shown === "true" && headerShown())
14825
15091
  turn(resetBtn);
14826
15092
  cue(root, "reset"), controls.reset();
14827
15093
  }, turn = (button) => {
@@ -14894,6 +15160,19 @@ function buildPanel(controls, host, options) {
14894
15160
  header.append(titleEl, headActions);
14895
15161
  if (options.header === !1 && !page)
14896
15162
  header.hidden = !0;
15163
+ let untitled = !page && !header.hidden && !titleEl.textContent, paneHead = untitled && inline ? paneHeadOf(host) : void 0, undock = paneHead ? dock(headActions, paneHead) : void 0;
15164
+ if (undock)
15165
+ header.hidden = !0;
15166
+ let headerShown = () => Boolean(undock) || !header.hidden, syncBand = (animate) => {
15167
+ if (!untitled || undock)
15168
+ return;
15169
+ let any = headerButtons.some((b) => !b.button.hidden && b.button.dataset.shown !== "false");
15170
+ if (!animate)
15171
+ header.hidden = !any;
15172
+ else if ((!header.hidden && header.dataset.leaving !== "true") !== any)
15173
+ reveal(header, any, () => wakeGpu());
15174
+ };
15175
+ presetStrip.display(page ? "chips" : options.presetDisplay ?? "auto");
14897
15176
  let allActions = [
14898
15177
  ...controls.actions ?? [],
14899
15178
  ...options.actions ?? []
@@ -15186,6 +15465,8 @@ function buildPanel(controls, host, options) {
15186
15465
  }
15187
15466
  let specs = [], placed = /* @__PURE__ */ new Set;
15188
15467
  for (let [key, field] of entries) {
15468
+ if (field.kind === "choice" && field.variants && !controls.isPinned(key))
15469
+ continue;
15189
15470
  let pair = field.kind === "knob" && field.spring ? halves.get(field.spring.id) : void 0;
15190
15471
  if (pair?.duration && pair.bounce && field.kind === "knob" && field.spring) {
15191
15472
  if (placed.has(field.spring.id))
@@ -15501,7 +15782,7 @@ function buildPanel(controls, host, options) {
15501
15782
  writeSession(controls.id, { expanded: open ? [...others, key] : others });
15502
15783
  },
15503
15784
  ...spec.parts ? { parts: spec.parts } : {}
15504
- }, live = { ...context, field }, editor = field.kind === "spring" ? springRow({ ...context, field }) : field.kind === "color" ? colorRow({ ...context, field }) : field.kind === "point" ? pointRow({ ...context, field }) : field.kind === "easing" ? easingRow({ ...context, field }) : field.kind === "gradient" ? gradientRow({ ...context, field }) : field.kind === "curve" ? curveRow({ ...context, field }) : field.kind === "vec" ? vecRow(live) : field.kind === "range" ? rangeRow(live) : field.kind === "angle" ? angleRow(live) : field.kind === "seed" ? seedRow(live) : keybindRow(live);
15785
+ }, live = { ...context, field }, editor = field.kind === "spring" ? springRow({ ...context, field }) : field.kind === "color" ? colorRow({ ...context, field }) : field.kind === "point" ? pointRow({ ...context, field }) : field.kind === "easing" ? easingRow({ ...context, field }) : field.kind === "gradient" ? gradientRow({ ...context, field }) : field.kind === "curve" ? curveRow({ ...context, field }) : field.kind === "vec" ? vecRow(live) : field.kind === "range" ? rangeRow(live) : field.kind === "angle" ? angleRow(live) : field.kind === "seed" ? seedRow(live) : field.kind === "text" ? textRow(live) : keybindRow(live);
15505
15786
  if (field.shortcut)
15506
15787
  editor.input.setAttribute("aria-keyshortcuts", field.shortcut);
15507
15788
  editor.labelEl.ondblclick = resetRow;
@@ -15651,6 +15932,8 @@ function buildPanel(controls, host, options) {
15651
15932
  else
15652
15933
  input.removeAttribute("aria-readonly");
15653
15934
  }
15935
+ if (row.input.classList.contains("knobs-text"))
15936
+ row.input.readOnly = disabled || readOnly;
15654
15937
  }
15655
15938
  let rows = [], leavingRows = /* @__PURE__ */ new Set, presenceRuns = 0, present = (node, show, delay, after) => {
15656
15939
  if (presenceRuns++, node.dataset.presence = show ? "in" : "out", show)
@@ -15914,7 +16197,12 @@ function buildPanel(controls, host, options) {
15914
16197
  let keys = rows.flatMap((r) => {
15915
16198
  let tab = tabOf(r);
15916
16199
  return !pinnedRow(r) && (tab === void 0 || tab === activeTab) ? r.keys : [];
15917
- });
16200
+ }), v = variantsOf(controls.schema, controls.isPinned);
16201
+ if (v) {
16202
+ let tab = v.field.tab ?? (v.field.group !== void 0 ? groupTab(v.field.group) : void 0);
16203
+ if (tab === void 0 || tab === activeTab)
16204
+ keys.push(v.key);
16205
+ }
15918
16206
  return presetsTouch(entries, keys, controls.locked);
15919
16207
  }
15920
16208
  function syncTabs() {
@@ -16148,7 +16436,7 @@ function buildPanel(controls, host, options) {
16148
16436
  apply.className = "knobs-apply-btn", apply.setAttribute("aria-label", "Import JSON"), apply.hidden = !0;
16149
16437
  let copy = async (value, inPlace = !0) => {
16150
16438
  try {
16151
- if (await doc.defaultView.navigator.clipboard.writeText(value), inPlace && !copyButton.hidden && !header.hidden)
16439
+ if (await doc.defaultView.navigator.clipboard.writeText(value), inPlace && !copyButton.hidden && headerShown())
16152
16440
  copied();
16153
16441
  else
16154
16442
  say("Copied.");
@@ -16200,6 +16488,9 @@ function buildPanel(controls, host, options) {
16200
16488
  root.append(customActions);
16201
16489
  root.append(text, apply, status, live);
16202
16490
  }
16491
+ let emptyEl = el("p", options.empty);
16492
+ if (emptyEl.className = "knobs-empty", emptyEl.hidden = !0, options.empty && !page)
16493
+ body.after(emptyEl);
16203
16494
  let shown = controls.get(), initialized = !1, cascade = (changedCount) => {
16204
16495
  if (changedCount < 2)
16205
16496
  return 0;
@@ -16245,6 +16536,8 @@ function buildPanel(controls, host, options) {
16245
16536
  land();
16246
16537
  if (controls.schema !== shape)
16247
16538
  shape = controls.schema, layoutRows(initialized && !landing);
16539
+ if (emptyEl.isConnected && emptyEl.hidden !== rows.length > 0)
16540
+ emptyEl.hidden = rows.length > 0;
16248
16541
  let values = controls.get(), before = shown, fresh = (r) => r.row.dataset.shown === void 0, changed = (r) => fresh(r) || Boolean(r.mixed) !== isMixed(r) || r.keys.some((k) => !same(values[k], before[k])), changedCount = 0;
16249
16542
  if (initialized && !landing) {
16250
16543
  for (let r of rows)
@@ -16343,7 +16636,7 @@ function buildPanel(controls, host, options) {
16343
16636
  let undoMoved = bloom(undoButton, controls.canUndo);
16344
16637
  if (bloom(redoButton, controls.canRedo) || undoMoved)
16345
16638
  wakeGpu();
16346
- if (controls.error && status.textContent !== controls.error)
16639
+ if (syncBand(initialized), controls.error && status.textContent !== controls.error)
16347
16640
  say(controls.error, 8000);
16348
16641
  for (let [group, c] of groupChips)
16349
16642
  c.show(String(controls.matchingGroupPreset(group)));
@@ -16929,7 +17222,7 @@ function buildPanel(controls, host, options) {
16929
17222
  wasArmed = armed, root.toggleAttribute("data-k-armed", armed), wakeGpu();
16930
17223
  if (queueLens(), !popover.hidden)
16931
17224
  describeScope();
16932
- optionKeys.show("keyboard" in root.dataset ? optionsAt(active) : void 0);
17225
+ optionKeys.show("keyboard" in root.dataset ? optionsAt(active) ?? stripOptionsAt(active) : void 0);
16933
17226
  }, scopeQueued = !1, queueScope = () => {
16934
17227
  if (scopeQueued)
16935
17228
  return;
@@ -16962,6 +17255,17 @@ function buildPanel(controls, host, options) {
16962
17255
  if (!r || r.row.closest("[data-disabled],[data-readonly]"))
16963
17256
  return;
16964
17257
  return optionsOf(r);
17258
+ }, stripDigits = () => visible(presetStrip.element) && presetStrip.getEntries().length > 1, stripOptions = {
17259
+ get count() {
17260
+ return presetStrip.getEntries().length;
17261
+ },
17262
+ choose: (i) => presetStrip.select(i),
17263
+ shown: () => presetStrip.getButtons(),
17264
+ anchor: presetStrip.element
17265
+ }, stripOptionsAt = (node) => {
17266
+ if (hintCtrl.isActive || !node || !stripDigits())
17267
+ return;
17268
+ return (presetStrip.element.contains(node) || focusLevel(node) === "panel") && !presetStrip.nameField() ? stripOptions : void 0;
16965
17269
  }, pickOption = (event) => {
16966
17270
  if (!/^[1-9]$/.test(event.key) || event.altKey || event.shiftKey)
16967
17271
  return !1;
@@ -17166,12 +17470,20 @@ function buildPanel(controls, host, options) {
17166
17470
  event.preventDefault(), searchReturn = target, search.focus();
17167
17471
  return;
17168
17472
  }
17169
- if (page && /^[1-9]$/.test(key)) {
17170
- let group = focusedGroup(target);
17171
- if (group && groupChips.has(group)) {
17473
+ if (/^[1-9]$/.test(key)) {
17474
+ let group = page ? focusedGroup(target) : void 0;
17475
+ if (Boolean(group && groupChips.has(group))) {
17172
17476
  event.preventDefault(), run(() => applyGroupPreset(group, Number(key) - 1));
17173
17477
  return;
17174
17478
  }
17479
+ if (digitRoute(key, {
17480
+ options: !1,
17481
+ keeps: tracks.has(target),
17482
+ presets: stripDigits() ? presetStrip.getEntries().length : 0
17483
+ }) === "presets") {
17484
+ event.preventDefault(), presetStrip.select(Number(key) - 1);
17485
+ return;
17486
+ }
17175
17487
  }
17176
17488
  if (pressed("hint", event)) {
17177
17489
  if (event.preventDefault(), hintScrollY = win?.scrollY ?? 0, !unregisterHints || !pageHints(doc).start())
@@ -17575,8 +17887,8 @@ function buildPanel(controls, host, options) {
17575
17887
  inline,
17576
17888
  header,
17577
17889
  titleEl,
17578
- headerButtons,
17579
- headerDie: { button: randomButton, die },
17890
+ headerButtons: undock ? [] : headerButtons,
17891
+ ...undock ? {} : { headerDie: { button: randomButton, die } },
17580
17892
  locks: lockIcons,
17581
17893
  presetStrip,
17582
17894
  tabs: tabStrip,
@@ -17699,7 +18011,7 @@ function buildPanel(controls, host, options) {
17699
18011
  inHand.clear(), positioned?.dispose(), die.dispose();
17700
18012
  for (let g of groupDice.values())
17701
18013
  g.die.dispose();
17702
- menu?.close(!1), doc.removeEventListener("keyup", endCompare, !0), doc.defaultView?.removeEventListener("blur", endCompare), controls.compare(!1), morph?.dispose(), toggled?.dispose(), presence.dispose(), untrackHas(), unsubscribeSound(), optionKeys.dispose(), root.remove();
18014
+ menu?.close(!1), doc.removeEventListener("keyup", endCompare, !0), doc.defaultView?.removeEventListener("blur", endCompare), controls.compare(!1), morph?.dispose(), toggled?.dispose(), presence.dispose(), untrackHas(), unsubscribeSound(), optionKeys.dispose(), undock?.(), root.remove();
17703
18015
  }
17704
18016
  };
17705
18017
  if (!layout)