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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,3 @@
1
- import {
2
- __preload
3
- } from "./chunk-mjdgzad5.js";
4
1
  import {
5
2
  applyLook,
6
3
  panelLook,
@@ -9,7 +6,7 @@ import {
9
6
  writeSession,
10
7
  injectStyles,
11
8
  toggleLayer
12
- } from "./chunk-j57rhjtm.js";
9
+ } from "./chunk-nwfm1bfj.js";
13
10
 
14
11
  // src/panel/attach.ts
15
12
  function attach(controls, element, options = {}) {
@@ -85,7 +82,7 @@ function attach(controls, element, options = {}) {
85
82
  return;
86
83
  loading = !0;
87
84
  try {
88
- let { createPanel } = await (__preload("bnhzf1tj"), import("./chunk-bnhzf1tj.js"));
85
+ let { createPanel } = await import("./chunk-9qtvxa6k.js");
89
86
  if (disposed || !controls.enabled || panel)
90
87
  return;
91
88
  if (panel = createPanel(controls, element, close, { restored, chip }), remember)
@@ -10,7 +10,7 @@ import {
10
10
  DEFAULT_LINEAR,
11
11
  motionCss,
12
12
  storageKey
13
- } from "./chunk-mjdgzad5.js";
13
+ } from "./chunk-7kns3qe6.js";
14
14
 
15
15
  // src/core/theme.ts
16
16
  var property = (k) => k.startsWith("--") ? k : `--k-${k}`;
@@ -631,20 +631,15 @@ function escTarget(trail, title) {
631
631
  return;
632
632
  return trail.at(-2)?.label ?? (title.trim() || "panel");
633
633
  }
634
- function fitCrumbs(widths, room, sep, more, hint = 0) {
635
- let n = widths.length;
636
- if (!n)
637
- return { from: 0, hint: !1 };
638
- let span = (from) => {
639
- let w = 0;
640
- for (let i = from;i < n; i++)
641
- w += widths[i] + (i > from ? sep : 0);
642
- return from > 0 ? w + more + sep : w;
643
- }, from = 0;
644
- while (from < n - 1 && span(from) > room)
645
- from++;
646
- let used = span(from);
647
- return { from, hint: hint > 0 && used + sep + hint <= room };
634
+ function fitCrumbs(widths, room, sep, hint = 0) {
635
+ let n = widths.length, from = n, used = 0;
636
+ for (let i = n - 1;i >= 0; i--) {
637
+ let w = used + widths[i] + (i < n - 1 ? sep : 0);
638
+ if (w > room)
639
+ break;
640
+ used = w, from = i;
641
+ }
642
+ return { from, hint: from < n && hint > 0 && used + sep + hint <= room };
648
643
  }
649
644
  function digitOption(key, count) {
650
645
  if (!/^[1-9]$/.test(key))
@@ -652,6 +647,15 @@ function digitOption(key, count) {
652
647
  let i = Number(key) - 1;
653
648
  return i < count ? i : void 0;
654
649
  }
650
+ function digitRoute(key, at) {
651
+ if (!/^[1-9]$/.test(key))
652
+ return;
653
+ if (at.options)
654
+ return "options";
655
+ if (at.keeps)
656
+ return;
657
+ return digitOption(key, at.presets) === void 0 ? void 0 : "presets";
658
+ }
655
659
  function digitHints(count, showing) {
656
660
  let n = Math.min(9, count);
657
661
  if (n <= 0)
@@ -684,9 +688,10 @@ function createCrumbs(o) {
684
688
  }, build = () => {
685
689
  if (!trail.length)
686
690
  return { pieces: [], xs: [] };
687
- let head = o.header.getBoundingClientRect(), { x, right } = titleEnd(head), sepW = width(SEP) + GAP * 2, escText = esc ? `esc ↑ ${esc}` : "", fit = fitCrumbs(trail.map((c) => width(c.label)), Math.max(0, right - x - sepW), sepW, width("…"), escText ? width(escText) + HINT_GAP - sepW : 0), pieces = [{ key: "sep:lead", text: SEP, kind: "sep" }];
688
- if (fit.from > 0)
689
- pieces.push({ key: "more", text: "…", kind: "more" }, { key: "sep:more", text: SEP, kind: "sep" });
691
+ let head = o.header.getBoundingClientRect(), { x, right } = titleEnd(head), sepW = width(SEP) + GAP * 2, escText = esc ? `esc ↑ ${esc}` : "", fit = fitCrumbs(trail.map((c) => width(c.label)), Math.max(0, right - x - sepW), sepW, escText ? width(escText) + HINT_GAP - sepW : 0);
692
+ if (fit.from >= trail.length)
693
+ return { pieces: [], xs: [] };
694
+ let pieces = [{ key: "sep:lead", text: SEP, kind: "sep" }];
690
695
  if (trail.forEach((c, i) => {
691
696
  if (i < fit.from)
692
697
  return;
@@ -2374,8 +2379,8 @@ ${GRAD_PROPS}
2374
2379
  .knobs-panel *:where(:not(.knobs-accessory,.knobs-accessory *,.knobs-live *)),.knobs-menu *{box-sizing:border-box;color:inherit;letter-spacing:inherit;word-spacing:inherit;line-height:inherit;text-transform:inherit;text-shadow:inherit;font-style:inherit}
2375
2380
  /* Hover washes come in at --k-hover-in and leave at --k-hover-out (ms). Set per element, so a
2376
2381
  hovered card doesn't hand its quick entrance to the rows inside it. */
2377
- :where(.knobs-panel,.knobs-menu) *{--k-hover-t:calc(var(--_k-hover-out) * 1ms)}
2378
- :where(.knobs-panel,.knobs-menu) :is(:hover,.knobs-active){--k-hover-t:calc(var(--_k-hover-in) * 1ms)}
2382
+ :where(.knobs-panel,.knobs-menu,.knobs-head-actions[data-docked]) *{--k-hover-t:calc(var(--_k-hover-out) * 1ms)}
2383
+ :where(.knobs-panel,.knobs-menu,.knobs-head-actions[data-docked]) :is(:hover,.knobs-active){--k-hover-t:calc(var(--_k-hover-in) * 1ms)}
2379
2384
 
2380
2385
  .knobs-panel,.knobs-chip{
2381
2386
  box-sizing:border-box;font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);text-align:left;
@@ -2495,7 +2500,7 @@ ${GRAD_PROPS}
2495
2500
  .knobs-crumbs{position:absolute;left:0;top:50%;height:0;pointer-events:none;white-space:nowrap;font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);line-height:1.2;letter-spacing:0;text-transform:none}
2496
2501
  .knobs-crumbs>*{position:absolute;left:0;top:0;translate:0 -50%;color:var(--_k-fg-3);transition:opacity .2s var(--k-ease),translate ${CRUMB_GLIDE},color .2s var(--k-ease)}
2497
2502
  .knobs-crumbs>[data-leaving]{transition:opacity .14s cubic-bezier(.4,0,1,1),translate .14s cubic-bezier(.4,0,1,1)}
2498
- .knobs-crumbs>.knobs-crumb-crumb{pointer-events:auto;cursor:pointer}
2503
+ .knobs-crumbs>.knobs-crumb-crumb{pointer-events:auto;cursor:default}
2499
2504
  .knobs-crumbs>.knobs-crumb-crumb[data-deep]{color:var(--_k-fg)}
2500
2505
  .knobs-crumbs>.knobs-crumb-crumb:not([data-deep]):hover{color:var(--_k-fg-2)}
2501
2506
  .knobs-crumbs>.knobs-crumb-sep{color:color-mix(in srgb,var(--_k-fg-3) 70%,transparent)}
@@ -2512,40 +2517,53 @@ ${GRAD_PROPS}
2512
2517
  .knobs-panel[data-gpu-active="true"] .knobs-die-icon>canvas{visibility:hidden}
2513
2518
  /* The tools sit quiet (--k-dim) until the panel has the pointer or keyboard focus, then read as
2514
2519
  labels, and as text under the pointer. */
2515
- .knobs-panel header button{position:relative;width:var(--k-icon-btn);height:var(--k-icon-btn);padding:0;display:inline-flex;align-items:center;justify-content:center;color:var(--_k-fg-3);background:transparent;border-radius:var(--_k-round);cursor:pointer;font-size:12px;line-height:1;transition:background var(--k-hover-t),color var(--k-hover-t),opacity .15s}
2520
+ .knobs-panel header button{position:relative;width:var(--k-icon-btn);height:var(--k-icon-btn);padding:0;display:inline-flex;align-items:center;justify-content:center;color:var(--_k-fg-3);background:transparent;border-radius:var(--_k-round);cursor:default;font-size:12px;line-height:1;transition:background var(--k-hover-t),color var(--k-hover-t),opacity .15s}
2516
2521
  .knobs-panel:is(:hover,:has(:focus-visible)) header button{color:var(--_k-fg-2)}
2517
2522
  .knobs-panel header button:is(:hover:not([disabled]),:focus-visible),.knobs-panel header button[aria-expanded="true"]{background:var(--_k-fill-selected);color:var(--_k-fg)}
2518
2523
  .knobs-panel header button[disabled]{opacity:var(--k-disabled-opacity);cursor:default}
2519
2524
  /* The close X's ink sits a fifth of its box in from each side, a digit's barely: nudged right, its
2520
2525
  ink ends where the values' digits do. */
2521
2526
  .knobs-panel header .knobs-close>.knobs-icon{translate:calc(var(--k-icon-size) * .1) 0}
2527
+ /* In a split pane's header (dock.ts) the buttons are the pane's: its size, ink and wash, which
2528
+ comes on at once and eases off. Outside the panel, they bring its resets with them. */
2529
+ .knobs-head-actions[data-docked]{--k-icon-size:13px;--k-icon-btn:20px;margin-right:0}
2530
+ .knobs-head-actions[data-docked] button{all:revert;box-sizing:border-box;position:relative;flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--k-icon-btn);height:var(--k-icon-btn);margin:0;padding:0;border:0;border-radius:var(--_k-round);
2531
+ background:transparent;color:var(--_k-fg-3);font:inherit;line-height:1;cursor:default;outline:none;box-shadow:none;transition:background var(--k-hover-t),color var(--k-hover-t)}
2532
+ .knobs-head-actions[data-docked] button:is(:hover:not([disabled]),:focus-visible){background:var(--_ku-wash-hi,var(--_k-fill-selected));color:var(--_k-fg)}
2533
+ .knobs-head-actions[data-docked] .knobs-icon>path{fill:currentColor}
2534
+ .knobs-head-actions[data-docked] button:focus-visible{--k-hover-t:calc(var(--_k-hover-in) * 1ms)}
2522
2535
  /* Undo · Redo sit apart from Reset · Copy · Random. Undo, Redo and Reset (and a group's reset)
2523
2536
  keep their slots while they're away and bloom in and out of them: nothing beside them moves. */
2524
2537
  .knobs-head-history{display:flex;flex:none;margin-right:calc(var(--_k-unit) * 1.5)}
2525
- .knobs-panel :is(.knobs-head-actions,.knobs-group-head) button[data-shown]{transition:background var(--k-hover-t),color var(--k-hover-t),opacity ${BLOOM_IN},scale ${BLOOM_IN},filter 0s,visibility 0s}
2526
- .knobs-panel :is(.knobs-head-actions,.knobs-group-head) button[data-shown="false"]{opacity:0;scale:.9;filter:blur(2.5px);visibility:hidden;
2527
- transition:background var(--k-hover-t),color var(--k-hover-t),opacity ${BLOOM_OUT},scale ${BLOOM_OUT},filter ${BLOOM_OUT},visibility 0s ${snappyOut.ms}ms}
2538
+ :is(.knobs-panel :is(.knobs-head-actions,.knobs-group-head),.knobs-head-actions[data-docked]) button[data-shown]{transition:background var(--k-hover-t),color var(--k-hover-t),opacity ${BLOOM_IN},scale ${BLOOM_IN},width ${BLOOM_IN}}
2539
+ :is(.knobs-panel :is(.knobs-head-actions,.knobs-group-head),.knobs-head-actions[data-docked]) button[data-shown="false"]{opacity:0;scale:.9;filter:blur(2.5px);visibility:hidden;
2540
+ transition:background var(--k-hover-t),color var(--k-hover-t),opacity ${BLOOM_OUT},scale ${BLOOM_OUT},filter ${BLOOM_OUT},width ${BLOOM_OUT},visibility 0s ${snappyOut.ms}ms}
2541
+ /* Docked, a slot opens and closes with its button (the width above) rather than waiting: the
2542
+ pane's title keeps the room. */
2543
+ .knobs-head-actions[data-docked] button[data-shown="false"]{width:0}
2544
+ .knobs-head-actions[data-docked]>.knobs-head-history{transition:margin ${BLOOM_IN}}
2545
+ .knobs-head-actions[data-docked]>.knobs-head-history:not(:has(>[data-shown="true"]:not([hidden]))){margin-right:0}
2528
2546
  @media(prefers-reduced-motion:reduce){
2529
- .knobs-panel :is(.knobs-head-actions,.knobs-group-head) button[data-shown]{transition:opacity .15s,visibility 0s!important}
2530
- .knobs-panel :is(.knobs-head-actions,.knobs-group-head) button[data-shown="false"]{scale:1;filter:none;transition:opacity .15s,visibility 0s .15s!important}}
2547
+ :is(.knobs-panel :is(.knobs-head-actions,.knobs-group-head),.knobs-head-actions[data-docked]) button[data-shown]{transition:opacity .15s!important}
2548
+ :is(.knobs-panel :is(.knobs-head-actions,.knobs-group-head),.knobs-head-actions[data-docked]) button[data-shown="false"]{scale:1;filter:none;transition:opacity .15s,visibility 0s .15s!important}}
2531
2549
  /* A press flash (flash.ts): from a key or a click, the button's wash brightens at once and eases
2532
2550
  back to its own, its ink with it. Two names, so a second press restarts it. */
2533
2551
  @keyframes knobs-flash-a{from{background-color:var(--k-flash-bg);color:var(--_k-fg)}}
2534
2552
  @keyframes knobs-flash-b{from{background-color:var(--k-flash-bg);color:var(--_k-fg)}}
2535
- .knobs-panel :is(header,.knobs-group-head) button{--k-flash-bg:color-mix(in srgb,var(--_k-fill-selected),var(--_k-fg) 10%)}
2536
- .knobs-panel :is(header,.knobs-group-head) button[data-k-flash="a"]{animation:knobs-flash-a ${FLASH_MS}ms cubic-bezier(.33,1,.68,1)}
2537
- .knobs-panel :is(header,.knobs-group-head) button[data-k-flash="b"]{animation:knobs-flash-b ${FLASH_MS}ms cubic-bezier(.33,1,.68,1)}
2553
+ .knobs-panel :is(header,.knobs-group-head) button,.knobs-head-actions[data-docked] button{--k-flash-bg:color-mix(in srgb,var(--_k-fill-selected),var(--_k-fg) 10%)}
2554
+ .knobs-panel :is(header,.knobs-group-head) button[data-k-flash="a"],.knobs-head-actions[data-docked] button[data-k-flash="a"]{animation:knobs-flash-a ${FLASH_MS}ms cubic-bezier(.33,1,.68,1)}
2555
+ .knobs-panel :is(header,.knobs-group-head) button[data-k-flash="b"],.knobs-head-actions[data-docked] button[data-k-flash="b"]{animation:knobs-flash-b ${FLASH_MS}ms cubic-bezier(.33,1,.68,1)}
2538
2556
  /* Copied: the glyph shrinks away and a check draws itself in its place, held, then they trade
2539
2557
  back (the check fading). Driven by --k-copy (the glyph), --k-check (how much of the check is
2540
2558
  drawn) and --k-check-a (its opacity), which the GPU renderer reads too. */
2541
- .knobs-panel header .knobs-copy{--k-copy:1;--k-check:0;--k-check-a:0;
2559
+ .knobs-panel header .knobs-copy,.knobs-head-actions[data-docked] .knobs-copy{--k-copy:1;--k-check:0;--k-check-a:0;
2542
2560
  transition:background var(--k-hover-t),color var(--k-hover-t),opacity .15s,--k-copy ${BLOOM_IN} .04s,--k-check 0s .1s,--k-check-a .1s cubic-bezier(.4,0,1,1)}
2543
- .knobs-panel header .knobs-copy[data-copied]{--k-copy:0;--k-check:1;--k-check-a:1;color:var(--_k-fg);
2561
+ .knobs-panel header .knobs-copy[data-copied],.knobs-head-actions[data-docked] .knobs-copy[data-copied]{--k-copy:0;--k-check:1;--k-check-a:1;color:var(--_k-fg);
2544
2562
  transition:background var(--k-hover-t),color var(--k-hover-t),opacity .15s,--k-copy .1s cubic-bezier(.4,0,1,1),--k-check .18s cubic-bezier(.2,.8,.2,1) .03s,--k-check-a .04s linear .03s}
2545
2563
  .knobs-copy>.knobs-icon:not(.knobs-check){opacity:var(--k-copy);scale:calc(.5 + .5 * var(--k-copy))}
2546
2564
  .knobs-copy>.knobs-check{position:absolute;inset:0;margin:auto;opacity:var(--k-check-a)}
2547
2565
  /* (Over .knobs-icon>path's fill: the check is a stroke, 16 of 256 wide like Phosphor's regular icons.) */
2548
- .knobs-panel .knobs-icon.knobs-check>path{fill:none;stroke:currentColor;stroke-width:16;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:1 2;stroke-dashoffset:calc(1 - var(--k-check))}
2566
+ .knobs-panel .knobs-icon.knobs-check>path,.knobs-head-actions[data-docked] .knobs-icon.knobs-check>path{fill:none;stroke:currentColor;stroke-width:16;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:1 2;stroke-dashoffset:calc(1 - var(--k-check))}
2549
2567
  .knobs-sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
2550
2568
 
2551
2569
  /* Shortcuts Popover */
@@ -2567,6 +2585,15 @@ ${GRAD_PROPS}
2567
2585
  .knobs-panel[data-buttons] .knobs-preset-chip:hover{background:var(--k-button-hover-bg,var(--_k-fill-selected))!important;box-shadow:inset 0 0 0 1px var(--k-button-hover-line,var(--k-button-line,transparent))}
2568
2586
  .knobs-panel .knobs-preset-chip:hover{color:var(--_k-fg)}
2569
2587
  .knobs-panel .knobs-preset-chip[aria-checked="true"]{color:var(--_k-fg);font-weight:var(--_k-weight-title)}
2588
+ /* A list (presetDisplay "list", or chips that would take more than two lines): rows like a
2589
+ menu's, their names on the rows' text edge and each wash bleeding 8 px past it, the selection a
2590
+ quiet wash that glides between them. Hover is a wash too, on at once. */
2591
+ .knobs-presets[data-display="list"]{padding:0;background:transparent}
2592
+ .knobs-presets[data-display="list"]>.knobs-presets-track{grid-template-columns:minmax(0,1fr);gap:0;margin:0 calc(var(--_k-inset) - 8px)}
2593
+ .knobs-panel .knobs-presets[data-display="list"] .knobs-preset-chip{height:var(--_k-control-h);padding:0 16px 0 8px;text-align:left;cursor:default;font-weight:var(--_k-weight-label);transition:color var(--k-hover-t),background var(--k-hover-t)}
2594
+ .knobs-panel .knobs-presets[data-display="list"] .knobs-preset-chip:hover:not([aria-checked="true"]){background:var(--_k-row-hover)!important}
2595
+ .knobs-panel .knobs-presets[data-display="list"] .knobs-preset-chip::after{top:50%;right:8px;margin-top:-2px}
2596
+ .knobs-presets[data-display="list"] .knobs-presets-pill{background:var(--_k-fill-selected);box-shadow:none}
2570
2597
  .knobs-panel .knobs-preset-save{flex:none;width:calc((var(--_k-inset) - var(--_k-tray-pad)) * 2 + 8px);padding:0!important;color:var(--_k-fg-3)!important;font-weight:400!important;font-size:var(--_k-text-title)!important}
2571
2598
  .knobs-panel .knobs-preset-save:hover{color:var(--_k-fg)!important}
2572
2599
  /* The active preset's modified mark: the values have drifted off it (a row's dot, in its corner). */
@@ -2911,6 +2938,14 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
2911
2938
  :where(.knobs-panel)[data-frame="flat"]{--k-group-divider:var(--_k-border);--k-group-divider-gap:var(--_k-gap)}
2912
2939
  :where(.knobs-panel.knobs-inline:not(.knobs-page))[data-frame="flat"]{--k-chrome:0;--k-pad:0px}
2913
2940
  .knobs-panel[data-frame="flat"] .knobs-card{background:transparent;border-radius:0}
2941
+ /* One surface: an inline panel on another card (a split's pane, anything .knobs-surface, or in
2942
+ [data-nested]) takes the flat frame by itself: no padding (its rows keep their inset, so their
2943
+ text sits on the card's, and its preset chips line up with them), groups as plain sections,
2944
+ rows on hairlines. */
2945
+ :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip,[data-nested]) :where(.knobs-panel.knobs-inline:not(.knobs-page)){--k-chrome:0;--k-group-divider:var(--_k-border);--k-group-divider-gap:var(--_k-gap)}
2946
+ :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip,[data-nested]) .knobs-panel.knobs-inline:not(.knobs-page){padding:0}
2947
+ :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip,[data-nested]) .knobs-panel.knobs-inline:not(.knobs-page) .knobs-card{background:transparent;border-radius:0}
2948
+ :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip,[data-nested]) .knobs-panel.knobs-inline:not(.knobs-page) .knobs-presets:not([data-display="list"]){margin-inline:var(--_k-inset)}
2914
2949
  .knobs-panel[data-frame="flat"][data-rows] .knobs-card>*,.knobs-panel[data-frame="flat"][data-rows] .knobs-sub{background:transparent;border-radius:0}
2915
2950
 
2916
2951
  /* Disabled rows dim and take no edits (focus still reaches them, to read); read-only rows keep
@@ -3063,6 +3098,10 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
3063
3098
  .knobs-panel .knobs-tool[disabled]{opacity:var(--k-disabled-opacity);cursor:default}
3064
3099
  .knobs-tool .knobs-icon{flex:none;width:var(--k-icon-sm);height:var(--k-icon-sm)}
3065
3100
  .knobs-tool-label{min-width:0;overflow:hidden;text-overflow:ellipsis}
3101
+ /* Icons only (labels that don't fit one row, or labels: false): one row of equal buttons, each
3102
+ its icon centred, its label and shortcut in its tooltip. */
3103
+ .knobs-panel .knobs-toolbar[data-icons] .knobs-tool{justify-content:center;padding:0;cursor:default}
3104
+ .knobs-panel .knobs-toolbar[data-icons] :is(.knobs-tool-label,.knobs-tool-key){display:none}
3066
3105
  /* The shortcut sits at the button's end, so a column of buttons lines their chips up. */
3067
3106
  .knobs-panel .knobs-tool-key{flex:none;margin-left:auto;padding-left:4px;font:inherit;font-size:var(--_k-text-cap);font-variant-numeric:tabular-nums;color:var(--_k-fg-3);letter-spacing:.06em}
3068
3107
  .knobs-panel :is([data-disabled],[data-readonly]) :is(.knobs-cell,.knobs-range-track,.knobs-angle,.knobs-dial,.knobs-seed,.knobs-keys,.knobs-tool){cursor:default}
@@ -3108,7 +3147,7 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
3108
3147
 
3109
3148
  /* A focused row's options: tiny digits in hint mode's look, once focus settles (optkeys.ts). */
3110
3149
  .knobs-optkeys{position:absolute;left:0;top:0;width:0;height:0;pointer-events:none;z-index:5}
3111
- .knobs-panel .knobs-optkey{position:absolute;transform:translateY(-50%);display:inline-flex;align-items:center;height:12px;padding:0 3px;border-radius:var(--_k-radius-sm);background:var(--_k-accent);color:var(--_k-bg);box-shadow:0 1px 3px rgba(0,0,0,calc(.35 * var(--k-lift)));
3150
+ .knobs-panel .knobs-optkey{position:absolute;transform:translateY(-50%);display:inline-flex;white-space:nowrap;align-items:center;height:12px;padding:0 3px;border-radius:var(--_k-radius-sm);background:var(--_k-accent);color:var(--_k-bg);box-shadow:0 1px 3px rgba(0,0,0,calc(.35 * var(--k-lift)));
3112
3151
  font:700 calc(var(--_k-text-cap) * .85)/1 var(--_k-mono);letter-spacing:.04em;pointer-events:none;opacity:0;transition:opacity .12s var(--k-ease)}
3113
3152
  .knobs-panel .knobs-optkey-range{transform:translate(-100%,-50%)}
3114
3153
  .knobs-panel .knobs-optkey[data-shown]{opacity:.85;transition-duration:.15s}
@@ -3150,7 +3189,18 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
3150
3189
  .knobs-menu-item[data-separated]{margin-top:calc(var(--_k-unit) * 2 + 1px)}
3151
3190
  .knobs-menu-item[data-separated]::before{content:"";position:absolute;left:8px;right:8px;top:calc(var(--_k-unit) * -1 - 1px);height:1px;transform:scaleY(.5);background:var(--_k-border)}
3152
3191
 
3192
+ /* Text: a field beside its label, or (multiline) a box under it that grows with its lines */
3193
+ .knobs-panel .knobs-text{flex:1 1 auto;min-width:0;max-width:62%;height:var(--_k-control-h);margin:0;padding:0 8px;border:0;border-radius:var(--_k-radius-md);outline:none;background:var(--_k-segmented-track);color:var(--_k-fg);caret-color:var(--_k-accent);font:inherit;font-size:var(--_k-text-control);-webkit-user-select:text;user-select:text;cursor:text}
3194
+ .knobs-panel .knobs-text::placeholder{color:var(--_k-fg-3);opacity:1}
3195
+ .knobs-panel .knobs-text:is(:hover,:focus){background:color-mix(in srgb,var(--_k-fill-selected) 70%,var(--_k-segmented-track))}
3196
+ .knobs-panel .knobs-text::selection{background:color-mix(in srgb,var(--_k-accent) 24%,transparent);color:var(--_k-fg)}
3197
+ .knobs-panel .knobs-insp[data-multiline]{flex-direction:column;align-items:stretch;justify-content:flex-start;gap:calc(var(--_k-unit) * 1.5);height:auto;padding-block:calc(var(--_k-unit) * 2)}
3198
+ .knobs-panel .knobs-insp[data-multiline]>span:first-child{align-self:flex-start;max-width:100%}
3199
+ .knobs-panel textarea.knobs-text{max-width:none;height:auto;min-height:calc(2lh + 12px);max-height:calc(8lh + 12px);margin:0;padding:6px 8px;resize:none;field-sizing:content;font:inherit;font-size:var(--_k-text-control);line-height:1.4}
3200
+ .knobs-panel :is([data-disabled],[data-readonly]) .knobs-text{cursor:default}
3201
+
3153
3202
  /* Status & Transfer Textarea */
3203
+ .knobs-panel .knobs-empty{margin:0;padding:calc(var(--_k-unit) * 2) var(--_k-inset);color:var(--_k-fg-3);font-size:var(--_k-text-control);overflow-wrap:anywhere}
3154
3204
  .knobs-panel .knobs-status{margin:6px 0 0;padding:0 var(--_k-inset);color:var(--_k-fg-2);font-size:var(--_k-text-meta);overflow-wrap:anywhere}
3155
3205
 
3156
3206
  .knobs-panel textarea{width:100%;min-height:84px;margin-top:6px;padding:6px var(--_k-inset);resize:vertical;font:var(--_k-text-meta)/1.4 var(--_k-mono)}
@@ -3182,11 +3232,11 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
3182
3232
  .knobs-panel[data-gpu-active="true"] .knobs-preset-chip::after,
3183
3233
  .knobs-panel[data-gpu-active="true"] :is(.knobs-tile:not(.knobs-live)>.knobs-veil,.knobs-tile:not(.knobs-live)>.knobs-mark,.knobs-section-head>.knobs-mark){opacity:0!important}
3184
3234
  /* Parts the canvas doesn't draw (shortcuts, status, editors) sit above it. */
3185
- .knobs-panel[data-gpu-active="true"] :where(.knobs-popover,.knobs-status,textarea,.knobs-apply-btn){position:relative;z-index:2147480003}
3235
+ .knobs-panel[data-gpu-active="true"] :where(.knobs-popover,.knobs-status,textarea,.knobs-text,.knobs-apply-btn){position:relative;z-index:2147480003}
3186
3236
  .knobs-panel[data-gpu-active="true"] :is(.knobs-number,.knobs-preset-name){z-index:2147480003}
3187
3237
  /* A row accessory is the host's own DOM, so it sits above the canvas too (its row then can't
3188
3238
  contain its layout, which would trap it in a stacking context under the canvas). */
3189
- .knobs-panel[data-gpu-active="true"] .knobs-row[data-accessory]{contain:style}
3239
+ .knobs-panel[data-gpu-active="true"] :is(.knobs-row[data-accessory],.knobs-insp[data-kind="text"]){contain:style}
3190
3240
  :where(.knobs-panel[data-gpu-active="true"] .knobs-row>.knobs-accessory){position:relative}
3191
3241
  .knobs-panel[data-gpu-active="true"] .knobs-row>.knobs-accessory{z-index:2147480003}
3192
3242
 
@@ -3568,6 +3618,8 @@ function changeCue(field, before, after) {
3568
3618
  let options = field.options ?? [];
3569
3619
  return { cue: "select", detail: { direction: options.indexOf(after) - options.indexOf(before) }, detents: 1, at: 1, was: 0 };
3570
3620
  }
3621
+ if (field.kind === "text")
3622
+ return null;
3571
3623
  if (field.kind === "keybind")
3572
3624
  return after ? { cue: "capture", detail: {}, detents: 1, at: 1, was: 0 } : { cue: "select", detail: { direction: -1 }, detents: 1, at: 1, was: 0 };
3573
3625
  let { min, max, step } = field, a = numbers(before), b = numbers(after);
@@ -4393,4 +4445,4 @@ function createLayer(doc) {
4393
4445
  };
4394
4446
  }
4395
4447
 
4396
- export { resolveLook, themeOf, applyLook, panelLook, CHECK_POINTS, CHECK_PATH, iconPath, iconSvg, readSession, writeSession, rolling_css_default, FILL, LEAD, trailScale, Edges, POP_MS, popScale, settleScale, VelocityTracker, clamp, Roll, rollTarget, numeral, entranceOffset, FLASH_MS, flash, flashOf, offRow, precisionRate, RULER, RULER_NEAR, RULER_FADE, rulerLevels, rulerAlpha, rulerDepth, THIN, THINNEST, thinOf, roundOf, stretchOf, stretchAt, giveRoom, detentScale, detentsOf, roundValues, unwarp, Notches, escapeTo, commandScope, scopeLabel, crumbTrail, escTarget, digitOption, digitHints, createCrumbs, TOKENS, knobs, aliased, injectTokens, DELAYED, injectStyles, resolveSound, sound, SOUND_PREF, soundPanel, changed, cue, createHintController, pageHints, toggleLayer };
4448
+ export { resolveLook, themeOf, applyLook, panelLook, CHECK_POINTS, CHECK_PATH, iconPath, iconSvg, readSession, writeSession, rolling_css_default, FILL, LEAD, trailScale, Edges, POP_MS, popScale, settleScale, VelocityTracker, clamp, Roll, rollTarget, numeral, entranceOffset, FLASH_MS, flash, flashOf, offRow, precisionRate, RULER, RULER_NEAR, RULER_FADE, rulerLevels, rulerAlpha, rulerDepth, THIN, THINNEST, thinOf, roundOf, stretchOf, stretchAt, giveRoom, detentScale, detentsOf, roundValues, unwarp, Notches, escapeTo, commandScope, scopeLabel, crumbTrail, escTarget, digitOption, digitRoute, digitHints, createCrumbs, TOKENS, knobs, aliased, injectTokens, DELAYED, injectStyles, resolveSound, sound, SOUND_PREF, soundPanel, changed, cue, createHintController, pageHints, toggleLayer };
package/dist/dev/index.js CHANGED
@@ -1,6 +1,4 @@
1
1
  import {
2
- __preload,
3
- __chunks,
4
2
  maxChroma,
5
3
  oklchToRgb,
6
4
  parseColor2,
@@ -27,6 +25,7 @@ import {
27
25
  pickMeta,
28
26
  isReadout2,
29
27
  hasValue2,
28
+ variantsOf,
30
29
  pinnedKeys,
31
30
  SPRING2,
32
31
  valueKeys,
@@ -45,6 +44,7 @@ import {
45
44
  angle2,
46
45
  seed2,
47
46
  keybind2,
47
+ text2,
48
48
  gradient2,
49
49
  curve2,
50
50
  toolbarShortcut2,
@@ -76,8 +76,7 @@ import {
76
76
  browserStorage,
77
77
  urlFlag2,
78
78
  storageKey
79
- } from "./chunk-mjdgzad5.js";
80
- __chunks(import.meta.url,["js6xfzn6","bnhzf1tj","75gbzbyd","mjdgzad5","j57rhjtm"],[["./index.js",3],["./chunk-bnhzf1tj.js",3,4],["./chunk-75gbzbyd.js",3,4],["./chunk-mjdgzad5.js"],["./chunk-j57rhjtm.js",3]],0);
79
+ } from "./chunk-7kns3qe6.js";
81
80
  // src/core/random.ts
82
81
  var lattice = (min, max, step, r) => min + Math.floor(r * (Math.floor((max - min) / step + 0.000000001) + 1)) * step, between = (min, max, r) => min + (max - min) * r;
83
82
  function pick(field, random) {
@@ -120,6 +119,7 @@ function pick(field, random) {
120
119
  case "seed":
121
120
  return lattice(field.min, field.max, 1, r());
122
121
  case "keybind":
122
+ case "text":
123
123
  return field.value;
124
124
  case "gradient":
125
125
  return randomStops(field.value.length, r);
@@ -205,6 +205,7 @@ function around(field, base, random) {
205
205
  ]);
206
206
  case "seed":
207
207
  case "keybind":
208
+ case "text":
208
209
  return pick(field, random);
209
210
  }
210
211
  }
@@ -304,6 +305,7 @@ function shift(field, value, random) {
304
305
  case "seed":
305
306
  return nudge(value, field.min, field.max, 1, random);
306
307
  case "keybind":
308
+ case "text":
307
309
  return value;
308
310
  case "gradient":
309
311
  return mutateStops(value, random, MUTATE.hue, MUTATE.lightness);
@@ -599,7 +601,7 @@ function defineControls(id, input, options = {}) {
599
601
  if (pinnedOf?.schema !== schema)
600
602
  pinnedOf = { schema, keys: pinnedKeys(schema, options.pinned) };
601
603
  return pinnedOf.keys;
602
- }, unpinned = (values) => {
604
+ }, variantsChoice = () => variantsOf(schema, (key) => pinned().has(key)), unpinned = (values) => {
603
605
  let keys = pinned();
604
606
  if (keys.size === 0)
605
607
  return values;
@@ -613,14 +615,24 @@ function defineControls(id, input, options = {}) {
613
615
  if (keys.size === 0)
614
616
  return values;
615
617
  return Object.fromEntries(Object.entries(values).filter(([k]) => !keys.has(k)));
616
- }, buildPresets = () => Object.freeze(Object.fromEntries([
617
- ["default", locked],
618
- ...Object.entries(options.presets ?? {}).map(([id, patch]) => {
619
- if (id === "default" || id === "custom")
620
- throw Error(`Reserved preset id: ${id}`);
621
- return [id, unpinned(normalize(schema, { ...locked, ...patch }))];
622
- })
623
- ])), presets = buildPresets(), buildGroupPresets = () => Object.freeze(Object.fromEntries(Object.entries(options.groupPresets ?? {}).map(([group, list]) => {
618
+ }, buildPresets = () => {
619
+ let own = options.presets ?? {}, v = variantsChoice();
620
+ if (v && Object.hasOwn(own, v.field.value))
621
+ throw Error(`The default variant (${v.field.value}) is the schema's defaults; set its values there.`);
622
+ let ids = [
623
+ ...v ? v.field.options.filter((o) => o !== v.field.value) : [],
624
+ ...Object.keys(own).filter((id) => !v?.field.options.includes(id))
625
+ ];
626
+ return Object.freeze(Object.fromEntries([
627
+ ["default", locked],
628
+ ...ids.map((id) => {
629
+ if (id === "default" || id === "custom")
630
+ throw Error(`Reserved preset id: ${id}`);
631
+ let variant = v?.field.options.includes(id) ? { [v.key]: id } : {};
632
+ return [id, unpinned(normalize(schema, { ...locked, ...variant, ...own[id] }))];
633
+ })
634
+ ]));
635
+ }, presets = buildPresets(), buildGroupPresets = () => Object.freeze(Object.fromEntries(Object.entries(options.groupPresets ?? {}).map(([group, list]) => {
624
636
  let named = list.flatMap((p) => Object.keys(p.values).filter((k) => Object.hasOwn(schema, k))), covered = new Set((named.length ? named : Object.keys(schema).filter((k) => schema[k].group === group)).filter((k) => hasValue2(schema[k])));
625
637
  return [
626
638
  group,
@@ -1088,7 +1100,10 @@ function defineControls(id, input, options = {}) {
1088
1100
  sectionDescription: (section) => options.sectionDescriptions?.[section] ?? fieldLabels().sectionText.get(section),
1089
1101
  groupFolded: options.groupFolded,
1090
1102
  sectionFolded: options.sectionFolded,
1091
- presetLabel: (presetId) => options.presetLabels?.[presetId] ?? (presetId === "default" ? options.defaultLabel : void 0) ?? presetId,
1103
+ presetLabel: (presetId) => {
1104
+ let v = variantsChoice(), option = presetId === "default" ? v?.field.value : presetId;
1105
+ 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);
1106
+ },
1092
1107
  isPinned: (key) => pinned().has(key),
1093
1108
  get,
1094
1109
  set,
@@ -1400,11 +1415,11 @@ function defineControls(id, input, options = {}) {
1400
1415
  code: (format, values) => formatCode(values ?? get(), format),
1401
1416
  variants,
1402
1417
  attach(element, chip) {
1403
- let load = options.ship ? () => options.ship().then((m) => m.attach) : () => (__preload("75gbzbyd"), import("./chunk-75gbzbyd.js")).then((m) => m.attach);
1418
+ let load = options.ship ? () => options.ship().then((m) => m.attach) : () => import("./chunk-ab7vfghg.js").then((m) => m.attach);
1404
1419
  return live(load, (attach) => attach(controls, element, chip), (detach) => detach());
1405
1420
  },
1406
1421
  mount(container, mountOptions) {
1407
- let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => (__preload("bnhzf1tj"), import("./chunk-bnhzf1tj.js")).then((m) => m.mountPanel);
1422
+ let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => import("./chunk-9qtvxa6k.js").then((m) => m.mountPanel);
1408
1423
  return live(load, (mountPanel) => {
1409
1424
  let panel = mountPanel(controls, container, mountOptions);
1410
1425
  return mountOptions?.onReady?.(panel), panel;
@@ -1441,7 +1456,7 @@ function descriptorToField(d) {
1441
1456
  if (readoutDescriptor(d))
1442
1457
  return readoutField(d);
1443
1458
  if (d.kind === "toolbar")
1444
- return toolbar2(d.items.map(({ key, ...item }) => ({ ...item, id: key })), meta);
1459
+ return toolbar2(d.items.map(({ key, ...item }) => ({ ...item, id: key })), d.labels !== void 0 ? { ...meta, labels: d.labels } : meta);
1445
1460
  if (d.bind)
1446
1461
  return flatSpring2({}, { ...meta, id: d.key }).duration;
1447
1462
  if (d.kind === "knob" || d.kind === "number") {
@@ -1516,6 +1531,12 @@ function descriptorToField(d) {
1516
1531
  });
1517
1532
  if (d.kind === "keybind")
1518
1533
  return keybind2(typeof d.default === "string" ? d.default : "", meta);
1534
+ if (d.kind === "text")
1535
+ return text2(typeof d.default === "string" ? d.default : "", {
1536
+ ...d.multiline ? { multiline: !0 } : {},
1537
+ ...d.placeholder ? { placeholder: d.placeholder } : {},
1538
+ ...meta
1539
+ });
1519
1540
  if (d.kind === "gradient") {
1520
1541
  let fallback = [
1521
1542
  { at: 0, color: "#000000" },
@@ -1594,7 +1615,7 @@ var decimal = (n) => String(Math.round(n * 1e4) / 1e4), swiftLiteral = (d, v) =>
1594
1615
  let [a, b] = v;
1595
1616
  return `${decimal(a)}...${decimal(b)}`;
1596
1617
  }
1597
- if (d.kind === "keybind")
1618
+ if (d.kind === "keybind" || d.kind === "text")
1598
1619
  return JSON.stringify(v);
1599
1620
  if (d.kind === "gradient")
1600
1621
  return `Gradient(stops: [${(Array.isArray(v) ? v : []).map((s) => {
@@ -1623,6 +1644,7 @@ var decimal = (n) => String(Math.round(n * 1e4) / 1e4), swiftLiteral = (d, v) =>
1623
1644
  range: "ClosedRange<Double>",
1624
1645
  seed: "Int",
1625
1646
  keybind: "String",
1647
+ text: "String",
1626
1648
  gradient: "Gradient",
1627
1649
  curve: "[CGPoint]"
1628
1650
  };
@@ -1832,6 +1854,7 @@ export {
1832
1854
  springCss2 as springCss,
1833
1855
  springMotion2 as springMotion,
1834
1856
  table2 as table,
1857
+ text2 as text,
1835
1858
  toggle2 as toggle,
1836
1859
  toolbar2 as toolbar,
1837
1860
  toolbarShortcut2 as toolbarShortcut,
package/dist/index.js CHANGED
@@ -473,6 +473,16 @@ function pickMeta(source, skip = []) {
473
473
  return out;
474
474
  }
475
475
  var READOUT_KINDS = /* @__PURE__ */ new Set(["watch", "sparkline", "graph", "meter", "table", "log"]), isReadout = (field) => READOUT_KINDS.has(field.kind), hasValue = (field) => field.kind !== "action" && field.kind !== "toolbar" && !READOUT_KINDS.has(field.kind);
476
+ function variantsOf(schema, isPinned = () => !1) {
477
+ let found;
478
+ for (let [key, field] of Object.entries(schema))
479
+ if (field.kind === "choice" && field.variants && !isPinned(key)) {
480
+ if (found)
481
+ throw Error(`Only one choice can hold the variants (${found.key}, ${key}).`);
482
+ found = { key, field };
483
+ }
484
+ return found;
485
+ }
476
486
  function pinnedKeys(schema, groups = []) {
477
487
  let byGroup = new Map(groups.map((g) => [g, !0]));
478
488
  for (let f of Object.values(schema))
@@ -515,8 +525,9 @@ function choice(value, options, meta) {
515
525
  value,
516
526
  options: Object.freeze([...options]),
517
527
  ...labels && Object.keys(labels).length ? { labels } : {},
518
- ...meta?.display ? { display: meta.display } : {}
519
- }, meta);
528
+ ...meta?.display ? { display: meta.display } : {},
529
+ ...meta?.variants ? { variants: !0 } : {}
530
+ }, meta?.variants ? { ...meta, random: meta.random ?? !1 } : meta);
520
531
  }
521
532
  function action(label, meta) {
522
533
  let { shortcut, global, rebind, run, ...rest } = meta ?? {}, key = shortcutOf({ ...shortcut ? { shortcut } : {}, ...global ? { global } : {} }), combo = key ? normalizeCombo(key.key) : "";
@@ -661,6 +672,17 @@ function keybind(value = "", meta) {
661
672
  let field = { kind: "keybind", value: normalizeCombo(value) };
662
673
  return withMeta(field, { random: !1, ...meta });
663
674
  }
675
+ function text(value = "", options) {
676
+ let { multiline, placeholder, ...meta } = options ?? {}, field = {
677
+ kind: "text",
678
+ value: "",
679
+ ...multiline ? { multiline: !0 } : {},
680
+ ...placeholder ? { placeholder } : {}
681
+ };
682
+ return withMeta({ ...field, value: normalizeText(field, value) }, { random: !1, ...meta });
683
+ }
684
+ var normalizeText = (field, input) => field.multiline ? input.replace(/\r\n?/g, `
685
+ `) : input.replace(/\r\n?|\n/g, " ");
664
686
  function gradient(stops, options = {}) {
665
687
  let value = normalizeStops(stops);
666
688
  if (!value)
@@ -715,7 +737,8 @@ function toolbar(items, meta) {
715
737
  throw Error("A toolbar needs at least one item.");
716
738
  return withMeta({
717
739
  kind: "toolbar",
718
- items: Object.freeze(items.map((item) => Object.freeze({ ...item })))
740
+ items: Object.freeze(items.map((item) => Object.freeze({ ...item }))),
741
+ ...meta?.labels !== void 0 ? { labels: meta.labels } : {}
719
742
  }, meta);
720
743
  }
721
744
  var readoutMeta = (o) => {
@@ -859,6 +882,8 @@ function normalizeField(field, input) {
859
882
  return typeof input === "number" && Number.isFinite(input) ? Math.round(Math.min(field.max, Math.max(field.min, input))) : field.value;
860
883
  case "keybind":
861
884
  return typeof input === "string" ? normalizeCombo(input) : field.value;
885
+ case "text":
886
+ return typeof input === "string" ? normalizeText(field, input) : field.value;
862
887
  case "gradient":
863
888
  return normalizeStops(input) ?? field.value;
864
889
  case "curve":
@@ -1009,6 +1034,7 @@ function pick(field, random) {
1009
1034
  case "seed":
1010
1035
  return lattice(field.min, field.max, 1, r());
1011
1036
  case "keybind":
1037
+ case "text":
1012
1038
  return field.value;
1013
1039
  case "gradient":
1014
1040
  return randomStops(field.value.length, r);
@@ -1094,6 +1120,7 @@ function around(field, base, random) {
1094
1120
  ]);
1095
1121
  case "seed":
1096
1122
  case "keybind":
1123
+ case "text":
1097
1124
  return pick(field, random);
1098
1125
  }
1099
1126
  }
@@ -1193,6 +1220,7 @@ function shift(field, value, random) {
1193
1220
  case "seed":
1194
1221
  return nudge(value, field.min, field.max, 1, random);
1195
1222
  case "keybind":
1223
+ case "text":
1196
1224
  return value;
1197
1225
  case "gradient":
1198
1226
  return mutateStops(value, random, MUTATE.hue, MUTATE.lightness);
@@ -1629,7 +1657,7 @@ function defineControls(id, input, options = {}) {
1629
1657
  if (pinnedOf?.schema !== schema)
1630
1658
  pinnedOf = { schema, keys: pinnedKeys(schema, options.pinned) };
1631
1659
  return pinnedOf.keys;
1632
- }, unpinned = (values) => {
1660
+ }, variantsChoice = () => variantsOf(schema, (key) => pinned().has(key)), unpinned = (values) => {
1633
1661
  let keys = pinned();
1634
1662
  if (keys.size === 0)
1635
1663
  return values;
@@ -1643,14 +1671,24 @@ function defineControls(id, input, options = {}) {
1643
1671
  if (keys.size === 0)
1644
1672
  return values;
1645
1673
  return Object.fromEntries(Object.entries(values).filter(([k]) => !keys.has(k)));
1646
- }, buildPresets = () => Object.freeze(Object.fromEntries([
1647
- ["default", locked],
1648
- ...Object.entries(options.presets ?? {}).map(([id, patch]) => {
1649
- if (id === "default" || id === "custom")
1650
- throw Error(`Reserved preset id: ${id}`);
1651
- return [id, unpinned(normalize(schema, { ...locked, ...patch }))];
1652
- })
1653
- ])), presets = buildPresets(), buildGroupPresets = () => Object.freeze(Object.fromEntries(Object.entries(options.groupPresets ?? {}).map(([group, list]) => {
1674
+ }, buildPresets = () => {
1675
+ let own = options.presets ?? {}, v = variantsChoice();
1676
+ if (v && Object.hasOwn(own, v.field.value))
1677
+ throw Error(`The default variant (${v.field.value}) is the schema's defaults; set its values there.`);
1678
+ let ids = [
1679
+ ...v ? v.field.options.filter((o) => o !== v.field.value) : [],
1680
+ ...Object.keys(own).filter((id) => !v?.field.options.includes(id))
1681
+ ];
1682
+ return Object.freeze(Object.fromEntries([
1683
+ ["default", locked],
1684
+ ...ids.map((id) => {
1685
+ if (id === "default" || id === "custom")
1686
+ throw Error(`Reserved preset id: ${id}`);
1687
+ let variant = v?.field.options.includes(id) ? { [v.key]: id } : {};
1688
+ return [id, unpinned(normalize(schema, { ...locked, ...variant, ...own[id] }))];
1689
+ })
1690
+ ]));
1691
+ }, presets = buildPresets(), buildGroupPresets = () => Object.freeze(Object.fromEntries(Object.entries(options.groupPresets ?? {}).map(([group, list]) => {
1654
1692
  let named = list.flatMap((p) => Object.keys(p.values).filter((k) => Object.hasOwn(schema, k))), covered = new Set((named.length ? named : Object.keys(schema).filter((k) => schema[k].group === group)).filter((k) => hasValue(schema[k])));
1655
1693
  return [
1656
1694
  group,
@@ -2118,7 +2156,10 @@ function defineControls(id, input, options = {}) {
2118
2156
  sectionDescription: (section) => options.sectionDescriptions?.[section] ?? fieldLabels().sectionText.get(section),
2119
2157
  groupFolded: options.groupFolded,
2120
2158
  sectionFolded: options.sectionFolded,
2121
- presetLabel: (presetId) => options.presetLabels?.[presetId] ?? (presetId === "default" ? options.defaultLabel : void 0) ?? presetId,
2159
+ presetLabel: (presetId) => {
2160
+ let v = variantsChoice(), option = presetId === "default" ? v?.field.value : presetId;
2161
+ 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);
2162
+ },
2122
2163
  isPinned: (key) => pinned().has(key),
2123
2164
  get,
2124
2165
  set,
@@ -2471,7 +2512,7 @@ function descriptorToField(d) {
2471
2512
  if (readoutDescriptor(d))
2472
2513
  return readoutField(d);
2473
2514
  if (d.kind === "toolbar")
2474
- return toolbar(d.items.map(({ key, ...item }) => ({ ...item, id: key })), meta);
2515
+ return toolbar(d.items.map(({ key, ...item }) => ({ ...item, id: key })), d.labels !== void 0 ? { ...meta, labels: d.labels } : meta);
2475
2516
  if (d.bind)
2476
2517
  return flatSpring({}, { ...meta, id: d.key }).duration;
2477
2518
  if (d.kind === "knob" || d.kind === "number") {
@@ -2546,6 +2587,12 @@ function descriptorToField(d) {
2546
2587
  });
2547
2588
  if (d.kind === "keybind")
2548
2589
  return keybind(typeof d.default === "string" ? d.default : "", meta);
2590
+ if (d.kind === "text")
2591
+ return text(typeof d.default === "string" ? d.default : "", {
2592
+ ...d.multiline ? { multiline: !0 } : {},
2593
+ ...d.placeholder ? { placeholder: d.placeholder } : {},
2594
+ ...meta
2595
+ });
2549
2596
  if (d.kind === "gradient") {
2550
2597
  let fallback = [
2551
2598
  { at: 0, color: "#000000" },
@@ -2624,7 +2671,7 @@ var decimal = (n) => String(Math.round(n * 1e4) / 1e4), swiftLiteral = (d, v) =>
2624
2671
  let [a, b] = v;
2625
2672
  return `${decimal(a)}...${decimal(b)}`;
2626
2673
  }
2627
- if (d.kind === "keybind")
2674
+ if (d.kind === "keybind" || d.kind === "text")
2628
2675
  return JSON.stringify(v);
2629
2676
  if (d.kind === "gradient")
2630
2677
  return `Gradient(stops: [${(Array.isArray(v) ? v : []).map((s) => {
@@ -2653,6 +2700,7 @@ var decimal = (n) => String(Math.round(n * 1e4) / 1e4), swiftLiteral = (d, v) =>
2653
2700
  range: "ClosedRange<Double>",
2654
2701
  seed: "Int",
2655
2702
  keybind: "String",
2703
+ text: "String",
2656
2704
  gradient: "Gradient",
2657
2705
  curve: "[CGPoint]"
2658
2706
  };
@@ -2862,6 +2910,7 @@ export {
2862
2910
  springCss,
2863
2911
  springMotion,
2864
2912
  table,
2913
+ text,
2865
2914
  toggle,
2866
2915
  toolbar,
2867
2916
  toolbarShortcut,