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

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.
@@ -46,7 +46,7 @@ import {
46
46
  pushReadout,
47
47
  READOUT,
48
48
  storageKey
49
- } from "./chunk-7kns3qe6.js";
49
+ } from "./chunk-v2g91mrm.js";
50
50
  import {
51
51
  resolveLook,
52
52
  themeOf,
@@ -119,7 +119,7 @@ import {
119
119
  createHintController,
120
120
  pageHints,
121
121
  toggleLayer
122
- } from "./chunk-nwfm1bfj.js";
122
+ } from "./chunk-bejz4qcg.js";
123
123
 
124
124
  // src/panel/die.ts
125
125
  var ROUND = 16 * Math.sqrt(5), DIE = { half: 64 + ROUND, round: ROUND, pip: 12 }, PIPS = {
@@ -960,8 +960,8 @@ function panOptions(el, o) {
960
960
 
961
961
  // src/panel/presets.ts
962
962
  var DRIFTED_PILL = 0.5;
963
- function activeEntryId(controls) {
964
- let ref = controls.activePreset();
963
+ function activeEntryId(controls, scope = "") {
964
+ let ref = controls.activePreset(scope);
965
965
  return ref ? `${ref.kind === "preset" ? "p" : "v"}:${ref.id}` : null;
966
966
  }
967
967
  function resolveActivePresetEntry(entries, currentValues, lastAppliedId, baseId = null) {
@@ -1012,7 +1012,7 @@ function nextPresetIndex(count, activeIndex, direction) {
1012
1012
  return direction > 0 ? 0 : count - 1;
1013
1013
  return ((activeIndex + direction) % count + count) % count;
1014
1014
  }
1015
- 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 = () => "") {
1016
1016
  let wake = () => onAfterChange?.(), el = (tag, cls, text) => {
1017
1017
  let node = doc.createElement(tag);
1018
1018
  if (cls)
@@ -1029,31 +1029,34 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1029
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);
1030
1030
  if (track.append(pill), root.append(track), saveButton)
1031
1031
  root.append(saveButton);
1032
- let pillEdges = new Edges, pillY = new Spring(0, 28, 0.86), pillAlpha = new Spring(1, 26, 0.9), entries = [], buttons = [], signature = "", lastVariantsRef = null, lastAppliedId = null, activeState = { activeId: "p:default", activeIndex: 0, isCustom: !1 }, prevActiveId = "", prevActiveIndex = -2, prevIsCustom = !1, prevDrifted = !1, initializedPill = !1, slideUntil = 0, display = "auto", chipMin = 0, unpinned = (values) => {
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", digits = !1, chipMin = 0, scope = null, at = () => scope ?? "", source = () => scope === null ? void 0 : scope === "" ? { presets: controls.presets, variants: controls.variants } : controls.scopes[scope], unpinned = (values) => {
1033
1033
  let out;
1034
1034
  for (let k of Object.keys(values))
1035
- if (controls.isPinned(k) && !same(values[k], controls.locked[k]))
1035
+ if (controls.scopeOf(k) !== at() && !same(values[k], controls.locked[k]))
1036
1036
  out ??= { ...values }, out[k] = controls.locked[k];
1037
1037
  return out ?? values;
1038
1038
  };
1039
1039
  function buildEntries() {
1040
- 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]) => ({
1041
1044
  id: `p:${id}`,
1042
1045
  kind: "preset",
1043
1046
  rawId: id,
1044
- label: presetLabel(id),
1047
+ label: presetLabel(id, where),
1045
1048
  values: presetValues,
1046
- apply: (coalesce) => controls.preset(id, { coalesce })
1049
+ apply: (coalesce) => controls.preset(id, { coalesce, scope: where })
1047
1050
  }));
1048
1051
  if (!showVariants)
1049
1052
  return presetEntries;
1050
- let variantEntries = controls.variants.list().map((v) => ({
1053
+ let variantEntries = from.variants.list().map((v) => ({
1051
1054
  id: `v:${v.id}`,
1052
1055
  kind: "variant",
1053
1056
  rawId: v.id,
1054
1057
  label: v.name ?? v.id.slice(0, 8),
1055
1058
  values: unpinned(v.values),
1056
- apply: (coalesce) => controls.variants.load(v.id, { coalesce })
1059
+ apply: (coalesce) => from.variants.load(v.id, { coalesce })
1057
1060
  }));
1058
1061
  return [...presetEntries, ...variantEntries];
1059
1062
  }
@@ -1087,7 +1090,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1087
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)
1088
1091
  doc.defaultView?.requestAnimationFrame(() => pill.style.transition = "");
1089
1092
  }
1090
- 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);
1091
1094
  function syncShown() {
1092
1095
  let show = wanted(entries);
1093
1096
  if (root.hidden === show && root.dataset.presence === void 0)
@@ -1130,16 +1133,24 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1130
1133
  }
1131
1134
  function update() {
1132
1135
  run(() => {
1133
- let variantsRef = showVariants ? controls.variants.list() : null;
1134
- 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) {
1135
1141
  syncShown(), syncSelection(!1);
1136
1142
  return;
1137
1143
  }
1138
1144
  lastVariantsRef = variantsRef;
1139
- let nextEntries = buildEntries(), nextSig = JSON.stringify(nextEntries.map((e) => [e.id, e.label]));
1145
+ let nextEntries = buildEntries(), nextSig = JSON.stringify([scope, digits, nextEntries.map((e) => [e.id, e.label])]);
1140
1146
  if (entries = nextEntries, syncShown(), nextSig !== signature)
1141
1147
  signature = nextSig, entries = nextEntries, buttons = entries.map((entry, idx) => {
1142
- let b = el("button", "knobs-preset-chip", entry.label);
1148
+ let b = el("button", "knobs-preset-chip");
1149
+ if (digits && idx < 9) {
1150
+ let key = el("span", "knobs-preset-key", String(idx + 1));
1151
+ key.setAttribute("aria-hidden", "true"), b.append(key, el("span", "knobs-preset-label", entry.label)), b.setAttribute("aria-keyshortcuts", String(idx + 1));
1152
+ } else
1153
+ b.textContent = entry.label;
1143
1154
  return b.type = "button", b.tabIndex = -1, b.dataset.presetId = entry.id, b.dataset.presetKind = entry.kind, b.dataset.presetName = entry.label, b.setAttribute("role", "radio"), b.setAttribute("aria-label", entry.label), b.onclick = (e) => {
1144
1155
  if (e.preventDefault(), focusQuietly(), idx !== activeState.activeIndex || activeState.drifted)
1145
1156
  selectAt(idx);
@@ -1175,11 +1186,14 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1175
1186
  }
1176
1187
  function save() {
1177
1188
  finishName(!0);
1178
- let used = new Set(controls.variants.list().map((v) => v.name)), n = 1;
1189
+ let variants = source()?.variants;
1190
+ if (!variants)
1191
+ return "";
1192
+ let used = new Set(variants.list().map((v) => v.name)), n = 1;
1179
1193
  while (used.has(`Variant ${n}`))
1180
1194
  n++;
1181
- let variant = controls.variants.save(`Variant ${n}`);
1182
- 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.`;
1195
+ let variant = variants.save(`Variant ${n}`);
1196
+ 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.`;
1183
1197
  }
1184
1198
  let naming = null;
1185
1199
  function placeName() {
@@ -1223,7 +1237,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1223
1237
  focusQuietly();
1224
1238
  let message;
1225
1239
  if (keep && entry?.kind === "variant" && name && name !== was)
1226
- controls.variants.rename(entry.rawId, name), update(), message = `Named “${name}”.`;
1240
+ source()?.variants.rename(entry.rawId, name), update(), message = `Named “${name}”.`;
1227
1241
  return onAfterChange?.(), message;
1228
1242
  }
1229
1243
  function rename() {
@@ -1239,7 +1253,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1239
1253
  let entry = entries[activeState.activeIndex];
1240
1254
  if (!entry || entry.kind !== "variant")
1241
1255
  return;
1242
- return controls.variants.remove(entry.rawId), lastAppliedId = null, update(), onAfterChange?.(), `Deleted “${entry.label}”.`;
1256
+ return source()?.variants.remove(entry.rawId), lastAppliedId = null, update(), onAfterChange?.(), `Deleted “${entry.label}”.`;
1243
1257
  }
1244
1258
  let scrubbing = !1, scrubCommitted = !1, chipIndexAt = (clientX, clientY) => {
1245
1259
  let best = -1, bestDist = 1 / 0;
@@ -1330,6 +1344,13 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1330
1344
  if (display = mode, placeDisplay())
1331
1345
  measurePill(!0);
1332
1346
  },
1347
+ digits(on) {
1348
+ if (digits === on)
1349
+ return;
1350
+ if (digits = on, root.toggleAttribute("data-digits", on), signature === "")
1351
+ return;
1352
+ signature = "", update();
1353
+ },
1333
1354
  save: () => {
1334
1355
  let message = "";
1335
1356
  return run(() => message = save()), message;
@@ -2971,7 +2992,8 @@ fn hash21(q: vec2f) -> f32 {
2971
2992
 
2972
2993
  // kind 0: Panel card surface + elevation shadow + border + specular rim. With a hairline width
2973
2994
  // (extra.z, whole device px) the edge is that hairline just outside the card, like CSS's
2974
- // 0 0 0 w spread shadow, and the rim is CSS's inset 0 w 0; otherwise a 1px border and a 1px rim.
2995
+ // 0 0 0 w spread shadow, and the rim lights its top, like CSS's 0 -w 0 (one edge, not two);
2996
+ // otherwise a 1px border and a 1px rim inside it.
2975
2997
  fn shadePanel(p: Prim, local: vec2f, size: vec2f) -> vec4f {
2976
2998
  let r = p.look.x;
2977
2999
  let dist = roundedBox(local, size, r);
@@ -2980,6 +3002,8 @@ fn shadePanel(p: Prim, local: vec2f, size: vec2f) -> vec4f {
2980
3002
  var out = vec4f(0.0);
2981
3003
  if (hair > 0.0 && dist > -1.0) {
2982
3004
  out = premul(p.color2) * clamp(covSd(dist - hair) - bodyCov, 0.0, 1.0);
3005
+ let raised = covSd(roundedBox(local + vec2f(0.0, hair), size, r));
3006
+ out = over(out, premul(p.aux2) * clamp(raised - bodyCov, 0.0, 1.0));
2983
3007
  }
2984
3008
 
2985
3009
  // Floating drop shadow when glowOrBlur (look.z) > 0; like box-shadow, never under the card.
@@ -2993,13 +3017,16 @@ fn shadePanel(p: Prim, local: vec2f, size: vec2f) -> vec4f {
2993
3017
 
2994
3018
  var card = premul(p.color) * bodyCov;
2995
3019
 
2996
- // The rim, built like CSS's inset 0 w 0: inside the border, minus that shape moved down w.
3020
+ // Without a hairline, the rim is CSS's inset 0 1px 0: inside the border, minus that shape
3021
+ // moved down.
2997
3022
  let inner = local - vec2f(1.0);
2998
3023
  let innerSize = size - vec2f(2.0);
2999
3024
  let w = select(1.0, hair, hair > 0.0);
3000
3025
  let innerCov = covSd(roundedBox(inner, innerSize, r - 1.0));
3001
- let rimCov = innerCov * (1.0 - covSd(roundedBox(inner - vec2f(0.0, w), innerSize, r - 1.0)));
3002
- card = over(card, premul(p.aux2) * rimCov);
3026
+ if (hair <= 0.0) {
3027
+ let rimCov = innerCov * (1.0 - covSd(roundedBox(inner - vec2f(0.0, w), innerSize, r - 1.0)));
3028
+ card = over(card, premul(p.aux2) * rimCov);
3029
+ }
3003
3030
 
3004
3031
  // A morphing card's specular ring (extra.y), w wide inside the border like CSS's inset 0 0 0 w.
3005
3032
  if (p.extra.y > 0.001) {
@@ -4007,6 +4034,111 @@ class GpuOverlayManager {
4007
4034
  let more = o.more;
4008
4035
  d[b + 40] = more ? more[0] : 0, d[b + 41] = more ? more[1] : 0, d[b + 42] = more ? more[2] : 0, d[b + 43] = more ? more[3] : 0;
4009
4036
  }
4037
+ drawPresetStrip(ps, tokens, clip, hinting, reducedMotion, dt, tray) {
4038
+ let moving = !1, psHovered = this.curPanelHovered && ps.element.matches(":hover");
4039
+ this.curSubHovered = psHovered;
4040
+ let psRect = this.rectOf(ps.element), trackRect = this.rectOf(ps.track);
4041
+ if (tray && !tokens.boxed)
4042
+ this.pushPrim({
4043
+ x: psRect.left,
4044
+ y: psRect.top,
4045
+ w: psRect.width,
4046
+ h: psRect.height,
4047
+ color: tokens.segBg,
4048
+ color2: tokens.accent,
4049
+ radius: tokens.radius,
4050
+ glowOrBlur: tokens.glowStrength * 8,
4051
+ kind: 1,
4052
+ p0: psHovered || this.isFocused(ps.element) ? 0.4 : 0
4053
+ });
4054
+ let stripClip = intersectClip(clip, [psRect.left, psRect.top, psRect.right, psRect.bottom]), activeState = ps.getActiveState(), chipButtons = ps.getButtons();
4055
+ if (tokens.boxed)
4056
+ for (let btn of chipButtons) {
4057
+ let br = this.rectOf(btn);
4058
+ if (br.right < stripClip[0] || br.left > stripClip[2])
4059
+ continue;
4060
+ this.drawButtonBox(btn, br, this.isHovered(btn), this.missed(btn, hinting), stripClip);
4061
+ }
4062
+ let activeBtn = activeState.activeIndex >= 0 ? chipButtons[activeState.activeIndex] : void 0, pe = ps.pillEdges, activeBtnRect = activeBtn ? this.rectOf(activeBtn) : void 0, panned = livePill(ps.track);
4063
+ if (activeBtn && activeBtnRect && !activeState.isCustom) {
4064
+ let left = activeBtnRect.left - trackRect.left, right = left + Math.max(20, activeBtnRect.width), top = activeBtnRect.top - trackRect.top;
4065
+ if (this.carry(pe, activeBtn, left, right, ps.pillY, top), panned)
4066
+ pe.snap(panned.left, panned.right), ps.pillY.snap(panned.top);
4067
+ else if (reducedMotion || pe.end.x - pe.start.x <= 1)
4068
+ pe.snap(left, right), ps.pillY.snap(top);
4069
+ else
4070
+ pe.to(left, right, 0, trailScale(tokens.blurStrength)), ps.pillY.set(top);
4071
+ ps.pillAlpha.set(activeState.drifted ? DRIFTED_PILL : 1);
4072
+ } else
4073
+ ps.pillAlpha.set(0);
4074
+ for (let sp of [pe.start, pe.end, ps.pillY, ps.pillAlpha])
4075
+ if (reducedMotion)
4076
+ sp.snap(sp.target);
4077
+ else if (sp.step(dt), !sp.resting)
4078
+ moving = !0;
4079
+ let pillAlpha = clamp(ps.pillAlpha.x, 0, 1);
4080
+ if (pillAlpha > 0.01)
4081
+ this.pushPrim({
4082
+ x: trackRect.left + pe.start.x,
4083
+ y: trackRect.top + ps.pillY.x,
4084
+ w: Math.max(18, pe.end.x - pe.start.x),
4085
+ h: Math.max(16, activeBtnRect?.height ?? trackRect.height),
4086
+ clip: stripClip,
4087
+ color: withAlpha(tokens.pillBg, pillAlpha),
4088
+ color2: withAlpha(tokens.pillBorder, pillAlpha),
4089
+ strokeWidth: this.hairline(tokens),
4090
+ aux2: withAlpha(tokens.rim, tokens.rim[3] * pillAlpha),
4091
+ radius: tokens.buttonRadius ?? tokens.radiusMd,
4092
+ glowOrBlur: tokens.glowStrength,
4093
+ kind: 3,
4094
+ blurX: reducedMotion ? 0 : clamp(pe.velocity * 0.006 * tokens.motionBlur, -8, 8)
4095
+ });
4096
+ for (let i = 0;i < chipButtons.length; i++) {
4097
+ let btn = chipButtons[i], br = this.rectOf(btn);
4098
+ if (br.right < stripClip[0] || br.left > stripClip[2])
4099
+ continue;
4100
+ let isActive = i === activeState.activeIndex, chipHovered = this.isHovered(btn);
4101
+ if (chipHovered)
4102
+ this.curSubHovered = !1;
4103
+ let mark = this.ease(btn, "dot", btn.hasAttribute("data-drifted") ? 1 : 0);
4104
+ if (mark > 0.01) {
4105
+ let size = 4 * (0.4 + 0.6 * mark);
4106
+ this.pushPrim({
4107
+ x: br.right - 6 - size / 2,
4108
+ y: br.top + 6 - size / 2,
4109
+ w: size,
4110
+ h: size,
4111
+ clip: stripClip,
4112
+ color: withAlpha(tokens.modified, 0.95 * mark),
4113
+ glowOrBlur: 0.5,
4114
+ kind: 7
4115
+ });
4116
+ }
4117
+ if (btn.hasAttribute("data-naming"))
4118
+ continue;
4119
+ let key = btn.querySelector(".knobs-preset-key"), name = key && btn.querySelector(".knobs-preset-label");
4120
+ if (key && name) {
4121
+ let kr = this.rectOf(key);
4122
+ this.drawText(key.textContent ?? "", kr.left, br.top + br.height * 0.5, withAlpha(tokens.dim, this.missed(btn, hinting)), { px: tokens.textMeta, weight: tokens.labelWeight, clip: stripClip });
4123
+ }
4124
+ let nr = name ? this.rectOf(name) : void 0;
4125
+ this.drawText(name ? name.textContent ?? "" : btn.textContent ?? "", nr ? nr.left : br.left + br.width * 0.5, br.top + br.height * 0.5, withAlpha(this.hot(btn, isActive || chipHovered, tokens.buttonColor ?? tokens.label), this.missed(btn, hinting)), {
4126
+ px: tokens.buttonFontSize ?? tokens.textControl,
4127
+ weight: isActive ? tokens.titleWeight : tokens.labelWeight,
4128
+ align: nr ? "left" : "center",
4129
+ clip: stripClip
4130
+ });
4131
+ }
4132
+ if (ps.saveButton && !ps.saveButton.hidden) {
4133
+ let sr = this.rectOf(ps.saveButton), hot = this.isHovered(ps.saveButton);
4134
+ this.drawText("+", sr.left + sr.width * 0.5, sr.top + sr.height * 0.5, this.hot(ps.saveButton, hot, tokens.dim), {
4135
+ px: tokens.textTitle,
4136
+ weight: 400,
4137
+ align: "center"
4138
+ });
4139
+ }
4140
+ return moving;
4141
+ }
4010
4142
  hairline(tokens) {
4011
4143
  return Math.max(1, Math.round(tokens.rimWidth * this.curScale)) / this.curScale;
4012
4144
  }
@@ -5753,7 +5885,6 @@ class GpuOverlayManager {
5753
5885
  this.placeBackdrop(panel);
5754
5886
  continue;
5755
5887
  }
5756
- let pillFill = tokens.pillBg;
5757
5888
  if (pageB)
5758
5889
  this.placeBackdrop(panel);
5759
5890
  let covers = drawn.some((r) => r.left < card.right && r.right > card.left && r.top < card.bottom && r.bottom > card.top);
@@ -5885,111 +6016,8 @@ class GpuOverlayManager {
5885
6016
  }
5886
6017
  }
5887
6018
  let ps = panel.presetStrip, hinting = panel.root.classList.contains("knobs-hinting"), stripAlpha = this.curAlpha;
5888
- if (!ps.element.hidden) {
5889
- this.curAlpha = stripAlpha * this.presence(ps.element);
5890
- let psHovered = this.curPanelHovered && ps.element.matches(":hover");
5891
- this.curSubHovered = psHovered;
5892
- let psRect = this.rectOf(ps.element), trackRect = this.rectOf(ps.track);
5893
- if (!tokens.boxed)
5894
- this.pushPrim({
5895
- x: psRect.left,
5896
- y: psRect.top,
5897
- w: psRect.width,
5898
- h: psRect.height,
5899
- color: tokens.segBg,
5900
- color2: tokens.accent,
5901
- radius: tokens.radius,
5902
- glowOrBlur: tokens.glowStrength * 8,
5903
- kind: 1,
5904
- p0: psHovered || this.isFocused(ps.element) ? 0.4 : 0
5905
- });
5906
- let stripClip = intersectClip(bodyClip, [
5907
- psRect.left,
5908
- psRect.top,
5909
- psRect.right,
5910
- psRect.bottom
5911
- ]), activeState = ps.getActiveState(), chipButtons = ps.getButtons();
5912
- if (tokens.boxed)
5913
- for (let btn of chipButtons) {
5914
- let br = this.rectOf(btn);
5915
- if (br.right < stripClip[0] || br.left > stripClip[2])
5916
- continue;
5917
- this.drawButtonBox(btn, br, this.isHovered(btn), this.missed(btn, hinting), stripClip);
5918
- }
5919
- let activeBtn = activeState.activeIndex >= 0 ? chipButtons[activeState.activeIndex] : void 0, pe = ps.pillEdges, activeBtnRect = activeBtn ? this.rectOf(activeBtn) : void 0, panned = livePill(ps.track);
5920
- if (activeBtn && activeBtnRect && !activeState.isCustom) {
5921
- let left = activeBtnRect.left - trackRect.left, right = left + Math.max(20, activeBtnRect.width), top = activeBtnRect.top - trackRect.top;
5922
- if (this.carry(pe, activeBtn, left, right, ps.pillY, top), panned)
5923
- pe.snap(panned.left, panned.right), ps.pillY.snap(panned.top);
5924
- else if (reducedMotion || pe.end.x - pe.start.x <= 1)
5925
- pe.snap(left, right), ps.pillY.snap(top);
5926
- else
5927
- pe.to(left, right, 0, trailScale(tokens.blurStrength)), ps.pillY.set(top);
5928
- ps.pillAlpha.set(activeState.drifted ? DRIFTED_PILL : 1);
5929
- } else
5930
- ps.pillAlpha.set(0);
5931
- for (let sp of [pe.start, pe.end, ps.pillY, ps.pillAlpha])
5932
- if (reducedMotion)
5933
- sp.snap(sp.target);
5934
- else if (sp.step(dt), !sp.resting)
5935
- anyMoving = !0;
5936
- let pillAlpha = clamp(ps.pillAlpha.x, 0, 1);
5937
- if (pillAlpha > 0.01)
5938
- this.pushPrim({
5939
- x: trackRect.left + pe.start.x,
5940
- y: trackRect.top + ps.pillY.x,
5941
- w: Math.max(18, pe.end.x - pe.start.x),
5942
- h: Math.max(16, activeBtnRect?.height ?? trackRect.height),
5943
- clip: stripClip,
5944
- color: withAlpha(pillFill, pillAlpha),
5945
- color2: withAlpha(tokens.pillBorder, pillAlpha),
5946
- strokeWidth: this.hairline(tokens),
5947
- aux2: withAlpha(tokens.rim, tokens.rim[3] * pillAlpha),
5948
- radius: tokens.buttonRadius ?? tokens.radiusMd,
5949
- glowOrBlur: tokens.glowStrength,
5950
- kind: 3,
5951
- blurX: reducedMotion ? 0 : clamp(pe.velocity * 0.006 * tokens.motionBlur, -8, 8)
5952
- });
5953
- for (let i = 0;i < chipButtons.length; i++) {
5954
- let btn = chipButtons[i], br = this.rectOf(btn);
5955
- if (br.right < stripClip[0] || br.left > stripClip[2])
5956
- continue;
5957
- let isActive = i === activeState.activeIndex, chipHovered = this.isHovered(btn);
5958
- if (chipHovered)
5959
- this.curSubHovered = !1;
5960
- let mark = this.ease(btn, "dot", btn.hasAttribute("data-drifted") ? 1 : 0);
5961
- if (mark > 0.01) {
5962
- let size = 4 * (0.4 + 0.6 * mark);
5963
- this.pushPrim({
5964
- x: br.right - 6 - size / 2,
5965
- y: br.top + 6 - size / 2,
5966
- w: size,
5967
- h: size,
5968
- clip: stripClip,
5969
- color: withAlpha(tokens.modified, 0.95 * mark),
5970
- glowOrBlur: 0.5,
5971
- kind: 7
5972
- });
5973
- }
5974
- if (btn.hasAttribute("data-naming"))
5975
- continue;
5976
- this.drawText(btn.textContent ?? "", br.left + br.width * 0.5, br.top + br.height * 0.5, withAlpha(this.hot(btn, isActive || chipHovered, tokens.buttonColor ?? tokens.label), this.missed(btn, hinting)), {
5977
- px: tokens.buttonFontSize ?? tokens.textControl,
5978
- weight: isActive ? tokens.titleWeight : tokens.labelWeight,
5979
- align: "center",
5980
- clip: stripClip
5981
- });
5982
- }
5983
- if (ps.saveButton && !ps.saveButton.hidden) {
5984
- let sr = this.rectOf(ps.saveButton), hot = this.isHovered(ps.saveButton);
5985
- this.drawText("+", sr.left + sr.width * 0.5, sr.top + sr.height * 0.5, this.hot(ps.saveButton, hot, tokens.dim), {
5986
- px: tokens.textTitle,
5987
- weight: 400,
5988
- align: "center"
5989
- });
5990
- }
5991
- this.curAlpha = stripAlpha;
5992
- }
6019
+ if (!ps.element.hidden)
6020
+ this.curAlpha = stripAlpha * this.presence(ps.element), anyMoving = this.drawPresetStrip(ps, tokens, bodyClip, hinting, reducedMotion, dt, !0) || anyMoving, this.curAlpha = stripAlpha;
5993
6021
  let tabs = panel.tabs;
5994
6022
  if (tabs && !tabs.element.hidden)
5995
6023
  anyMoving = this.drawTabStrip(tabs, hinting, reducedMotion, dt, {
@@ -6210,6 +6238,13 @@ class GpuOverlayManager {
6210
6238
  this.drawScope(card, cr, tokens.radius, 1);
6211
6239
  }
6212
6240
  }
6241
+ for (let gs of panel.groupStrips?.() ?? []) {
6242
+ let card = gs.element.parentElement;
6243
+ if (!card || gs.element.closest("[hidden]"))
6244
+ continue;
6245
+ let cr = cardRects.get(card) ?? this.rectOf(card);
6246
+ this.curClip = clipToTile(card, intersectClip(bodyClip, [cr.left, cr.top, cr.right, cr.bottom])), this.curAlpha = panelAlpha * this.presence(card) * fadeOf(card), anyMoving = this.drawPresetStrip(gs, tokens, this.curClip, hinting, reducedMotion, dt, !1) || anyMoving;
6247
+ }
6213
6248
  this.curClip = bodyClip, this.curAlpha = panelAlpha;
6214
6249
  let washed = /* @__PURE__ */ new Set, rowsAlpha = this.curAlpha;
6215
6250
  for (let row of panel.rows) {
@@ -11296,7 +11331,7 @@ function presence(el, o = {}) {
11296
11331
  return !1;
11297
11332
  }), tune = (seconds, zeta) => {
11298
11333
  let reduced = reducedMotion(win);
11299
- spring.omega = 2 * Math.PI / (reduced ? FADE.duration : seconds), spring.zeta = reduced ? 1 : zeta;
11334
+ spring.omega = 2 * Math.PI / (reduced ? Math.min(FADE.duration, seconds) : seconds), spring.zeta = reduced ? 1 : zeta;
11300
11335
  };
11301
11336
  return render(), {
11302
11337
  show() {
@@ -15047,7 +15082,7 @@ function buildPanel(controls, host, options) {
15047
15082
  }
15048
15083
  popover.append(popGrid);
15049
15084
  let baseAt = (scope, from) => {
15050
- 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;
15085
+ 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;
15051
15086
  if (!name)
15052
15087
  return null;
15053
15088
  let off = keys ? keys.some((k) => controls.isModified(k)) : controls.isModified();
@@ -15141,7 +15176,7 @@ function buildPanel(controls, host, options) {
15141
15176
  if (helpButton?.hidden)
15142
15177
  titleEl.title = `${titleEl.textContent ? `${titleEl.textContent}
15143
15178
  ` : ""}Press ${show("help")} for keyboard shortcuts`;
15144
- 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, {
15179
+ let presetLabel = (id, scope = "") => options.presetLabels?.[id] ?? (id === "default" ? typeof options.defaultLabel === "object" ? options.defaultLabel[scope] : options.defaultLabel : void 0) ?? controls.presetLabel(id, scope), hintReach = (typeof options.hints === "object" ? options.hints.reach : options.hints) ?? "page", stripDigitsShown = typeof options.hints === "object" && options.hints.digits === "always", 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, {
15145
15180
  placeholder: searchOptions.placeholder ?? "Search",
15146
15181
  chips: (searchOptions.chips ?? ["modified"]).map((chip) => {
15147
15182
  if (chip === "modified")
@@ -15172,8 +15207,19 @@ function buildPanel(controls, host, options) {
15172
15207
  else if ((!header.hidden && header.dataset.leaving !== "true") !== any)
15173
15208
  reveal(header, any, () => wakeGpu());
15174
15209
  };
15175
- presetStrip.display(page ? "chips" : options.presetDisplay ?? "auto");
15176
- let allActions = [
15210
+ presetStrip.display(page ? "chips" : options.presetDisplay ?? "auto"), presetStrip.digits(stripDigitsShown);
15211
+ let groupStrips = /* @__PURE__ */ new Map, tabNamed = (id) => (options.tabs ?? []).some((t) => (typeof t === "string" ? t : t.id) === id) || Object.values(controls.schema).some((f) => f.tab === id), groupStripFor = (group) => {
15212
+ if (page || !Object.hasOwn(controls.scopes, group) || tabNamed(group))
15213
+ return;
15214
+ let strip = groupStrips.get(group);
15215
+ if (!strip)
15216
+ strip = createPresetStrip(doc, controls, showVariants, run, () => wakeGpu(), presetLabel, void 0, () => stripScope() === group ? null : group), strip.element.setAttribute("aria-label", `${groupTitle(group)} presets`), strip.display(options.presetDisplay ?? "auto"), strip.digits(stripDigitsShown), groupStrips.set(group, strip);
15217
+ return strip;
15218
+ }, updateStrips = () => {
15219
+ presetStrip.update();
15220
+ for (let strip of groupStrips.values())
15221
+ strip.update();
15222
+ }, allActions = [
15177
15223
  ...controls.actions ?? [],
15178
15224
  ...options.actions ?? []
15179
15225
  ], body = el("div"), tabMotion = tabTransition(body, root, () => wakeGpu()), pinnedBody = el("div");
@@ -15465,7 +15511,7 @@ function buildPanel(controls, host, options) {
15465
15511
  }
15466
15512
  let specs = [], placed = /* @__PURE__ */ new Set;
15467
15513
  for (let [key, field] of entries) {
15468
- if (field.kind === "choice" && field.variants && !controls.isPinned(key))
15514
+ if (field.kind === "choice" && field.variants && controls.inPresets(key))
15469
15515
  continue;
15470
15516
  let pair = field.kind === "knob" && field.spring ? halves.get(field.spring.id) : void 0;
15471
15517
  if (pair?.duration && pair.bounce && field.kind === "knob" && field.spring) {
@@ -16034,7 +16080,7 @@ function buildPanel(controls, host, options) {
16034
16080
  else
16035
16081
  runs.push({ group: r.group, pinned, rows: [r] });
16036
16082
  }
16037
- let free = blocks.filter((b) => !b.leaving), chosen = [], pinnedBlocks = /* @__PURE__ */ new Set, usedSubheads = /* @__PURE__ */ new Set;
16083
+ let free = blocks.filter((b) => !b.leaving), chosen = [], pinnedBlocks = /* @__PURE__ */ new Set, usedSubheads = /* @__PURE__ */ new Set, stripped = /* @__PURE__ */ new Set;
16038
16084
  for (let run of runs) {
16039
16085
  let at = free.findIndex((b) => b.group === run.group), block = at >= 0 ? free.splice(at, 1)[0] : newBlock(run.group);
16040
16086
  if (at < 0) {
@@ -16061,7 +16107,10 @@ function buildPanel(controls, host, options) {
16061
16107
  }
16062
16108
  under = name, nodes.push(r.row);
16063
16109
  }
16064
- if (arrange(block.card, nodes), animate && at >= 0) {
16110
+ let strip = run.group !== void 0 && !run.pinned && !stripped.has(run.group) ? groupStripFor(run.group) : void 0;
16111
+ if (strip)
16112
+ stripped.add(run.group);
16113
+ if (arrange(block.card, strip ? [strip.element, ...nodes] : nodes), animate && at >= 0) {
16065
16114
  for (let r of run.rows)
16066
16115
  if (entering.has(r))
16067
16116
  motion.push({ node: r.row, show: !0 });
@@ -16192,12 +16241,12 @@ function buildPanel(controls, host, options) {
16192
16241
  return list;
16193
16242
  };
16194
16243
  function presetsTouchTab(entries) {
16195
- if (page || activeTab === void 0)
16244
+ if (page || activeTab === void 0 || stripScope() !== "")
16196
16245
  return !0;
16197
16246
  let keys = rows.flatMap((r) => {
16198
16247
  let tab = tabOf(r);
16199
- return !pinnedRow(r) && (tab === void 0 || tab === activeTab) ? r.keys : [];
16200
- }), v = variantsOf(controls.schema, controls.isPinned);
16248
+ return !pinnedRow(r) && (tab === void 0 || tab === activeTab) ? r.keys.filter((k) => controls.scopeOf(k) === "") : [];
16249
+ }), v = variantsOf(controls.schema, (key) => !controls.inPresets(key));
16201
16250
  if (v) {
16202
16251
  let tab = v.field.tab ?? (v.field.group !== void 0 ? groupTab(v.field.group) : void 0);
16203
16252
  if (tab === void 0 || tab === activeTab)
@@ -16205,6 +16254,20 @@ function buildPanel(controls, host, options) {
16205
16254
  }
16206
16255
  return presetsTouch(entries, keys, controls.locked);
16207
16256
  }
16257
+ function stripScope() {
16258
+ let named = Object.keys(controls.scopes), panelWide = named.length === 0 || Object.keys(controls.presets).length > 1;
16259
+ if (page)
16260
+ return panelWide ? "" : null;
16261
+ if (activeTab === void 0)
16262
+ return panelWide ? "" : named[0] ?? null;
16263
+ if (named.includes(activeTab))
16264
+ return activeTab;
16265
+ return named.find((id) => rows.some((r) => r.group === id && tabOf(r) === activeTab)) ?? (panelWide ? "" : null);
16266
+ }
16267
+ let stripVariants = () => {
16268
+ let scope = stripScope();
16269
+ return (scope ? controls.scopes[scope]?.variants : void 0) ?? controls.variants;
16270
+ };
16208
16271
  function syncTabs() {
16209
16272
  let list = tabList(), has = (id) => id !== void 0 && list.some((t) => t.id === id);
16210
16273
  if (!has(activeTab))
@@ -16218,7 +16281,7 @@ function buildPanel(controls, host, options) {
16218
16281
  writeSession(controls.id, { tab: id });
16219
16282
  tabStrip.select(id, animate);
16220
16283
  let focused = doc.activeElement;
16221
- if (applyVisibility(animate ? "tab" : "instant"), focused && root.contains(focused) && focused.closest("[hidden],[data-leaving]"))
16284
+ if (updateStrips(), applyVisibility(animate ? "tab" : "instant"), focused && root.contains(focused) && focused.closest("[hidden],[data-leaving]"))
16222
16285
  tabStrip.focus({ preventScroll: !0, focusVisible: !1 });
16223
16286
  options.onTab?.(id), wakeGpu();
16224
16287
  }
@@ -16431,7 +16494,7 @@ function buildPanel(controls, host, options) {
16431
16494
  }, text = el("textarea");
16432
16495
  text.hidden = !0, text.name = "variant-json", text.spellcheck = !1, text.setAttribute("aria-label", "Import or copy text");
16433
16496
  let apply = button("Apply import", () => {
16434
- controls.variants.import(text.value), say("Presets imported."), reveal(text, !1), reveal(apply, !1), positioned?.constrain();
16497
+ stripVariants().import(text.value), say("Presets imported."), reveal(text, !1), reveal(apply, !1), positioned?.constrain();
16435
16498
  });
16436
16499
  apply.className = "knobs-apply-btn", apply.setAttribute("aria-label", "Import JSON"), apply.hidden = !0;
16437
16500
  let copy = async (value, inPlace = !0) => {
@@ -16456,7 +16519,7 @@ function buildPanel(controls, host, options) {
16456
16519
  }
16457
16520
  if (showVariants && showFooter) {
16458
16521
  let transfer = el("div");
16459
- transfer.className = "knobs-transfer", transfer.append(button("Export presets", () => void copy(controls.variants.export(), !1)), button("Import presets", () => {
16522
+ transfer.className = "knobs-transfer", transfer.append(button("Export presets", () => void copy(stripVariants().export(), !1)), button("Import presets", () => {
16460
16523
  reveal(text, !0), reveal(apply, !0), text.value = "", positioned?.constrain(), text.focus();
16461
16524
  })), popover.append(transfer);
16462
16525
  }
@@ -16524,6 +16587,7 @@ function buildPanel(controls, host, options) {
16524
16587
  }
16525
16588
  let shownBase = "", baseSignature = () => JSON.stringify([
16526
16589
  controls.activePreset(),
16590
+ Object.keys(controls.scopes).map((s) => controls.activePreset(s)),
16527
16591
  Object.keys(controls.groupPresets).map((g) => controls.activeGroupPreset(g))
16528
16592
  ]);
16529
16593
  function update() {
@@ -16545,7 +16609,7 @@ function buildPanel(controls, host, options) {
16545
16609
  changedCount++;
16546
16610
  }
16547
16611
  let stagger = initialized ? cascade(changedCount) : 0, moving = initialized ? rows.filter(changed) : rows;
16548
- if (presetStrip.update(), batchMeasures(() => {
16612
+ if (updateStrips(), batchMeasures(() => {
16549
16613
  for (let row of moving)
16550
16614
  if (row.slider?.prime(), row.editor?.prime?.(), row.choice)
16551
16615
  primeSegPill(row.choice.seg, row.choice.buttons);
@@ -16677,13 +16741,14 @@ function buildPanel(controls, host, options) {
16677
16741
  if (s.label?.hasAttribute("data-foldable"))
16678
16742
  add(s.label, sectionTitle(s.name), () => s.label.focus(), s.label.firstElementChild);
16679
16743
  }
16680
- presetStrip.getEntries().forEach((entry, i) => {
16681
- let chip = presetStrip.getButtons()[i];
16682
- if (chip)
16683
- add(chip, entry.label, () => {
16684
- presetStrip.selectById(entry.id), presetStrip.element.focus({ preventScroll: !0 });
16685
- });
16686
- });
16744
+ for (let strip of [presetStrip, ...groupStrips.values()])
16745
+ strip.getEntries().forEach((entry, i) => {
16746
+ let chip = strip.getButtons()[i];
16747
+ if (chip)
16748
+ add(chip, entry.label, () => {
16749
+ strip.selectById(entry.id), strip.element.focus({ preventScroll: !0 });
16750
+ });
16751
+ });
16687
16752
  for (let b of root.querySelectorAll(page ? ".knobs-head-cmd, .knobs-group-head button:not(.knobs-group-die,.knobs-group-reset,.knobs-chips button), .knobs-custom-actions button, .knobs-preset-save" : ".knobs-head-cmd, .knobs-group-head button, .knobs-custom-actions button, .knobs-preset-save, .knobs-popover button"))
16688
16753
  add(b, b.dataset.hint ?? b.getAttribute("aria-label") ?? b.textContent ?? "", () => b.focus());
16689
16754
  let focusedHead = page ? doc.activeElement?.closest(".knobs-tile")?.querySelector(":scope > .knobs-group-head") : null, presetUi = focusedHead ? groupChips.get(focusedHead.dataset.group ?? "") : void 0;
@@ -16699,7 +16764,7 @@ function buildPanel(controls, host, options) {
16699
16764
  add(stop, label, () => stop.focus());
16700
16765
  }
16701
16766
  return targets;
16702
- }, unregisterHints = (options.hints ?? "page") === "page" && bound("hint") ? pageHints(doc).register(root, () => collectHintTargets().map((t) => ({
16767
+ }, unregisterHints = hintReach === "page" && bound("hint") ? pageHints(doc).register(root, () => collectHintTargets().map((t) => ({
16703
16768
  el: t.el,
16704
16769
  label: t.display,
16705
16770
  ...t.labelEl ? { labelEl: t.labelEl } : {},
@@ -16756,6 +16821,9 @@ function buildPanel(controls, host, options) {
16756
16821
  runs.push({ at: b.head, stops: shown([b.head]) });
16757
16822
  for (let sh of subheads.values())
16758
16823
  runs.push({ at: sh.el, stops: shown([sh.el]) });
16824
+ for (let gs of groupStrips.values())
16825
+ if (visible(gs.element))
16826
+ runs.push({ at: gs.element, stops: shown([gs.element]) });
16759
16827
  runs.push(...strips), runs.sort((a, b) => a.at.compareDocumentPosition(b.at) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
16760
16828
  for (let run of runs)
16761
16829
  list.push(...run.stops);
@@ -17134,6 +17202,9 @@ function buildPanel(controls, host, options) {
17134
17202
  return { el: sub, level: "row" };
17135
17203
  return { el: r.row.closest(".knobs-card > *") ?? r.row, level: "row" };
17136
17204
  }
17205
+ let strip = active.closest(".knobs-card > .knobs-presets");
17206
+ if (strip)
17207
+ return { el: strip, level: "row" };
17137
17208
  let card = cardOf(active);
17138
17209
  return card ? { el: card, level: "card" } : null;
17139
17210
  }, partName = (part, active) => part.querySelector(".knobs-strip-label")?.textContent || active?.getAttribute("aria-label") || part.getAttribute("aria-label") || "Editor", trailFor = (t, active) => {
@@ -17145,7 +17216,7 @@ function buildPanel(controls, host, options) {
17145
17216
  row: rows[rowAt(active)]?.display,
17146
17217
  editor: t.level === "editor" ? partName(t.el, active) : void 0
17147
17218
  });
17148
- }, crumbs = options.crumbs === !1 || header.hidden || !titleEl.isConnected ? void 0 : createCrumbs({
17219
+ }, crumbs = options.crumbs !== !0 || header.hidden || !titleEl.isConnected ? void 0 : createCrumbs({
17149
17220
  header,
17150
17221
  title: titleEl,
17151
17222
  reduced: () => reducedMotion(),
@@ -17163,7 +17234,21 @@ function buildPanel(controls, host, options) {
17163
17234
  return;
17164
17235
  let t = lensTarget(to);
17165
17236
  jumpedTo = t && t !== "out" ? t.el : null, to.focus(quiet), queueScope();
17166
- }, titleDown;
17237
+ };
17238
+ root.addEventListener("mousedown", (event) => {
17239
+ if (event.button !== 0 || event.shiftKey || event.metaKey || event.ctrlKey)
17240
+ return;
17241
+ let target = event.target instanceof Element ? event.target : null, r = rows[rowAt(target)];
17242
+ if (!target || !r || r.leaving || r.row.closest("[hidden]"))
17243
+ return;
17244
+ let own = target.closest("button,input,select,textarea,a[href],[tabindex],[contenteditable]");
17245
+ if (own && r.row.contains(own))
17246
+ return;
17247
+ event.preventDefault(), r.input.focus({ preventScroll: !0, focusVisible: !1 });
17248
+ let t = lensTarget(doc.activeElement);
17249
+ jumpedTo = t && t !== "out" ? t.el : null, queueScope();
17250
+ }, !0);
17251
+ let titleDown;
17167
17252
  titleEl.addEventListener("pointerdown", (event) => {
17168
17253
  titleDown = [event.clientX, event.clientY];
17169
17254
  }), titleEl.addEventListener("click", (event) => {
@@ -17255,6 +17340,18 @@ function buildPanel(controls, host, options) {
17255
17340
  if (!r || r.row.closest("[data-disabled],[data-readonly]"))
17256
17341
  return;
17257
17342
  return optionsOf(r);
17343
+ }, groupStripAt = (node) => node ? [...groupStrips.values()].find((s) => s.element.contains(node)) : void 0, groupStripOptions = /* @__PURE__ */ new Map, optionsOfStrip = (strip) => {
17344
+ let o = groupStripOptions.get(strip);
17345
+ if (!o)
17346
+ o = {
17347
+ get count() {
17348
+ return strip.getEntries().length;
17349
+ },
17350
+ choose: (i) => strip.select(i),
17351
+ shown: () => strip.getButtons(),
17352
+ anchor: strip.element
17353
+ }, groupStripOptions.set(strip, o);
17354
+ return o;
17258
17355
  }, stripDigits = () => visible(presetStrip.element) && presetStrip.getEntries().length > 1, stripOptions = {
17259
17356
  get count() {
17260
17357
  return presetStrip.getEntries().length;
@@ -17263,7 +17360,12 @@ function buildPanel(controls, host, options) {
17263
17360
  shown: () => presetStrip.getButtons(),
17264
17361
  anchor: presetStrip.element
17265
17362
  }, stripOptionsAt = (node) => {
17266
- if (hintCtrl.isActive || !node || !stripDigits())
17363
+ if (hintCtrl.isActive || !node || stripDigitsShown)
17364
+ return;
17365
+ let own = groupStripAt(node);
17366
+ if (own)
17367
+ return visible(own.element) && own.getEntries().length > 1 && !own.nameField() ? optionsOfStrip(own) : void 0;
17368
+ if (!stripDigits())
17267
17369
  return;
17268
17370
  return (presetStrip.element.contains(node) || focusLevel(node) === "panel") && !presetStrip.nameField() ? stripOptions : void 0;
17269
17371
  }, pickOption = (event) => {
@@ -17442,9 +17544,9 @@ function buildPanel(controls, host, options) {
17442
17544
  return;
17443
17545
  }
17444
17546
  if (pressed("presets", event)) {
17445
- let direction = key === keys.presets[0] ? -1 : 1;
17446
- if (visible(presetStrip.element) && presetStrip.getEntries().length > 1)
17447
- presetStrip.cycle(direction);
17547
+ let direction = key === keys.presets[0] ? -1 : 1, strip = groupStripAt(target) ?? presetStrip;
17548
+ if (visible(strip.element) && strip.getEntries().length > 1)
17549
+ strip.cycle(direction);
17448
17550
  else if (!cycleTab(direction))
17449
17551
  return;
17450
17552
  event.preventDefault(), event.stopPropagation();
@@ -17470,6 +17572,8 @@ function buildPanel(controls, host, options) {
17470
17572
  event.preventDefault(), searchReturn = target, search.focus();
17471
17573
  return;
17472
17574
  }
17575
+ if (/^[1-9]$/.test(key) && groupStripAt(target))
17576
+ return;
17473
17577
  if (/^[1-9]$/.test(key)) {
17474
17578
  let group = page ? focusedGroup(target) : void 0;
17475
17579
  if (Boolean(group && groupChips.has(group))) {
@@ -17518,7 +17622,7 @@ function buildPanel(controls, host, options) {
17518
17622
  return;
17519
17623
  }
17520
17624
  if (pressed("save", event) && showVariants) {
17521
- event.preventDefault(), presetStrip.save();
17625
+ event.preventDefault(), (groupStripAt(target) ?? presetStrip).save();
17522
17626
  return;
17523
17627
  }
17524
17628
  let index = rowAt(target);
@@ -17891,6 +17995,7 @@ function buildPanel(controls, host, options) {
17891
17995
  ...undock ? {} : { headerDie: { button: randomButton, die } },
17892
17996
  locks: lockIcons,
17893
17997
  presetStrip,
17998
+ groupStrips: () => groupStrips.values(),
17894
17999
  tabs: tabStrip,
17895
18000
  groups: gpuGroups,
17896
18001
  cards,
@@ -17997,7 +18102,10 @@ function buildPanel(controls, host, options) {
17997
18102
  dropGpu(r);
17998
18103
  for (let c of groupChips.values())
17999
18104
  c.dispose();
18000
- presetStrip.dispose(), tabStrip.dispose(), tabMotion.finish(), doc.removeEventListener("keydown", onGlobalKey), layout?.dispose(), tileSizes.disconnect(), headSize.disconnect(), win?.removeEventListener("scroll", onScroll), win?.removeEventListener("scrollend", onScrollEnd);
18105
+ presetStrip.dispose();
18106
+ for (let strip of groupStrips.values())
18107
+ strip.dispose();
18108
+ tabStrip.dispose(), tabMotion.finish(), doc.removeEventListener("keydown", onGlobalKey), layout?.dispose(), tileSizes.disconnect(), headSize.disconnect(), win?.removeEventListener("scroll", onScroll), win?.removeEventListener("scrollend", onScrollEnd);
18001
18109
  for (let type of ["wheel", "touchstart", "keydown"])
18002
18110
  doc.removeEventListener(type, touched, !0);
18003
18111
  doc.removeEventListener("pointerdown", onPointerDown, !0);