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

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-pdf3zy95.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 = {
@@ -957,8 +960,8 @@ function panOptions(el, o) {
957
960
 
958
961
  // src/panel/presets.ts
959
962
  var DRIFTED_PILL = 0.5;
960
- function activeEntryId(controls) {
961
- let ref = controls.activePreset();
963
+ function activeEntryId(controls, scope = "") {
964
+ let ref = controls.activePreset(scope);
962
965
  return ref ? `${ref.kind === "preset" ? "p" : "v"}:${ref.id}` : null;
963
966
  }
964
967
  function resolveActivePresetEntry(entries, currentValues, lastAppliedId, baseId = null) {
@@ -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
  }
@@ -1004,7 +1012,7 @@ function nextPresetIndex(count, activeIndex, direction) {
1004
1012
  return direction > 0 ? 0 : count - 1;
1005
1013
  return ((activeIndex + direction) % count + count) % count;
1006
1014
  }
1007
- function createPresetStrip(doc, controls, showVariants, run, onAfterChange, presetLabel = (id) => controls.presetLabel(id), relevant) {
1015
+ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, presetLabel = (id, scope) => controls.presetLabel(id, scope), relevant, scopeOf = () => "") {
1008
1016
  let wake = () => onAfterChange?.(), el = (tag, cls, text) => {
1009
1017
  let node = doc.createElement(tag);
1010
1018
  if (cls)
@@ -1021,31 +1029,34 @@ 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, scope = null, at = () => scope ?? "", source = () => scope === null ? void 0 : scope === "" ? { presets: controls.presets, variants: controls.variants } : controls.scopes[scope], unpinned = (values) => {
1025
1033
  let out;
1026
1034
  for (let k of Object.keys(values))
1027
- if (controls.isPinned(k) && !same(values[k], controls.locked[k]))
1035
+ if (controls.scopeOf(k) !== at() && !same(values[k], controls.locked[k]))
1028
1036
  out ??= { ...values }, out[k] = controls.locked[k];
1029
1037
  return out ?? values;
1030
1038
  };
1031
1039
  function buildEntries() {
1032
- let presetEntries = Object.entries(controls.presets).map(([id, presetValues]) => ({
1040
+ let from = source();
1041
+ if (!from)
1042
+ return [];
1043
+ let where = at(), presetEntries = Object.entries(from.presets).map(([id, presetValues]) => ({
1033
1044
  id: `p:${id}`,
1034
1045
  kind: "preset",
1035
1046
  rawId: id,
1036
- label: presetLabel(id),
1047
+ label: presetLabel(id, where),
1037
1048
  values: presetValues,
1038
- apply: (coalesce) => controls.preset(id, { coalesce })
1049
+ apply: (coalesce) => controls.preset(id, { coalesce, scope: where })
1039
1050
  }));
1040
1051
  if (!showVariants)
1041
1052
  return presetEntries;
1042
- let variantEntries = controls.variants.list().map((v) => ({
1053
+ let variantEntries = from.variants.list().map((v) => ({
1043
1054
  id: `v:${v.id}`,
1044
1055
  kind: "variant",
1045
1056
  rawId: v.id,
1046
1057
  label: v.name ?? v.id.slice(0, 8),
1047
1058
  values: unpinned(v.values),
1048
- apply: (coalesce) => controls.variants.load(v.id, { coalesce })
1059
+ apply: (coalesce) => from.variants.load(v.id, { coalesce })
1049
1060
  }));
1050
1061
  return [...presetEntries, ...variantEntries];
1051
1062
  }
@@ -1079,7 +1090,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1079
1090
  if (pill.style.left = `${left}px`, pill.style.right = `${box.trackWidth - right}px`, pill.style.top = `${top}px`, pill.style.height = `${box.height}px`, pill.style.opacity = activeState.drifted ? String(DRIFTED_PILL) : "1", pill.style.scale = "", first)
1080
1091
  doc.defaultView?.requestAnimationFrame(() => pill.style.transition = "");
1081
1092
  }
1082
- let resolve = () => resolveActivePresetEntry(entries, unpinned(controls.get()), lastAppliedId, activeEntryId(controls)), wanted = (list) => (list.length > 1 || showVariants) && (relevant?.(list) ?? !0);
1093
+ let resolve = () => resolveActivePresetEntry(entries, unpinned(controls.get()), lastAppliedId, activeEntryId(controls, at())), wanted = (list) => scope !== null && (list.length > 1 || showVariants) && (relevant?.(list) ?? !0);
1083
1094
  function syncShown() {
1084
1095
  let show = wanted(entries);
1085
1096
  if (root.hidden === show && root.dataset.presence === void 0)
@@ -1122,13 +1133,16 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1122
1133
  }
1123
1134
  function update() {
1124
1135
  run(() => {
1125
- let variantsRef = showVariants ? controls.variants.list() : null;
1126
- if (signature !== "" && variantsRef === lastVariantsRef) {
1136
+ let was = scope;
1137
+ if (scope = scopeOf(), scope !== was)
1138
+ lastAppliedId = null;
1139
+ let variantsRef = showVariants ? source()?.variants.list() ?? null : null;
1140
+ if (signature !== "" && scope === was && variantsRef === lastVariantsRef) {
1127
1141
  syncShown(), syncSelection(!1);
1128
1142
  return;
1129
1143
  }
1130
1144
  lastVariantsRef = variantsRef;
1131
- let nextEntries = buildEntries(), nextSig = JSON.stringify(nextEntries.map((e) => [e.id, e.label]));
1145
+ let nextEntries = buildEntries(), nextSig = JSON.stringify([scope, nextEntries.map((e) => [e.id, e.label])]);
1132
1146
  if (entries = nextEntries, syncShown(), nextSig !== signature)
1133
1147
  signature = nextSig, entries = nextEntries, buttons = entries.map((entry, idx) => {
1134
1148
  let b = el("button", "knobs-preset-chip", entry.label);
@@ -1152,15 +1166,29 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1152
1166
  range.selectNodeContents(b), widest = Math.max(widest, range.getBoundingClientRect().width);
1153
1167
  }
1154
1168
  if (widest > 0)
1155
- track.style.setProperty("--k-preset-min", `${Math.ceil(widest * 1.06 + 14)}px`);
1169
+ chipMin = Math.ceil(widest * 1.06 + 14), track.style.setProperty("--k-preset-min", `${chipMin}px`);
1170
+ placeDisplay();
1171
+ }
1172
+ function chipLines() {
1173
+ let pad = parseFloat(getComputedStyle(root).getPropertyValue("--_k-tray-pad")) || 0, save = saveButton ? saveButton.offsetWidth + pad : 0;
1174
+ return presetLines(buttons.length, chipMin, root.clientWidth - pad * 2 - save, pad);
1175
+ }
1176
+ function placeDisplay() {
1177
+ let want = display === "list" || display === "auto" && chipLines() > 2 ? "list" : "chips";
1178
+ if (root.dataset.display === want)
1179
+ return !1;
1180
+ return root.dataset.display = want, !0;
1156
1181
  }
1157
1182
  function save() {
1158
1183
  finishName(!0);
1159
- let used = new Set(controls.variants.list().map((v) => v.name)), n = 1;
1184
+ let variants = source()?.variants;
1185
+ if (!variants)
1186
+ return "";
1187
+ let used = new Set(variants.list().map((v) => v.name)), n = 1;
1160
1188
  while (used.has(`Variant ${n}`))
1161
1189
  n++;
1162
- let variant = controls.variants.save(`Variant ${n}`);
1163
- return controls.variants.load(variant.id), lastAppliedId = `v:${variant.id}`, update(), startName(entries.findIndex((e) => e.id === `v:${variant.id}`), !0), onAfterChange?.(), `Saved “Variant ${n}”. Type a name, or Enter to keep it.`;
1190
+ let variant = variants.save(`Variant ${n}`);
1191
+ return variants.load(variant.id), lastAppliedId = `v:${variant.id}`, update(), startName(entries.findIndex((e) => e.id === `v:${variant.id}`), !0), onAfterChange?.(), `Saved “Variant ${n}”. Type a name, or Enter to keep it.`;
1164
1192
  }
1165
1193
  let naming = null;
1166
1194
  function placeName() {
@@ -1204,7 +1232,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1204
1232
  focusQuietly();
1205
1233
  let message;
1206
1234
  if (keep && entry?.kind === "variant" && name && name !== was)
1207
- controls.variants.rename(entry.rawId, name), update(), message = `Named “${name}”.`;
1235
+ source()?.variants.rename(entry.rawId, name), update(), message = `Named “${name}”.`;
1208
1236
  return onAfterChange?.(), message;
1209
1237
  }
1210
1238
  function rename() {
@@ -1220,7 +1248,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1220
1248
  let entry = entries[activeState.activeIndex];
1221
1249
  if (!entry || entry.kind !== "variant")
1222
1250
  return;
1223
- return controls.variants.remove(entry.rawId), lastAppliedId = null, update(), onAfterChange?.(), `Deleted “${entry.label}”.`;
1251
+ return source()?.variants.remove(entry.rawId), lastAppliedId = null, update(), onAfterChange?.(), `Deleted “${entry.label}”.`;
1224
1252
  }
1225
1253
  let scrubbing = !1, scrubCommitted = !1, chipIndexAt = (clientX, clientY) => {
1226
1254
  let best = -1, bestDist = 1 / 0;
@@ -1288,8 +1316,8 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1288
1316
  minWidth: 20,
1289
1317
  rows: !0
1290
1318
  }), doc.fonts?.ready.then(fitColumns);
1291
- let ro = new ResizeObserver(() => measurePill(performance.now() >= slideUntil));
1292
- return ro.observe(track), {
1319
+ let ro = new ResizeObserver(() => measurePill(placeDisplay() || performance.now() >= slideUntil));
1320
+ return ro.observe(track), ro.observe(root), {
1293
1321
  element: root,
1294
1322
  track,
1295
1323
  pill,
@@ -1302,7 +1330,15 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1302
1330
  getActiveState: () => activeState,
1303
1331
  shows: () => wanted(entries),
1304
1332
  cycle,
1333
+ select: (index) => {
1334
+ if (index !== activeState.activeIndex || activeState.drifted)
1335
+ selectAt(index);
1336
+ },
1305
1337
  selectById,
1338
+ display(mode) {
1339
+ if (display = mode, placeDisplay())
1340
+ measurePill(!0);
1341
+ },
1306
1342
  save: () => {
1307
1343
  let message = "";
1308
1344
  return run(() => message = save()), message;
@@ -2944,7 +2980,8 @@ fn hash21(q: vec2f) -> f32 {
2944
2980
 
2945
2981
  // kind 0: Panel card surface + elevation shadow + border + specular rim. With a hairline width
2946
2982
  // (extra.z, whole device px) the edge is that hairline just outside the card, like CSS's
2947
- // 0 0 0 w spread shadow, and the rim is CSS's inset 0 w 0; otherwise a 1px border and a 1px rim.
2983
+ // 0 0 0 w spread shadow, and the rim lights its top, like CSS's 0 -w 0 (one edge, not two);
2984
+ // otherwise a 1px border and a 1px rim inside it.
2948
2985
  fn shadePanel(p: Prim, local: vec2f, size: vec2f) -> vec4f {
2949
2986
  let r = p.look.x;
2950
2987
  let dist = roundedBox(local, size, r);
@@ -2953,6 +2990,8 @@ fn shadePanel(p: Prim, local: vec2f, size: vec2f) -> vec4f {
2953
2990
  var out = vec4f(0.0);
2954
2991
  if (hair > 0.0 && dist > -1.0) {
2955
2992
  out = premul(p.color2) * clamp(covSd(dist - hair) - bodyCov, 0.0, 1.0);
2993
+ let raised = covSd(roundedBox(local + vec2f(0.0, hair), size, r));
2994
+ out = over(out, premul(p.aux2) * clamp(raised - bodyCov, 0.0, 1.0));
2956
2995
  }
2957
2996
 
2958
2997
  // Floating drop shadow when glowOrBlur (look.z) > 0; like box-shadow, never under the card.
@@ -2966,13 +3005,16 @@ fn shadePanel(p: Prim, local: vec2f, size: vec2f) -> vec4f {
2966
3005
 
2967
3006
  var card = premul(p.color) * bodyCov;
2968
3007
 
2969
- // The rim, built like CSS's inset 0 w 0: inside the border, minus that shape moved down w.
3008
+ // Without a hairline, the rim is CSS's inset 0 1px 0: inside the border, minus that shape
3009
+ // moved down.
2970
3010
  let inner = local - vec2f(1.0);
2971
3011
  let innerSize = size - vec2f(2.0);
2972
3012
  let w = select(1.0, hair, hair > 0.0);
2973
3013
  let innerCov = covSd(roundedBox(inner, innerSize, r - 1.0));
2974
- let rimCov = innerCov * (1.0 - covSd(roundedBox(inner - vec2f(0.0, w), innerSize, r - 1.0)));
2975
- card = over(card, premul(p.aux2) * rimCov);
3014
+ if (hair <= 0.0) {
3015
+ let rimCov = innerCov * (1.0 - covSd(roundedBox(inner - vec2f(0.0, w), innerSize, r - 1.0)));
3016
+ card = over(card, premul(p.aux2) * rimCov);
3017
+ }
2976
3018
 
2977
3019
  // A morphing card's specular ring (extra.y), w wide inside the border like CSS's inset 0 0 0 w.
2978
3020
  if (p.extra.y > 0.001) {
@@ -5642,7 +5684,9 @@ class GpuOverlayManager {
5642
5684
  this.drawIcon(item.icon, t.iconSm, at, ink, cardClip, k);
5643
5685
  }
5644
5686
  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) {
5687
+ if (lr.width > 0)
5688
+ this.drawText(words, lr.left, cy, ink, { px, weight: t.labelWeight, ...zoom });
5689
+ if (item.chip && item.chip.getClientRects().length) {
5646
5690
  let kr = this.rectOf(item.chip);
5647
5691
  this.drawText(item.chip.textContent ?? "", kr.left, cy, withAlpha(t.dim, alpha), {
5648
5692
  px: t.textCap,
@@ -6447,7 +6491,10 @@ class GpuOverlayManager {
6447
6491
  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
6492
  } else if (row.kind === "keybind")
6449
6493
  this.drawKeybind(row, rRect, cardClip, rowAlpha, hovered);
6450
- else if (row.kind === "toolbar")
6494
+ else if (row.kind === "text") {
6495
+ let labelY = row.row.hasAttribute("data-multiline") ? this.rectOf(row.labelEl).top + this.rectOf(row.labelEl).height / 2 : rowCenterY;
6496
+ 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);
6497
+ } else if (row.kind === "toolbar")
6451
6498
  this.drawToolbar(row, rRect, cardClip, rowAlpha, hinting);
6452
6499
  else if (row.kind === "readout")
6453
6500
  this.drawReadout(row, rRect, cardClip, rowAlpha, hovered);
@@ -10392,954 +10439,6 @@ function toolbarColumns(count, widest, avail, gap) {
10392
10439
  return Math.ceil(count / Math.ceil(count / cols));
10393
10440
  }
10394
10441
 
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
10442
  // src/ui/activity-css.ts
11344
10443
  var open = springLinear(SPRINGS2.snappy), activityCss = `
11345
10444
  :where(.knobs-activity,.knobs-activity *){box-sizing:border-box}
@@ -12014,13 +11113,17 @@ var css = `
12014
11113
  .knobs-tree-row[aria-expanded="true"]>.knobs-tree-caret{transform:rotate(90deg)}
12015
11114
  .knobs-tree-row:not([aria-expanded])>.knobs-tree-caret{visibility:hidden}
12016
11115
  .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)}
11116
+ /* The label wins the room: it keeps its width while the detail (which only takes what's left)
11117
+ fades out at its end, and gives way only once the detail is gone. Each fades where it's cut. */
11118
+ .knobs-tree-label{position:relative;flex:0 1 auto;min-width:0;overflow:hidden;font-weight:var(--_k-weight)}
11119
+ .knobs-tree-row[data-editing]>.knobs-tree-label{flex-grow:1}
12018
11120
  .knobs-tree-row[data-ancestor] .knobs-tree-label{color:var(--_k-fg-2)}
12019
11121
  .knobs-tree-label .knobs-tree-mark{color:var(--_k-accent)}
12020
11122
  .knobs-tree-row[data-off~="visible"] :is(.knobs-tree-label,.knobs-tree-icon){opacity:.45}
12021
11123
  .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}
11124
+ .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}
11125
+ .knobs-tree-row[data-editing]>.knobs-tree-detail{flex-grow:0;visibility:hidden}
11126
+ .knobs-tree-tools{flex:none;display:flex;align-items:center;gap:1px;margin-left:auto;padding-left:4px}
12024
11127
  .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
11128
  .knobs-ui .knobs-tree-toggle svg{width:12px;height:12px}
12026
11129
  .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 +11240,1277 @@ function injectUiStyles(doc) {
12137
11240
  let style = doc.createElement("style");
12138
11241
  style.dataset.knobsUi = "", style.textContent = uiCss(), doc.head.append(style);
12139
11242
  }
12140
- function applyLook2(root, look) {
12141
- injectUiStyles(root.ownerDocument), root.classList.add("knobs-root", "knobs-ui"), applyLook(root, resolveLook(look)), syncFrame(root);
11243
+ function applyLook2(root, look) {
11244
+ injectUiStyles(root.ownerDocument), root.classList.add("knobs-root", "knobs-ui"), applyLook(root, resolveLook(look)), syncFrame(root);
11245
+ }
11246
+ function syncFrame(root) {
11247
+ if (!root.isConnected)
11248
+ return;
11249
+ if (root.ownerDocument.defaultView?.getComputedStyle(root).getPropertyValue("--k-frame").trim() === "flat")
11250
+ root.dataset.frame = "flat";
11251
+ else
11252
+ delete root.dataset.frame;
11253
+ }
11254
+ var lookDefaults = { current: {} };
11255
+ function frameLoop(win, step) {
11256
+ let raf = 0, last = 0, tick = (now) => {
11257
+ let dt = last ? Math.min(0.064, (now - last) / 1000) : 0.016666666666666666;
11258
+ if (last = now, raf = step(dt) ? win.requestAnimationFrame(tick) : 0, !raf)
11259
+ last = 0;
11260
+ };
11261
+ return {
11262
+ wake() {
11263
+ if (!raf)
11264
+ raf = win.requestAnimationFrame(tick);
11265
+ },
11266
+ get running() {
11267
+ return raf !== 0;
11268
+ },
11269
+ stop() {
11270
+ if (raf)
11271
+ win.cancelAnimationFrame(raf);
11272
+ raf = 0, last = 0;
11273
+ }
11274
+ };
11275
+ }
11276
+ 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 = {
11277
+ caret: svg('<path d="M6 3.5 10.5 8 6 12.5"/>'),
11278
+ chevronDown: svg('<path d="M3.5 6 8 10.5 12.5 6"/>'),
11279
+ 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"/>'),
11280
+ 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"/>'),
11281
+ 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"/>'),
11282
+ 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"/>')
11283
+ };
11284
+
11285
+ // src/ui/layer.ts
11286
+ var lookOf = (look) => ({ ...lookDefaults.current, ...look }), roots = /* @__PURE__ */ new WeakMap;
11287
+ function layerRoot(doc) {
11288
+ let root = roots.get(doc);
11289
+ if (root?.isConnected)
11290
+ return root;
11291
+ return root = doc.createElement("div"), root.className = "knobs-layers", doc.body.append(root), roots.set(doc, root), root;
11292
+ }
11293
+ function layerElement(doc, cls, look) {
11294
+ let el = doc.createElement("div");
11295
+ return el.className = cls, layerRoot(doc).append(el), applyLook2(el, lookOf(look)), el;
11296
+ }
11297
+ function presence(el, o = {}) {
11298
+ 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 = () => {
11299
+ 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;
11300
+ 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);
11301
+ }, loop = frameLoop(win, (dt) => {
11302
+ if (spring.step(dt), render(), !spring.resting)
11303
+ return !0;
11304
+ if (!shown) {
11305
+ let done = gone;
11306
+ gone = [];
11307
+ for (let f of done)
11308
+ f();
11309
+ o.onGone?.();
11310
+ }
11311
+ return !1;
11312
+ }), tune = (seconds, zeta) => {
11313
+ let reduced = reducedMotion(win);
11314
+ spring.omega = 2 * Math.PI / (reduced ? FADE.duration : seconds), spring.zeta = reduced ? 1 : zeta;
11315
+ };
11316
+ return render(), {
11317
+ show() {
11318
+ shown = !0, gone = [], el.style.pointerEvents = "", tune(o.duration ?? 0.32, 1 - (o.bounce ?? 0.2)), spring.set(1), loop.wake();
11319
+ },
11320
+ hide() {
11321
+ return shown = !1, el.style.pointerEvents = "none", tune(o.exit ?? 0.2, 1), spring.set(0), loop.wake(), new Promise((resolve) => gone.push(resolve));
11322
+ },
11323
+ snap(to = 1) {
11324
+ if (shown = to === 1, spring.snap(to), loop.stop(), render(), !shown) {
11325
+ let done = gone;
11326
+ gone = [];
11327
+ for (let f of done)
11328
+ f();
11329
+ o.onGone?.();
11330
+ } else
11331
+ gone = [];
11332
+ },
11333
+ get shown() {
11334
+ return shown;
11335
+ },
11336
+ from(x, y) {
11337
+ dx = x, dy = y;
11338
+ },
11339
+ dispose() {
11340
+ loop.stop();
11341
+ for (let f of gone)
11342
+ f();
11343
+ gone = [];
11344
+ }
11345
+ };
11346
+ }
11347
+ var anchorBox = (a) => {
11348
+ if (typeof a.getBoundingClientRect === "function") {
11349
+ let r = a.getBoundingClientRect();
11350
+ return { x: r.left, y: r.top, w: r.width, h: r.height };
11351
+ }
11352
+ let r = a;
11353
+ return { x: r.x, y: r.y, w: r.width ?? 0, h: r.height ?? 0 };
11354
+ }, viewBox = (doc) => ({
11355
+ x: 0,
11356
+ y: 0,
11357
+ w: doc.documentElement.clientWidth,
11358
+ h: doc.documentElement.clientHeight
11359
+ });
11360
+
11361
+ // src/ui/overlay-model.ts
11362
+ var opposite = { top: "bottom", bottom: "top", left: "right", right: "left" };
11363
+ function place2(o) {
11364
+ 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;
11365
+ if (room(want) < need(want) && room(opposite[want]) > room(want))
11366
+ side = opposite[want];
11367
+ let vertical = side === "top" || side === "bottom", x, y;
11368
+ if (vertical)
11369
+ 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);
11370
+ else
11371
+ 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);
11372
+ 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) };
11373
+ return {
11374
+ x: Math.round(x),
11375
+ y: Math.round(y),
11376
+ placement: align === "center" ? side : `${side}-${align}`,
11377
+ origin
11378
+ };
11379
+ }
11380
+ var clamp2 = (v, lo, hi) => hi < lo ? lo : Math.max(lo, Math.min(hi, v));
11381
+
11382
+ // src/ui/tooltip.ts
11383
+ var tips = /* @__PURE__ */ new WeakMap, HANDOFF_MS = 400, controllers = /* @__PURE__ */ new WeakMap;
11384
+ function tipOf(start) {
11385
+ let node = start instanceof Element ? start : null;
11386
+ while (node) {
11387
+ let own = tips.get(node);
11388
+ if (own)
11389
+ return { el: node, tip: own };
11390
+ let data = node.getAttribute("data-tip");
11391
+ if (data) {
11392
+ let shortcut = node.getAttribute("data-tip-shortcut") ?? void 0, placement = node.getAttribute("data-tip-placement") ?? void 0;
11393
+ return {
11394
+ el: node,
11395
+ tip: {
11396
+ text: data,
11397
+ opts: { ...shortcut ? { shortcut } : {}, ...placement ? { placement } : {} }
11398
+ }
11399
+ };
11400
+ }
11401
+ node = node.parentElement;
11402
+ }
11403
+ return null;
11404
+ }
11405
+ function controllerOf(doc) {
11406
+ let existing = controllers.get(doc);
11407
+ if (existing)
11408
+ return existing;
11409
+ let win = doc.defaultView, layer, motion, text, keys, target = null, pending = null, timer = 0, hiddenAt = -1 / 0, quiet = null, keyboard = !1, build = (look) => {
11410
+ 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 });
11411
+ }, show = (el, tip, instant) => {
11412
+ if (!layer?.isConnected)
11413
+ build(tip.opts.look);
11414
+ 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) => {
11415
+ let kbd = doc.createElement("kbd");
11416
+ return kbd.className = "knobs-kbd", kbd.textContent = k, kbd;
11417
+ })), keys.hidden = !tip.opts.shortcut;
11418
+ let p = place2({
11419
+ anchor: anchorBox(el),
11420
+ size: { w: layer.offsetWidth, h: layer.offsetHeight },
11421
+ view: viewBox(doc),
11422
+ placement: tip.opts.placement ?? "top",
11423
+ offset: 6
11424
+ });
11425
+ layer.style.left = `${p.x}px`, layer.style.top = `${p.y}px`, layer.style.transformOrigin = `${p.origin.x}px ${p.origin.y}px`;
11426
+ let side = p.placement.split("-")[0];
11427
+ if (motion.from(0, side === "top" ? 3 : side === "bottom" ? -3 : 0), el.setAttribute("aria-describedby", layer.id), instant)
11428
+ motion.snap(1);
11429
+ else
11430
+ motion.show();
11431
+ }, hide = (now = !1) => {
11432
+ if (win.clearTimeout(timer), pending = null, !target)
11433
+ return;
11434
+ if (target.removeAttribute("aria-describedby"), target = null, hiddenAt = performance.now(), now)
11435
+ motion?.snap(0);
11436
+ else
11437
+ motion?.hide();
11438
+ }, want = (found) => {
11439
+ if (!found)
11440
+ return hide();
11441
+ if (found.el === target || found.el === pending?.el)
11442
+ return;
11443
+ if (found.el === quiet)
11444
+ return;
11445
+ if (win.clearTimeout(timer), target !== null || performance.now() - hiddenAt < HANDOFF_MS) {
11446
+ pending = null, show(found.el, found.tip, !0);
11447
+ return;
11448
+ }
11449
+ if (target)
11450
+ hide();
11451
+ pending = found, timer = win.setTimeout(() => {
11452
+ if (pending = null, found.el.isConnected)
11453
+ show(found.el, found.tip, !1);
11454
+ }, found.tip.opts.delay ?? 500);
11455
+ };
11456
+ doc.addEventListener("pointerover", (e) => {
11457
+ if (e.pointerType === "touch")
11458
+ return;
11459
+ keyboard = !1, want(tipOf(e.target));
11460
+ }), doc.addEventListener("pointerout", (e) => {
11461
+ let to = e.relatedTarget, from = tipOf(e.target);
11462
+ if (!from)
11463
+ return;
11464
+ if (to && from.el.contains(to))
11465
+ return;
11466
+ if (quiet === from.el)
11467
+ quiet = null;
11468
+ if (!tipOf(to))
11469
+ hide();
11470
+ }), doc.addEventListener("pointerdown", (e) => {
11471
+ let found = tipOf(e.target);
11472
+ if (found)
11473
+ quiet = found.el;
11474
+ hide(!0);
11475
+ }, !0), doc.addEventListener("keydown", (e) => {
11476
+ if (keyboard = !0, e.key === "Escape" && target)
11477
+ hide();
11478
+ }, !0), doc.addEventListener("focusin", (e) => {
11479
+ let found = tipOf(e.target);
11480
+ if (found && keyboard && e.target.matches(":focus-visible"))
11481
+ want(found);
11482
+ }), doc.addEventListener("focusout", (e) => {
11483
+ if (target && target.contains(e.target))
11484
+ hide();
11485
+ }), doc.addEventListener("scroll", () => target && hide(!0), !0);
11486
+ let c = { hide };
11487
+ return controllers.set(doc, c), c;
11488
+ }
11489
+ function tooltip(el, content, options = {}) {
11490
+ let { text, ...rest } = typeof content === "string" || typeof content === "function" ? { text: content } : content;
11491
+ tips.set(el, { text, opts: { ...options, ...rest } });
11492
+ let c = controllerOf(el.ownerDocument), shortcut = rest.shortcut ?? options.shortcut;
11493
+ if (shortcut && !el.hasAttribute("aria-keyshortcuts"))
11494
+ el.setAttribute("aria-keyshortcuts", shortcut);
11495
+ return () => {
11496
+ tips.delete(el), c.hide(!0);
11497
+ };
11498
+ }
11499
+
11500
+ // src/panel/inspector.ts
11501
+ function make(doc) {
11502
+ return (tag, cls, text) => {
11503
+ let node = doc.createElement(tag);
11504
+ if (cls)
11505
+ node.className = cls;
11506
+ if (text !== void 0)
11507
+ node.textContent = text;
11508
+ return node;
11509
+ };
11510
+ }
11511
+ function shell(c, kind) {
11512
+ let el = make(c.doc), row = el("div", "knobs-row knobs-insp");
11513
+ row.dataset.field = c.key, row.dataset.kind = kind;
11514
+ let label = el("span", void 0, c.display);
11515
+ return row.append(label), { row, label, el };
11516
+ }
11517
+ var fmt = (v, places) => (Object.is(v, -0) ? 0 : v).toFixed(places);
11518
+ function inlineEdit(doc, host, label, commit, back) {
11519
+ let input = doc.createElement("input");
11520
+ 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);
11521
+ let open = !1, done = (keep) => {
11522
+ if (!open)
11523
+ return;
11524
+ open = !1;
11525
+ let next = Number(input.value.trim().replace(/[^\d.eE+-]/g, ""));
11526
+ if (input.hidden = !0, host.classList.remove("knobs-editing"), keep && input.value.trim() !== "" && Number.isFinite(next))
11527
+ commit(next);
11528
+ let target = back();
11529
+ if (doc.activeElement === input || doc.activeElement === doc.body)
11530
+ target.focus({ preventScroll: !0 });
11531
+ };
11532
+ return input.addEventListener("keydown", (event) => {
11533
+ if (event.key === "Enter" || event.key === "Escape")
11534
+ event.preventDefault(), event.stopPropagation(), done(event.key === "Enter");
11535
+ else
11536
+ event.stopPropagation();
11537
+ }), input.addEventListener("blur", () => done(!0)), {
11538
+ input,
11539
+ get open() {
11540
+ return open;
11541
+ },
11542
+ start(text, select = !0) {
11543
+ if (open = !0, input.value = text, input.hidden = !1, host.classList.add("knobs-editing"), input.focus({ preventScroll: !0 }), select)
11544
+ input.select();
11545
+ else
11546
+ input.setSelectionRange(text.length, text.length);
11547
+ },
11548
+ done
11549
+ };
11550
+ }
11551
+ var typed = (event) => plainKey(event) && !event.altKey && /^[0-9.\-]$/.test(event.key) ? event.key : void 0;
11552
+ function scrub(target, row, o) {
11553
+ let at, moved = !1, last = 0, carried = 0, rect;
11554
+ target.addEventListener("pointerdown", (event) => {
11555
+ if (event.button !== 0 || event.shiftKey || event.metaKey)
11556
+ return;
11557
+ event.preventDefault(), target.setPointerCapture(event.pointerId), target.focus({ preventScroll: !0, focusVisible: !1 }), at = last = event.clientX, carried = 0, moved = !1;
11558
+ }), target.addEventListener("pointermove", (event) => {
11559
+ if (at === void 0)
11560
+ return;
11561
+ if (!moved && Math.abs(event.clientX - at) < 3)
11562
+ return;
11563
+ if (!moved)
11564
+ moved = !0, last = event.clientX, rect = row.getBoundingClientRect(), row.classList.add("knobs-active"), o.start();
11565
+ let r = rect, rate = precisionRate(offRow(event.clientY, r.top, r.bottom)) * (event.altKey ? 0.1 : 1);
11566
+ carried += (event.clientX - last) * rate, last = event.clientX, o.move(carried, event.altKey, event.shiftKey);
11567
+ });
11568
+ let release = (event) => {
11569
+ if (at === void 0)
11570
+ return;
11571
+ at = void 0, rect = void 0, row.classList.remove("knobs-active"), o.end(moved, event);
11572
+ };
11573
+ return target.addEventListener("pointerup", release), target.addEventListener("pointercancel", release), { scrubbing: () => at !== void 0 && moved };
11574
+ }
11575
+ 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"];
11576
+ function vecRow(c) {
11577
+ 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");
11578
+ if (axes.length > 2)
11579
+ cells.dataset.wide = "";
11580
+ let linked = Boolean(c.field.linked), link;
11581
+ if (c.field.link) {
11582
+ link = el("button", "knobs-link"), link.type = "button", link.tabIndex = -1;
11583
+ let paint = () => {
11584
+ link.replaceChildren(iconSvg(c.doc, linked ? "link" : "unlink")), link.setAttribute("aria-pressed", String(linked)), link.setAttribute("aria-label", linked ? "Unlink axes" : "Link axes");
11585
+ };
11586
+ paint(), link.onclick = () => {
11587
+ linked = !linked, paint(), c.wake();
11588
+ }, row.append(link);
11589
+ }
11590
+ row.append(cells);
11591
+ let shown = c.get(), mixed = !1, g = gesture(c), base = shown, withAxis = (from, axis, v) => {
11592
+ let { min, max, step } = f(), next = clamp(onGrid(v, min, step), min, max);
11593
+ if (!linked)
11594
+ return { ...from, [axis]: next };
11595
+ let was = from[axis] ?? 0, k = Math.abs(was) > 0.000000001 ? next / was : void 0, out = {};
11596
+ for (let a of axes) {
11597
+ let own = from[a] ?? 0;
11598
+ out[a] = a === axis ? next : clamp(Number((k !== void 0 ? own * k : own + (next - was)).toPrecision(12)), min, max);
11599
+ }
11600
+ return out;
11601
+ }, gpuCells = [], reads = [], rolls = [], stops = [];
11602
+ for (let axis of axes) {
11603
+ let cell = el("span", "knobs-cell");
11604
+ cell.tabIndex = 0, cell.dataset.axis = axis, cell.setAttribute("role", "spinbutton");
11605
+ let name = c.field.axisLabels?.[axes.indexOf(axis)] ?? axis;
11606
+ cell.setAttribute("aria-label", `${c.display} ${name}`);
11607
+ let letter = el("i", "knobs-cell-axis", name.toUpperCase());
11608
+ letter.setAttribute("aria-hidden", "true");
11609
+ let read = el("span", "knobs-cell-read");
11610
+ cell.append(letter, read), cells.append(cell), reads.push(read), rolls.push(rollingReadout(read)), stops.push(cell);
11611
+ let editor = inlineEdit(c.doc, cell, `${c.display} ${name} value`, (v) => {
11612
+ g.start(), g.set(withAxis(c.get(), axis, v));
11613
+ }, () => cell), drag = scrub(cell, row, {
11614
+ perPx: () => rate(f().min, f().max, f().step),
11615
+ start() {
11616
+ g.start(), base = c.get();
11617
+ },
11618
+ move(dx, _fine, coarse) {
11619
+ let by = dx * rate(f().min, f().max, f().step) * (coarse ? 10 : 1);
11620
+ g.set(withAxis(base, axis, (base[axis] ?? 0) + by));
11621
+ },
11622
+ end(moved) {
11623
+ if (!moved)
11624
+ editor.start(mixed ? "" : fmt(shown[axis] ?? 0, places()));
11625
+ c.wake();
11626
+ }
11627
+ }), panAt, give = textGive(read, () => c.wake());
11628
+ onPan(cell, {
11629
+ accept: () => !drag.scrubbing() && !editor.open,
11630
+ move(dx) {
11631
+ let perPx = rate(f().min, f().max, f().step);
11632
+ if (panAt === void 0)
11633
+ g.start(), base = c.get(), panAt = { at: 0, give: 0 }, row.classList.add("knobs-active"), cell.setAttribute("data-panned", "");
11634
+ let from = base[axis] ?? 0;
11635
+ 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)
11636
+ give.hold(panAt.give);
11637
+ },
11638
+ lift: () => give.release(),
11639
+ end() {
11640
+ panAt = void 0, give.release(), row.classList.remove("knobs-active"), cell.removeAttribute("data-panned"), c.wake();
11641
+ },
11642
+ step(direction) {
11643
+ g.start(), g.set(withAxis(c.get(), axis, (shown[axis] ?? 0) + direction * f().step));
11644
+ }
11645
+ }), cell.addEventListener("keydown", (event) => {
11646
+ if (event.target !== cell || !plainKey(event))
11647
+ return;
11648
+ let dir = arrowOf(event), char = typed(event);
11649
+ if (dir) {
11650
+ event.preventDefault(), event.stopPropagation();
11651
+ let by = f().step * (event.shiftKey ? bigOf(event) : event.altKey ? 0.1 : 1);
11652
+ g.start(), g.set(withAxis(c.get(), axis, (shown[axis] ?? 0) + dir * by));
11653
+ } else if (event.key === "Home" || event.key === "End")
11654
+ event.preventDefault(), g.start(), g.set(withAxis(c.get(), axis, event.key === "Home" ? f().min : f().max));
11655
+ else if (event.key === "Enter" && keyPrefs(cell).edit)
11656
+ event.preventDefault(), event.stopPropagation(), editor.start(mixed ? "" : fmt(shown[axis] ?? 0, places()));
11657
+ else if (char && keyPrefs(cell).edit)
11658
+ event.preventDefault(), event.stopPropagation(), editor.start(char, !1);
11659
+ }), gpuCells.push({
11660
+ el: cell,
11661
+ axis: letter,
11662
+ read,
11663
+ editing: () => editor.open,
11664
+ active: () => drag.scrubbing() || panAt !== void 0
11665
+ });
11666
+ }
11667
+ let paint = (ms = 0, delay = 0) => {
11668
+ axes.forEach((axis, i) => {
11669
+ let text = mixed ? "—" : fmt(shown[axis] ?? 0, places()), unit = f().unit && !mixed ? ` ${f().unit}` : "";
11670
+ rolls[i].show(mixed ? ["—"] : [{ value: shown[axis] ?? 0, decimals: places() }], ms, delay), stops[i].setAttribute("aria-valuetext", `${text}${unit}`);
11671
+ });
11672
+ };
11673
+ paint();
11674
+ let placed = !1, gpu = {
11675
+ kind: "vec",
11676
+ key: c.key,
11677
+ display: c.display,
11678
+ row,
11679
+ labelEl: label,
11680
+ cells: gpuCells,
11681
+ ...link ? { link } : {},
11682
+ linked: () => linked,
11683
+ wave: c.wave,
11684
+ isModified: c.isModified
11685
+ };
11686
+ return {
11687
+ row,
11688
+ labelEl: label,
11689
+ input: stops[0],
11690
+ stops: () => stops,
11691
+ update(value, delay, instant, isMixed = !1) {
11692
+ shown = value, mixed = isMixed;
11693
+ let hand = row.classList.contains("knobs-active");
11694
+ paint(!placed || instant || hand ? 0 : rollMs(row), delay), placed = !0;
11695
+ },
11696
+ dispose() {
11697
+ for (let r of rolls)
11698
+ r.dispose();
11699
+ },
11700
+ tracks: [],
11701
+ gpu: [gpu]
11702
+ };
11703
+ }
11704
+ function rangeRow(c) {
11705
+ let el = make(c.doc), f = () => c.field, places = () => f().decimals ?? decimals(f().step), row = el("div", "knobs-row knobs-range");
11706
+ row.dataset.field = c.key;
11707
+ 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) => {
11708
+ let t = el("span", "knobs-range-thumb");
11709
+ return t.dataset.end = end, t.tabIndex = 0, t.setAttribute("role", "slider"), t.setAttribute("aria-label", `${c.display} ${end === "lo" ? "low" : "high"}`), t;
11710
+ }), words = el("span", "knobs-range-text");
11711
+ words.append(label, read);
11712
+ let band2 = el("i", "knobs-range-smear");
11713
+ track.append(fill, band2, ...thumbs, words), row.append(track);
11714
+ let shown = c.get(), mixed = !1, dragging = !1, smearFrame = 0, smearAt = 0, smearStrength = 0, lastEnds = [0, 0], smears = [0, 0], ends = () => {
11715
+ let cs = getComputedStyle(track);
11716
+ return [
11717
+ parseFloat(cs.getPropertyValue("--kr-lo")) || 0,
11718
+ parseFloat(cs.getPropertyValue("--kr-hi")) || 0
11719
+ ];
11720
+ }, setSmears = (lo, hi) => {
11721
+ 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));
11722
+ }, runSmear = (now) => {
11723
+ 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);
11724
+ lastEnds = at, smearAt = now;
11725
+ let gliding = !dragging && track.getAnimations().length > 0;
11726
+ setSmears(gliding ? rangeSmear(speeds[0], smearStrength) : 0, gliding ? rangeSmear(speeds[1], smearStrength) : 0), smearFrame = gliding ? requestAnimationFrame(runSmear) : 0;
11727
+ };
11728
+ track.addEventListener("transitionrun", (event) => {
11729
+ if (event.target !== track || smearFrame || dragging)
11730
+ return;
11731
+ if (track.closest('[data-gpu-active="true"]'))
11732
+ return;
11733
+ smearStrength = clamp(parseFloat(getComputedStyle(track).getPropertyValue("--k-range-blur")) || 0, 0, 1);
11734
+ let reduced = c.doc.defaultView?.matchMedia("(prefers-reduced-motion: reduce)").matches;
11735
+ if (smearStrength <= 0 || reduced)
11736
+ return;
11737
+ lastEnds = ends(), smearAt = performance.now(), smearFrame = requestAnimationFrame(runSmear);
11738
+ });
11739
+ 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) => {
11740
+ let q = quant(v, fine);
11741
+ return end === 0 ? [Math.min(q, from[1]), from[1]] : [from[0], Math.max(q, from[0])];
11742
+ }, rolling = rollingReadout(read), showText = (ms, delay) => rolling.show(mixed ? ["—"] : [
11743
+ { value: shown[0], decimals: places() },
11744
+ "–",
11745
+ { value: shown[1], decimals: places() },
11746
+ f().unit ? ` ${f().unit}` : ""
11747
+ ], ms, delay), place = (instant) => {
11748
+ let [a, b] = [ratio(shown[0]), ratio(shown[1])], nodes = [fill, track];
11749
+ if (instant)
11750
+ for (let n of nodes)
11751
+ n.style.transition = "none";
11752
+ 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) {
11753
+ fill.offsetWidth;
11754
+ for (let n of nodes)
11755
+ n.style.transition = "";
11756
+ }
11757
+ }, paint = (instant, delay = 0) => {
11758
+ showText(instant ? 0 : rollMs(row), delay), thumbs.forEach((th, i) => {
11759
+ 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));
11760
+ }), place(instant);
11761
+ }, 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(), {
11762
+ home: STRETCH_HOME_S,
11763
+ draw: (px) => drawStretch(row, px, 0, { w: track.clientWidth, h: 0 }, { thin: !0, unclip: card() })
11764
+ }), most = GIVE, fit = () => {
11765
+ let room = roomOf(row, card());
11766
+ most = giveRoom(Math.min(room.left, room.right) - 1, GIVE);
11767
+ }, valueAt = (x) => f().min + clamp((x - rect.left) / rect.width, 0, 1) * span();
11768
+ track.addEventListener("pointerdown", (event) => {
11769
+ if (event.button !== 0 || event.shiftKey || event.metaKey)
11770
+ return;
11771
+ if (event.target.closest(".knobs-cell-edit,.knobs-accessory"))
11772
+ return;
11773
+ event.preventDefault(), track.setPointerCapture(event.pointerId), rect = track.getBoundingClientRect();
11774
+ let x = event.clientX - rect.left, [ax, bx] = [ratio(shown[0]) * rect.width, ratio(shown[1]) * rect.width];
11775
+ 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)
11776
+ grab = x < ax ? 0 : 1;
11777
+ if (grab !== 2)
11778
+ lastEnd = grab;
11779
+ if (thumbs[grab === 2 ? lastEnd : grab].focus({
11780
+ preventScroll: !0,
11781
+ focusVisible: !1
11782
+ }), lastX = event.clientX, startValue = shown, moved = 0, fit(), hand = {
11783
+ at: grab === 2 || event.altKey ? ratio(shown[grab === 2 ? 0 : grab]) * rect.width : clamp(x, 0, rect.width),
11784
+ give: 0
11785
+ }, dragging = !0, row.classList.add("knobs-active"), g.start(), grab !== 2 && !event.altKey)
11786
+ g.set(withEnd(shown, grab, valueAt(event.clientX)));
11787
+ c.wake();
11788
+ }), track.addEventListener("pointermove", (event) => {
11789
+ if (!dragging || !rect)
11790
+ return;
11791
+ let rate = precisionRate(offRow(event.clientY, rect.top, rect.bottom)) * (event.altKey ? 0.1 : 1), dx = (event.clientX - lastX) * rate;
11792
+ if (lastX = event.clientX, grab === 2) {
11793
+ moved += dx;
11794
+ let width = startValue[1] - startValue[0], by = moved / rect.width * span(), lo = clamp(quant(startValue[0] + by, event.altKey), f().min, f().max - width);
11795
+ g.set([lo, Number((lo + width).toPrecision(12))]);
11796
+ return;
11797
+ }
11798
+ 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)
11799
+ give.hold(hand.give);
11800
+ });
11801
+ let release = () => {
11802
+ if (!dragging)
11803
+ return;
11804
+ dragging = !1, rect = void 0, give.release(), row.classList.remove("knobs-active"), c.wake();
11805
+ };
11806
+ track.addEventListener("pointerup", release), track.addEventListener("pointercancel", release);
11807
+ let panAt;
11808
+ onPan(track, {
11809
+ accept: () => !dragging,
11810
+ move(dx) {
11811
+ let w = track.clientWidth;
11812
+ if (!w)
11813
+ return;
11814
+ if (panAt === void 0)
11815
+ g.start(), panAt = { at: (shown[lastEnd] - f().min) / span() * w, give: 0 }, fit(), dragging = !0, row.classList.add("knobs-active");
11816
+ if (panAt = band(panAt, dx, 0, w, most), g.set(withEnd(c.get(), lastEnd, f().min + panAt.at / w * span())), panAt.give !== give.give)
11817
+ give.hold(panAt.give);
11818
+ },
11819
+ lift: () => give.release(),
11820
+ end() {
11821
+ panAt = void 0, give.release(), dragging = !1, row.classList.remove("knobs-active"), c.wake();
11822
+ },
11823
+ step(direction) {
11824
+ g.start(), g.set(withEnd(shown, lastEnd, shown[lastEnd] + direction * f().step));
11825
+ }
11826
+ });
11827
+ let editors = thumbs.map((th, i) => inlineEdit(c.doc, row, `${c.display} ${i === 0 ? "low" : "high"} value`, (v) => {
11828
+ g.start(), g.set(withEnd(c.get(), i, v));
11829
+ }, () => th));
11830
+ thumbs.forEach((th, i) => {
11831
+ let end = i;
11832
+ th.addEventListener("focus", () => {
11833
+ lastEnd = end, row.dataset.endFocus = th.dataset.end, c.wake();
11834
+ }), th.addEventListener("blur", () => {
11835
+ if (row.dataset.endFocus === th.dataset.end)
11836
+ delete row.dataset.endFocus;
11837
+ }), th.addEventListener("keydown", (event) => {
11838
+ if (!plainKey(event))
11839
+ return;
11840
+ let dir = arrowOf(event), char = typed(event);
11841
+ if (dir) {
11842
+ event.preventDefault(), event.stopPropagation();
11843
+ let by = f().step * (event.shiftKey ? bigOf(event) : event.altKey ? 0.1 : 1);
11844
+ g.start(), g.set(withEnd(shown, end, shown[end] + dir * by, event.altKey));
11845
+ } else if (event.key === "Home" || event.key === "End")
11846
+ event.preventDefault(), g.start(), g.set(withEnd(shown, end, event.key === "Home" ? f().min : f().max));
11847
+ else if (event.key === "Enter" && keyPrefs(th).edit)
11848
+ event.preventDefault(), event.stopPropagation(), editors[end].start(mixed ? "" : fmt(shown[end], places()));
11849
+ else if (char && keyPrefs(th).edit)
11850
+ event.preventDefault(), event.stopPropagation(), editors[end].start(char, !1);
11851
+ });
11852
+ });
11853
+ let first = !0, gpu = {
11854
+ kind: "range",
11855
+ key: c.key,
11856
+ display: c.display,
11857
+ row,
11858
+ labelEl: label,
11859
+ track,
11860
+ read,
11861
+ thumbs,
11862
+ ratios: () => [ratio(shown[0]), ratio(shown[1])],
11863
+ dragging: () => dragging,
11864
+ editing: () => editors.some((e) => e.open),
11865
+ wave: c.wave,
11866
+ isModified: c.isModified
11867
+ };
11868
+ return {
11869
+ row,
11870
+ labelEl: label,
11871
+ input: thumbs[0],
11872
+ stops: () => thumbs,
11873
+ update(value, delay, instant, isMixed = !1) {
11874
+ shown = value, mixed = isMixed, paint(first || Boolean(instant) || dragging, delay), first = !1;
11875
+ },
11876
+ dispose() {
11877
+ cancelAnimationFrame(smearFrame), rolling.dispose();
11878
+ },
11879
+ tracks: [],
11880
+ gpu: [gpu]
11881
+ };
11882
+ }
11883
+ function angleRow(c) {
11884
+ let { row, label, el } = shell(c, "angle"), f = () => c.field, control = el("span", "knobs-angle");
11885
+ control.tabIndex = 0, control.setAttribute("role", "slider"), control.setAttribute("aria-label", c.display);
11886
+ let read = el("span", "knobs-angle-read"), dial = el("span", "knobs-dial"), hand = el("i", "knobs-dial-hand");
11887
+ dial.append(hand), control.append(read, dial), row.append(control);
11888
+ let value = c.get(), turn = value, mixed = !1, dragging = !1, g = gesture(c), places = () => decimals(f().step), snapTo = (v, coarse) => {
11889
+ let step = coarse ? Math.max(15, f().step) : f().step, q = Number((Math.round(v / step) * step).toPrecision(12));
11890
+ return f().wrap ? wrapAngle2(q, f().min, f().max) : clamp(q, f().min, f().max);
11891
+ }, rolling = rollingReadout(read), paint = (instant, delay = 0) => {
11892
+ let t = mixed ? "—" : `${fmt(value, places())}°`;
11893
+ 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))
11894
+ hand.style.transition = "none";
11895
+ if (hand.style.rotate = `${turn.toFixed(3)}deg`, instant)
11896
+ hand.offsetWidth, hand.style.transition = "";
11897
+ }, follow = (next) => {
11898
+ let d = next - value;
11899
+ if (f().wrap) {
11900
+ let s = f().max - f().min;
11901
+ d = ((d + s / 2) % s + s) % s - s / 2;
11902
+ }
11903
+ turn += d, value = next;
11904
+ }, 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;
11905
+ control.addEventListener("pointerdown", (event) => {
11906
+ if (event.button !== 0 || event.shiftKey || event.metaKey)
11907
+ return;
11908
+ if (event.target.closest(".knobs-cell-edit"))
11909
+ return;
11910
+ event.preventDefault(), control.setPointerCapture(event.pointerId), control.focus({ preventScroll: !0, focusVisible: !1 });
11911
+ let r = dial.getBoundingClientRect();
11912
+ 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")
11913
+ moved = !0, row.classList.add("knobs-active"), g.set(snapTo(angleAt(event.clientX, event.clientY), !1));
11914
+ c.wake();
11915
+ }), control.addEventListener("pointermove", (event) => {
11916
+ if (!dragging)
11917
+ return;
11918
+ if (mode === "dial") {
11919
+ let a = angleAt(event.clientX, event.clientY);
11920
+ g.set(snapTo(f().wrap ? a : clamp(a, f().min, f().max), event.shiftKey));
11921
+ return;
11922
+ }
11923
+ if (!moved && Math.abs(event.clientX - startX) < 3)
11924
+ return;
11925
+ if (!moved)
11926
+ moved = !0, startX = event.clientX, rowRect = row.getBoundingClientRect(), row.classList.add("knobs-active");
11927
+ let r = rowRect, rate = precisionRate(offRow(event.clientY, r.top, r.bottom)) * (event.altKey ? 0.1 : 1);
11928
+ carried += (event.clientX - startX) * rate, startX = event.clientX, g.set(snapTo(startValue + carried * f().step, event.shiftKey));
11929
+ });
11930
+ let editor = inlineEdit(c.doc, row, `${c.display} value`, (v) => {
11931
+ g.start(), g.set(snapTo(v, !1));
11932
+ }, () => control), release = () => {
11933
+ if (!dragging)
11934
+ return;
11935
+ if (dragging = !1, row.classList.remove("knobs-active"), !moved)
11936
+ editor.start(mixed ? "" : fmt(value, places()));
11937
+ c.wake();
11938
+ };
11939
+ control.addEventListener("pointerup", release), control.addEventListener("pointercancel", release);
11940
+ let panAt, give = textGive(read, () => c.wake());
11941
+ onPan(control, {
11942
+ accept: () => !dragging,
11943
+ move(dx) {
11944
+ let step = f().step;
11945
+ if (panAt === void 0)
11946
+ g.start(), panAt = { at: value / step, give: 0 }, row.classList.add("knobs-active");
11947
+ 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)
11948
+ give.hold(panAt.give);
11949
+ },
11950
+ lift: () => give.release(),
11951
+ end() {
11952
+ panAt = void 0, give.release(), row.classList.remove("knobs-active"), c.wake();
11953
+ },
11954
+ step(direction) {
11955
+ g.start(), g.set(snapTo(value + direction * f().step, !1));
11956
+ }
11957
+ }), control.addEventListener("keydown", (event) => {
11958
+ if (event.target !== control || !plainKey(event))
11959
+ return;
11960
+ let dir = arrowOf(event), char = typed(event);
11961
+ if (dir)
11962
+ if (event.preventDefault(), event.stopPropagation(), g.start(), event.shiftKey) {
11963
+ let s = Math.max(15, f().step), k = dir > 0 ? Math.floor(value / s + 0.000000001) + 1 : Math.ceil(value / s - 0.000000001) - 1;
11964
+ g.set(snapTo(k * s, !1));
11965
+ } else
11966
+ g.set(snapTo(value + dir * f().step * (event.altKey ? 0.1 : 1), !1));
11967
+ else if (event.key === "Home" || event.key === "End")
11968
+ event.preventDefault(), g.start(), g.set(snapTo(event.key === "Home" ? f().min : f().max - (f().wrap ? f().step : 0), !1));
11969
+ else if (event.key === "Enter" && keyPrefs(control).edit)
11970
+ event.preventDefault(), event.stopPropagation(), editor.start(mixed ? "" : fmt(value, places()));
11971
+ else if (char && keyPrefs(control).edit)
11972
+ event.preventDefault(), event.stopPropagation(), editor.start(char, !1);
11973
+ });
11974
+ let first = !0, gpu = {
11975
+ kind: "angle",
11976
+ key: c.key,
11977
+ display: c.display,
11978
+ row,
11979
+ labelEl: label,
11980
+ control,
11981
+ read,
11982
+ dial,
11983
+ turn: () => mixed ? 0 : turn,
11984
+ dragging: () => dragging,
11985
+ editing: () => editor.open,
11986
+ wave: c.wave,
11987
+ isModified: c.isModified
11988
+ };
11989
+ return {
11990
+ row,
11991
+ labelEl: label,
11992
+ input: control,
11993
+ stops: () => [control],
11994
+ update(next, delay, instant, isMixed = !1) {
11995
+ if (mixed = isMixed, first || instant)
11996
+ value = turn = next;
11997
+ else
11998
+ follow(next);
11999
+ paint(first || Boolean(instant) || dragging, delay), first = !1;
12000
+ },
12001
+ dispose() {
12002
+ rolling.dispose();
12003
+ },
12004
+ tracks: [],
12005
+ gpu: [gpu]
12006
+ };
12007
+ }
12008
+ var SEED_STEP = 12, DICE_ROLL_MS = 620;
12009
+ function seedRow(c) {
12010
+ let { row, label, el } = shell(c, "seed"), f = () => c.field, control = el("span", "knobs-seed");
12011
+ control.tabIndex = 0, control.setAttribute("role", "spinbutton"), control.setAttribute("aria-label", c.display);
12012
+ let read = el("span", "knobs-seed-read");
12013
+ control.append(read);
12014
+ let reel = el("span", "knobs-seed-digits");
12015
+ read.append(reel);
12016
+ let roller = it(reel, {
12017
+ value: c.get(),
12018
+ format: { useGrouping: !1, maximumFractionDigits: 0 },
12019
+ duration: 1,
12020
+ motionBlur: !0
12021
+ }), places = [], initialized = !1, diced = !1, roll = el("button", "knobs-seed-roll");
12022
+ roll.type = "button", roll.tabIndex = -1, roll.setAttribute("aria-label", `Roll ${c.display}`);
12023
+ let die = createHeaderDie(c.doc, () => c.wake(), GROUP_DIE);
12024
+ roll.append(die.node), row.append(control, roll);
12025
+ let value = c.get(), mixed = !1, g = gesture(c), set = (v) => {
12026
+ stopCarry(), g.start(), g.set(Math.round(clamp(v, f().min, f().max)));
12027
+ }, reroll = () => {
12028
+ let { min, max } = f(), next = value;
12029
+ for (let i = 0;i < 8 && next === value; i++)
12030
+ next = min + Math.floor(Math.random() * (max - min + 1));
12031
+ die.roll(), diced = !0, set(next), diced = !1;
12032
+ };
12033
+ roll.onclick = () => reroll();
12034
+ let editor = inlineEdit(c.doc, row, `${c.display} value`, set, () => control), give = textGive(read, () => c.wake()), recent = moves(), panned, carry = [], stopCarry = () => {
12035
+ for (let t of carry)
12036
+ clearTimeout(t);
12037
+ carry = [];
12038
+ }, scrubBy = (steps) => {
12039
+ let { min, max } = f(), next = clamp(value + steps, min, max);
12040
+ if (next === value)
12041
+ return !1;
12042
+ let dir = next > value ? 1 : -1;
12043
+ for (let i = 0;i < Math.min(3, Math.abs(next - value)); i++)
12044
+ die.turn(dir);
12045
+ return g.set(next), tick(control), !0;
12046
+ }, letGo = () => {
12047
+ panned = void 0, give.release(), row.classList.remove("knobs-active"), c.wake();
12048
+ };
12049
+ onPan(row, {
12050
+ accept: () => !editor.open,
12051
+ move(dx) {
12052
+ if (!panned)
12053
+ stopCarry(), g.start(), recent.reset(), panned = { travel: 0, give: 0 }, row.classList.add("knobs-active"), c.wake();
12054
+ recent.add(dx);
12055
+ let { min, max } = f(), { next, steps } = stepBand(panned, dx, SEED_STEP, [value - min, max - value]);
12056
+ if (panned = next, steps)
12057
+ scrubBy(steps);
12058
+ if (next.give !== give.give)
12059
+ give.hold(next.give);
12060
+ },
12061
+ lift() {
12062
+ if (!panned)
12063
+ return;
12064
+ let n = carrySteps(recent.speed()), dir = Math.sign(recent.speed());
12065
+ letGo();
12066
+ for (let i = 0;i < n; i++)
12067
+ carry.push(setTimeout(() => {
12068
+ if (!scrubBy(dir))
12069
+ stopCarry();
12070
+ }, carryAt(i)));
12071
+ },
12072
+ end: letGo,
12073
+ step(direction) {
12074
+ stopCarry(), set(value + direction);
12075
+ }
12076
+ }), control.addEventListener("click", () => {
12077
+ if (!editor.open)
12078
+ editor.start(mixed ? "" : String(value));
12079
+ }), control.addEventListener("keydown", (event) => {
12080
+ if (event.target !== control || !plainKey(event))
12081
+ return;
12082
+ let dir = arrowOf(event), char = typed(event);
12083
+ if (dir)
12084
+ event.preventDefault(), event.stopPropagation(), set(value + dir * bigOf(event));
12085
+ else if (event.key === " ")
12086
+ event.preventDefault(), event.stopPropagation(), reroll();
12087
+ else if (event.key === "Enter" && keyPrefs(control).edit)
12088
+ event.preventDefault(), event.stopPropagation(), editor.start(mixed ? "" : String(value));
12089
+ else if (char && keyPrefs(control).edit)
12090
+ event.preventDefault(), event.stopPropagation(), editor.start(char, !1);
12091
+ });
12092
+ let paint = (previous, instant) => {
12093
+ read.toggleAttribute("data-mixed", mixed), control.setAttribute("aria-valuenow", String(value));
12094
+ let ms = !initialized || instant ? 0 : diced ? DICE_ROLL_MS : rollMs(row);
12095
+ initialized = !0, syncPlaces(places, String(value), diced ? 1 : Math.sign(value - previous), ms, 0);
12096
+ let dom = ms > 0 && row.closest('[data-gpu-active="true"]') === null;
12097
+ if (roller.update({
12098
+ value,
12099
+ animated: !0,
12100
+ duration: dom ? ms : 1,
12101
+ motionBlur: dom,
12102
+ direction: diced ? "up" : "auto"
12103
+ }), ms > 0)
12104
+ c.wake();
12105
+ }, gpu = {
12106
+ kind: "seed",
12107
+ key: c.key,
12108
+ display: c.display,
12109
+ row,
12110
+ labelEl: label,
12111
+ control,
12112
+ read,
12113
+ places,
12114
+ mixed: () => mixed,
12115
+ die: { button: roll, die },
12116
+ editing: () => editor.open,
12117
+ wave: c.wave,
12118
+ isModified: c.isModified
12119
+ };
12120
+ return {
12121
+ row,
12122
+ labelEl: label,
12123
+ input: control,
12124
+ stops: () => [control],
12125
+ update(next, _delay, instant, isMixed = !1) {
12126
+ let previous = value;
12127
+ value = next, mixed = isMixed, paint(previous, Boolean(instant));
12128
+ },
12129
+ dispose: () => {
12130
+ stopCarry(), die.dispose(), roller.destroy();
12131
+ },
12132
+ tracks: [],
12133
+ gpu: [gpu]
12134
+ };
12135
+ }
12136
+ var CAP_MORPH = springLinear(SPRINGS2.snappy);
12137
+ function keyCap(o) {
12138
+ let chip = o.doc.createElement("button");
12139
+ chip.className = "knobs-keys", chip.type = "button";
12140
+ let text = o.doc.createElement("span");
12141
+ text.className = "knobs-keys-text", chip.append(text);
12142
+ let value = "", capturing = !1, preview = "", morph, write = (shown) => {
12143
+ let glide = chip.isConnected && chip.offsetWidth > 0 && !chip.closest('[data-gpu-active="true"]') && !reducedMotion4(o.doc), from = glide ? chip.getBoundingClientRect().width : 0;
12144
+ if (morph?.cancel(), text.textContent = shown, !glide)
12145
+ return;
12146
+ let to = chip.getBoundingClientRect().width;
12147
+ if (text.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 150, easing: "ease-out" }), Math.abs(to - from) < 0.5)
12148
+ return;
12149
+ chip.dataset.morph = "", morph = chip.animate([{ width: `${from}px` }, { width: `${to}px` }], {
12150
+ duration: CAP_MORPH.ms,
12151
+ easing: CAP_MORPH.easing
12152
+ }), morph.onfinish = () => delete chip.dataset.morph, morph.oncancel = () => delete chip.dataset.morph;
12153
+ }, paint = () => {
12154
+ let shown = capturing ? preview || "Type keys…" : value === void 0 ? "—" : value ? comboLabel2(value) : o.empty;
12155
+ if (text.textContent !== shown)
12156
+ write(shown);
12157
+ 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();
12158
+ }, capture = (on) => {
12159
+ if (capturing === on)
12160
+ return;
12161
+ if (on && o.row.closest("[data-disabled],[data-readonly]"))
12162
+ return;
12163
+ capturing = on, preview = "", o.row.classList.toggle("knobs-active", on), paint();
12164
+ }, held = (event) => {
12165
+ let mods = heldMods(event);
12166
+ preview = mods.length ? comboLabel2(`${mods.join("+")}+…`) : "", paint();
12167
+ };
12168
+ return chip.onclick = () => capture(!capturing), chip.addEventListener("keydown", (event) => {
12169
+ if (event.isComposing)
12170
+ return;
12171
+ if (!capturing) {
12172
+ if ((event.key === "Enter" || event.key === " ") && plainKey(event) && !event.altKey)
12173
+ event.preventDefault(), event.stopPropagation(), capture(!0);
12174
+ return;
12175
+ }
12176
+ if (event.key === "Tab")
12177
+ return capture(!1);
12178
+ event.preventDefault(), event.stopPropagation();
12179
+ let mods = heldMods(event);
12180
+ if (event.key === "Escape" && !mods.length)
12181
+ return capture(!1);
12182
+ let take = (combo) => {
12183
+ value = combo, capture(!1), o.set(combo);
12184
+ };
12185
+ if ((event.key === "Backspace" || event.key === "Delete") && !mods.length)
12186
+ return take("");
12187
+ let combo = comboFromEvent2(event);
12188
+ if (!combo)
12189
+ return held(event);
12190
+ take(combo);
12191
+ }), chip.addEventListener("keyup", (event) => {
12192
+ if (capturing)
12193
+ held(event);
12194
+ }), chip.addEventListener("blur", () => capture(!1)), paint(), {
12195
+ chip,
12196
+ text,
12197
+ capturing: () => capturing,
12198
+ show(combo) {
12199
+ value = combo, paint();
12200
+ }
12201
+ };
12142
12202
  }
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;
12203
+ function keybindRow(c) {
12204
+ let { row, label } = shell(c, "keybind"), cap = keyCap({
12205
+ doc: c.doc,
12206
+ row,
12207
+ empty: "None",
12208
+ name: () => c.display,
12209
+ set: (combo) => c.set(combo),
12210
+ wake: c.wake
12211
+ }), { chip, text } = cap;
12212
+ row.append(chip), cap.show(c.get());
12213
+ let gpu = {
12214
+ kind: "keybind",
12215
+ key: c.key,
12216
+ display: c.display,
12217
+ row,
12218
+ labelEl: label,
12219
+ chip,
12220
+ text,
12221
+ capturing: cap.capturing,
12222
+ wave: c.wave,
12223
+ isModified: c.isModified
12224
+ };
12225
+ return {
12226
+ row,
12227
+ labelEl: label,
12228
+ input: chip,
12229
+ stops: () => [chip],
12230
+ update(next, _delay, _instant, isMixed = !1) {
12231
+ cap.show(isMixed ? void 0 : next);
12232
+ },
12233
+ dispose() {},
12234
+ tracks: [],
12235
+ gpu: [gpu]
12236
+ };
12150
12237
  }
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;
12238
+ function textRow(c) {
12239
+ let { row, label, el } = shell(c, "text"), multiline = Boolean(c.field.multiline);
12240
+ row.toggleAttribute("data-multiline", multiline);
12241
+ let input = multiline ? Object.assign(el("textarea", "knobs-text"), { rows: 2 }) : Object.assign(el("input", "knobs-text"), { type: "text" });
12242
+ input.spellcheck = !1, input.autocomplete = "off", input.placeholder = c.field.placeholder ?? "", input.setAttribute("aria-label", c.display), input.value = c.get(), row.append(input);
12243
+ let typed = !1;
12244
+ input.addEventListener("focus", () => typed = !1), input.addEventListener("input", () => {
12245
+ c.set(input.value, { coalesce: typed }), typed = !0;
12246
+ }), input.addEventListener("keydown", (e) => {
12247
+ let event = e;
12248
+ if (event.isComposing)
12249
+ return;
12250
+ if (!(event.key === "Escape" || event.key === "Enter" && (!multiline || event.metaKey || event.ctrlKey)))
12251
+ return;
12252
+ event.preventDefault(), event.stopPropagation(), input.blur();
12253
+ });
12254
+ let gpu = {
12255
+ kind: "text",
12256
+ key: c.key,
12257
+ display: c.display,
12258
+ row,
12259
+ labelEl: label,
12260
+ wave: c.wave,
12261
+ isModified: c.isModified
12262
+ };
12263
+ return {
12264
+ row,
12265
+ labelEl: label,
12266
+ input,
12267
+ stops: () => [input],
12268
+ update(next, _delay, _instant, isMixed = !1) {
12269
+ input.placeholder = isMixed ? "—" : c.field.placeholder ?? "";
12270
+ let shown = isMixed ? "" : next;
12271
+ if (input.value !== shown)
12272
+ input.value = shown;
12273
+ },
12274
+ dispose() {},
12275
+ tracks: [],
12276
+ gpu: [gpu]
12277
+ };
12278
+ }
12279
+ function naturalWidth(b) {
12280
+ let cs = getComputedStyle(b), w = (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0), parts = [...b.children];
12281
+ for (let part of parts) {
12282
+ let ps = getComputedStyle(part), margin = parseFloat(ps.marginRight) || 0;
12283
+ w += part instanceof HTMLElement ? part.scrollWidth + margin : parseFloat(ps.width) || 0;
12284
+ }
12285
+ return Math.ceil(w + (parseFloat(cs.columnGap) || 0) * Math.max(0, parts.length - 1));
12286
+ }
12287
+ function toolbarRow(c, press, keyOf = toolbarShortcut2) {
12288
+ let el = make(c.doc), row = el("div", "knobs-row knobs-toolbar");
12289
+ row.dataset.field = c.key, row.setAttribute("role", "toolbar"), row.setAttribute("aria-label", c.display);
12290
+ let field = c.field, buttons = [], last = 0, keys = "", untips = [], keysNow = () => field.items.map((item) => keyOf(item)?.key ?? "").join("\x00"), showIcons = (on) => {
12291
+ if (row.hasAttribute("data-icons") === on && untips.length === (on ? buttons.length : 0))
12292
+ return;
12293
+ row.toggleAttribute("data-icons", on);
12294
+ for (let off of untips)
12295
+ off();
12296
+ untips = on ? field.items.map((item, i) => {
12297
+ let key = keyOf(item);
12298
+ return tooltip(buttons[i], item.label, key ? { shortcut: key.key } : {});
12299
+ }) : [];
12300
+ }, layout = () => {
12301
+ let cs = getComputedStyle(row), avail = row.clientWidth - (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0);
12302
+ if (avail <= 0 || !buttons.length)
12303
+ return;
12304
+ let gap = parseFloat(cs.columnGap) || 0, iconed = field.items.every((item) => item.icon), icons = iconed && field.labels === !1;
12305
+ if (iconed && field.labels === void 0) {
12306
+ let was = row.hasAttribute("data-icons");
12307
+ if (was)
12308
+ row.removeAttribute("data-icons");
12309
+ let widest = Math.max(...buttons.map(naturalWidth));
12310
+ if (was)
12311
+ row.setAttribute("data-icons", "");
12312
+ icons = toolbarColumns(buttons.length, widest, avail, gap) < buttons.length;
12313
+ }
12314
+ showIcons(icons);
12315
+ let cols = String(icons ? buttons.length : toolbarColumns(buttons.length, Math.max(...buttons.map(naturalWidth)), avail, gap));
12316
+ if (row.style.getPropertyValue("--k-tool-cols") !== cols)
12317
+ row.style.setProperty("--k-tool-cols", cols), c.wake();
12318
+ }, sizes = new ResizeObserver(layout);
12319
+ sizes.observe(row), c.doc.fonts?.ready.then(() => row.isConnected && layout());
12320
+ let gpu = {
12321
+ kind: "toolbar",
12322
+ key: c.key,
12323
+ display: c.display,
12324
+ row,
12325
+ items: [],
12326
+ wave: c.wave,
12327
+ isModified: () => !1
12328
+ }, build = () => {
12329
+ let items = [];
12330
+ buttons = field.items.map((item, i) => {
12331
+ let b = el("button", "knobs-tool");
12332
+ b.type = "button", b.tabIndex = -1, b.disabled = Boolean(item.disabled), b.dataset.item = item.id ?? item.label, b.setAttribute("aria-label", item.label);
12333
+ let iconEl;
12334
+ if (item.icon)
12335
+ iconEl = iconSvg(c.doc, item.icon), b.append(iconEl);
12336
+ let name = el("span", "knobs-tool-label", item.label);
12337
+ b.append(name);
12338
+ let chip, key = keyOf(item);
12339
+ if (key) {
12340
+ if (chip = el("kbd", "knobs-tool-key", comboLabel2(key.key)), b.append(chip), key.scope !== "none")
12341
+ b.setAttribute("aria-keyshortcuts", key.key);
12342
+ }
12343
+ return b.onclick = () => {
12344
+ last = i, press(item);
12345
+ }, b.addEventListener("focus", () => last = i), items.push({
12346
+ button: b,
12347
+ ...item.icon ? { icon: item.icon } : {},
12348
+ ...iconEl ? { iconEl } : {},
12349
+ label: name,
12350
+ ...chip ? { chip } : {}
12351
+ }), b;
12352
+ }), row.replaceChildren(...buttons), gpu.items = items, keys = keysNow();
12353
+ for (let off of untips)
12354
+ off();
12355
+ if (untips = [], row.isConnected)
12356
+ layout();
12157
12357
  };
12358
+ build(), row.addEventListener("keydown", (event) => {
12359
+ let at = buttons.indexOf(event.target), dir = arrowOf(event);
12360
+ if (at < 0 || !dir || !plainKey(event))
12361
+ return;
12362
+ event.preventDefault(), event.stopPropagation();
12363
+ let live = buttons.filter((b) => !b.disabled), i = live.indexOf(buttons[at]);
12364
+ live[(i + dir + live.length) % live.length]?.focus();
12365
+ });
12366
+ let first = () => buttons.find((b) => !b.disabled) ?? buttons[0];
12158
12367
  return {
12159
- wake() {
12160
- if (!raf)
12161
- raf = win.requestAnimationFrame(tick);
12368
+ row,
12369
+ labelEl: row,
12370
+ get input() {
12371
+ let b = buttons[last];
12372
+ return b && !b.disabled ? b : first();
12162
12373
  },
12163
- get running() {
12164
- return raf !== 0;
12374
+ get field() {
12375
+ return field;
12165
12376
  },
12166
- stop() {
12167
- if (raf)
12168
- win.cancelAnimationFrame(raf);
12169
- raf = 0, last = 0;
12377
+ stops() {
12378
+ let b = buttons[last];
12379
+ return [b && !b.disabled ? b : first()];
12380
+ },
12381
+ update() {},
12382
+ refresh() {
12383
+ if (keysNow() === keys)
12384
+ return;
12385
+ let focused = buttons.indexOf(c.doc.activeElement);
12386
+ if (build(), focused >= 0)
12387
+ buttons[focused]?.focus({ preventScroll: !0 });
12388
+ c.wake();
12389
+ },
12390
+ match(event, global) {
12391
+ return field.items.find((item) => {
12392
+ let key = keyOf(item);
12393
+ return key !== void 0 && !item.disabled && key.scope === (global ? "page" : "panel") && matchesCombo2(key.key, event);
12394
+ });
12395
+ },
12396
+ press(item) {
12397
+ let i = field.items.indexOf(item), b = buttons[i];
12398
+ if (b && !reducedMotion4(c.doc))
12399
+ b.classList.add("knobs-pressed"), setTimeout(() => b.classList.remove("knobs-pressed"), 140), c.wake();
12400
+ press(item);
12401
+ },
12402
+ swap(next) {
12403
+ field = next, build(), c.wake();
12404
+ },
12405
+ dispose() {
12406
+ sizes.disconnect();
12407
+ for (let off of untips)
12408
+ off();
12409
+ },
12410
+ tracks: [],
12411
+ gpu: [gpu]
12412
+ };
12413
+ }
12414
+
12415
+ // src/panel/search.ts
12416
+ function createSearch(doc, options) {
12417
+ let element = doc.createElement("div");
12418
+ element.className = "knobs-search";
12419
+ let field = doc.createElement("label");
12420
+ field.className = "knobs-search-field", field.append(iconSvg(doc, "search"));
12421
+ let input = doc.createElement("input");
12422
+ 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);
12423
+ let on = /* @__PURE__ */ new Set, chips = options.chips.map((spec) => {
12424
+ let b = doc.createElement("button");
12425
+ b.type = "button", b.className = "knobs-search-chip", b.tabIndex = -1, b.dataset.chip = spec.id, b.setAttribute("aria-pressed", "false");
12426
+ let name = doc.createElement("span");
12427
+ if (name.textContent = spec.label, b.append(name), spec.count) {
12428
+ let n = doc.createElement("span");
12429
+ n.className = "knobs-search-count", b.append(n);
12430
+ }
12431
+ return b.onclick = () => {
12432
+ if (on.has(spec.id))
12433
+ on.delete(spec.id);
12434
+ else
12435
+ on.add(spec.id);
12436
+ b.setAttribute("aria-pressed", String(on.has(spec.id))), options.change();
12437
+ }, element.append(b), b;
12438
+ });
12439
+ return input.addEventListener("input", () => options.change()), {
12440
+ element,
12441
+ input,
12442
+ chips,
12443
+ query: () => input.value.trim().toLowerCase(),
12444
+ active: () => on,
12445
+ filtering: () => input.value.trim() !== "" || on.size > 0,
12446
+ counts(counts) {
12447
+ for (let b of chips) {
12448
+ let n = b.querySelector(".knobs-search-count");
12449
+ if (!n)
12450
+ continue;
12451
+ let text = String(counts.get(b.dataset.chip) ?? 0);
12452
+ if (n.textContent !== text)
12453
+ n.textContent = text;
12454
+ let none = text === "0" && !on.has(b.dataset.chip);
12455
+ if (b.hasAttribute("data-none") !== none)
12456
+ b.toggleAttribute("data-none", none);
12457
+ }
12458
+ },
12459
+ set(query, next) {
12460
+ let changed = !1;
12461
+ if (query !== void 0 && input.value !== query)
12462
+ input.value = query, changed = !0;
12463
+ if (next) {
12464
+ let want = new Set(next.filter((id) => chips.some((b) => b.dataset.chip === id)));
12465
+ if (want.size !== on.size || [...want].some((id) => !on.has(id))) {
12466
+ on.clear();
12467
+ for (let id of want)
12468
+ on.add(id);
12469
+ for (let b of chips)
12470
+ b.setAttribute("aria-pressed", String(on.has(b.dataset.chip)));
12471
+ changed = !0;
12472
+ }
12473
+ }
12474
+ if (changed)
12475
+ options.change();
12476
+ },
12477
+ clear() {
12478
+ let had = input.value !== "" || on.size > 0;
12479
+ input.value = "", on.clear();
12480
+ for (let b of chips)
12481
+ b.setAttribute("aria-pressed", "false");
12482
+ if (had)
12483
+ options.change();
12484
+ },
12485
+ focus() {
12486
+ input.focus({ preventScroll: !0 }), input.select();
12170
12487
  }
12171
12488
  };
12172
12489
  }
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
- };
12490
+ function matchSpan(text, query) {
12491
+ if (!query)
12492
+ return;
12493
+ let at = text.toLowerCase().indexOf(query);
12494
+ return at < 0 ? void 0 : [at, at + query.length];
12495
+ }
12496
+ function markLabel(label, text, span) {
12497
+ let doc = label.ownerDocument, parts = [];
12498
+ if (!span)
12499
+ parts.push(doc.createTextNode(text));
12500
+ else {
12501
+ if (span[0] > 0)
12502
+ parts.push(doc.createTextNode(text.slice(0, span[0])));
12503
+ let mark = doc.createElement("mark");
12504
+ if (mark.className = "knobs-match", mark.textContent = text.slice(span[0], span[1]), parts.push(mark), span[1] < text.length)
12505
+ parts.push(doc.createTextNode(text.slice(span[1])));
12506
+ }
12507
+ let current = [...label.childNodes].filter((c) => c.nodeType === 3 || c instanceof HTMLElement && c.classList.contains("knobs-match"));
12508
+ if (current.length === parts.length && current.every((c, i) => c.nodeName === parts[i].nodeName && c.textContent === parts[i].textContent))
12509
+ return;
12510
+ for (let c of current)
12511
+ c.remove();
12512
+ label.prepend(...parts);
12513
+ }
12181
12514
 
12182
12515
  // src/ui/readout-model.ts
12183
12516
  var SERIES_TOKENS = [
@@ -13454,69 +13787,6 @@ function readoutRow(c) {
13454
13787
  };
13455
13788
  }
13456
13789
 
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
13790
  // src/ui/toast.ts
13521
13791
  var regions = /* @__PURE__ */ new WeakMap;
13522
13792
  function toast(message, options = {}) {
@@ -14496,6 +14766,17 @@ function positionPanel(panel, header, anchor, id, storage) {
14496
14766
  };
14497
14767
  }
14498
14768
 
14769
+ // src/panel/dock.ts
14770
+ function paneHeadOf(host) {
14771
+ let head = host.closest(".knobs-pane-body")?.parentElement?.querySelector(":scope > .knobs-pane-head");
14772
+ return head && !head.querySelector(":scope > [data-docked]") ? head : void 0;
14773
+ }
14774
+ function dock(actions, head) {
14775
+ return actions.dataset.docked = "", head.insertBefore(actions, head.querySelector(":scope > .knobs-pane-collapse")), () => {
14776
+ actions.remove(), delete actions.dataset.docked;
14777
+ };
14778
+ }
14779
+
14499
14780
  // src/panel/presence.ts
14500
14781
  var EXIT = springMotion2(SPRINGS2.snappy), ENTER = springMotion2(SPRINGS2.default), FADE2 = springMotion2(FADE), BLUR2 = 4, SCALE = 0.96, LEAD2 = 0.4, FOLDED = [
14501
14782
  "height",
@@ -14781,7 +15062,7 @@ function buildPanel(controls, host, options) {
14781
15062
  }
14782
15063
  popover.append(popGrid);
14783
15064
  let baseAt = (scope, from) => {
14784
- let group = scope === "row" ? rows[rowAt(from)]?.group : scope === "group" ? cardOf(from)?.dataset.group ?? void 0 : void 0, keys = scope === "row" ? rows[rowAt(from)]?.keys ?? [] : scope === "group" ? cardKeys(cardOf(from)) : scope === "selection" ? selectionKeys() ?? [] : void 0, index = group === void 0 ? -1 : controls.activeGroupPreset(group), ref = controls.activePreset(), name = index >= 0 ? controls.groupPresets[group]?.[index]?.name : ref?.kind === "preset" ? presetLabel(ref.id) : ref ? presetStrip.getEntries().find((e) => e.id === `v:${ref.id}`)?.label : void 0;
15065
+ let group = scope === "row" ? rows[rowAt(from)]?.group : scope === "group" ? cardOf(from)?.dataset.group ?? void 0 : void 0, keys = scope === "row" ? rows[rowAt(from)]?.keys ?? [] : scope === "group" ? cardKeys(cardOf(from)) : scope === "selection" ? selectionKeys() ?? [] : void 0, index = group === void 0 ? -1 : controls.activeGroupPreset(group), where = (keys?.length ? controls.scopeOf(keys[0]) : stripScope()) ?? null, ref = where === null ? null : controls.activePreset(where), name = index >= 0 ? controls.groupPresets[group]?.[index]?.name : ref?.kind === "preset" ? presetLabel(ref.id, where) : ref ? (where === "" ? controls.variants : controls.scopes[where]?.variants)?.list().find((v) => v.id === ref.id)?.name : void 0;
14785
15066
  if (!name)
14786
15067
  return null;
14787
15068
  let off = keys ? keys.some((k) => controls.isModified(k)) : controls.isModified();
@@ -14821,7 +15102,7 @@ function buildPanel(controls, host, options) {
14821
15102
  randomButton.hidden = !0;
14822
15103
  randomButton.onclick = (event) => run(() => reroll(void 0, event.altKey)), randomButton.querySelector(".knobs-icon")?.replaceWith(die.node);
14823
15104
  let resetAll = () => {
14824
- if (!resetBtn.hidden && resetBtn.dataset.shown === "true" && !header.hidden)
15105
+ if (!resetBtn.hidden && resetBtn.dataset.shown === "true" && headerShown())
14825
15106
  turn(resetBtn);
14826
15107
  cue(root, "reset"), controls.reset();
14827
15108
  }, turn = (button) => {
@@ -14875,7 +15156,7 @@ function buildPanel(controls, host, options) {
14875
15156
  if (helpButton?.hidden)
14876
15157
  titleEl.title = `${titleEl.textContent ? `${titleEl.textContent}
14877
15158
  ` : ""}Press ${show("help")} for keyboard shortcuts`;
14878
- let presetLabel = (id) => options.presetLabels?.[id] ?? (id === "default" ? options.defaultLabel : void 0) ?? controls.presetLabel(id), presetStrip = createPresetStrip(doc, controls, showVariants, run, () => wakeGpu(), presetLabel, (entries) => presetsTouchTab(entries)), tabStrip = createTabStrip(doc, (id, animate) => selectTab(id, animate)), activeTab, searchOptions = typeof options.search === "object" ? options.search : {}, chipTests = /* @__PURE__ */ new Map, search = options.search && !page ? createSearch(doc, {
15159
+ let presetLabel = (id, scope = "") => options.presetLabels?.[id] ?? (id === "default" ? options.defaultLabel : void 0) ?? controls.presetLabel(id, scope), presetStrip = createPresetStrip(doc, controls, showVariants, run, () => wakeGpu(), presetLabel, (entries) => presetsTouchTab(entries), () => stripScope()), tabStrip = createTabStrip(doc, (id, animate) => selectTab(id, animate)), activeTab, searchOptions = typeof options.search === "object" ? options.search : {}, chipTests = /* @__PURE__ */ new Map, search = options.search && !page ? createSearch(doc, {
14879
15160
  placeholder: searchOptions.placeholder ?? "Search",
14880
15161
  chips: (searchOptions.chips ?? ["modified"]).map((chip) => {
14881
15162
  if (chip === "modified")
@@ -14894,6 +15175,19 @@ function buildPanel(controls, host, options) {
14894
15175
  header.append(titleEl, headActions);
14895
15176
  if (options.header === !1 && !page)
14896
15177
  header.hidden = !0;
15178
+ let untitled = !page && !header.hidden && !titleEl.textContent, paneHead = untitled && inline ? paneHeadOf(host) : void 0, undock = paneHead ? dock(headActions, paneHead) : void 0;
15179
+ if (undock)
15180
+ header.hidden = !0;
15181
+ let headerShown = () => Boolean(undock) || !header.hidden, syncBand = (animate) => {
15182
+ if (!untitled || undock)
15183
+ return;
15184
+ let any = headerButtons.some((b) => !b.button.hidden && b.button.dataset.shown !== "false");
15185
+ if (!animate)
15186
+ header.hidden = !any;
15187
+ else if ((!header.hidden && header.dataset.leaving !== "true") !== any)
15188
+ reveal(header, any, () => wakeGpu());
15189
+ };
15190
+ presetStrip.display(page ? "chips" : options.presetDisplay ?? "auto");
14897
15191
  let allActions = [
14898
15192
  ...controls.actions ?? [],
14899
15193
  ...options.actions ?? []
@@ -15186,6 +15480,8 @@ function buildPanel(controls, host, options) {
15186
15480
  }
15187
15481
  let specs = [], placed = /* @__PURE__ */ new Set;
15188
15482
  for (let [key, field] of entries) {
15483
+ if (field.kind === "choice" && field.variants && !controls.isPinned(key))
15484
+ continue;
15189
15485
  let pair = field.kind === "knob" && field.spring ? halves.get(field.spring.id) : void 0;
15190
15486
  if (pair?.duration && pair.bounce && field.kind === "knob" && field.spring) {
15191
15487
  if (placed.has(field.spring.id))
@@ -15501,7 +15797,7 @@ function buildPanel(controls, host, options) {
15501
15797
  writeSession(controls.id, { expanded: open ? [...others, key] : others });
15502
15798
  },
15503
15799
  ...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);
15800
+ }, 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
15801
  if (field.shortcut)
15506
15802
  editor.input.setAttribute("aria-keyshortcuts", field.shortcut);
15507
15803
  editor.labelEl.ondblclick = resetRow;
@@ -15651,6 +15947,8 @@ function buildPanel(controls, host, options) {
15651
15947
  else
15652
15948
  input.removeAttribute("aria-readonly");
15653
15949
  }
15950
+ if (row.input.classList.contains("knobs-text"))
15951
+ row.input.readOnly = disabled || readOnly;
15654
15952
  }
15655
15953
  let rows = [], leavingRows = /* @__PURE__ */ new Set, presenceRuns = 0, present = (node, show, delay, after) => {
15656
15954
  if (presenceRuns++, node.dataset.presence = show ? "in" : "out", show)
@@ -15909,14 +16207,33 @@ function buildPanel(controls, host, options) {
15909
16207
  return list;
15910
16208
  };
15911
16209
  function presetsTouchTab(entries) {
15912
- if (page || activeTab === void 0)
16210
+ if (page || activeTab === void 0 || stripScope() !== "")
15913
16211
  return !0;
15914
16212
  let keys = rows.flatMap((r) => {
15915
16213
  let tab = tabOf(r);
15916
- return !pinnedRow(r) && (tab === void 0 || tab === activeTab) ? r.keys : [];
15917
- });
16214
+ return !pinnedRow(r) && (tab === void 0 || tab === activeTab) ? r.keys.filter((k) => controls.scopeOf(k) === "") : [];
16215
+ }), v = variantsOf(controls.schema, controls.isPinned);
16216
+ if (v) {
16217
+ let tab = v.field.tab ?? (v.field.group !== void 0 ? groupTab(v.field.group) : void 0);
16218
+ if (tab === void 0 || tab === activeTab)
16219
+ keys.push(v.key);
16220
+ }
15918
16221
  return presetsTouch(entries, keys, controls.locked);
15919
16222
  }
16223
+ function stripScope() {
16224
+ let named = Object.keys(controls.scopes), panelWide = named.length === 0 || Object.keys(controls.presets).length > 1;
16225
+ if (page)
16226
+ return panelWide ? "" : null;
16227
+ if (activeTab === void 0)
16228
+ return panelWide ? "" : named[0] ?? null;
16229
+ if (named.includes(activeTab))
16230
+ return activeTab;
16231
+ return named.find((id) => rows.some((r) => r.group === id && tabOf(r) === activeTab)) ?? (panelWide ? "" : null);
16232
+ }
16233
+ let stripVariants = () => {
16234
+ let scope = stripScope();
16235
+ return (scope ? controls.scopes[scope]?.variants : void 0) ?? controls.variants;
16236
+ };
15920
16237
  function syncTabs() {
15921
16238
  let list = tabList(), has = (id) => id !== void 0 && list.some((t) => t.id === id);
15922
16239
  if (!has(activeTab))
@@ -15930,7 +16247,7 @@ function buildPanel(controls, host, options) {
15930
16247
  writeSession(controls.id, { tab: id });
15931
16248
  tabStrip.select(id, animate);
15932
16249
  let focused = doc.activeElement;
15933
- if (applyVisibility(animate ? "tab" : "instant"), focused && root.contains(focused) && focused.closest("[hidden],[data-leaving]"))
16250
+ if (presetStrip.update(), applyVisibility(animate ? "tab" : "instant"), focused && root.contains(focused) && focused.closest("[hidden],[data-leaving]"))
15934
16251
  tabStrip.focus({ preventScroll: !0, focusVisible: !1 });
15935
16252
  options.onTab?.(id), wakeGpu();
15936
16253
  }
@@ -16143,12 +16460,12 @@ function buildPanel(controls, host, options) {
16143
16460
  }, text = el("textarea");
16144
16461
  text.hidden = !0, text.name = "variant-json", text.spellcheck = !1, text.setAttribute("aria-label", "Import or copy text");
16145
16462
  let apply = button("Apply import", () => {
16146
- controls.variants.import(text.value), say("Presets imported."), reveal(text, !1), reveal(apply, !1), positioned?.constrain();
16463
+ stripVariants().import(text.value), say("Presets imported."), reveal(text, !1), reveal(apply, !1), positioned?.constrain();
16147
16464
  });
16148
16465
  apply.className = "knobs-apply-btn", apply.setAttribute("aria-label", "Import JSON"), apply.hidden = !0;
16149
16466
  let copy = async (value, inPlace = !0) => {
16150
16467
  try {
16151
- if (await doc.defaultView.navigator.clipboard.writeText(value), inPlace && !copyButton.hidden && !header.hidden)
16468
+ if (await doc.defaultView.navigator.clipboard.writeText(value), inPlace && !copyButton.hidden && headerShown())
16152
16469
  copied();
16153
16470
  else
16154
16471
  say("Copied.");
@@ -16168,7 +16485,7 @@ function buildPanel(controls, host, options) {
16168
16485
  }
16169
16486
  if (showVariants && showFooter) {
16170
16487
  let transfer = el("div");
16171
- transfer.className = "knobs-transfer", transfer.append(button("Export presets", () => void copy(controls.variants.export(), !1)), button("Import presets", () => {
16488
+ transfer.className = "knobs-transfer", transfer.append(button("Export presets", () => void copy(stripVariants().export(), !1)), button("Import presets", () => {
16172
16489
  reveal(text, !0), reveal(apply, !0), text.value = "", positioned?.constrain(), text.focus();
16173
16490
  })), popover.append(transfer);
16174
16491
  }
@@ -16200,6 +16517,9 @@ function buildPanel(controls, host, options) {
16200
16517
  root.append(customActions);
16201
16518
  root.append(text, apply, status, live);
16202
16519
  }
16520
+ let emptyEl = el("p", options.empty);
16521
+ if (emptyEl.className = "knobs-empty", emptyEl.hidden = !0, options.empty && !page)
16522
+ body.after(emptyEl);
16203
16523
  let shown = controls.get(), initialized = !1, cascade = (changedCount) => {
16204
16524
  if (changedCount < 2)
16205
16525
  return 0;
@@ -16233,6 +16553,7 @@ function buildPanel(controls, host, options) {
16233
16553
  }
16234
16554
  let shownBase = "", baseSignature = () => JSON.stringify([
16235
16555
  controls.activePreset(),
16556
+ Object.keys(controls.scopes).map((s) => controls.activePreset(s)),
16236
16557
  Object.keys(controls.groupPresets).map((g) => controls.activeGroupPreset(g))
16237
16558
  ]);
16238
16559
  function update() {
@@ -16245,6 +16566,8 @@ function buildPanel(controls, host, options) {
16245
16566
  land();
16246
16567
  if (controls.schema !== shape)
16247
16568
  shape = controls.schema, layoutRows(initialized && !landing);
16569
+ if (emptyEl.isConnected && emptyEl.hidden !== rows.length > 0)
16570
+ emptyEl.hidden = rows.length > 0;
16248
16571
  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
16572
  if (initialized && !landing) {
16250
16573
  for (let r of rows)
@@ -16343,7 +16666,7 @@ function buildPanel(controls, host, options) {
16343
16666
  let undoMoved = bloom(undoButton, controls.canUndo);
16344
16667
  if (bloom(redoButton, controls.canRedo) || undoMoved)
16345
16668
  wakeGpu();
16346
- if (controls.error && status.textContent !== controls.error)
16669
+ if (syncBand(initialized), controls.error && status.textContent !== controls.error)
16347
16670
  say(controls.error, 8000);
16348
16671
  for (let [group, c] of groupChips)
16349
16672
  c.show(String(controls.matchingGroupPreset(group)));
@@ -16852,7 +17175,7 @@ function buildPanel(controls, host, options) {
16852
17175
  row: rows[rowAt(active)]?.display,
16853
17176
  editor: t.level === "editor" ? partName(t.el, active) : void 0
16854
17177
  });
16855
- }, crumbs = options.crumbs === !1 || header.hidden || !titleEl.isConnected ? void 0 : createCrumbs({
17178
+ }, crumbs = options.crumbs !== !0 || header.hidden || !titleEl.isConnected ? void 0 : createCrumbs({
16856
17179
  header,
16857
17180
  title: titleEl,
16858
17181
  reduced: () => reducedMotion(),
@@ -16870,7 +17193,21 @@ function buildPanel(controls, host, options) {
16870
17193
  return;
16871
17194
  let t = lensTarget(to);
16872
17195
  jumpedTo = t && t !== "out" ? t.el : null, to.focus(quiet), queueScope();
16873
- }, titleDown;
17196
+ };
17197
+ root.addEventListener("mousedown", (event) => {
17198
+ if (event.button !== 0 || event.shiftKey || event.metaKey || event.ctrlKey)
17199
+ return;
17200
+ let target = event.target instanceof Element ? event.target : null, r = rows[rowAt(target)];
17201
+ if (!target || !r || r.leaving || r.row.closest("[hidden]"))
17202
+ return;
17203
+ let own = target.closest("button,input,select,textarea,a[href],[tabindex],[contenteditable]");
17204
+ if (own && r.row.contains(own))
17205
+ return;
17206
+ event.preventDefault(), r.input.focus({ preventScroll: !0, focusVisible: !1 });
17207
+ let t = lensTarget(doc.activeElement);
17208
+ jumpedTo = t && t !== "out" ? t.el : null, queueScope();
17209
+ }, !0);
17210
+ let titleDown;
16874
17211
  titleEl.addEventListener("pointerdown", (event) => {
16875
17212
  titleDown = [event.clientX, event.clientY];
16876
17213
  }), titleEl.addEventListener("click", (event) => {
@@ -16929,7 +17266,7 @@ function buildPanel(controls, host, options) {
16929
17266
  wasArmed = armed, root.toggleAttribute("data-k-armed", armed), wakeGpu();
16930
17267
  if (queueLens(), !popover.hidden)
16931
17268
  describeScope();
16932
- optionKeys.show("keyboard" in root.dataset ? optionsAt(active) : void 0);
17269
+ optionKeys.show("keyboard" in root.dataset ? optionsAt(active) ?? stripOptionsAt(active) : void 0);
16933
17270
  }, scopeQueued = !1, queueScope = () => {
16934
17271
  if (scopeQueued)
16935
17272
  return;
@@ -16962,6 +17299,17 @@ function buildPanel(controls, host, options) {
16962
17299
  if (!r || r.row.closest("[data-disabled],[data-readonly]"))
16963
17300
  return;
16964
17301
  return optionsOf(r);
17302
+ }, stripDigits = () => visible(presetStrip.element) && presetStrip.getEntries().length > 1, stripOptions = {
17303
+ get count() {
17304
+ return presetStrip.getEntries().length;
17305
+ },
17306
+ choose: (i) => presetStrip.select(i),
17307
+ shown: () => presetStrip.getButtons(),
17308
+ anchor: presetStrip.element
17309
+ }, stripOptionsAt = (node) => {
17310
+ if (hintCtrl.isActive || !node || !stripDigits())
17311
+ return;
17312
+ return (presetStrip.element.contains(node) || focusLevel(node) === "panel") && !presetStrip.nameField() ? stripOptions : void 0;
16965
17313
  }, pickOption = (event) => {
16966
17314
  if (!/^[1-9]$/.test(event.key) || event.altKey || event.shiftKey)
16967
17315
  return !1;
@@ -17166,12 +17514,20 @@ function buildPanel(controls, host, options) {
17166
17514
  event.preventDefault(), searchReturn = target, search.focus();
17167
17515
  return;
17168
17516
  }
17169
- if (page && /^[1-9]$/.test(key)) {
17170
- let group = focusedGroup(target);
17171
- if (group && groupChips.has(group)) {
17517
+ if (/^[1-9]$/.test(key)) {
17518
+ let group = page ? focusedGroup(target) : void 0;
17519
+ if (Boolean(group && groupChips.has(group))) {
17172
17520
  event.preventDefault(), run(() => applyGroupPreset(group, Number(key) - 1));
17173
17521
  return;
17174
17522
  }
17523
+ if (digitRoute(key, {
17524
+ options: !1,
17525
+ keeps: tracks.has(target),
17526
+ presets: stripDigits() ? presetStrip.getEntries().length : 0
17527
+ }) === "presets") {
17528
+ event.preventDefault(), presetStrip.select(Number(key) - 1);
17529
+ return;
17530
+ }
17175
17531
  }
17176
17532
  if (pressed("hint", event)) {
17177
17533
  if (event.preventDefault(), hintScrollY = win?.scrollY ?? 0, !unregisterHints || !pageHints(doc).start())
@@ -17575,8 +17931,8 @@ function buildPanel(controls, host, options) {
17575
17931
  inline,
17576
17932
  header,
17577
17933
  titleEl,
17578
- headerButtons,
17579
- headerDie: { button: randomButton, die },
17934
+ headerButtons: undock ? [] : headerButtons,
17935
+ ...undock ? {} : { headerDie: { button: randomButton, die } },
17580
17936
  locks: lockIcons,
17581
17937
  presetStrip,
17582
17938
  tabs: tabStrip,
@@ -17699,7 +18055,7 @@ function buildPanel(controls, host, options) {
17699
18055
  inHand.clear(), positioned?.dispose(), die.dispose();
17700
18056
  for (let g of groupDice.values())
17701
18057
  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();
18058
+ 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
18059
  }
17704
18060
  };
17705
18061
  if (!layout)