@kitlangton/knobs 0.4.0-next.12 → 0.4.0-next.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -11
- package/dist/dev/{chunk-ab7vfghg.js → chunk-44r02asc.js} +2 -2
- package/dist/dev/{chunk-9qtvxa6k.js → chunk-mbhy4jg5.js} +77 -33
- package/dist/dev/{chunk-nwfm1bfj.js → chunk-pdf3zy95.js} +6 -5
- package/dist/dev/index.js +156 -54
- package/dist/index.js +154 -52
- package/dist/panel.js +93 -37
- package/dist/types/core/remote.d.ts +2 -0
- package/dist/types/core/store.d.ts +38 -8
- package/dist/types/core/variants.d.ts +3 -1
- package/dist/types/panel/presets.d.ts +12 -4
- package/package.json +1 -1
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
|
|
|
@@ -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
|
|
435
|
-
Cohesion`, the deepest crumb bright and its parents dim. Crumbs
|
|
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
|
-
|
|
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,21 @@ 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
|
-
|
|
837
|
+
its own tab (or every tab, if it has none).
|
|
838
|
+
- **Presets of a tab's own.** `scopedPresets` gives a tab (or a group) presets that belong to it:
|
|
839
|
+
`{ Sidebars: { snappy, smooth, calm } }`. Its strip shows only on that tab, and a tab without any
|
|
840
|
+
shows none. A scoped preset moves only its tab's (or group's) keys, never pinned ones or another
|
|
841
|
+
tab's, so each tab is its own experiment: it has its own active preset (Reset's base and
|
|
842
|
+
Random's centre for its keys), its own modified mark on the chip and its own saved variants
|
|
843
|
+
(`controls.scopes.Sidebars.variants`, kept apart in storage). A preset switch is one undo step.
|
|
844
|
+
A `variants: true` choice on a tab adds its options to that tab's presets. Keys belong to their
|
|
845
|
+
tab's scope first, then their group's. Plain `presets` then cover only the keys no scope holds;
|
|
846
|
+
leave them out and only the scoped strips show. One strip shows per tab: the tab's own, else the
|
|
847
|
+
first group scope on it. In code, `controls.preset(id, { scope })`, `activePreset(scope)` and
|
|
848
|
+
`matchingPreset(scope)` take the tab or group, and `controls.scopeOf(key)` says whose presets
|
|
849
|
+
move a key (`""` for plain `presets`, undefined when pinned). An active preset saved while the
|
|
850
|
+
presets were panel-wide comes back on the scope that now has one by that id, and variants saved
|
|
851
|
+
then start out in each scope they move.
|
|
838
852
|
|
|
839
853
|
```ts
|
|
840
854
|
const motion = defineControls(
|
|
@@ -845,7 +859,12 @@ panel.tab("emitter") // shows a tab; panel.tab() answers the one showing
|
|
|
845
859
|
sidebars: spring({ duration: 0.3, bounce: 0 }, { group: "Sidebars", tab: "Sidebars" }),
|
|
846
860
|
hover: spring({ duration: 0.14, bounce: 0 }, { group: "Experiments", tab: "Experiments" }),
|
|
847
861
|
},
|
|
848
|
-
{
|
|
862
|
+
{
|
|
863
|
+
pinned: ["Global"],
|
|
864
|
+
defaultLabel: "Shipped",
|
|
865
|
+
// Only the Sidebars tab has a strip, and its presets set only its springs.
|
|
866
|
+
scopedPresets: { Sidebars: { snappy, smooth, calm } },
|
|
867
|
+
},
|
|
849
868
|
)
|
|
850
869
|
motion.mount(el, { title: "Knobs", tabs: ["Sidebars", "Experiments"] })
|
|
851
870
|
```
|
|
@@ -2007,7 +2026,7 @@ The package has two core exports selected through the standard `development` con
|
|
|
2007
2026
|
| `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
2027
|
| 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
2028
|
| 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` (`
|
|
2029
|
+
| `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
2030
|
| `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
2031
|
| `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
2032
|
| `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 +2054,11 @@ The package has two core exports selected through the standard `development` con
|
|
|
2035
2054
|
- `controls.randomize(keys?)` / `controls.mutate(keys?)`: skip locked fields; one undo step each
|
|
2036
2055
|
- `controls.lock(keys, locked = true)` / `controls.isLocked(key)`: locks are saved with the values (remote controls keep them in memory)
|
|
2037
2056
|
- `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
|
|
2057
|
+
- `controls.preset(id, { coalesce?, scope? })` / `controls.matchingPreset(scope?)`
|
|
2058
|
+
- `controls.activePreset(scope?)` / `controls.activeGroupPreset(group)` / `controls.baseValues()`: the base Reset returns to and Random explores around
|
|
2059
|
+
- `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
2060
|
- `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`)
|
|
2061
|
+
- `controls.groupLabel(id)` / `controls.sectionLabel(id)` / `controls.presetLabel(id, scope?)`: display labels (`groupLabels`, `sectionLabels`, `presetLabels`, `defaultLabel`, or the fields' `groupLabel` / `sectionLabel`)
|
|
2042
2062
|
- `controls.groupDescription(group): string | undefined` / `controls.sectionDescription(section): string | undefined`: `groupDescriptions` / `sectionDescriptions`, else the fields' `groupDescription` / `sectionDescription`
|
|
2043
2063
|
- `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
2064
|
- `controls.code("ts" | "json" | "jsx", values?)`
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
writeSession,
|
|
7
7
|
injectStyles,
|
|
8
8
|
toggleLayer
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-pdf3zy95.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-
|
|
85
|
+
let { createPanel } = await import("./chunk-mbhy4jg5.js");
|
|
86
86
|
if (disposed || !controls.enabled || panel)
|
|
87
87
|
return;
|
|
88
88
|
if (panel = createPanel(controls, element, close, { restored, chip }), remember)
|
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
createHintController,
|
|
120
120
|
pageHints,
|
|
121
121
|
toggleLayer
|
|
122
|
-
} from "./chunk-
|
|
122
|
+
} from "./chunk-pdf3zy95.js";
|
|
123
123
|
|
|
124
124
|
// src/panel/die.ts
|
|
125
125
|
var ROUND = 16 * Math.sqrt(5), DIE = { half: 64 + ROUND, round: ROUND, pip: 12 }, PIPS = {
|
|
@@ -960,8 +960,8 @@ function panOptions(el, o) {
|
|
|
960
960
|
|
|
961
961
|
// src/panel/presets.ts
|
|
962
962
|
var DRIFTED_PILL = 0.5;
|
|
963
|
-
function activeEntryId(controls) {
|
|
964
|
-
let ref = controls.activePreset();
|
|
963
|
+
function activeEntryId(controls, scope = "") {
|
|
964
|
+
let ref = controls.activePreset(scope);
|
|
965
965
|
return ref ? `${ref.kind === "preset" ? "p" : "v"}:${ref.id}` : null;
|
|
966
966
|
}
|
|
967
967
|
function resolveActivePresetEntry(entries, currentValues, lastAppliedId, baseId = null) {
|
|
@@ -1012,7 +1012,7 @@ function nextPresetIndex(count, activeIndex, direction) {
|
|
|
1012
1012
|
return direction > 0 ? 0 : count - 1;
|
|
1013
1013
|
return ((activeIndex + direction) % count + count) % count;
|
|
1014
1014
|
}
|
|
1015
|
-
function createPresetStrip(doc, controls, showVariants, run, onAfterChange, presetLabel = (id) => controls.presetLabel(id), relevant) {
|
|
1015
|
+
function createPresetStrip(doc, controls, showVariants, run, onAfterChange, presetLabel = (id, scope) => controls.presetLabel(id, scope), relevant, scopeOf = () => "") {
|
|
1016
1016
|
let wake = () => onAfterChange?.(), el = (tag, cls, text) => {
|
|
1017
1017
|
let node = doc.createElement(tag);
|
|
1018
1018
|
if (cls)
|
|
@@ -1029,31 +1029,34 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
|
|
|
1029
1029
|
saveButton.type = "button", saveButton.tabIndex = -1, saveButton.setAttribute("aria-label", "Save as variant"), saveButton.setAttribute("aria-keyshortcuts", "Shift+S"), saveButton.dataset.hint = "Save", saveButton.onclick = () => run(save);
|
|
1030
1030
|
if (track.append(pill), root.append(track), saveButton)
|
|
1031
1031
|
root.append(saveButton);
|
|
1032
|
-
let pillEdges = new Edges, pillY = new Spring(0, 28, 0.86), pillAlpha = new Spring(1, 26, 0.9), entries = [], buttons = [], signature = "", lastVariantsRef = null, lastAppliedId = null, activeState = { activeId: "p:default", activeIndex: 0, isCustom: !1 }, prevActiveId = "", prevActiveIndex = -2, prevIsCustom = !1, prevDrifted = !1, initializedPill = !1, slideUntil = 0, display = "auto", chipMin = 0, unpinned = (values) => {
|
|
1032
|
+
let pillEdges = new Edges, pillY = new Spring(0, 28, 0.86), pillAlpha = new Spring(1, 26, 0.9), entries = [], buttons = [], signature = "", lastVariantsRef = null, lastAppliedId = null, activeState = { activeId: "p:default", activeIndex: 0, isCustom: !1 }, prevActiveId = "", prevActiveIndex = -2, prevIsCustom = !1, prevDrifted = !1, initializedPill = !1, slideUntil = 0, display = "auto", chipMin = 0, scope = null, at = () => scope ?? "", source = () => scope === null ? void 0 : scope === "" ? { presets: controls.presets, variants: controls.variants } : controls.scopes[scope], unpinned = (values) => {
|
|
1033
1033
|
let out;
|
|
1034
1034
|
for (let k of Object.keys(values))
|
|
1035
|
-
if (controls.
|
|
1035
|
+
if (controls.scopeOf(k) !== at() && !same(values[k], controls.locked[k]))
|
|
1036
1036
|
out ??= { ...values }, out[k] = controls.locked[k];
|
|
1037
1037
|
return out ?? values;
|
|
1038
1038
|
};
|
|
1039
1039
|
function buildEntries() {
|
|
1040
|
-
let
|
|
1040
|
+
let from = source();
|
|
1041
|
+
if (!from)
|
|
1042
|
+
return [];
|
|
1043
|
+
let where = at(), presetEntries = Object.entries(from.presets).map(([id, presetValues]) => ({
|
|
1041
1044
|
id: `p:${id}`,
|
|
1042
1045
|
kind: "preset",
|
|
1043
1046
|
rawId: id,
|
|
1044
|
-
label: presetLabel(id),
|
|
1047
|
+
label: presetLabel(id, where),
|
|
1045
1048
|
values: presetValues,
|
|
1046
|
-
apply: (coalesce) => controls.preset(id, { coalesce })
|
|
1049
|
+
apply: (coalesce) => controls.preset(id, { coalesce, scope: where })
|
|
1047
1050
|
}));
|
|
1048
1051
|
if (!showVariants)
|
|
1049
1052
|
return presetEntries;
|
|
1050
|
-
let variantEntries =
|
|
1053
|
+
let variantEntries = from.variants.list().map((v) => ({
|
|
1051
1054
|
id: `v:${v.id}`,
|
|
1052
1055
|
kind: "variant",
|
|
1053
1056
|
rawId: v.id,
|
|
1054
1057
|
label: v.name ?? v.id.slice(0, 8),
|
|
1055
1058
|
values: unpinned(v.values),
|
|
1056
|
-
apply: (coalesce) =>
|
|
1059
|
+
apply: (coalesce) => from.variants.load(v.id, { coalesce })
|
|
1057
1060
|
}));
|
|
1058
1061
|
return [...presetEntries, ...variantEntries];
|
|
1059
1062
|
}
|
|
@@ -1087,7 +1090,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
|
|
|
1087
1090
|
if (pill.style.left = `${left}px`, pill.style.right = `${box.trackWidth - right}px`, pill.style.top = `${top}px`, pill.style.height = `${box.height}px`, pill.style.opacity = activeState.drifted ? String(DRIFTED_PILL) : "1", pill.style.scale = "", first)
|
|
1088
1091
|
doc.defaultView?.requestAnimationFrame(() => pill.style.transition = "");
|
|
1089
1092
|
}
|
|
1090
|
-
let resolve = () => resolveActivePresetEntry(entries, unpinned(controls.get()), lastAppliedId, activeEntryId(controls)), wanted = (list) => (list.length > 1 || showVariants) && (relevant?.(list) ?? !0);
|
|
1093
|
+
let resolve = () => resolveActivePresetEntry(entries, unpinned(controls.get()), lastAppliedId, activeEntryId(controls, at())), wanted = (list) => scope !== null && (list.length > 1 || showVariants) && (relevant?.(list) ?? !0);
|
|
1091
1094
|
function syncShown() {
|
|
1092
1095
|
let show = wanted(entries);
|
|
1093
1096
|
if (root.hidden === show && root.dataset.presence === void 0)
|
|
@@ -1130,13 +1133,16 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
|
|
|
1130
1133
|
}
|
|
1131
1134
|
function update() {
|
|
1132
1135
|
run(() => {
|
|
1133
|
-
let
|
|
1134
|
-
if (
|
|
1136
|
+
let was = scope;
|
|
1137
|
+
if (scope = scopeOf(), scope !== was)
|
|
1138
|
+
lastAppliedId = null;
|
|
1139
|
+
let variantsRef = showVariants ? source()?.variants.list() ?? null : null;
|
|
1140
|
+
if (signature !== "" && scope === was && variantsRef === lastVariantsRef) {
|
|
1135
1141
|
syncShown(), syncSelection(!1);
|
|
1136
1142
|
return;
|
|
1137
1143
|
}
|
|
1138
1144
|
lastVariantsRef = variantsRef;
|
|
1139
|
-
let nextEntries = buildEntries(), nextSig = JSON.stringify(nextEntries.map((e) => [e.id, e.label]));
|
|
1145
|
+
let nextEntries = buildEntries(), nextSig = JSON.stringify([scope, nextEntries.map((e) => [e.id, e.label])]);
|
|
1140
1146
|
if (entries = nextEntries, syncShown(), nextSig !== signature)
|
|
1141
1147
|
signature = nextSig, entries = nextEntries, buttons = entries.map((entry, idx) => {
|
|
1142
1148
|
let b = el("button", "knobs-preset-chip", entry.label);
|
|
@@ -1175,11 +1181,14 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
|
|
|
1175
1181
|
}
|
|
1176
1182
|
function save() {
|
|
1177
1183
|
finishName(!0);
|
|
1178
|
-
let
|
|
1184
|
+
let variants = source()?.variants;
|
|
1185
|
+
if (!variants)
|
|
1186
|
+
return "";
|
|
1187
|
+
let used = new Set(variants.list().map((v) => v.name)), n = 1;
|
|
1179
1188
|
while (used.has(`Variant ${n}`))
|
|
1180
1189
|
n++;
|
|
1181
|
-
let variant =
|
|
1182
|
-
return
|
|
1190
|
+
let variant = variants.save(`Variant ${n}`);
|
|
1191
|
+
return variants.load(variant.id), lastAppliedId = `v:${variant.id}`, update(), startName(entries.findIndex((e) => e.id === `v:${variant.id}`), !0), onAfterChange?.(), `Saved “Variant ${n}”. Type a name, or Enter to keep it.`;
|
|
1183
1192
|
}
|
|
1184
1193
|
let naming = null;
|
|
1185
1194
|
function placeName() {
|
|
@@ -1223,7 +1232,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
|
|
|
1223
1232
|
focusQuietly();
|
|
1224
1233
|
let message;
|
|
1225
1234
|
if (keep && entry?.kind === "variant" && name && name !== was)
|
|
1226
|
-
|
|
1235
|
+
source()?.variants.rename(entry.rawId, name), update(), message = `Named “${name}”.`;
|
|
1227
1236
|
return onAfterChange?.(), message;
|
|
1228
1237
|
}
|
|
1229
1238
|
function rename() {
|
|
@@ -1239,7 +1248,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
|
|
|
1239
1248
|
let entry = entries[activeState.activeIndex];
|
|
1240
1249
|
if (!entry || entry.kind !== "variant")
|
|
1241
1250
|
return;
|
|
1242
|
-
return
|
|
1251
|
+
return source()?.variants.remove(entry.rawId), lastAppliedId = null, update(), onAfterChange?.(), `Deleted “${entry.label}”.`;
|
|
1243
1252
|
}
|
|
1244
1253
|
let scrubbing = !1, scrubCommitted = !1, chipIndexAt = (clientX, clientY) => {
|
|
1245
1254
|
let best = -1, bestDist = 1 / 0;
|
|
@@ -2971,7 +2980,8 @@ fn hash21(q: vec2f) -> f32 {
|
|
|
2971
2980
|
|
|
2972
2981
|
// kind 0: Panel card surface + elevation shadow + border + specular rim. With a hairline width
|
|
2973
2982
|
// (extra.z, whole device px) the edge is that hairline just outside the card, like CSS's
|
|
2974
|
-
// 0 0 0 w spread shadow, and the rim
|
|
2983
|
+
// 0 0 0 w spread shadow, and the rim lights its top, like CSS's 0 -w 0 (one edge, not two);
|
|
2984
|
+
// otherwise a 1px border and a 1px rim inside it.
|
|
2975
2985
|
fn shadePanel(p: Prim, local: vec2f, size: vec2f) -> vec4f {
|
|
2976
2986
|
let r = p.look.x;
|
|
2977
2987
|
let dist = roundedBox(local, size, r);
|
|
@@ -2980,6 +2990,8 @@ fn shadePanel(p: Prim, local: vec2f, size: vec2f) -> vec4f {
|
|
|
2980
2990
|
var out = vec4f(0.0);
|
|
2981
2991
|
if (hair > 0.0 && dist > -1.0) {
|
|
2982
2992
|
out = premul(p.color2) * clamp(covSd(dist - hair) - bodyCov, 0.0, 1.0);
|
|
2993
|
+
let raised = covSd(roundedBox(local + vec2f(0.0, hair), size, r));
|
|
2994
|
+
out = over(out, premul(p.aux2) * clamp(raised - bodyCov, 0.0, 1.0));
|
|
2983
2995
|
}
|
|
2984
2996
|
|
|
2985
2997
|
// Floating drop shadow when glowOrBlur (look.z) > 0; like box-shadow, never under the card.
|
|
@@ -2993,13 +3005,16 @@ fn shadePanel(p: Prim, local: vec2f, size: vec2f) -> vec4f {
|
|
|
2993
3005
|
|
|
2994
3006
|
var card = premul(p.color) * bodyCov;
|
|
2995
3007
|
|
|
2996
|
-
//
|
|
3008
|
+
// Without a hairline, the rim is CSS's inset 0 1px 0: inside the border, minus that shape
|
|
3009
|
+
// moved down.
|
|
2997
3010
|
let inner = local - vec2f(1.0);
|
|
2998
3011
|
let innerSize = size - vec2f(2.0);
|
|
2999
3012
|
let w = select(1.0, hair, hair > 0.0);
|
|
3000
3013
|
let innerCov = covSd(roundedBox(inner, innerSize, r - 1.0));
|
|
3001
|
-
|
|
3002
|
-
|
|
3014
|
+
if (hair <= 0.0) {
|
|
3015
|
+
let rimCov = innerCov * (1.0 - covSd(roundedBox(inner - vec2f(0.0, w), innerSize, r - 1.0)));
|
|
3016
|
+
card = over(card, premul(p.aux2) * rimCov);
|
|
3017
|
+
}
|
|
3003
3018
|
|
|
3004
3019
|
// A morphing card's specular ring (extra.y), w wide inside the border like CSS's inset 0 0 0 w.
|
|
3005
3020
|
if (p.extra.y > 0.001) {
|
|
@@ -15047,7 +15062,7 @@ function buildPanel(controls, host, options) {
|
|
|
15047
15062
|
}
|
|
15048
15063
|
popover.append(popGrid);
|
|
15049
15064
|
let baseAt = (scope, from) => {
|
|
15050
|
-
let group = scope === "row" ? rows[rowAt(from)]?.group : scope === "group" ? cardOf(from)?.dataset.group ?? void 0 : void 0, keys = scope === "row" ? rows[rowAt(from)]?.keys ?? [] : scope === "group" ? cardKeys(cardOf(from)) : scope === "selection" ? selectionKeys() ?? [] : void 0, index = group === void 0 ? -1 : controls.activeGroupPreset(group), ref = controls.activePreset(), name = index >= 0 ? controls.groupPresets[group]?.[index]?.name : ref?.kind === "preset" ? presetLabel(ref.id) : ref ?
|
|
15065
|
+
let group = scope === "row" ? rows[rowAt(from)]?.group : scope === "group" ? cardOf(from)?.dataset.group ?? void 0 : void 0, keys = scope === "row" ? rows[rowAt(from)]?.keys ?? [] : scope === "group" ? cardKeys(cardOf(from)) : scope === "selection" ? selectionKeys() ?? [] : void 0, index = group === void 0 ? -1 : controls.activeGroupPreset(group), where = (keys?.length ? controls.scopeOf(keys[0]) : stripScope()) ?? null, ref = where === null ? null : controls.activePreset(where), name = index >= 0 ? controls.groupPresets[group]?.[index]?.name : ref?.kind === "preset" ? presetLabel(ref.id, where) : ref ? (where === "" ? controls.variants : controls.scopes[where]?.variants)?.list().find((v) => v.id === ref.id)?.name : void 0;
|
|
15051
15066
|
if (!name)
|
|
15052
15067
|
return null;
|
|
15053
15068
|
let off = keys ? keys.some((k) => controls.isModified(k)) : controls.isModified();
|
|
@@ -15141,7 +15156,7 @@ function buildPanel(controls, host, options) {
|
|
|
15141
15156
|
if (helpButton?.hidden)
|
|
15142
15157
|
titleEl.title = `${titleEl.textContent ? `${titleEl.textContent}
|
|
15143
15158
|
` : ""}Press ${show("help")} for keyboard shortcuts`;
|
|
15144
|
-
let presetLabel = (id) => options.presetLabels?.[id] ?? (id === "default" ? options.defaultLabel : void 0) ?? controls.presetLabel(id), presetStrip = createPresetStrip(doc, controls, showVariants, run, () => wakeGpu(), presetLabel, (entries) => presetsTouchTab(entries)), tabStrip = createTabStrip(doc, (id, animate) => selectTab(id, animate)), activeTab, searchOptions = typeof options.search === "object" ? options.search : {}, chipTests = /* @__PURE__ */ new Map, search = options.search && !page ? createSearch(doc, {
|
|
15159
|
+
let presetLabel = (id, scope = "") => options.presetLabels?.[id] ?? (id === "default" ? options.defaultLabel : void 0) ?? controls.presetLabel(id, scope), presetStrip = createPresetStrip(doc, controls, showVariants, run, () => wakeGpu(), presetLabel, (entries) => presetsTouchTab(entries), () => stripScope()), tabStrip = createTabStrip(doc, (id, animate) => selectTab(id, animate)), activeTab, searchOptions = typeof options.search === "object" ? options.search : {}, chipTests = /* @__PURE__ */ new Map, search = options.search && !page ? createSearch(doc, {
|
|
15145
15160
|
placeholder: searchOptions.placeholder ?? "Search",
|
|
15146
15161
|
chips: (searchOptions.chips ?? ["modified"]).map((chip) => {
|
|
15147
15162
|
if (chip === "modified")
|
|
@@ -16192,11 +16207,11 @@ function buildPanel(controls, host, options) {
|
|
|
16192
16207
|
return list;
|
|
16193
16208
|
};
|
|
16194
16209
|
function presetsTouchTab(entries) {
|
|
16195
|
-
if (page || activeTab === void 0)
|
|
16210
|
+
if (page || activeTab === void 0 || stripScope() !== "")
|
|
16196
16211
|
return !0;
|
|
16197
16212
|
let keys = rows.flatMap((r) => {
|
|
16198
16213
|
let tab = tabOf(r);
|
|
16199
|
-
return !pinnedRow(r) && (tab === void 0 || tab === activeTab) ? r.keys : [];
|
|
16214
|
+
return !pinnedRow(r) && (tab === void 0 || tab === activeTab) ? r.keys.filter((k) => controls.scopeOf(k) === "") : [];
|
|
16200
16215
|
}), v = variantsOf(controls.schema, controls.isPinned);
|
|
16201
16216
|
if (v) {
|
|
16202
16217
|
let tab = v.field.tab ?? (v.field.group !== void 0 ? groupTab(v.field.group) : void 0);
|
|
@@ -16205,6 +16220,20 @@ function buildPanel(controls, host, options) {
|
|
|
16205
16220
|
}
|
|
16206
16221
|
return presetsTouch(entries, keys, controls.locked);
|
|
16207
16222
|
}
|
|
16223
|
+
function stripScope() {
|
|
16224
|
+
let named = Object.keys(controls.scopes), panelWide = named.length === 0 || Object.keys(controls.presets).length > 1;
|
|
16225
|
+
if (page)
|
|
16226
|
+
return panelWide ? "" : null;
|
|
16227
|
+
if (activeTab === void 0)
|
|
16228
|
+
return panelWide ? "" : named[0] ?? null;
|
|
16229
|
+
if (named.includes(activeTab))
|
|
16230
|
+
return activeTab;
|
|
16231
|
+
return named.find((id) => rows.some((r) => r.group === id && tabOf(r) === activeTab)) ?? (panelWide ? "" : null);
|
|
16232
|
+
}
|
|
16233
|
+
let stripVariants = () => {
|
|
16234
|
+
let scope = stripScope();
|
|
16235
|
+
return (scope ? controls.scopes[scope]?.variants : void 0) ?? controls.variants;
|
|
16236
|
+
};
|
|
16208
16237
|
function syncTabs() {
|
|
16209
16238
|
let list = tabList(), has = (id) => id !== void 0 && list.some((t) => t.id === id);
|
|
16210
16239
|
if (!has(activeTab))
|
|
@@ -16218,7 +16247,7 @@ function buildPanel(controls, host, options) {
|
|
|
16218
16247
|
writeSession(controls.id, { tab: id });
|
|
16219
16248
|
tabStrip.select(id, animate);
|
|
16220
16249
|
let focused = doc.activeElement;
|
|
16221
|
-
if (applyVisibility(animate ? "tab" : "instant"), focused && root.contains(focused) && focused.closest("[hidden],[data-leaving]"))
|
|
16250
|
+
if (presetStrip.update(), applyVisibility(animate ? "tab" : "instant"), focused && root.contains(focused) && focused.closest("[hidden],[data-leaving]"))
|
|
16222
16251
|
tabStrip.focus({ preventScroll: !0, focusVisible: !1 });
|
|
16223
16252
|
options.onTab?.(id), wakeGpu();
|
|
16224
16253
|
}
|
|
@@ -16431,7 +16460,7 @@ function buildPanel(controls, host, options) {
|
|
|
16431
16460
|
}, text = el("textarea");
|
|
16432
16461
|
text.hidden = !0, text.name = "variant-json", text.spellcheck = !1, text.setAttribute("aria-label", "Import or copy text");
|
|
16433
16462
|
let apply = button("Apply import", () => {
|
|
16434
|
-
|
|
16463
|
+
stripVariants().import(text.value), say("Presets imported."), reveal(text, !1), reveal(apply, !1), positioned?.constrain();
|
|
16435
16464
|
});
|
|
16436
16465
|
apply.className = "knobs-apply-btn", apply.setAttribute("aria-label", "Import JSON"), apply.hidden = !0;
|
|
16437
16466
|
let copy = async (value, inPlace = !0) => {
|
|
@@ -16456,7 +16485,7 @@ function buildPanel(controls, host, options) {
|
|
|
16456
16485
|
}
|
|
16457
16486
|
if (showVariants && showFooter) {
|
|
16458
16487
|
let transfer = el("div");
|
|
16459
|
-
transfer.className = "knobs-transfer", transfer.append(button("Export presets", () => void copy(
|
|
16488
|
+
transfer.className = "knobs-transfer", transfer.append(button("Export presets", () => void copy(stripVariants().export(), !1)), button("Import presets", () => {
|
|
16460
16489
|
reveal(text, !0), reveal(apply, !0), text.value = "", positioned?.constrain(), text.focus();
|
|
16461
16490
|
})), popover.append(transfer);
|
|
16462
16491
|
}
|
|
@@ -16524,6 +16553,7 @@ function buildPanel(controls, host, options) {
|
|
|
16524
16553
|
}
|
|
16525
16554
|
let shownBase = "", baseSignature = () => JSON.stringify([
|
|
16526
16555
|
controls.activePreset(),
|
|
16556
|
+
Object.keys(controls.scopes).map((s) => controls.activePreset(s)),
|
|
16527
16557
|
Object.keys(controls.groupPresets).map((g) => controls.activeGroupPreset(g))
|
|
16528
16558
|
]);
|
|
16529
16559
|
function update() {
|
|
@@ -17145,7 +17175,7 @@ function buildPanel(controls, host, options) {
|
|
|
17145
17175
|
row: rows[rowAt(active)]?.display,
|
|
17146
17176
|
editor: t.level === "editor" ? partName(t.el, active) : void 0
|
|
17147
17177
|
});
|
|
17148
|
-
}, crumbs = options.crumbs
|
|
17178
|
+
}, crumbs = options.crumbs !== !0 || header.hidden || !titleEl.isConnected ? void 0 : createCrumbs({
|
|
17149
17179
|
header,
|
|
17150
17180
|
title: titleEl,
|
|
17151
17181
|
reduced: () => reducedMotion(),
|
|
@@ -17163,7 +17193,21 @@ function buildPanel(controls, host, options) {
|
|
|
17163
17193
|
return;
|
|
17164
17194
|
let t = lensTarget(to);
|
|
17165
17195
|
jumpedTo = t && t !== "out" ? t.el : null, to.focus(quiet), queueScope();
|
|
17166
|
-
}
|
|
17196
|
+
};
|
|
17197
|
+
root.addEventListener("mousedown", (event) => {
|
|
17198
|
+
if (event.button !== 0 || event.shiftKey || event.metaKey || event.ctrlKey)
|
|
17199
|
+
return;
|
|
17200
|
+
let target = event.target instanceof Element ? event.target : null, r = rows[rowAt(target)];
|
|
17201
|
+
if (!target || !r || r.leaving || r.row.closest("[hidden]"))
|
|
17202
|
+
return;
|
|
17203
|
+
let own = target.closest("button,input,select,textarea,a[href],[tabindex],[contenteditable]");
|
|
17204
|
+
if (own && r.row.contains(own))
|
|
17205
|
+
return;
|
|
17206
|
+
event.preventDefault(), r.input.focus({ preventScroll: !0, focusVisible: !1 });
|
|
17207
|
+
let t = lensTarget(doc.activeElement);
|
|
17208
|
+
jumpedTo = t && t !== "out" ? t.el : null, queueScope();
|
|
17209
|
+
}, !0);
|
|
17210
|
+
let titleDown;
|
|
17167
17211
|
titleEl.addEventListener("pointerdown", (event) => {
|
|
17168
17212
|
titleDown = [event.clientX, event.clientY];
|
|
17169
17213
|
}), titleEl.addEventListener("click", (event) => {
|
|
@@ -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)))
|
|
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
|
|
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
|
|
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
|
|
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);
|