@kitlangton/knobs 0.4.0-next.13 → 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.
package/dist/index.js CHANGED
@@ -461,6 +461,7 @@ var META_KEYS = [
461
461
  "sectionFolded",
462
462
  "tab",
463
463
  "pinned",
464
+ "inPresets",
464
465
  "subsection"
465
466
  ];
466
467
  function pickMeta(source, skip = []) {
@@ -494,6 +495,13 @@ function pinnedKeys(schema, groups = []) {
494
495
  out.add(key);
495
496
  return out;
496
497
  }
498
+ function presetFreeKeys(schema, pinned, groups = {}) {
499
+ let out = new Set(pinned);
500
+ for (let [key, f] of Object.entries(schema))
501
+ if (f.inPresets === !1 || f.group !== void 0 && groups[f.group] === !1)
502
+ out.add(key);
503
+ return out;
504
+ }
497
505
  var SPRING = {
498
506
  duration: { min: 0.05, max: 2, step: 0.01 },
499
507
  bounce: { min: -0.5, max: 1, step: 0.01 }
@@ -1631,7 +1639,7 @@ function createVariants(schema, id, key, storage, get, set, notify, enabled, see
1631
1639
  }
1632
1640
 
1633
1641
  // src/core/store.ts
1634
- var devBuild = () => !1;
1642
+ var devBuild = () => !1, defaultLabelOf = (label, scope) => typeof label === "object" ? label[scope] : label;
1635
1643
  function sameField(a, b) {
1636
1644
  if (a === b)
1637
1645
  return !0;
@@ -1661,7 +1669,11 @@ function defineControls(id, input, options = {}) {
1661
1669
  if (pinnedOf?.schema !== schema)
1662
1670
  pinnedOf = { schema, keys: pinnedKeys(schema, options.pinned) };
1663
1671
  return pinnedOf.keys;
1664
- }, variantsChoice = () => variantsOf(schema, (key) => pinned().has(key)), scopingOf, scoping = () => {
1672
+ }, freeOf, presetFree = () => {
1673
+ if (freeOf?.schema !== schema)
1674
+ freeOf = { schema, keys: presetFreeKeys(schema, pinned(), options.inPresets) };
1675
+ return freeOf.keys;
1676
+ }, variantsChoice = () => variantsOf(schema, (key) => presetFree().has(key)), scopingOf, scoping = () => {
1665
1677
  if (scopingOf?.schema === schema)
1666
1678
  return scopingOf;
1667
1679
  let fields = schema, groupTab = /* @__PURE__ */ new Map;
@@ -1676,7 +1688,7 @@ function defineControls(id, input, options = {}) {
1676
1688
  named.add(variants);
1677
1689
  let of = /* @__PURE__ */ new Map;
1678
1690
  for (let k of valueKeys(schema)) {
1679
- if (pinned().has(k))
1691
+ if (presetFree().has(k))
1680
1692
  continue;
1681
1693
  let f = fields[k], tab = tabOf(f);
1682
1694
  of.set(k, tab !== void 0 && named.has(tab) ? tab : f.group !== void 0 && named.has(f.group) ? f.group : "");
@@ -2256,9 +2268,10 @@ function defineControls(id, input, options = {}) {
2256
2268
  sectionFolded: options.sectionFolded,
2257
2269
  presetLabel: (presetId, scope = "") => {
2258
2270
  let v = scoping().variants === scope ? variantsChoice() : void 0, option = presetId === "default" ? v?.field.value : presetId;
2259
- return options.presetLabels?.[presetId] ?? (presetId === "default" ? options.defaultLabel : void 0) ?? (v && option !== void 0 && v.field.options.includes(option) ? v.field.labels?.[option] ?? option : presetId);
2271
+ return options.presetLabels?.[presetId] ?? (presetId === "default" ? defaultLabelOf(options.defaultLabel, scope) : void 0) ?? (v && option !== void 0 && v.field.options.includes(option) ? v.field.labels?.[option] ?? option : presetId);
2260
2272
  },
2261
2273
  isPinned: (key) => pinned().has(key),
2274
+ inPresets: (key) => !presetFree().has(key),
2262
2275
  get,
2263
2276
  set,
2264
2277
  sync,
@@ -2877,6 +2890,7 @@ function createRemoteControls(id, descriptors, options = {}) {
2877
2890
  presets: options.presets,
2878
2891
  scopedPresets: options.scopedPresets,
2879
2892
  pinned: options.pinned,
2893
+ inPresets: options.inPresets,
2880
2894
  groupPresets: options.groupPresets,
2881
2895
  onPreset: options.onPreset,
2882
2896
  actions: options.actions,
package/dist/panel.js CHANGED
@@ -3303,9 +3303,16 @@ ${GRAD_PROPS}
3303
3303
  /* The active preset's modified mark: the values have drifted off it (a row's dot, in its corner). */
3304
3304
  .knobs-panel .knobs-preset-chip::after{content:"";position:absolute;top:4px;right:4px;width:4px;height:4px;border-radius:50%;background:var(--_k-modified);opacity:0;transform:scale(.4);pointer-events:none;transition:opacity .16s ease-out,transform .28s cubic-bezier(.34,1.5,.64,1)}
3305
3305
  .knobs-panel .knobs-preset-chip[data-drifted]::after{opacity:.95;transform:none}
3306
+ /* Digits that stay on the chips (hints: { digits: "always" }): quiet, in a column of their own. */
3307
+ .knobs-panel .knobs-preset-key{display:inline-block;margin-right:6px;color:var(--_k-fg-3);font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);font-variant-numeric:tabular-nums}
3308
+ /* A group's own presets at the top of its card: the chips sit on the card, with no tray. */
3309
+ .knobs-panel .knobs-card>.knobs-presets{margin:0;padding:max(4px,calc((var(--_k-row) - var(--k-button-h,var(--_k-control-h))) / 2)) var(--_k-inset);background:transparent;border-radius:0}
3310
+ .knobs-panel .knobs-card>.knobs-presets[data-display="list"]{padding-inline:0}
3311
+ .knobs-card>.knobs-presets:hover{background:transparent}
3306
3312
  /* Naming a saved preset: a field over its chip that opens out from the middle to the chip's width;
3307
3313
  the chip's own name hides beneath it. Leaving, it fades. */
3308
3314
  .knobs-panel .knobs-preset-chip[data-naming]{color:transparent!important}
3315
+ .knobs-panel .knobs-preset-chip[data-naming] .knobs-preset-key{color:transparent}
3309
3316
  .knobs-panel .knobs-preset-name{position:absolute;z-index:2;box-sizing:border-box;min-width:0;margin:0;padding:0 6px;border:1px solid color-mix(in srgb,var(--_k-accent) 55%,transparent);border-radius:var(--k-button-radius,var(--_k-radius-md));outline:none;background:color-mix(in srgb,var(--_k-accent) 10%,transparent);color:var(--_k-fg);caret-color:var(--_k-accent);
3310
3317
  font-family:inherit;font-size:var(--k-button-font-size,var(--_k-text-control));font-weight:var(--_k-weight-title);letter-spacing:inherit;text-align:center;animation:knobs-name-in .25s cubic-bezier(.2,.8,.2,1);transition:opacity .11s cubic-bezier(.4,0,1,1)}
3311
3318
  .knobs-preset-name::selection{background:color-mix(in srgb,var(--_k-accent) 24%,transparent);color:var(--_k-fg)}
@@ -3916,7 +3923,7 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
3916
3923
  .knobs-panel[data-gpu-active="true"] :where(header strong,header button,.knobs-tile,.knobs-section-head,.knobs-group-label,.knobs-peek,.knobs-card>*,.knobs-presets,.knobs-preset-chip,.knobs-group-head,.knobs-group-head button,.knobs-card,.knobs-row,.knobs-row>span:first-child,.knobs-slider,.knobs-track,.knobs-label,.knobs-value,.knobs-digits,.knobs-digits *,.knobs-unit,.knobs-seg,.knobs-seg button,.knobs-custom-actions button,.knobs-action,.knobs-select,.knobs-select-value,
3917
3924
  .knobs-sub,.knobs-x-head,.knobs-x-label,.knobs-x-read,.knobs-strip-label,.knobs-strip-read,.knobs-pad,.knobs-ease,.knobs-spring-curve,.knobs-ease-chips,.knobs-ease-chip,.knobs-graph,.knobs-graph-read,
3918
3925
  .knobs-tabs,.knobs-tab,.knobs-subhead,.knobs-subhead-label,.knobs-match,.knobs-cell,.knobs-cell-axis,.knobs-cell-read,.knobs-link,.knobs-range-read,.knobs-angle,.knobs-angle-read,.knobs-dial,
3919
- .knobs-seed,.knobs-seed-read,.knobs-seed-digits,.knobs-seed-digits *,.knobs-seed-roll,.knobs-keys,.knobs-keys-text,.knobs-tool,.knobs-tool-label,.knobs-tool-key,
3926
+ .knobs-seed,.knobs-seed-read,.knobs-seed-digits,.knobs-seed-digits *,.knobs-seed-roll,.knobs-keys,.knobs-keys-text,.knobs-tool,.knobs-tool-label,.knobs-tool-key,.knobs-preset-key,
3920
3927
  .knobs-section-name,.knobs-section-summary,.knobs-section-desc,.knobs-group-desc,.knobs-group-summary,.knobs-tab-count):not(.knobs-live,.knobs-live *){
3921
3928
  background:transparent!important;color:transparent!important;border-color:transparent!important;box-shadow:none!important;
3922
3929
  }
@@ -7057,6 +7064,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
7057
7064
  let initializedPill = false;
7058
7065
  let slideUntil = 0;
7059
7066
  let display = "auto";
7067
+ let digits = false;
7060
7068
  let chipMin = 0;
7061
7069
  let scope = null;
7062
7070
  const at = () => scope ?? "";
@@ -7216,14 +7224,21 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
7216
7224
  }
7217
7225
  lastVariantsRef = variantsRef;
7218
7226
  const nextEntries = buildEntries();
7219
- const nextSig = JSON.stringify([scope, nextEntries.map((e) => [e.id, e.label])]);
7227
+ const nextSig = JSON.stringify([scope, digits, nextEntries.map((e) => [e.id, e.label])]);
7220
7228
  entries = nextEntries;
7221
7229
  syncShown();
7222
7230
  if (nextSig !== signature) {
7223
7231
  signature = nextSig;
7224
7232
  entries = nextEntries;
7225
7233
  buttons = entries.map((entry, idx) => {
7226
- const b = el("button", "knobs-preset-chip", entry.label);
7234
+ const b = el("button", "knobs-preset-chip");
7235
+ if (digits && idx < 9) {
7236
+ const key = el("span", "knobs-preset-key", String(idx + 1));
7237
+ key.setAttribute("aria-hidden", "true");
7238
+ b.append(key, el("span", "knobs-preset-label", entry.label));
7239
+ b.setAttribute("aria-keyshortcuts", String(idx + 1));
7240
+ } else
7241
+ b.textContent = entry.label;
7227
7242
  b.type = "button";
7228
7243
  b.tabIndex = -1;
7229
7244
  b.dataset.presetId = entry.id;
@@ -7521,6 +7536,16 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
7521
7536
  if (placeDisplay())
7522
7537
  measurePill(true);
7523
7538
  },
7539
+ digits(on) {
7540
+ if (digits === on)
7541
+ return;
7542
+ digits = on;
7543
+ root.toggleAttribute("data-digits", on);
7544
+ if (signature === "")
7545
+ return;
7546
+ signature = "";
7547
+ update();
7548
+ },
7524
7549
  save: () => {
7525
7550
  let message = "";
7526
7551
  run(() => message = save());
@@ -10638,6 +10663,135 @@ class GpuOverlayManager {
10638
10663
  d[b + 42] = more ? more[2] : 0;
10639
10664
  d[b + 43] = more ? more[3] : 0;
10640
10665
  }
10666
+ drawPresetStrip(ps, tokens, clip, hinting, reducedMotion, dt, tray) {
10667
+ let moving = false;
10668
+ const psHovered = this.curPanelHovered && ps.element.matches(":hover");
10669
+ this.curSubHovered = psHovered;
10670
+ const psRect = this.rectOf(ps.element);
10671
+ const trackRect = this.rectOf(ps.track);
10672
+ if (tray && !tokens.boxed)
10673
+ this.pushPrim({
10674
+ x: psRect.left,
10675
+ y: psRect.top,
10676
+ w: psRect.width,
10677
+ h: psRect.height,
10678
+ color: tokens.segBg,
10679
+ color2: tokens.accent,
10680
+ radius: tokens.radius,
10681
+ glowOrBlur: tokens.glowStrength * 8,
10682
+ kind: 1,
10683
+ p0: psHovered || this.isFocused(ps.element) ? 0.4 : 0
10684
+ });
10685
+ const stripClip = intersectClip(clip, [psRect.left, psRect.top, psRect.right, psRect.bottom]);
10686
+ const activeState = ps.getActiveState();
10687
+ const chipButtons = ps.getButtons();
10688
+ if (tokens.boxed)
10689
+ for (const btn of chipButtons) {
10690
+ const br = this.rectOf(btn);
10691
+ if (br.right < stripClip[0] || br.left > stripClip[2])
10692
+ continue;
10693
+ this.drawButtonBox(btn, br, this.isHovered(btn), this.missed(btn, hinting), stripClip);
10694
+ }
10695
+ const activeBtn = activeState.activeIndex >= 0 ? chipButtons[activeState.activeIndex] : undefined;
10696
+ const pe = ps.pillEdges;
10697
+ const activeBtnRect = activeBtn ? this.rectOf(activeBtn) : undefined;
10698
+ const panned = livePill(ps.track);
10699
+ if (activeBtn && activeBtnRect && !activeState.isCustom) {
10700
+ const left = activeBtnRect.left - trackRect.left;
10701
+ const right = left + Math.max(20, activeBtnRect.width);
10702
+ const top = activeBtnRect.top - trackRect.top;
10703
+ this.carry(pe, activeBtn, left, right, ps.pillY, top);
10704
+ if (panned) {
10705
+ pe.snap(panned.left, panned.right);
10706
+ ps.pillY.snap(panned.top);
10707
+ } else if (reducedMotion || pe.end.x - pe.start.x <= 1) {
10708
+ pe.snap(left, right);
10709
+ ps.pillY.snap(top);
10710
+ } else {
10711
+ pe.to(left, right, 0, trailScale(tokens.blurStrength));
10712
+ ps.pillY.set(top);
10713
+ }
10714
+ ps.pillAlpha.set(activeState.drifted ? DRIFTED_PILL : 1);
10715
+ } else {
10716
+ ps.pillAlpha.set(0);
10717
+ }
10718
+ for (const sp of [pe.start, pe.end, ps.pillY, ps.pillAlpha]) {
10719
+ if (reducedMotion)
10720
+ sp.snap(sp.target);
10721
+ else {
10722
+ sp.step(dt);
10723
+ if (!sp.resting)
10724
+ moving = true;
10725
+ }
10726
+ }
10727
+ const pillAlpha = clamp2(ps.pillAlpha.x, 0, 1);
10728
+ if (pillAlpha > 0.01) {
10729
+ this.pushPrim({
10730
+ x: trackRect.left + pe.start.x,
10731
+ y: trackRect.top + ps.pillY.x,
10732
+ w: Math.max(18, pe.end.x - pe.start.x),
10733
+ h: Math.max(16, activeBtnRect?.height ?? trackRect.height),
10734
+ clip: stripClip,
10735
+ color: withAlpha2(tokens.pillBg, pillAlpha),
10736
+ color2: withAlpha2(tokens.pillBorder, pillAlpha),
10737
+ strokeWidth: this.hairline(tokens),
10738
+ aux2: withAlpha2(tokens.rim, tokens.rim[3] * pillAlpha),
10739
+ radius: tokens.buttonRadius ?? tokens.radiusMd,
10740
+ glowOrBlur: tokens.glowStrength,
10741
+ kind: 3,
10742
+ blurX: reducedMotion ? 0 : clamp2(pe.velocity * 0.006 * tokens.motionBlur, -8, 8)
10743
+ });
10744
+ }
10745
+ for (let i = 0;i < chipButtons.length; i++) {
10746
+ const btn = chipButtons[i];
10747
+ const br = this.rectOf(btn);
10748
+ if (br.right < stripClip[0] || br.left > stripClip[2])
10749
+ continue;
10750
+ const isActive = i === activeState.activeIndex;
10751
+ const chipHovered = this.isHovered(btn);
10752
+ if (chipHovered)
10753
+ this.curSubHovered = false;
10754
+ const mark = this.ease(btn, "dot", btn.hasAttribute("data-drifted") ? 1 : 0);
10755
+ if (mark > 0.01) {
10756
+ const size = 4 * (0.4 + 0.6 * mark);
10757
+ this.pushPrim({
10758
+ x: br.right - 6 - size / 2,
10759
+ y: br.top + 6 - size / 2,
10760
+ w: size,
10761
+ h: size,
10762
+ clip: stripClip,
10763
+ color: withAlpha2(tokens.modified, 0.95 * mark),
10764
+ glowOrBlur: 0.5,
10765
+ kind: 7
10766
+ });
10767
+ }
10768
+ if (btn.hasAttribute("data-naming"))
10769
+ continue;
10770
+ const key = btn.querySelector(".knobs-preset-key");
10771
+ const name = key && btn.querySelector(".knobs-preset-label");
10772
+ if (key && name) {
10773
+ const kr = this.rectOf(key);
10774
+ this.drawText(key.textContent ?? "", kr.left, br.top + br.height * 0.5, withAlpha2(tokens.dim, this.missed(btn, hinting)), { px: tokens.textMeta, weight: tokens.labelWeight, clip: stripClip });
10775
+ }
10776
+ const nr = name ? this.rectOf(name) : undefined;
10777
+ this.drawText(name ? name.textContent ?? "" : btn.textContent ?? "", nr ? nr.left : br.left + br.width * 0.5, br.top + br.height * 0.5, withAlpha2(this.hot(btn, isActive || chipHovered, tokens.buttonColor ?? tokens.label), this.missed(btn, hinting)), {
10778
+ px: tokens.buttonFontSize ?? tokens.textControl,
10779
+ weight: isActive ? tokens.titleWeight : tokens.labelWeight,
10780
+ align: nr ? "left" : "center",
10781
+ clip: stripClip
10782
+ });
10783
+ }
10784
+ if (ps.saveButton && !ps.saveButton.hidden) {
10785
+ const sr = this.rectOf(ps.saveButton);
10786
+ const hot = this.isHovered(ps.saveButton);
10787
+ this.drawText("+", sr.left + sr.width * 0.5, sr.top + sr.height * 0.5, this.hot(ps.saveButton, hot, tokens.dim), {
10788
+ px: tokens.textTitle,
10789
+ weight: 400,
10790
+ align: "center"
10791
+ });
10792
+ }
10793
+ return moving;
10794
+ }
10641
10795
  hairline(tokens) {
10642
10796
  return Math.max(1, Math.round(tokens.rimWidth * this.curScale)) / this.curScale;
10643
10797
  }
@@ -12851,7 +13005,6 @@ class GpuOverlayManager {
12851
13005
  this.placeBackdrop(panel);
12852
13006
  continue;
12853
13007
  }
12854
- const pillFill = tokens.pillBg;
12855
13008
  if (pageB)
12856
13009
  this.placeBackdrop(panel);
12857
13010
  const covers = drawn.some((r) => r.left < card.right && r.right > card.left && r.top < card.bottom && r.bottom > card.top);
@@ -13019,129 +13172,7 @@ class GpuOverlayManager {
13019
13172
  const stripAlpha = this.curAlpha;
13020
13173
  if (!ps.element.hidden) {
13021
13174
  this.curAlpha = stripAlpha * this.presence(ps.element);
13022
- const psHovered = this.curPanelHovered && ps.element.matches(":hover");
13023
- this.curSubHovered = psHovered;
13024
- const psRect = this.rectOf(ps.element);
13025
- const trackRect = this.rectOf(ps.track);
13026
- if (!tokens.boxed)
13027
- this.pushPrim({
13028
- x: psRect.left,
13029
- y: psRect.top,
13030
- w: psRect.width,
13031
- h: psRect.height,
13032
- color: tokens.segBg,
13033
- color2: tokens.accent,
13034
- radius: tokens.radius,
13035
- glowOrBlur: tokens.glowStrength * 8,
13036
- kind: 1,
13037
- p0: psHovered || this.isFocused(ps.element) ? 0.4 : 0
13038
- });
13039
- const stripClip = intersectClip(bodyClip, [
13040
- psRect.left,
13041
- psRect.top,
13042
- psRect.right,
13043
- psRect.bottom
13044
- ]);
13045
- const activeState = ps.getActiveState();
13046
- const chipButtons = ps.getButtons();
13047
- if (tokens.boxed)
13048
- for (const btn of chipButtons) {
13049
- const br = this.rectOf(btn);
13050
- if (br.right < stripClip[0] || br.left > stripClip[2])
13051
- continue;
13052
- this.drawButtonBox(btn, br, this.isHovered(btn), this.missed(btn, hinting), stripClip);
13053
- }
13054
- const activeBtn = activeState.activeIndex >= 0 ? chipButtons[activeState.activeIndex] : undefined;
13055
- const pe = ps.pillEdges;
13056
- const activeBtnRect = activeBtn ? this.rectOf(activeBtn) : undefined;
13057
- const panned = livePill(ps.track);
13058
- if (activeBtn && activeBtnRect && !activeState.isCustom) {
13059
- const left = activeBtnRect.left - trackRect.left;
13060
- const right = left + Math.max(20, activeBtnRect.width);
13061
- const top = activeBtnRect.top - trackRect.top;
13062
- this.carry(pe, activeBtn, left, right, ps.pillY, top);
13063
- if (panned) {
13064
- pe.snap(panned.left, panned.right);
13065
- ps.pillY.snap(panned.top);
13066
- } else if (reducedMotion || pe.end.x - pe.start.x <= 1) {
13067
- pe.snap(left, right);
13068
- ps.pillY.snap(top);
13069
- } else {
13070
- pe.to(left, right, 0, trailScale(tokens.blurStrength));
13071
- ps.pillY.set(top);
13072
- }
13073
- ps.pillAlpha.set(activeState.drifted ? DRIFTED_PILL : 1);
13074
- } else {
13075
- ps.pillAlpha.set(0);
13076
- }
13077
- for (const sp of [pe.start, pe.end, ps.pillY, ps.pillAlpha]) {
13078
- if (reducedMotion)
13079
- sp.snap(sp.target);
13080
- else {
13081
- sp.step(dt);
13082
- if (!sp.resting)
13083
- anyMoving = true;
13084
- }
13085
- }
13086
- const pillAlpha = clamp2(ps.pillAlpha.x, 0, 1);
13087
- if (pillAlpha > 0.01) {
13088
- this.pushPrim({
13089
- x: trackRect.left + pe.start.x,
13090
- y: trackRect.top + ps.pillY.x,
13091
- w: Math.max(18, pe.end.x - pe.start.x),
13092
- h: Math.max(16, activeBtnRect?.height ?? trackRect.height),
13093
- clip: stripClip,
13094
- color: withAlpha2(pillFill, pillAlpha),
13095
- color2: withAlpha2(tokens.pillBorder, pillAlpha),
13096
- strokeWidth: this.hairline(tokens),
13097
- aux2: withAlpha2(tokens.rim, tokens.rim[3] * pillAlpha),
13098
- radius: tokens.buttonRadius ?? tokens.radiusMd,
13099
- glowOrBlur: tokens.glowStrength,
13100
- kind: 3,
13101
- blurX: reducedMotion ? 0 : clamp2(pe.velocity * 0.006 * tokens.motionBlur, -8, 8)
13102
- });
13103
- }
13104
- for (let i = 0;i < chipButtons.length; i++) {
13105
- const btn = chipButtons[i];
13106
- const br = this.rectOf(btn);
13107
- if (br.right < stripClip[0] || br.left > stripClip[2])
13108
- continue;
13109
- const isActive = i === activeState.activeIndex;
13110
- const chipHovered = this.isHovered(btn);
13111
- if (chipHovered)
13112
- this.curSubHovered = false;
13113
- const mark = this.ease(btn, "dot", btn.hasAttribute("data-drifted") ? 1 : 0);
13114
- if (mark > 0.01) {
13115
- const size = 4 * (0.4 + 0.6 * mark);
13116
- this.pushPrim({
13117
- x: br.right - 6 - size / 2,
13118
- y: br.top + 6 - size / 2,
13119
- w: size,
13120
- h: size,
13121
- clip: stripClip,
13122
- color: withAlpha2(tokens.modified, 0.95 * mark),
13123
- glowOrBlur: 0.5,
13124
- kind: 7
13125
- });
13126
- }
13127
- if (btn.hasAttribute("data-naming"))
13128
- continue;
13129
- this.drawText(btn.textContent ?? "", br.left + br.width * 0.5, br.top + br.height * 0.5, withAlpha2(this.hot(btn, isActive || chipHovered, tokens.buttonColor ?? tokens.label), this.missed(btn, hinting)), {
13130
- px: tokens.buttonFontSize ?? tokens.textControl,
13131
- weight: isActive ? tokens.titleWeight : tokens.labelWeight,
13132
- align: "center",
13133
- clip: stripClip
13134
- });
13135
- }
13136
- if (ps.saveButton && !ps.saveButton.hidden) {
13137
- const sr = this.rectOf(ps.saveButton);
13138
- const hot = this.isHovered(ps.saveButton);
13139
- this.drawText("+", sr.left + sr.width * 0.5, sr.top + sr.height * 0.5, this.hot(ps.saveButton, hot, tokens.dim), {
13140
- px: tokens.textTitle,
13141
- weight: 400,
13142
- align: "center"
13143
- });
13144
- }
13175
+ anyMoving = this.drawPresetStrip(ps, tokens, bodyClip, hinting, reducedMotion, dt, true) || anyMoving;
13145
13176
  this.curAlpha = stripAlpha;
13146
13177
  }
13147
13178
  const tabs = panel.tabs;
@@ -13398,6 +13429,15 @@ class GpuOverlayManager {
13398
13429
  this.drawScope(card, cr, tokens.radius, 1);
13399
13430
  }
13400
13431
  }
13432
+ for (const gs of panel.groupStrips?.() ?? []) {
13433
+ const card = gs.element.parentElement;
13434
+ if (!card || gs.element.closest("[hidden]"))
13435
+ continue;
13436
+ const cr = cardRects.get(card) ?? this.rectOf(card);
13437
+ this.curClip = clipToTile(card, intersectClip(bodyClip, [cr.left, cr.top, cr.right, cr.bottom]));
13438
+ this.curAlpha = panelAlpha * this.presence(card) * fadeOf(card);
13439
+ anyMoving = this.drawPresetStrip(gs, tokens, this.curClip, hinting, reducedMotion, dt, false) || anyMoving;
13440
+ }
13401
13441
  this.curClip = bodyClip;
13402
13442
  this.curAlpha = panelAlpha;
13403
13443
  const washed = new Set;
@@ -20513,7 +20553,7 @@ function presence(el, o = {}) {
20513
20553
  });
20514
20554
  const tune = (seconds, zeta) => {
20515
20555
  const reduced = reducedMotion(win);
20516
- spring.omega = 2 * Math.PI / (reduced ? FADE.duration : seconds);
20556
+ spring.omega = 2 * Math.PI / (reduced ? Math.min(FADE.duration, seconds) : seconds);
20517
20557
  spring.zeta = reduced ? 1 : zeta;
20518
20558
  };
20519
20559
  render();
@@ -26222,7 +26262,9 @@ function buildPanel(controls, host, options) {
26222
26262
  if (helpButton?.hidden)
26223
26263
  titleEl.title = `${titleEl.textContent ? `${titleEl.textContent}
26224
26264
  ` : ""}Press ${show("help")} for keyboard shortcuts`;
26225
- const presetLabel = (id, scope = "") => options.presetLabels?.[id] ?? (id === "default" ? options.defaultLabel : undefined) ?? controls.presetLabel(id, scope);
26265
+ const presetLabel = (id, scope = "") => options.presetLabels?.[id] ?? (id === "default" ? typeof options.defaultLabel === "object" ? options.defaultLabel[scope] : options.defaultLabel : undefined) ?? controls.presetLabel(id, scope);
26266
+ const hintReach = (typeof options.hints === "object" ? options.hints.reach : options.hints) ?? "page";
26267
+ const stripDigitsShown = typeof options.hints === "object" && options.hints.digits === "always";
26226
26268
  const presetStrip = createPresetStrip(doc, controls, showVariants, run, () => wakeGpu(), presetLabel, (entries) => presetsTouchTab(entries), () => stripScope());
26227
26269
  const tabStrip = createTabStrip(doc, (id, animate) => selectTab(id, animate));
26228
26270
  let activeTab;
@@ -26276,6 +26318,27 @@ function buildPanel(controls, host, options) {
26276
26318
  reveal(header, any, () => wakeGpu());
26277
26319
  };
26278
26320
  presetStrip.display(page ? "chips" : options.presetDisplay ?? "auto");
26321
+ presetStrip.digits(stripDigitsShown);
26322
+ const groupStrips = new Map;
26323
+ const tabNamed = (id) => (options.tabs ?? []).some((t) => (typeof t === "string" ? t : t.id) === id) || Object.values(controls.schema).some((f) => f.tab === id);
26324
+ const groupStripFor = (group) => {
26325
+ if (page || !Object.hasOwn(controls.scopes, group) || tabNamed(group))
26326
+ return;
26327
+ let strip = groupStrips.get(group);
26328
+ if (!strip) {
26329
+ strip = createPresetStrip(doc, controls, showVariants, run, () => wakeGpu(), presetLabel, undefined, () => stripScope() === group ? null : group);
26330
+ strip.element.setAttribute("aria-label", `${groupTitle(group)} presets`);
26331
+ strip.display(options.presetDisplay ?? "auto");
26332
+ strip.digits(stripDigitsShown);
26333
+ groupStrips.set(group, strip);
26334
+ }
26335
+ return strip;
26336
+ };
26337
+ const updateStrips = () => {
26338
+ presetStrip.update();
26339
+ for (const strip of groupStrips.values())
26340
+ strip.update();
26341
+ };
26279
26342
  const allActions = [
26280
26343
  ...controls.actions ?? [],
26281
26344
  ...options.actions ?? []
@@ -26734,7 +26797,7 @@ function buildPanel(controls, host, options) {
26734
26797
  const specs = [];
26735
26798
  const placed = new Set;
26736
26799
  for (const [key, field] of entries) {
26737
- if (field.kind === "choice" && field.variants && !controls.isPinned(key))
26800
+ if (field.kind === "choice" && field.variants && controls.inPresets(key))
26738
26801
  continue;
26739
26802
  const pair = field.kind === "knob" && field.spring ? halves.get(field.spring.id) : undefined;
26740
26803
  if (pair?.duration && pair.bounce && field.kind === "knob" && field.spring) {
@@ -27432,6 +27495,7 @@ function buildPanel(controls, host, options) {
27432
27495
  const chosen = [];
27433
27496
  const pinnedBlocks = new Set;
27434
27497
  const usedSubheads = new Set;
27498
+ const stripped = new Set;
27435
27499
  for (const run of runs) {
27436
27500
  const at = free.findIndex((b) => b.group === run.group);
27437
27501
  const block = at >= 0 ? free.splice(at, 1)[0] : newBlock(run.group);
@@ -27466,7 +27530,10 @@ function buildPanel(controls, host, options) {
27466
27530
  under = name;
27467
27531
  nodes.push(r.row);
27468
27532
  }
27469
- arrange(block.card, nodes);
27533
+ const strip = run.group !== undefined && !run.pinned && !stripped.has(run.group) ? groupStripFor(run.group) : undefined;
27534
+ if (strip)
27535
+ stripped.add(run.group);
27536
+ arrange(block.card, strip ? [strip.element, ...nodes] : nodes);
27470
27537
  if (animate && at >= 0) {
27471
27538
  for (const r of run.rows)
27472
27539
  if (entering.has(r))
@@ -27634,7 +27701,7 @@ function buildPanel(controls, host, options) {
27634
27701
  const tab = tabOf(r);
27635
27702
  return !pinnedRow(r) && (tab === undefined || tab === activeTab) ? r.keys.filter((k) => controls.scopeOf(k) === "") : [];
27636
27703
  });
27637
- const v = variantsOf(controls.schema, controls.isPinned);
27704
+ const v = variantsOf(controls.schema, (key) => !controls.inPresets(key));
27638
27705
  if (v) {
27639
27706
  const tab = v.field.tab ?? (v.field.group !== undefined ? groupTab(v.field.group) : undefined);
27640
27707
  if (tab === undefined || tab === activeTab)
@@ -27674,7 +27741,7 @@ function buildPanel(controls, host, options) {
27674
27741
  writeSession(controls.id, { tab: id });
27675
27742
  tabStrip.select(id, animate);
27676
27743
  const focused = doc.activeElement;
27677
- presetStrip.update();
27744
+ updateStrips();
27678
27745
  applyVisibility(animate ? "tab" : "instant");
27679
27746
  if (focused && root.contains(focused) && focused.closest("[hidden],[data-leaving]"))
27680
27747
  tabStrip.focus({ preventScroll: true, focusVisible: false });
@@ -28121,7 +28188,7 @@ function buildPanel(controls, host, options) {
28121
28188
  }
28122
28189
  const stagger = initialized ? cascade(changedCount) : 0;
28123
28190
  const moving = initialized ? rows.filter(changed) : rows;
28124
- presetStrip.update();
28191
+ updateStrips();
28125
28192
  batchMeasures(() => {
28126
28193
  for (const row of moving) {
28127
28194
  row.slider?.prime();
@@ -28291,14 +28358,15 @@ function buildPanel(controls, host, options) {
28291
28358
  if (s.label?.hasAttribute("data-foldable"))
28292
28359
  add(s.label, sectionTitle(s.name), () => s.label.focus(), s.label.firstElementChild);
28293
28360
  }
28294
- presetStrip.getEntries().forEach((entry, i) => {
28295
- const chip = presetStrip.getButtons()[i];
28296
- if (chip)
28297
- add(chip, entry.label, () => {
28298
- presetStrip.selectById(entry.id);
28299
- presetStrip.element.focus({ preventScroll: true });
28300
- });
28301
- });
28361
+ for (const strip of [presetStrip, ...groupStrips.values()])
28362
+ strip.getEntries().forEach((entry, i) => {
28363
+ const chip = strip.getButtons()[i];
28364
+ if (chip)
28365
+ add(chip, entry.label, () => {
28366
+ strip.selectById(entry.id);
28367
+ strip.element.focus({ preventScroll: true });
28368
+ });
28369
+ });
28302
28370
  for (const 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")) {
28303
28371
  add(b, b.dataset.hint ?? b.getAttribute("aria-label") ?? b.textContent ?? "", () => b.focus());
28304
28372
  }
@@ -28317,7 +28385,7 @@ function buildPanel(controls, host, options) {
28317
28385
  }
28318
28386
  return targets;
28319
28387
  };
28320
- const unregisterHints = (options.hints ?? "page") === "page" && bound("hint") ? pageHints(doc).register(root, () => collectHintTargets().map((t) => ({
28388
+ const unregisterHints = hintReach === "page" && bound("hint") ? pageHints(doc).register(root, () => collectHintTargets().map((t) => ({
28321
28389
  el: t.el,
28322
28390
  label: t.display,
28323
28391
  ...t.labelEl ? { labelEl: t.labelEl } : {},
@@ -28383,6 +28451,9 @@ function buildPanel(controls, host, options) {
28383
28451
  runs.push({ at: b.head, stops: shown([b.head]) });
28384
28452
  for (const sh of subheads.values())
28385
28453
  runs.push({ at: sh.el, stops: shown([sh.el]) });
28454
+ for (const gs of groupStrips.values())
28455
+ if (visible(gs.element))
28456
+ runs.push({ at: gs.element, stops: shown([gs.element]) });
28386
28457
  runs.push(...strips);
28387
28458
  runs.sort((a, b) => a.at.compareDocumentPosition(b.at) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
28388
28459
  for (const run of runs)
@@ -28860,6 +28931,9 @@ function buildPanel(controls, host, options) {
28860
28931
  return { el: sub, level: "row" };
28861
28932
  return { el: r.row.closest(".knobs-card > *") ?? r.row, level: "row" };
28862
28933
  }
28934
+ const strip = active.closest(".knobs-card > .knobs-presets");
28935
+ if (strip)
28936
+ return { el: strip, level: "row" };
28863
28937
  const card = cardOf(active);
28864
28938
  return card ? { el: card, level: "card" } : null;
28865
28939
  };
@@ -29049,6 +29123,23 @@ function buildPanel(controls, host, options) {
29049
29123
  return;
29050
29124
  return optionsOf(r);
29051
29125
  };
29126
+ const groupStripAt = (node) => node ? [...groupStrips.values()].find((s) => s.element.contains(node)) : undefined;
29127
+ const groupStripOptions = new Map;
29128
+ const optionsOfStrip = (strip) => {
29129
+ let o = groupStripOptions.get(strip);
29130
+ if (!o) {
29131
+ o = {
29132
+ get count() {
29133
+ return strip.getEntries().length;
29134
+ },
29135
+ choose: (i) => strip.select(i),
29136
+ shown: () => strip.getButtons(),
29137
+ anchor: strip.element
29138
+ };
29139
+ groupStripOptions.set(strip, o);
29140
+ }
29141
+ return o;
29142
+ };
29052
29143
  const stripDigits = () => visible(presetStrip.element) && presetStrip.getEntries().length > 1;
29053
29144
  const stripOptions = {
29054
29145
  get count() {
@@ -29059,7 +29150,12 @@ function buildPanel(controls, host, options) {
29059
29150
  anchor: presetStrip.element
29060
29151
  };
29061
29152
  const stripOptionsAt = (node) => {
29062
- if (hintCtrl.isActive || !node || !stripDigits())
29153
+ if (hintCtrl.isActive || !node || stripDigitsShown)
29154
+ return;
29155
+ const own = groupStripAt(node);
29156
+ if (own)
29157
+ return visible(own.element) && own.getEntries().length > 1 && !own.nameField() ? optionsOfStrip(own) : undefined;
29158
+ if (!stripDigits())
29063
29159
  return;
29064
29160
  const at = presetStrip.element.contains(node) || focusLevel(node) === "panel";
29065
29161
  return at && !presetStrip.nameField() ? stripOptions : undefined;
@@ -29296,9 +29392,10 @@ function buildPanel(controls, host, options) {
29296
29392
  }
29297
29393
  if (pressed("presets", event)) {
29298
29394
  const direction = key === keys.presets[0] ? -1 : 1;
29299
- const presetsShown = visible(presetStrip.element) && presetStrip.getEntries().length > 1;
29395
+ const strip = groupStripAt(target) ?? presetStrip;
29396
+ const presetsShown = visible(strip.element) && strip.getEntries().length > 1;
29300
29397
  if (presetsShown)
29301
- presetStrip.cycle(direction);
29398
+ strip.cycle(direction);
29302
29399
  else if (!cycleTab(direction))
29303
29400
  return;
29304
29401
  event.preventDefault();
@@ -29331,6 +29428,8 @@ function buildPanel(controls, host, options) {
29331
29428
  search.focus();
29332
29429
  return;
29333
29430
  }
29431
+ if (/^[1-9]$/.test(key) && groupStripAt(target))
29432
+ return;
29334
29433
  if (/^[1-9]$/.test(key)) {
29335
29434
  const group = page ? focusedGroup(target) : undefined;
29336
29435
  const groupDigits = Boolean(group && groupChips.has(group));
@@ -29391,7 +29490,7 @@ function buildPanel(controls, host, options) {
29391
29490
  }
29392
29491
  if (pressed("save", event) && showVariants) {
29393
29492
  event.preventDefault();
29394
- presetStrip.save();
29493
+ (groupStripAt(target) ?? presetStrip).save();
29395
29494
  return;
29396
29495
  }
29397
29496
  const index = rowAt(target);
@@ -29890,6 +29989,7 @@ function buildPanel(controls, host, options) {
29890
29989
  ...undock ? {} : { headerDie: { button: randomButton, die } },
29891
29990
  locks: lockIcons,
29892
29991
  presetStrip,
29992
+ groupStrips: () => groupStrips.values(),
29893
29993
  tabs: tabStrip,
29894
29994
  groups: gpuGroups,
29895
29995
  cards,
@@ -30018,6 +30118,8 @@ function buildPanel(controls, host, options) {
30018
30118
  for (const c of groupChips.values())
30019
30119
  c.dispose();
30020
30120
  presetStrip.dispose();
30121
+ for (const strip of groupStrips.values())
30122
+ strip.dispose();
30021
30123
  tabStrip.dispose();
30022
30124
  tabMotion.finish();
30023
30125
  doc.removeEventListener("keydown", onGlobalKey);