@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.
package/README.md CHANGED
@@ -369,7 +369,7 @@ The preset you apply last (a chip, `[` / `]`, a pan, `controls.preset(id)`, `var
369
369
  - **The `?` popover says so:** "Random around Storm / Back to Storm", then "Reset all to defaults" once you're there.
370
370
  - **Undo covers it:** applying a preset, each reset and each roll is one undo step, and undoing restores the active preset with the values. The active preset is kept with the values in storage.
371
371
 
372
- `controls.activePreset()` returns `{ kind: "preset" | "variant", id }` or null, `controls.activeGroupPreset(group)` the group's index or -1, `controls.baseValues()` what Reset returns to first, and `controls.canReset(keys?)` whether a reset would change anything. A reset back to a preset reaches `onChange` with `source: "reset"` and `base: true`; remote controls send its values with `onSet` (nulls only for a reset to the defaults), or call `onPreset` again for a group whose peer applies its own presets.
372
+ `controls.activePreset(scope?)` returns `{ kind: "preset" | "variant", id }` or null (a tab's or group's with `scope`, see `scopedPresets` under [Tabs](#tabs-sub-headings-and-search)), `controls.activeGroupPreset(group)` the group's index or -1, `controls.baseValues()` what Reset returns to first, and `controls.canReset(keys?)` whether a reset would change anything. A reset back to a preset reaches `onChange` with `source: "reset"` and `base: true`; remote controls send its values with `onSet` (nulls only for a reset to the defaults), or call `onPreset` again for a group whose peer applies its own presets.
373
373
 
374
374
  ## Keyboard
375
375
 
@@ -384,7 +384,7 @@ Panels and switchers are keyboard-first:
384
384
  | Arrows on a pad or curve handle | Move the puck or handle (`⇧` ×10, `⌥` finest); `j` / `k` or `Tab` move on |
385
385
  | On a gradient strip or curve graph | Arrows move the selected stop or point; `Tab` / `⇧Tab` and `[` / `]` pick another; `n` adds one, `Delete` removes it; `Enter` edits a stop's colour (`Esc` returns) |
386
386
  | `[` / `]` | Cycle presets and saved variants from anywhere in the panel; with no preset strip, cycle tabs |
387
- | `1`–`9` | Pick the focused row's Nth option: a choice (segmented or menu) or a curve's, easing's or gradient's presets. Anywhere else in the panel, pick the preset strip's Nth chip (a design's variants). While you use keys, tiny digit hints show on the options once focus settles, and on the strip's chips while focus is on the strip or the panel. A number row keeps its digits for inline edits, and a page's card for its group presets |
387
+ | `1`–`9` | Pick the focused row's Nth option: a choice (segmented or menu) or a curve's, easing's or gradient's presets. Anywhere else in the panel, pick the preset strip's Nth chip (a design's variants). While you use keys, tiny digit hints show on the options once focus settles, and on the strip's chips while focus is on the strip or the panel (`hints: { digits: "always" }` on the mount keeps them on the chips all the time, small and dim before each name). A number row keeps its digits for inline edits, and a page's card for its group presets |
388
388
  | `⌥1`–`⌥9` | Show tab 1–9 |
389
389
  | `/` | Focus the search field (`Esc` clears it, then hands focus back; `↓` or `Enter` go to the first row left) |
390
390
  | `Home` / `End` | Jump slider to `min` or `max` |
@@ -431,14 +431,14 @@ Three things show which, in both renderers, on floating and inline panels and on
431
431
  its hairline edge brightens a touch toward the accent and its title is at full strength. At rest
432
432
  the title is a little quieter, so on a page with several panels the one listening is plain.
433
433
  Focus leaving (or `Esc` out of the panel, or the window going to the background) disarms it.
434
- - **The scope line.** Beside the title, a quiet trail says where keys go: `Swarm › Shape ›
435
- Cohesion`, the deepest crumb bright and its parents dim. Crumbs glide to their places as focus
436
- moves, and one that changes fades out as the new one fades in. For a moment after a key below
434
+ - **The scope line** (opt-in: `crumbs: true` on the mount). Beside the title, a quiet trail says
435
+ where keys go: `Swarm › Shape › Cohesion`, the deepest crumb bright and its parents dim. Crumbs
436
+ glide to their places as focus moves, and one that changes fades out as the new one fades in. For a moment after a key below
437
437
  the panel's level, a faint `esc ↑ Shape` follows it, then fades. Clicking a crumb takes focus to
438
438
  its level; clicking the title takes it to the panel. The line lives in the room between the
439
439
  title and the header's buttons: when it doesn't fit, the first crumbs drop off, and without room
440
440
  for the deepest it stays empty, so it never runs under the buttons and the title never moves.
441
- `crumbs: false` on the mount drops the line (the lens and the armed frame stay).
441
+ Without it the header is the title and its buttons; the lens and the armed frame show either way.
442
442
 
443
443
  Tune them with tokens on the panel or any ancestor:
444
444
 
@@ -834,7 +834,29 @@ panel.tab("emitter") // shows a tab; panel.tab() answers the one showing
834
834
  preset strip, then the tab's rows. `controls.isPinned(key)` says whether a key is pinned.
835
835
  A pinned `variants: true` choice is an ordinary choice with a row: pinned wins, and its options
836
836
  make no presets. A tab whose rows no preset moves shows no preset strip; a variants choice moves
837
- every tab (or its own, if it names one).
837
+ its own tab (or every tab, if it has none).
838
+ - **Keys presets leave alone, in place.** `inPresets: false` on a field, or `inPresets: { Motion:
839
+ false }` on the store for a whole group, keeps presets, scoped presets and saved variants off
840
+ those keys exactly as they stay off pinned ones (and a preset's chip stays lit when they move),
841
+ but the rows stay where they are. A pinned key is one of these, moved to the top.
842
+ `controls.inPresets(key)` says whether presets move a key.
843
+ - **Presets of a tab's own.** `scopedPresets` gives a tab (or a group) presets that belong to it:
844
+ `{ Sidebars: { snappy, smooth, calm } }`. Its strip shows only on that tab, and a tab without any
845
+ shows none. A scoped preset moves only its tab's (or group's) keys, never pinned ones or another
846
+ tab's, so each tab is its own experiment: it has its own active preset (Reset's base and
847
+ Random's centre for its keys), its own modified mark on the chip and its own saved variants
848
+ (`controls.scopes.Sidebars.variants`, kept apart in storage). A preset switch is one undo step.
849
+ A `variants: true` choice on a tab adds its options to that tab's presets. Keys belong to their
850
+ tab's scope first, then their group's. Plain `presets` then cover only the keys no scope holds;
851
+ leave them out and only the scoped strips show. One strip shows per tab: the tab's own, else the
852
+ first group scope on it. In a panel, any other group's presets show at the top of its own card,
853
+ in the strip's look without its tray, so a story's states can lead the panel while its Motion
854
+ group keeps Shipped, Snappy and Calm beside its knobs. Every strip's default chip reads `defaultLabel`; by scope,
855
+ `defaultLabel: { "": "Empty", Motion: "Shipped" }` names each on its own. In code, `controls.preset(id, { scope })`, `activePreset(scope)` and
856
+ `matchingPreset(scope)` take the tab or group, and `controls.scopeOf(key)` says whose presets
857
+ move a key (`""` for plain `presets`, undefined when pinned). An active preset saved while the
858
+ presets were panel-wide comes back on the scope that now has one by that id, and variants saved
859
+ then start out in each scope they move.
838
860
 
839
861
  ```ts
840
862
  const motion = defineControls(
@@ -845,7 +867,12 @@ panel.tab("emitter") // shows a tab; panel.tab() answers the one showing
845
867
  sidebars: spring({ duration: 0.3, bounce: 0 }, { group: "Sidebars", tab: "Sidebars" }),
846
868
  hover: spring({ duration: 0.14, bounce: 0 }, { group: "Experiments", tab: "Experiments" }),
847
869
  },
848
- { pinned: ["Global"], defaultLabel: "Shipped", presets: { snappy, smooth, calm } },
870
+ {
871
+ pinned: ["Global"],
872
+ defaultLabel: "Shipped",
873
+ // Only the Sidebars tab has a strip, and its presets set only its springs.
874
+ scopedPresets: { Sidebars: { snappy, smooth, calm } },
875
+ },
849
876
  )
