@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/dist/panel.js
CHANGED
|
@@ -3099,7 +3099,7 @@ ${GRAD_PROPS}
|
|
|
3099
3099
|
|
|
3100
3100
|
/* The chip: a round cog button, or a capsule with a label beside the cog. */
|
|
3101
3101
|
.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;
|
|
3102
|
-
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)))
|
|
3102
|
+
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};}
|
|
3103
3103
|
.knobs-chip[data-label]{width:auto;padding:0 10px 0 7px}
|
|
3104
3104
|
.knobs-chip .knobs-icon{display:block;flex:none;width:15px;height:15px}
|
|
3105
3105
|
.knobs-chip:hover{color:var(--_k-fg);--k-chip-edge:var(--_k-border-strong)}
|
|
@@ -3126,9 +3126,10 @@ ${GRAD_PROPS}
|
|
|
3126
3126
|
/* Panel material: --k-glass (0 opaque … 1 clearest) thins the surface over a blurred, saturated
|
|
3127
3127
|
backdrop, toned toward the theme (--k-glass-tone dims it under dark panels) so text keeps its
|
|
3128
3128
|
contrast. Its edge is a hairline (--k-edge, --k-rim-width wide) and a hairline of light (--k-rim)
|
|
3129
|
-
catches its top.
|
|
3129
|
+
catches its top. The light rides on the edge (a shadow raised by its width, so only its top
|
|
3130
|
+
shows), not inside the card's 1px border, where it read as a second edge. */
|
|
3130
3131
|
.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);
|
|
3131
|
-
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
|
|
3132
|
+
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;
|
|
3132
3133
|
/* Armed (focus inside: keys come here), the edge takes the accent and a faint glow lifts it. */
|
|
3133
3134
|
--ka-edge:color-mix(in srgb,var(--_k-accent) calc(var(--k-armed) * var(--k-armed-edge) * 100%),var(--_k-border-subtle));
|
|
3134
3135
|
--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);
|
|
@@ -3141,7 +3142,7 @@ ${GRAD_PROPS}
|
|
|
3141
3142
|
.knobs-panel[data-morphing]{background:transparent;border-color:transparent;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none;animation:none}
|
|
3142
3143
|
.knobs-morph{position:fixed;left:0;top:0;z-index:2147480001;pointer-events:none;box-sizing:border-box;--p:0;--spec:0;
|
|
3143
3144
|
background:color-mix(in srgb,var(--_k-bg) calc(100% - var(--_k-glass) * 20% * var(--p)),transparent);border:1px solid transparent;
|
|
3144
|
-
box-shadow:0
|
|
3145
|
+
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));
|
|
3145
3146
|
--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)));
|
|
3146
3147
|
-webkit-backdrop-filter:var(--k-backdrop);backdrop-filter:var(--k-backdrop)}
|
|
3147
3148
|
.knobs-morph::before,.knobs-morph::after{content:"";position:absolute;inset:-1px;border-radius:inherit;pointer-events:none}
|
|
@@ -3149,7 +3150,7 @@ ${GRAD_PROPS}
|
|
|
3149
3150
|
.knobs-morph::after{box-shadow:var(--_k-shadow-overlay);opacity:var(--p)}
|
|
3150
3151
|
/* Meanwhile the chip is just its label, fading on the shape. */
|
|
3151
3152
|
.knobs-chip.knobs-chip[data-morph]{visibility:visible;pointer-events:auto;transform:none;transition:none;background:transparent;border-color:transparent;box-shadow:none}
|
|
3152
|
-
.knobs-panel.knobs-inline{position:relative;z-index:auto;left:auto!important;top:auto!important;max-width:100%;max-height:none;box-shadow:0
|
|
3153
|
+
.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;
|
|
3153
3154
|
/* --k-chrome 0: an embedded panel sheds its card (background, border, rim) for the host's column,
|
|
3154
3155
|
border width too, so its rows line up with the column's edges. */
|
|
3155
3156
|
border-width:calc(var(--k-chrome) * 1px);
|
|
@@ -6955,8 +6956,8 @@ var init_swipe = __esm(() => {
|
|
|
6955
6956
|
});
|
|
6956
6957
|
|
|
6957
6958
|
// src/panel/presets.ts
|
|
6958
|
-
function activeEntryId(controls) {
|
|
6959
|
-
const ref = controls.activePreset();
|
|
6959
|
+
function activeEntryId(controls, scope = "") {
|
|
6960
|
+
const ref = controls.activePreset(scope);
|
|
6960
6961
|
return ref ? `${ref.kind === "preset" ? "p" : "v"}:${ref.id}` : null;
|
|
6961
6962
|
}
|
|
6962
6963
|
function resolveActivePresetEntry(entries, currentValues, lastAppliedId, baseId = null) {
|
|
@@ -7009,7 +7010,7 @@ function nextPresetIndex(count, activeIndex, direction) {
|
|
|
7009
7010
|
return direction > 0 ? 0 : count - 1;
|
|
7010
7011
|
return ((activeIndex + direction) % count + count) % count;
|
|
7011
7012
|
}
|
|
7012
|
-
function createPresetStrip(doc, controls, showVariants, run, onAfterChange, presetLabel = (id) => controls.presetLabel(id), relevant) {
|
|
7013
|
+
function createPresetStrip(doc, controls, showVariants, run, onAfterChange, presetLabel = (id, scope) => controls.presetLabel(id, scope), relevant, scopeOf = () => "") {
|
|
7013
7014
|
const wake = () => onAfterChange?.();
|
|
7014
7015
|
const el = (tag, cls, text) => {
|
|
7015
7016
|
const node = doc.createElement(tag);
|
|
@@ -7057,34 +7058,41 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
|
|
|
7057
7058
|
let slideUntil = 0;
|
|
7058
7059
|
let display = "auto";
|
|
7059
7060
|
let chipMin = 0;
|
|
7061
|
+
let scope = null;
|
|
7062
|
+
const at = () => scope ?? "";
|
|
7063
|
+
const source = () => scope === null ? undefined : scope === "" ? { presets: controls.presets, variants: controls.variants } : controls.scopes[scope];
|
|
7060
7064
|
const unpinned = (values) => {
|
|
7061
7065
|
let out;
|
|
7062
7066
|
for (const k of Object.keys(values))
|
|
7063
|
-
if (controls.
|
|
7067
|
+
if (controls.scopeOf(k) !== at() && !same2(values[k], controls.locked[k])) {
|
|
7064
7068
|
out ??= { ...values };
|
|
7065
7069
|
out[k] = controls.locked[k];
|
|
7066
7070
|
}
|
|
7067
7071
|
return out ?? values;
|
|
7068
7072
|
};
|
|
7069
7073
|
function buildEntries() {
|
|
7070
|
-
const
|
|
7074
|
+
const from = source();
|
|
7075
|
+
if (!from)
|
|
7076
|
+
return [];
|
|
7077
|
+
const where = at();
|
|
7078
|
+
const presetEntries = Object.entries(from.presets).map(([id, presetValues]) => ({
|
|
7071
7079
|
id: `p:${id}`,
|
|
7072
7080
|
kind: "preset",
|
|
7073
7081
|
rawId: id,
|
|
7074
|
-
label: presetLabel(id),
|
|
7082
|
+
label: presetLabel(id, where),
|
|
7075
7083
|
values: presetValues,
|
|
7076
|
-
apply: (coalesce) => controls.preset(id, { coalesce })
|
|
7084
|
+
apply: (coalesce) => controls.preset(id, { coalesce, scope: where })
|
|
7077
7085
|
}));
|
|
7078
7086
|
if (!showVariants)
|
|
7079
7087
|
return presetEntries;
|
|
7080
|
-
const variants =
|
|
7088
|
+
const variants = from.variants.list();
|
|
7081
7089
|
const variantEntries = variants.map((v) => ({
|
|
7082
7090
|
id: `v:${v.id}`,
|
|
7083
7091
|
kind: "variant",
|
|
7084
7092
|
rawId: v.id,
|
|
7085
7093
|
label: v.name ?? v.id.slice(0, 8),
|
|
7086
7094
|
values: unpinned(v.values),
|
|
7087
|
-
apply: (coalesce) =>
|
|
7095
|
+
apply: (coalesce) => from.variants.load(v.id, { coalesce })
|
|
7088
7096
|
}));
|
|
7089
7097
|
return [...presetEntries, ...variantEntries];
|
|
7090
7098
|
}
|
|
@@ -7135,8 +7143,8 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
|
|
|
7135
7143
|
if (first)
|
|
7136
7144
|
doc.defaultView?.requestAnimationFrame(() => pill.style.transition = "");
|
|
7137
7145
|
}
|
|
7138
|
-
const resolve = () => resolveActivePresetEntry(entries, unpinned(controls.get()), lastAppliedId, activeEntryId(controls));
|
|
7139
|
-
const wanted = (list) => (list.length > 1 || showVariants) && (relevant?.(list) ?? true);
|
|
7146
|
+
const resolve = () => resolveActivePresetEntry(entries, unpinned(controls.get()), lastAppliedId, activeEntryId(controls, at()));
|
|
7147
|
+
const wanted = (list) => scope !== null && (list.length > 1 || showVariants) && (relevant?.(list) ?? true);
|
|
7140
7148
|
function syncShown() {
|
|
7141
7149
|
const show = wanted(entries);
|
|
7142
7150
|
if (root.hidden === show && root.dataset.presence === undefined)
|
|
@@ -7196,15 +7204,19 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
|
|
|
7196
7204
|
}
|
|
7197
7205
|
function update() {
|
|
7198
7206
|
run(() => {
|
|
7199
|
-
const
|
|
7200
|
-
|
|
7207
|
+
const was = scope;
|
|
7208
|
+
scope = scopeOf();
|
|
7209
|
+
if (scope !== was)
|
|
7210
|
+
lastAppliedId = null;
|
|
7211
|
+
const variantsRef = showVariants ? source()?.variants.list() ?? null : null;
|
|
7212
|
+
if (signature !== "" && scope === was && variantsRef === lastVariantsRef) {
|
|
7201
7213
|
syncShown();
|
|
7202
7214
|
syncSelection(false);
|
|
7203
7215
|
return;
|
|
7204
7216
|
}
|
|
7205
7217
|
lastVariantsRef = variantsRef;
|
|
7206
7218
|
const nextEntries = buildEntries();
|
|
7207
|
-
const nextSig = JSON.stringify(nextEntries.map((e) => [e.id, e.label]));
|
|
7219
|
+
const nextSig = JSON.stringify([scope, nextEntries.map((e) => [e.id, e.label])]);
|
|
7208
7220
|
entries = nextEntries;
|
|
7209
7221
|
syncShown();
|
|
7210
7222
|
if (nextSig !== signature) {
|
|
@@ -7269,12 +7281,15 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
|
|
|
7269
7281
|
}
|
|
7270
7282
|
function save() {
|
|
7271
7283
|
finishName(true);
|
|
7272
|
-
const
|
|
7284
|
+
const variants = source()?.variants;
|
|
7285
|
+
if (!variants)
|
|
7286
|
+
return "";
|
|
7287
|
+
const used = new Set(variants.list().map((v) => v.name));
|
|
7273
7288
|
let n = 1;
|
|
7274
7289
|
while (used.has(`Variant ${n}`))
|
|
7275
7290
|
n++;
|
|
7276
|
-
const variant =
|
|
7277
|
-
|
|
7291
|
+
const variant = variants.save(`Variant ${n}`);
|
|
7292
|
+
variants.load(variant.id);
|
|
7278
7293
|
lastAppliedId = `v:${variant.id}`;
|
|
7279
7294
|
update();
|
|
7280
7295
|
startName(entries.findIndex((e) => e.id === `v:${variant.id}`), true);
|
|
@@ -7349,7 +7364,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
|
|
|
7349
7364
|
focusQuietly();
|
|
7350
7365
|
let message;
|
|
7351
7366
|
if (keep && entry?.kind === "variant" && name && name !== was) {
|
|
7352
|
-
|
|
7367
|
+
source()?.variants.rename(entry.rawId, name);
|
|
7353
7368
|
update();
|
|
7354
7369
|
message = `Named “${name}”.`;
|
|
7355
7370
|
}
|
|
@@ -7370,7 +7385,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
|
|
|
7370
7385
|
const entry = entries[activeState.activeIndex];
|
|
7371
7386
|
if (!entry || entry.kind !== "variant")
|
|
7372
7387
|
return;
|
|
7373
|
-
|
|
7388
|
+
source()?.variants.remove(entry.rawId);
|
|
7374
7389
|
lastAppliedId = null;
|
|
7375
7390
|
update();
|
|
7376
7391
|
onAfterChange?.();
|
|
@@ -9459,7 +9474,8 @@ fn hash21(q: vec2f) -> f32 {
|
|
|
9459
9474
|
|
|
9460
9475
|
// kind 0: Panel card surface + elevation shadow + border + specular rim. With a hairline width
|
|
9461
9476
|
// (extra.z, whole device px) the edge is that hairline just outside the card, like CSS's
|
|
9462
|
-
// 0 0 0 w spread shadow, and the rim
|
|
9477
|
+
// 0 0 0 w spread shadow, and the rim lights its top, like CSS's 0 -w 0 (one edge, not two);
|
|
9478
|
+
// otherwise a 1px border and a 1px rim inside it.
|
|
9463
9479
|
fn shadePanel(p: Prim, local: vec2f, size: vec2f) -> vec4f {
|
|
9464
9480
|
let r = p.look.x;
|
|
9465
9481
|
let dist = roundedBox(local, size, r);
|
|
@@ -9468,6 +9484,8 @@ fn shadePanel(p: Prim, local: vec2f, size: vec2f) -> vec4f {
|
|
|
9468
9484
|
var out = vec4f(0.0);
|
|
9469
9485
|
if (hair > 0.0 && dist > -1.0) {
|
|
9470
9486
|
out = premul(p.color2) * clamp(covSd(dist - hair) - bodyCov, 0.0, 1.0);
|
|
9487
|
+
let raised = covSd(roundedBox(local + vec2f(0.0, hair), size, r));
|
|
9488
|
+
out = over(out, premul(p.aux2) * clamp(raised - bodyCov, 0.0, 1.0));
|
|
9471
9489
|
}
|
|
9472
9490
|
|
|
9473
9491
|
// Floating drop shadow when glowOrBlur (look.z) > 0; like box-shadow, never under the card.
|
|
@@ -9481,13 +9499,16 @@ fn shadePanel(p: Prim, local: vec2f, size: vec2f) -> vec4f {
|
|
|
9481
9499
|
|
|
9482
9500
|
var card = premul(p.color) * bodyCov;
|
|
9483
9501
|
|
|
9484
|
-
//
|
|
9502
|
+
// Without a hairline, the rim is CSS's inset 0 1px 0: inside the border, minus that shape
|
|
9503
|
+
// moved down.
|
|
9485
9504
|
let inner = local - vec2f(1.0);
|
|
9486
9505
|
let innerSize = size - vec2f(2.0);
|
|
9487
9506
|
let w = select(1.0, hair, hair > 0.0);
|
|
9488
9507
|
let innerCov = covSd(roundedBox(inner, innerSize, r - 1.0));
|
|
9489
|
-
|
|
9490
|
-
|
|
9508
|
+
if (hair <= 0.0) {
|
|
9509
|
+
let rimCov = innerCov * (1.0 - covSd(roundedBox(inner - vec2f(0.0, w), innerSize, r - 1.0)));
|
|
9510
|
+
card = over(card, premul(p.aux2) * rimCov);
|
|
9511
|
+
}
|
|
9491
9512
|
|
|
9492
9513
|
// A morphing card's specular ring (extra.y), w wide inside the border like CSS's inset 0 0 0 w.
|
|
9493
9514
|
if (p.extra.y > 0.001) {
|
|
@@ -26046,8 +26067,9 @@ function buildPanel(controls, host, options) {
|
|
|
26046
26067
|
const group = scope === "row" ? rows[rowAt(from)]?.group : scope === "group" ? cardOf(from)?.dataset.group ?? undefined : undefined;
|
|
26047
26068
|
const keys = scope === "row" ? rows[rowAt(from)]?.keys ?? [] : scope === "group" ? cardKeys(cardOf(from)) : scope === "selection" ? selectionKeys() ?? [] : undefined;
|
|
26048
26069
|
const index = group === undefined ? -1 : controls.activeGroupPreset(group);
|
|
26049
|
-
const
|
|
26050
|
-
const
|
|
26070
|
+
const where = (keys?.length ? controls.scopeOf(keys[0]) : stripScope()) ?? null;
|
|
26071
|
+
const ref = where === null ? null : controls.activePreset(where);
|
|
26072
|
+
const 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 : undefined;
|
|
26051
26073
|
if (!name)
|
|
26052
26074
|
return null;
|
|
26053
26075
|
const off = keys ? keys.some((k) => controls.isModified(k)) : controls.isModified();
|
|
@@ -26200,8 +26222,8 @@ function buildPanel(controls, host, options) {
|
|
|
26200
26222
|
if (helpButton?.hidden)
|
|
26201
26223
|
titleEl.title = `${titleEl.textContent ? `${titleEl.textContent}
|
|
26202
26224
|
` : ""}Press ${show("help")} for keyboard shortcuts`;
|
|
26203
|
-
const presetLabel = (id) => options.presetLabels?.[id] ?? (id === "default" ? options.defaultLabel : undefined) ?? controls.presetLabel(id);
|
|
26204
|
-
const presetStrip = createPresetStrip(doc, controls, showVariants, run, () => wakeGpu(), presetLabel, (entries) => presetsTouchTab(entries));
|
|
26225
|
+
const presetLabel = (id, scope = "") => options.presetLabels?.[id] ?? (id === "default" ? options.defaultLabel : undefined) ?? controls.presetLabel(id, scope);
|
|
26226
|
+
const presetStrip = createPresetStrip(doc, controls, showVariants, run, () => wakeGpu(), presetLabel, (entries) => presetsTouchTab(entries), () => stripScope());
|
|
26205
26227
|
const tabStrip = createTabStrip(doc, (id, animate) => selectTab(id, animate));
|
|
26206
26228
|
let activeTab;
|
|
26207
26229
|
const searchOptions = typeof options.search === "object" ? options.search : {};
|
|
@@ -27606,11 +27628,11 @@ function buildPanel(controls, host, options) {
|
|
|
27606
27628
|
return list;
|
|
27607
27629
|
};
|
|
27608
27630
|
function presetsTouchTab(entries) {
|
|
27609
|
-
if (page || activeTab === undefined)
|
|
27631
|
+
if (page || activeTab === undefined || stripScope() !== "")
|
|
27610
27632
|
return true;
|
|
27611
27633
|
const keys = rows.flatMap((r) => {
|
|
27612
27634
|
const tab = tabOf(r);
|
|
27613
|
-
return !pinnedRow(r) && (tab === undefined || tab === activeTab) ? r.keys : [];
|
|
27635
|
+
return !pinnedRow(r) && (tab === undefined || tab === activeTab) ? r.keys.filter((k) => controls.scopeOf(k) === "") : [];
|
|
27614
27636
|
});
|
|
27615
27637
|
const v = variantsOf(controls.schema, controls.isPinned);
|
|
27616
27638
|
if (v) {
|
|
@@ -27620,6 +27642,22 @@ function buildPanel(controls, host, options) {
|
|
|
27620
27642
|
}
|
|
27621
27643
|
return presetsTouch(entries, keys, controls.locked);
|
|
27622
27644
|
}
|
|
27645
|
+
function stripScope() {
|
|
27646
|
+
const named = Object.keys(controls.scopes);
|
|
27647
|
+
const panelWide = named.length === 0 || Object.keys(controls.presets).length > 1;
|
|
27648
|
+
if (page)
|
|
27649
|
+
return panelWide ? "" : null;
|
|
27650
|
+
if (activeTab === undefined)
|
|
27651
|
+
return panelWide ? "" : named[0] ?? null;
|
|
27652
|
+
if (named.includes(activeTab))
|
|
27653
|
+
return activeTab;
|
|
27654
|
+
const group = named.find((id) => rows.some((r) => r.group === id && tabOf(r) === activeTab));
|
|
27655
|
+
return group ?? (panelWide ? "" : null);
|
|
27656
|
+
}
|
|
27657
|
+
const stripVariants = () => {
|
|
27658
|
+
const scope = stripScope();
|
|
27659
|
+
return (scope ? controls.scopes[scope]?.variants : undefined) ?? controls.variants;
|
|
27660
|
+
};
|
|
27623
27661
|
function syncTabs() {
|
|
27624
27662
|
const list = tabList();
|
|
27625
27663
|
const has = (id) => id !== undefined && list.some((t) => t.id === id);
|
|
@@ -27636,6 +27674,7 @@ function buildPanel(controls, host, options) {
|
|
|
27636
27674
|
writeSession(controls.id, { tab: id });
|
|
27637
27675
|
tabStrip.select(id, animate);
|
|
27638
27676
|
const focused = doc.activeElement;
|
|
27677
|
+
presetStrip.update();
|
|
27639
27678
|
applyVisibility(animate ? "tab" : "instant");
|
|
27640
27679
|
if (focused && root.contains(focused) && focused.closest("[hidden],[data-leaving]"))
|
|
27641
27680
|
tabStrip.focus({ preventScroll: true, focusVisible: false });
|
|
@@ -27911,7 +27950,7 @@ function buildPanel(controls, host, options) {
|
|
|
27911
27950
|
text.spellcheck = false;
|
|
27912
27951
|
text.setAttribute("aria-label", "Import or copy text");
|
|
27913
27952
|
const apply = button("Apply import", () => {
|
|
27914
|
-
|
|
27953
|
+
stripVariants().import(text.value);
|
|
27915
27954
|
say("Presets imported.");
|
|
27916
27955
|
reveal(text, false);
|
|
27917
27956
|
reveal(apply, false);
|
|
@@ -27957,7 +27996,7 @@ function buildPanel(controls, host, options) {
|
|
|
27957
27996
|
if (showVariants && showFooter) {
|
|
27958
27997
|
const transfer = el("div");
|
|
27959
27998
|
transfer.className = "knobs-transfer";
|
|
27960
|
-
transfer.append(button("Export presets", () => void copy(
|
|
27999
|
+
transfer.append(button("Export presets", () => void copy(stripVariants().export(), false)), button("Import presets", () => {
|
|
27961
28000
|
reveal(text, true);
|
|
27962
28001
|
reveal(apply, true);
|
|
27963
28002
|
text.value = "";
|
|
@@ -28053,6 +28092,7 @@ function buildPanel(controls, host, options) {
|
|
|
28053
28092
|
let shownBase = "";
|
|
28054
28093
|
const baseSignature = () => JSON.stringify([
|
|
28055
28094
|
controls.activePreset(),
|
|
28095
|
+
Object.keys(controls.scopes).map((s) => controls.activePreset(s)),
|
|
28056
28096
|
Object.keys(controls.groupPresets).map((g) => controls.activeGroupPreset(g))
|
|
28057
28097
|
]);
|
|
28058
28098
|
function update() {
|
|
@@ -28834,7 +28874,7 @@ function buildPanel(controls, host, options) {
|
|
|
28834
28874
|
editor: t.level === "editor" ? partName(t.el, active) : undefined
|
|
28835
28875
|
});
|
|
28836
28876
|
};
|
|
28837
|
-
const crumbs = options.crumbs
|
|
28877
|
+
const crumbs = options.crumbs !== true || header.hidden || !titleEl.isConnected ? undefined : createCrumbs({
|
|
28838
28878
|
header,
|
|
28839
28879
|
title: titleEl,
|
|
28840
28880
|
reduced: () => reducedMotion(),
|
|
@@ -28858,6 +28898,22 @@ function buildPanel(controls, host, options) {
|
|
|
28858
28898
|
to.focus(quiet);
|
|
28859
28899
|
queueScope();
|
|
28860
28900
|
};
|
|
28901
|
+
root.addEventListener("mousedown", (event) => {
|
|
28902
|
+
if (event.button !== 0 || event.shiftKey || event.metaKey || event.ctrlKey)
|
|
28903
|
+
return;
|
|
28904
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
28905
|
+
const r = rows[rowAt(target)];
|
|
28906
|
+
if (!target || !r || r.leaving || r.row.closest("[hidden]"))
|
|
28907
|
+
return;
|
|
28908
|
+
const own = target.closest("button,input,select,textarea,a[href],[tabindex],[contenteditable]");
|
|
28909
|
+
if (own && r.row.contains(own))
|
|
28910
|
+
return;
|
|
28911
|
+
event.preventDefault();
|
|
28912
|
+
r.input.focus({ preventScroll: true, focusVisible: false });
|
|
28913
|
+
const t = lensTarget(doc.activeElement);
|
|
28914
|
+
jumpedTo = t && t !== "out" ? t.el : null;
|
|
28915
|
+
queueScope();
|
|
28916
|
+
}, true);
|
|
28861
28917
|
let titleDown;
|
|
28862
28918
|
titleEl.addEventListener("pointerdown", (event) => {
|
|
28863
28919
|
titleDown = [event.clientX, event.clientY];
|
|
@@ -136,6 +136,8 @@ export interface RemoteControlsOptions {
|
|
|
136
136
|
readonly nullDefaults?: boolean;
|
|
137
137
|
readonly values?: State;
|
|
138
138
|
readonly presets?: Readonly<Record<string, State>>;
|
|
139
|
+
/** Presets that belong to one tab or group (see `ControlsOptions.scopedPresets`). */
|
|
140
|
+
readonly scopedPresets?: Readonly<Record<string, Readonly<Record<string, State>>>>;
|
|
139
141
|
/** Groups pinned above the tab strip, outside the presets (see `ControlsOptions.pinned`). */
|
|
140
142
|
readonly pinned?: readonly string[];
|
|
141
143
|
/** Numbered presets per group, e.g. the phone's preset sets (see `ControlsOptions.groupPresets`). */
|
|
@@ -50,6 +50,19 @@ export interface PresetRef {
|
|
|
50
50
|
readonly kind: "preset" | "variant";
|
|
51
51
|
readonly id: string;
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* A preset strip that belongs to one tab or group (see `ControlsOptions.scopedPresets`): its
|
|
55
|
+
* presets and saved variants move only its own keys, and it keeps its own active preset.
|
|
56
|
+
*/
|
|
57
|
+
export interface PresetScope<V = State> {
|
|
58
|
+
/** The tab or group it's named for. */
|
|
59
|
+
readonly id: string;
|
|
60
|
+
/** The keys it moves: the tab's or group's, pinned ones aside. */
|
|
61
|
+
readonly keys: readonly string[];
|
|
62
|
+
/** By id, `default` first; keys outside the scope sit at their defaults. */
|
|
63
|
+
readonly presets: Readonly<Record<string, V>>;
|
|
64
|
+
readonly variants: Variants<V>;
|
|
65
|
+
}
|
|
53
66
|
export interface ControlAction<V = State> {
|
|
54
67
|
readonly id?: string;
|
|
55
68
|
readonly label: string;
|
|
@@ -169,8 +182,9 @@ export interface MountPanelOptions extends LabelOptions {
|
|
|
169
182
|
*/
|
|
170
183
|
readonly header?: boolean | HeaderOptions;
|
|
171
184
|
/**
|
|
172
|
-
* `
|
|
173
|
-
* `esc ↑ Shape` for where Esc goes).
|
|
185
|
+
* `true` shows a scope line beside the title (`Shape › Cohesion`, where keys go, and a faint
|
|
186
|
+
* `esc ↑ Shape` for where Esc goes). Off by default; the focus lens and the armed frame show
|
|
187
|
+
* either way.
|
|
174
188
|
*/
|
|
175
189
|
readonly crumbs?: boolean;
|
|
176
190
|
/**
|
|
@@ -355,6 +369,14 @@ export interface ControlsOptions<S extends Schema> extends LabelOptions {
|
|
|
355
369
|
/** An undo stack the host owns; undo and redo call it (see `HostHistory`). */
|
|
356
370
|
history?: HostHistory;
|
|
357
371
|
presets?: Readonly<Record<string, Partial<Values<S>>>>;
|
|
372
|
+
/**
|
|
373
|
+
* Presets that belong to one tab or group, keyed by its id: `{ Sidebars: { snappy: {…} } }`.
|
|
374
|
+
* Each shows its own strip on that tab (a tab without any shows none), moves only that tab's
|
|
375
|
+
* or group's keys, and has its own active preset, modified mark and saved variants. A variants
|
|
376
|
+
* choice on a tab adds its options to that tab's. `presets` then covers only the keys no scope
|
|
377
|
+
* holds; leave it out and only the scoped strips show.
|
|
378
|
+
*/
|
|
379
|
+
scopedPresets?: Readonly<Record<string, Readonly<Record<string, Partial<Values<S>>>>>>;
|
|
358
380
|
/**
|
|
359
381
|
* Groups pinned to the top of a panel, under the header and above the tab strip, on every tab
|
|
360
382
|
* (as fields' `pinned: true` pins theirs). Presets and saved variants leave them alone: applying
|
|
@@ -408,6 +430,10 @@ export interface Controls<S extends Schema = Schema> {
|
|
|
408
430
|
readonly persist: PersistMode;
|
|
409
431
|
readonly presets: Readonly<Record<string, Values<S>>>;
|
|
410
432
|
readonly variants: Variants<Values<S>>;
|
|
433
|
+
/** The tabs and groups with presets of their own, by id (see `ControlsOptions.scopedPresets`). */
|
|
434
|
+
readonly scopes: Readonly<Record<string, PresetScope<Values<S>>>>;
|
|
435
|
+
/** The scope `key`'s presets come from: a tab or group's id, `""` for `presets`, or undefined when pinned. */
|
|
436
|
+
scopeOf(key: string): string | undefined;
|
|
411
437
|
readonly actions: readonly ControlAction<Values<S>>[];
|
|
412
438
|
/** Its panels' theme, as given (see `ControlsOptions.theme`). */
|
|
413
439
|
readonly theme: Theme | ThemeMode | undefined;
|
|
@@ -437,8 +463,8 @@ export interface Controls<S extends Schema = Schema> {
|
|
|
437
463
|
readonly groupFolded: Readonly<Record<string, boolean>> | undefined;
|
|
438
464
|
/** How a page's sections start (`sectionFolded`), as given. */
|
|
439
465
|
readonly sectionFolded: SectionFolded | undefined;
|
|
440
|
-
/** A preset's display label (`presetLabels`, `defaultLabel`), else its id
|
|
441
|
-
presetLabel(id: string): string;
|
|
466
|
+
/** A preset's display label (`presetLabels`, `defaultLabel`), else its id; `scope` as in `preset`. */
|
|
467
|
+
presetLabel(id: string, scope?: string): string;
|
|
442
468
|
/** Whether `key` is pinned (its group is, see `ControlsOptions.pinned`), outside the presets. */
|
|
443
469
|
isPinned(key: string): boolean;
|
|
444
470
|
readonly error: string | null;
|
|
@@ -555,17 +581,21 @@ export interface Controls<S extends Schema = Schema> {
|
|
|
555
581
|
readonly comparing: boolean;
|
|
556
582
|
/**
|
|
557
583
|
* Applies a preset and makes it the active one (`"default"` clears it); one undo step, or
|
|
558
|
-
* folded into the last with `coalesce` (a pan across the strip).
|
|
584
|
+
* folded into the last with `coalesce` (a pan across the strip). With `scope`, one of that tab
|
|
585
|
+
* or group's presets, which moves only its keys.
|
|
559
586
|
*/
|
|
560
587
|
preset(id: string, options?: {
|
|
561
588
|
coalesce?: boolean;
|
|
589
|
+
scope?: string;
|
|
562
590
|
}): void;
|
|
563
|
-
|
|
591
|
+
/** The preset the current values match (in `scope`, over its keys), else `"custom"`. */
|
|
592
|
+
matchingPreset(scope?: string): string;
|
|
564
593
|
/**
|
|
565
594
|
* The preset (or saved variant) last applied, which Reset returns to and Random explores
|
|
566
|
-
* around, or null. Kept with the values; forgotten by a reset to the defaults.
|
|
595
|
+
* around, or null. Kept with the values; forgotten by a reset to the defaults. With `scope`,
|
|
596
|
+
* that tab or group's.
|
|
567
597
|
*/
|
|
568
|
-
activePreset(): PresetRef | null;
|
|
598
|
+
activePreset(scope?: string): PresetRef | null;
|
|
569
599
|
/** The group preset last applied to `group` (0-based), or -1. */
|
|
570
600
|
activeGroupPreset(group: string): number;
|
|
571
601
|
/** The values Reset returns to first: the active presets over the defaults. */
|
|
@@ -22,4 +22,6 @@ export interface Variants<V> {
|
|
|
22
22
|
export(): string;
|
|
23
23
|
import(json: string): void;
|
|
24
24
|
}
|
|
25
|
-
export declare function createVariants<S extends Schema>(schema: () => S, id: string, key: string, storage: () => StoragePort | null, get: () => Values<S>, set: (v: Values<S>, id: string, coalesce: boolean) => void, notify: () => void, enabled: () => boolean
|
|
25
|
+
export declare function createVariants<S extends Schema>(schema: () => S, id: string, key: string, storage: () => StoragePort | null, get: () => Values<S>, set: (v: Values<S>, id: string, coalesce: boolean) => void, notify: () => void, enabled: () => boolean,
|
|
26
|
+
/** What the list starts as while storage holds none yet (it's kept once something's saved). */
|
|
27
|
+
seed?: () => readonly Variant<Values<S>>[]): Variants<Values<S>>;
|
|
@@ -19,8 +19,11 @@ export interface ActivePresetState {
|
|
|
19
19
|
}
|
|
20
20
|
/** How strong the selection pill stays on the active preset once the values drift off it. */
|
|
21
21
|
export declare const DRIFTED_PILL = 0.5;
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The strip's id for the store's active preset (`p:` a preset, `v:` a saved variant), if any;
|
|
24
|
+
* `scope` as in `Controls.preset`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function activeEntryId(controls: Controls<any>, scope?: string): string | null;
|
|
24
27
|
/**
|
|
25
28
|
* Resolves which preset or variant is currently active.
|
|
26
29
|
* The store's active preset (`baseId`) holds the selection however far the values drift from it.
|
|
@@ -75,6 +78,11 @@ export interface PresetStrip {
|
|
|
75
78
|
}
|
|
76
79
|
export declare function createPresetStrip<S extends Schema>(doc: Document, controls: Controls<S>, showVariants: boolean, run: (action: () => unknown) => void, onAfterChange?: () => void,
|
|
77
80
|
/** What a preset's chip says (its id stays its identity); `controls.presetLabel` by default. */
|
|
78
|
-
presetLabel?: (id: string) => string,
|
|
81
|
+
presetLabel?: (id: string, scope: string) => string,
|
|
79
82
|
/** Whether these entries matter where the strip is shown (a tab they don't touch hides it). */
|
|
80
|
-
relevant?: (entries: readonly PresetEntry<any>[]) => boolean
|
|
83
|
+
relevant?: (entries: readonly PresetEntry<any>[]) => boolean,
|
|
84
|
+
/**
|
|
85
|
+
* Whose presets it shows: a tab or group's (see `ControlsOptions.scopedPresets`), `""` for the
|
|
86
|
+
* panel's own, or null for none (the strip hides).
|
|
87
|
+
*/
|
|
88
|
+
scopeOf?: () => string | null): PresetStrip;
|