850
877
  motion.mount(el, { title: "Knobs", tabs: ["Sidebars", "Experiments"] })
851
878
  ```
@@ -1187,7 +1214,7 @@ Scrolling pane bodies, trees and the command palette's list fade their content o
1187
1214
  | Key | Action |
1188
1215
  | --- | --- |
1189
1216
  | `↑` / `↓`, `Home` / `End` | Move; type letters to jump to a label (see `typeahead`) |
1190
- | `←` / `→` | Close or go up / open or go down |
1217
+ | `←` / `→` | Close or go up / open or go down (swapped when the tree is `dir="rtl"`) |
1191
1218
  | `⇧` click or `⇧↑↓`, `⌘` click, `⌘A` | Extend, toggle, select all |
1192
1219
  | `Space` | Select the active row (`⌘Space` toggles it) |
1193
1220
  | `Enter` / `F2` | Rename in place (with `onRename`); otherwise `Enter` calls `onActivate` |
@@ -1240,7 +1267,7 @@ toast("Deleted Barrier", { action: { label: "Undo", run: undo } })
1240
1267
 
1241
1268
  - **`popover(anchor, content, options?)`** places `content` beside an element, a rect or a point. `placement` is `top`, `bottom`, `left` or `right`, each with `-start` or `-end`; it flips when its side is short and the other has more room, then shifts to stay 8px inside the view, and follows its anchor through scrolls and resizes. It grows from the anchor's edge. It takes focus (`focus: "first" | "layer" | "none"`) and gives it back when it closes; Esc, a click outside or focus leaving closes it (`closeOnOutside: false` keeps it), and `trap: true` keeps Tab inside. The handle has `element`, `open`, `reposition()` and `close()` (a promise that resolves once it's gone).
1242
1269
  - **`tooltip(el, content, options?)`** shows after a 500ms pause over the element, or on keyboard focus (not a click's). While one is up, or just after, moving to a neighbour shows its tip at once, with no entrance. A press hides it until the pointer leaves; Esc hides it. `shortcut` shows as key chips (`⌘K` on Apple platforms, `Ctrl+K` elsewhere) and sets `aria-keyshortcuts`; the target gets `aria-describedby` while it shows. Content can be a function, read each time it shows. Split panes use these for their buttons and rails.
1243
- - **`commandPalette({ commands, onRun?, hotkey?, recents?, placeholder? })`** opens on `⌘K` (`hotkey`, `"Mod+K"` by default; `false` for none). With no query it shows recent commands (remembered in localStorage under `recents`; `false` for none), then each `section`. Typing ranks commands by a fuzzy match: letters in order, best at the start, at word starts and in runs. The matched letters light up in the accent while the rest of the label dims, and letters strewn thinly across words don't count. A command answers to its `keywords` and section too. `↑`/`↓` move a highlight that glides (`PgUp`/`PgDn`, `⌘↑`/`⌘↓`), disabled commands are passed over, `Enter` runs, `Esc` or a click outside closes. A `run` that returns a string (or a promise of one) shows it as a toast; a throw shows as an error toast. The handle has `open(query?)`, `close()`, `toggle()`, `isOpen` and `dispose()`.
1270
+ - **`commandPalette({ commands, onRun?, hotkey?, recents?, placeholder?, presence? })`** opens on `⌘K` (`hotkey`, `"Mod+K"` by default; `false` for none). A key summons it, so it fades in and out in about 80ms (`presence: "fade"`); `presence: "none"` (or `false`) shows and hides it at once, and `{ duration?, scale?, blur?, dy? }` gives it the full entrance (from .96, 8px blur and 8px above, over .34s), with any of those changed. With no query it shows recent commands (remembered in localStorage under `recents`; `false` for none), then each `section`. Typing ranks commands by a fuzzy match: letters in order, best at the start, at word starts and in runs. The matched letters light up in the accent while the rest of the label dims, and letters strewn thinly across words don't count. A command answers to its `keywords` and section too. `↑`/`↓` move a highlight that glides (`PgUp`/`PgDn`, `⌘↑`/`⌘↓`), disabled commands are passed over, `Enter` runs, `Esc` or a click outside closes. A `run` that returns a string (or a promise of one) shows it as a toast; a throw shows as an error toast. The handle has `open(query?)`, `close()`, `toggle()`, `isOpen` and `dispose()`.
1244
1271
  - **`commandsFrom(controls, { title?, fields?, reveal? })`** lists a store as commands: its actions and toolbar buttons (with their shortcuts), presets and group presets (the current one marked), toggles ("Turn on …"), one command per choice option, "Reset …" for modified fields, and, with `reveal(key)`, every other field (its current value as the hint) to go to.
1245
1272
  - **`confirm({ title, body?, confirm?, cancel?, destructive? })`** returns `Promise<boolean>`. It's modal over a scrim, keeps Tab inside, and Esc or a click outside cancels. `destructive` puts the button in `--k-critical` and focus on Cancel; otherwise focus starts on the confirming button.
1246
1273
  - **`toast(message, { tone?, linger?, action?, within? })`** shows a pill at the bottom centre for 3.2s (6s for `tone: "error"`). A new message replaces the one showing, and hovering keeps it. Pages show their status (Copied, errors) this way.
@@ -2007,7 +2034,7 @@ The package has two core exports selected through the standard `development` con
2007
2034
  | `action(label, { run, shortcut?, global?, rebind? })` / `flatSpring({ duration, bounce }, meta)` | A button row, with a shortcut its key cap can rebind; two flat number fields drawn as one spring card. `actionShortcut(field)` reads an action's shortcut as `{ key, scope }`. |
2008
2035
  | Field meta `random?`, `disabled?`, `readOnly?`, `folded?`, `tab?`, `subsection?` | On any field or descriptor: `random: false` stays out of Random and Mutate (switches, seeds and key combinations start out; `random: true` brings them in); `disabled` / `readOnly` (boolean or `(values) => boolean`) gate the row; `folded` makes its group foldable, starting folded; `tab` puts the row on a tab; `subsection` puts it under a sub-heading in its group. |
2009
2036
  | Field meta `groupDescription?: string`, `sectionDescription?: string`, `sectionFolded?: boolean` | On any field or descriptor, for pages (the first field of a group or section that gives one wins): a muted line under the card's title or the section's heading; `sectionFolded` makes the section foldable, starting folded. |
2010
- | `mountPanel(controls, container, options?)` (`knobs/panel`) | An inline panel. Beside theme, density, renderer and keys: `preset`, `header` (`false` or `{ title?, buttons? }`), `crumbs` (`false` drops the scope line), `session`, `groupFolded`, `subsectionFolded`, `tabs`, `tab`, `onTab`, `search`, `groupRandom(group, mode)`, `rowAccessory(key)`, `marks`, `haptics`, `detents`, `sound`. The handle adds `panel.fold(group, folded?)`, `panel.tab(id?)`, `panel.search(query?, chips?)` (returns `{ query, chips }`) and `panel.updateAccessories()`. |
2037
+ | `mountPanel(controls, container, options?)` (`knobs/panel`) | An inline panel. Beside theme, density, renderer and keys: `preset`, `header` (`false` or `{ title?, buttons? }`), `crumbs` (`true` adds the scope line), `session`, `groupFolded`, `subsectionFolded`, `tabs`, `tab`, `onTab`, `search`, `groupRandom(group, mode)`, `rowAccessory(key)`, `marks`, `haptics`, `detents`, `sound`. The handle adds `panel.fold(group, folded?)`, `panel.tab(id?)`, `panel.search(query?, chips?)` (returns `{ query, chips }`) and `panel.updateAccessories()`. |
2011
2038
  | `sound` (`knobs/panel`) | The page's sound effects (see **Sound**): `enabled`, `enable(on?)`, `volume`, `setVolume(v)`, `theme`, `setTheme("glass" \| "mech")`, `play(cue \| layers, detail?)`, `output()`, `subscribe(listener)`. Panels play only with `sound` on their mount. |
2012
2039
  | `mountPage(controls, container, options?)` (`knobs/panel`) | A full page of group cards in sections under a sticky header; `page.card({ title?, section?, element?, collapsed?, summary?, description? })` adds custom cards (`card.collapse(collapsed?)`, `card.collapsed`, `card.summary(content \| null)`), and `page.highlight({ groups?, sections? } \| null, { reveal?, expand? })` marks what's on screen. Options take `groupLabels`, `sectionLabels`, `groupDescriptions`, `sectionDescriptions`, `presetLabels`, `defaultLabel`, `groupPresetDisplay` and `groupFolded` over the store's. |
2013
2040
  | `MountPageOptions` folds | `sectionFolded?: SectionFolded` (`boolean \| Record<string, boolean>`), `foldStorage?: StoragePort \| null`, `nav?: boolean`, `groupSummary?(group, info: FoldSummaryInfo): string \| Node \| null`, `sectionSummary?(section, info: FoldSummaryInfo): string \| Node \| null`; `FoldSummaryInfo` is `{ preset: string \| null, changed: number, keys: readonly string[] }`. |
@@ -2035,10 +2062,11 @@ The package has two core exports selected through the standard `development` con
2035
2062
  - `controls.randomize(keys?)` / `controls.mutate(keys?)`: skip locked fields; one undo step each
2036
2063
  - `controls.lock(keys, locked = true)` / `controls.isLocked(key)`: locks are saved with the values (remote controls keep them in memory)
2037
2064
  - `controls.compare(hold)` / `controls.comparing`: while held, `get()` returns the previous undo entry; no history, no storage
2038
- - `controls.preset(id, { coalesce? })` / `controls.matchingPreset()`
2039
- - `controls.activePreset()` / `controls.activeGroupPreset(group)` / `controls.baseValues()`: the base Reset returns to and Random explores around
2065
+ - `controls.preset(id, { coalesce?, scope? })` / `controls.matchingPreset(scope?)`
2066
+ - `controls.activePreset(scope?)` / `controls.activeGroupPreset(group)` / `controls.baseValues()`: the base Reset returns to and Random explores around
2067
+ - `controls.scopes` / `controls.scopeOf(key)`: the tabs and groups with presets of their own (`scopedPresets`), each `{ id, keys, presets, variants }`, and whose presets move a key
2040
2068
  - `controls.groupPresets` / `controls.groupPreset(group, index, { coalesce? })` / `controls.matchingGroupPreset(group)` / `controls.groupPresetDisplay`
2041
- - `controls.groupLabel(id)` / `controls.sectionLabel(id)` / `controls.presetLabel(id)`: display labels (`groupLabels`, `sectionLabels`, `presetLabels`, `defaultLabel`, or the fields' `groupLabel` / `sectionLabel`)
2069
+ - `controls.groupLabel(id)` / `controls.sectionLabel(id)` / `controls.presetLabel(id, scope?)`: display labels (`groupLabels`, `sectionLabels`, `presetLabels`, `defaultLabel`, or the fields' `groupLabel` / `sectionLabel`)
2042
2070
  - `controls.groupDescription(group): string | undefined` / `controls.sectionDescription(section): string | undefined`: `groupDescriptions` / `sectionDescriptions`, else the fields' `groupDescription` / `sectionDescription`
2043
2071
  - `controls.groupFolded` / `controls.sectionFolded`: how groups and a page's sections start, as given to `defineControls` or `createRemoteControls` (`groupFolded?: Record<string, boolean>`, `sectionFolded?: SectionFolded`); mounts read them under their own
2044
2072
  - `controls.code("ts" | "json" | "jsx", values?)`
@@ -2058,7 +2086,7 @@ The package has two core exports selected through the standard `development` con
2058
2086
  | `readout(field, { preset?, theme?, label? })` | A readout on its own: `{ element, push(value), value, dispose() }`. |
2059
2087
  | `popover(anchor, content, { placement?, offset?, role?, label?, focus?, trap?, closeOnOutside?, onClose? })` | A floating layer that flips and shifts; `{ element, open, reposition(), close() }`. |
2060
2088
  | `tooltip(el, text \| { text, shortcut?, placement?, delay? })` / `tooltips(doc)` | Delayed tooltips with instant hand-off and shortcut chips; `data-tip` attributes. |
2061
- | `commandPalette({ commands, onRun?, hotkey?, recents?, placeholder? })` / `commandsFrom(controls, options?)` | `⌘K` fuzzy command search; a store's actions, presets and fields as commands. |
2089
+ | `commandPalette({ commands, onRun?, hotkey?, recents?, placeholder?, presence? })` / `commandsFrom(controls, options?)` | `⌘K` fuzzy command search; a store's actions, presets and fields as commands. |
2062
2090
  | `confirm({ title, body?, confirm?, cancel?, destructive? })` | `Promise<boolean>`, modal, focus kept inside. |
2063
2091
  | `toast(message, { tone?, linger?, action?, within? })` | A status pill at the bottom centre. |
2064
2092
  | `menu(anchor, items, { placement?, label?, onSelect?, onClose? })` / `contextMenu(target, items, options?)` | Menus with icons, shortcut chips, checks, radios and submenus; a right-click or menu-key menu. |
@@ -10,7 +10,7 @@ import {
10
10
  DEFAULT_LINEAR,
11
11
  motionCss,
12
12
  storageKey
13
- } from "./chunk-7kns3qe6.js";
13
+ } from "./chunk-v2g91mrm.js";
14
14
 
15
15
  // src/core/theme.ts
16
16
  var property = (k) => k.startsWith("--") ? k : `--k-${k}`;
@@ -2396,7 +2396,7 @@ ${GRAD_PROPS}
2396
2396
 
2397
2397
  /* The chip: a round cog button, or a capsule with a label beside the cog. */
2398
2398
  .knobs-chip{position:fixed;z-index:2147480000;display:inline-flex;align-items:center;justify-content:center;gap:5px;width:26px;height:26px;padding:0;border-radius:999px;cursor:pointer;background:var(--_k-bg);border:1px solid transparent;
2399
- color:var(--_k-fg-2);font-size:var(--_k-text-control);line-height:1;letter-spacing:.01em;white-space:nowrap;--k-chip-edge:var(--_k-border-subtle);box-shadow:0 0 0 var(--k-rim-width) var(--k-chip-edge),0 4px 16px rgba(0,0,0,calc(.35 * var(--_k-shadow-k,1))),inset 0 var(--k-rim-width) 0 var(--_k-rim);transition:color .15s,border-color .15s,transform .2s var(--k-ease),opacity .2s,scale .12s var(--k-ease),${TOKEN_TRANSITIONS};}
2399
+ color:var(--_k-fg-2);font-size:var(--_k-text-control);line-height:1;letter-spacing:.01em;white-space:nowrap;--k-chip-edge:var(--_k-border-subtle);box-shadow:0 calc(var(--k-rim-width) * -1) 0 var(--_k-rim),0 0 0 var(--k-rim-width) var(--k-chip-edge),0 4px 16px rgba(0,0,0,calc(.35 * var(--_k-shadow-k,1)));transition:color .15s,border-color .15s,transform .2s var(--k-ease),opacity .2s,scale .12s var(--k-ease),${TOKEN_TRANSITIONS};}
2400
2400
  .knobs-chip[data-label]{width:auto;padding:0 10px 0 7px}
2401
2401
  .knobs-chip .knobs-icon{display:block;flex:none;width:15px;height:15px}
2402
2402
  .knobs-chip:hover{color:var(--_k-fg);--k-chip-edge:var(--_k-border-strong)}
@@ -2423,9 +2423,10 @@ ${GRAD_PROPS}
2423
2423
  /* Panel material: --k-glass (0 opaque … 1 clearest) thins the surface over a blurred, saturated
2424
2424
  backdrop, toned toward the theme (--k-glass-tone dims it under dark panels) so text keeps its
2425
2425
  contrast. Its edge is a hairline (--k-edge, --k-rim-width wide) and a hairline of light (--k-rim)
2426
- catches its top. */
2426
+ catches its top. The light rides on the edge (a shadow raised by its width, so only its top
2427
+ shows), not inside the card's 1px border, where it read as a second edge. */
2427
2428
  .knobs-panel{-webkit-user-select:none;user-select:none;transition:${TOKEN_TRANSITIONS},--k-armed ${ARMED_T};position:fixed;z-index:2147480001;width:var(--_k-width);max-width:calc(100vw - 16px);max-height:calc(100dvh - 16px);overflow:auto;padding:var(--_k-pad);
2428
- background:color-mix(in srgb,var(--_k-bg) calc(100% - var(--_k-glass) * 20%),transparent);border:1px solid transparent;border-radius:var(--_k-radius-panel);box-shadow:var(--_k-shadow-overlay),0 0 0 var(--k-rim-width) var(--ka-edge),inset 0 var(--k-rim-width) 0 var(--_k-rim),var(--ka-glow);scrollbar-width:thin;
2429
+ background:color-mix(in srgb,var(--_k-bg) calc(100% - var(--_k-glass) * 20%),transparent);border:1px solid transparent;border-radius:var(--_k-radius-panel);box-shadow:var(--_k-shadow-overlay),0 calc(var(--k-rim-width) * -1) 0 var(--_k-rim),0 0 0 var(--k-rim-width) var(--ka-edge),var(--ka-glow);scrollbar-width:thin;
2429
2430
  /* Armed (focus inside: keys come here), the edge takes the accent and a faint glow lifts it. */
2430
2431
  --ka-edge:color-mix(in srgb,var(--_k-accent) calc(var(--k-armed) * var(--k-armed-edge) * 100%),var(--_k-border-subtle));
2431
2432
  --ka-glow:0 0 calc(var(--k-armed-glow) * 1px) color-mix(in srgb,var(--_k-accent) calc(var(--k-armed) * var(--k-armed-glow-a) * var(--k-chrome,1) * 100%),transparent);
@@ -2438,7 +2439,7 @@ ${GRAD_PROPS}
2438
2439
  .knobs-panel[data-morphing]{background:transparent;border-color:transparent;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none;animation:none}
2439
2440
  .knobs-morph{position:fixed;left:0;top:0;z-index:2147480001;pointer-events:none;box-sizing:border-box;--p:0;--spec:0;
2440
2441
  background:color-mix(in srgb,var(--_k-bg) calc(100% - var(--_k-glass) * 20% * var(--p)),transparent);border:1px solid transparent;
2441
- box-shadow:0 0 0 var(--k-rim-width) var(--_k-border-subtle),inset 0 0 0 var(--k-rim-width) rgb(255 255 255/var(--spec)),inset 0 var(--k-rim-width) 0 color-mix(in srgb,var(--_k-rim) calc(var(--p) * 100%),transparent);
2442
+ box-shadow:0 calc(var(--k-rim-width) * -1) 0 color-mix(in srgb,var(--_k-rim) calc(var(--p) * 100%),transparent),0 0 0 var(--k-rim-width) var(--_k-border-subtle),inset 0 0 0 var(--k-rim-width) rgb(255 255 255/var(--spec));
2442
2443
  --k-backdrop:blur(calc(var(--_k-glass) * 24px)) saturate(calc(1 + var(--_k-glass) * .8)) brightness(calc(1 + (var(--_k-glass-tone) - 1) * var(--_k-glass)));
2443
2444
  -webkit-backdrop-filter:var(--k-backdrop);backdrop-filter:var(--k-backdrop)}
2444
2445
  .knobs-morph::before,.knobs-morph::after{content:"";position:absolute;inset:-1px;border-radius:inherit;pointer-events:none}
@@ -2446,7 +2447,7 @@ ${GRAD_PROPS}
2446
2447
  .knobs-morph::after{box-shadow:var(--_k-shadow-overlay);opacity:var(--p)}
2447
2448
  /* Meanwhile the chip is just its label, fading on the shape. */
2448
2449
  .knobs-chip.knobs-chip[data-morph]{visibility:visible;pointer-events:auto;transform:none;transition:none;background:transparent;border-color:transparent;box-shadow:none}
2449
- .knobs-panel.knobs-inline{position:relative;z-index:auto;left:auto!important;top:auto!important;max-width:100%;max-height:none;box-shadow:0 0 0 var(--k-rim-width) color-mix(in srgb,var(--ka-edge) calc(var(--k-chrome) * 100%),transparent),inset 0 var(--k-rim-width) 0 color-mix(in srgb,var(--_k-rim) calc(var(--k-chrome) * 100%),transparent),var(--ka-glow);animation:none;
2450
+ .knobs-panel.knobs-inline{position:relative;z-index:auto;left:auto!important;top:auto!important;max-width:100%;max-height:none;box-shadow:0 calc(var(--k-rim-width) * -1) 0 color-mix(in srgb,var(--_k-rim) calc(var(--k-chrome) * 100%),transparent),0 0 0 var(--k-rim-width) color-mix(in srgb,var(--ka-edge) calc(var(--k-chrome) * 100%),transparent),var(--ka-glow);animation:none;
2450
2451
  /* --k-chrome 0: an embedded panel sheds its card (background, border, rim) for the host's column,
2451
2452
  border width too, so its rows line up with the column's edges. */
2452
2453
  border-width:calc(var(--k-chrome) * 1px);
@@ -2599,9 +2600,16 @@ ${GRAD_PROPS}
2599
2600
  /* The active preset's modified mark: the values have drifted off it (a row's dot, in its corner). */
2600
2601
  .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)}
2601
2602
  .knobs-panel .knobs-preset-chip[data-drifted]::after{opacity:.95;transform:none}
2603
+ /* Digits that stay on the chips (hints: { digits: "always" }): quiet, in a column of their own. */
2604
+ .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}
2605
+ /* A group's own presets at the top of its card: the chips sit on the card, with no tray. */
2606
+ .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}
2607
+ .knobs-panel .knobs-card>.knobs-presets[data-display="list"]{padding-inline:0}
2608
+ .knobs-card>.knobs-presets:hover{background:transparent}
2602
2609
  /* Naming a saved preset: a field over its chip that opens out from the middle to the chip's width;
2603
2610
  the chip's own name hides beneath it. Leaving, it fades. */
2604
2611
  .knobs-panel .knobs-preset-chip[data-naming]{color:transparent!important}
2612
+ .knobs-panel .knobs-preset-chip[data-naming] .knobs-preset-key{color:transparent}
2605
2613
  .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);
2606
2614
  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)}
2607
2615
  .knobs-preset-name::selection{background:color-mix(in srgb,var(--_k-accent) 24%,transparent);color:var(--_k-fg)}
@@ -3212,7 +3220,7 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
3212
3220
  .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,
3213
3221
  .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,
3214
3222
  .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,
3215
- .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,
3223
+ .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,
3216
3224
  .knobs-section-name,.knobs-section-summary,.knobs-section-desc,.knobs-group-desc,.knobs-group-summary,.knobs-tab-count):not(.knobs-live,.knobs-live *){
3217
3225
  background:transparent!important;color:transparent!important;border-color:transparent!important;box-shadow:none!important;
3218
3226
  }
@@ -6,7 +6,7 @@ import {
6
6
  writeSession,
7
7
  injectStyles,
8
8
  toggleLayer
9
- } from "./chunk-nwfm1bfj.js";
9
+ } from "./chunk-bejz4qcg.js";
10
10
 
11
11
  // src/panel/attach.ts
12
12
  function attach(controls, element, options = {}) {
@@ -82,7 +82,7 @@ function attach(controls, element, options = {}) {
82
82
  return;
83
83
  loading = !0;
84
84
  try {
85
- let { createPanel } = await import("./chunk-9qtvxa6k.js");
85
+ let { createPanel } = await import("./chunk-s7d5a8vb.js");
86
86
  if (disposed || !controls.enabled || panel)
87
87
  return;
88
88
  if (panel = createPanel(controls, element, close, { restored, chip }), remember)