@kitlangton/knobs 0.4.0-next.15 → 0.4.0-next.16
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 +18 -5
- package/dist/dev/{chunk-bejz4qcg.js → chunk-9y93net8.js} +8 -1
- package/dist/dev/{chunk-49hkdggk.js → chunk-e5hbs8fe.js} +193 -43
- package/dist/dev/{chunk-902v1jgd.js → chunk-exaya50m.js} +2 -2
- package/dist/dev/index.js +2 -2
- package/dist/panel.js +258 -39
- package/dist/types/core/schema.d.ts +4 -1
- package/dist/types/core/store.d.ts +29 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/panel/active.d.ts +53 -0
- package/dist/types/panel/gpu/renderer.d.ts +8 -2
- package/dist/types/panel/index.d.ts +1 -0
- package/dist/types/panel/page.d.ts +2 -15
- package/dist/types/panel/summary.d.ts +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -777,7 +777,19 @@ mountPanel(station, column, {
|
|
|
777
777
|
foldable, starting folded. Its header folds and opens it with a click, Enter or Space; it's a ↑↓
|
|
778
778
|
stop of its own, ← folds and → opens. `panel.fold(group, folded?)` does it from code. On a
|
|
779
779
|
[full page](#full-page) the group's tile folds to its header, the cards below follow, and the
|
|
780
|
-
filter opens it while it runs.
|
|
780
|
+
filter opens it while it runs. Folded, the header sums the card up on its right, quietly and
|
|
781
|
+
live: a one-row card's value ("CSS orb"), otherwise its matching group preset (or "Custom"),
|
|
782
|
+
else its first changed value, else its first choice, then "· 2 changed". The title keeps its
|
|
783
|
+
room; the summary truncates first. `groupSummary(group, info)` on the mount replaces it, as on a
|
|
784
|
+
page (`info` is `{ preset, changed, keys }`; `null` shows nothing).
|
|
785
|
+
- **Card descriptions.** A group's `groupDescription` (on any of its fields; the first wins) or the
|
|
786
|
+
store's `groupDescriptions` puts one muted line under its card's title, wrapping under the whole
|
|
787
|
+
header. Search matches it.
|
|
788
|
+
- **What's active.** `onActive: (at) => …` hears which card or row the pointer or keyboard focus is
|
|
789
|
+
on: `{ group }` for a card's header or body, `{ group, key }` for a row, `null` for nothing
|
|
790
|
+
(pinned cards and group preset strips count). Whichever moved last wins; it's called at most once
|
|
791
|
+
a frame, never twice with the same value, and once more with `null` when the panel goes. Use it
|
|
792
|
+
to outline the part of your app a card changes. `mountPage` takes it too.
|
|
781
793
|
- **Group Random by the host.** `groupRandom: (group, mode) => …` runs first for a group's die, `g`
|
|
782
794
|
and `⌥G` (`mode` is `"random"` or `"mutate"`). Return `true` when the host rolled the group (its
|
|
783
795
|
own seeded "Randomize <group>"): the die tumbles and knobs leaves the values alone. Return
|
|
@@ -1099,7 +1111,7 @@ A page with many sections reads better folded. `sectionFolded` starts sections f
|
|
|
1099
1111
|
|
|
1100
1112
|
Folded things say what's in them. A folded group card trades its presets, reset and die for "Compact · 7 changed" (the matching group preset, or "Custom", then its changed rows; each part only when it applies). A folded section's heading reads "2 changed · 3 cards". `groupSummary(group, info)` and `sectionSummary(section, info)` on the mount replace either (`info` is a `FoldSummaryInfo`: `{ preset, changed, keys }`; return `null` for nothing). Group cards fold as in a panel, by a field's or descriptor's `folded`, or `groupFolded` on the mount, `defineControls` or `createRemoteControls` (both take `sectionFolded` too).
|
|
1101
1113
|
|
|
1102
|
-
Descriptions add one muted line under a title: `groupDescriptions` and `sectionDescriptions` by id (on the store, remote options or the mount), or `groupDescription` / `sectionDescription` on any field or descriptor (the first that gives one wins). `page.card({ description })` does the same for a custom card. The filter matches descriptions too.
|
|
1114
|
+
Descriptions add one muted line under a title: `groupDescriptions` and `sectionDescriptions` by id (on the store, remote options or the mount), or `groupDescription` / `sectionDescription` on any field or descriptor (the first that gives one wins). `page.card({ description })` does the same for a custom card. The filter matches descriptions too. Group descriptions show in panels as well, wrapping under the card's header.
|
|
1103
1115
|
|
|
1104
1116
|
What the user folds and opens (sections and group cards) is remembered under `knobs:<id>:folds:v1` (`storageKey(id, "folds")`) as `{ sections: Record<string, boolean>, groups: Record<string, boolean> }`, and wins over `sectionFolded` and `groupFolded`. Only the user's folds and those made with `remember` are written; folds for context (`remember: false`, a highlight's `expand`) and the filter's never are. By default they go to the store's own storage, or nowhere with `persist: "none"`. `foldStorage` on the mount always wins and keeps folds only, never values, locks or variants, so a remote store on `storage: null` (the phone's values always win) can still remember them; `null` keeps none.
|
|
1105
1117
|
|
|
@@ -2039,11 +2051,12 @@ The package has two core exports selected through the standard `development` con
|
|
|
2039
2051
|
| `unlock({ panel, sequence?, query?, corner?, persist? })` / `KONAMI` | Lets a production deploy be unlocked by a typed secret, `?knobs` or three corner taps; returns `{ unlocked, unlock(), lock(), dispose() }`. See [Show, hide and unlock](#show-hide-and-unlock). |
|
|
2040
2052
|
| `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 }`. |
|
|
2041
2053
|
| 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. |
|
|
2042
|
-
| Field meta `groupDescription?: string`, `sectionDescription?: string`, `sectionFolded?: boolean` | On any field or descriptor
|
|
2043
|
-
| `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()`. |
|
|
2054
|
+
| Field meta `groupDescription?: string`, `sectionDescription?: string`, `sectionFolded?: boolean` | On any field or descriptor (the first field of a group or section that gives one wins): a muted line under the card's title (in a panel or a page) or a page section's heading; `sectionFolded` makes a page section foldable, starting folded. |
|
|
2055
|
+
| `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`, `groupSummary`, `onActive`, `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()`. |
|
|
2044
2056
|
| `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. |
|
|
2045
2057
|
| `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. |
|
|
2046
|
-
| `MountPageOptions` folds | `sectionFolded?: SectionFolded` (`boolean \| Record<string, boolean>`), `foldStorage?: StoragePort \| null`, `nav?: boolean`, `groupSummary?(group, info: FoldSummaryInfo): string \| Node \| null
|
|
2058
|
+
| `MountPageOptions` folds | `sectionFolded?: SectionFolded` (`boolean \| Record<string, boolean>`), `foldStorage?: StoragePort \| null`, `nav?: boolean`, `groupSummary?(group, info: FoldSummaryInfo): string \| Node \| null` (a panel's option too), `sectionSummary?(section, info: FoldSummaryInfo): string \| Node \| null`; `FoldSummaryInfo` is `{ preset: string \| null, changed: number, keys: readonly string[] }`. |
|
|
2059
|
+
| `MountPanelOptions.onActive?: (at: PanelActive \| null) => void` | The card (`{ group }`) or row (`{ group, key }`) under the pointer or focus, `null` for nothing; on `mountPanel`, `controls.mount` and `mountPage`. At most once a frame, never the same value twice, and a last `null` when the panel goes. `PanelActive` is `{ group: string, key?: string }`. |
|
|
2047
2060
|
| `page.fold(group: string, folded?: boolean, options?: PageFoldOptions): void` / `page.foldSection(section: string, folded?: boolean, options?: PageFoldOptions): void` / `page.sectionFolded(section: string): boolean` | Fold a group's card or a section or open it (toggles without `folded`), and ask which a section is. `PageFoldOptions` is `{ remember?: boolean, reveal?: boolean }`; `remember` defaults to `true`. |
|
|
2048
2061
|
| `page.reveal(target: { section: string } \| { group: string }, options?: { remember?: boolean }): void` | Open a section (or a group's card and its section) and glide it into view; `remember` defaults to `false`. |
|
|
2049
2062
|
| `page.sections(): readonly PageSectionInfo[]` / `page.onSections(listener: (sections: readonly PageSectionInfo[]) => void): () => void` | `PageSectionInfo` is `{ id, label, description?, folded, changed, groups: PageGroupInfo[] }`; `PageGroupInfo` is `{ id, label, folded, changed, preset: string \| null }`. |
|
|
@@ -2632,6 +2632,13 @@ ${GRAD_PROPS}
|
|
|
2632
2632
|
.knobs-group-head .knobs-group-die{width:20px;padding:0;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--_k-round);color:var(--_k-fg-3)}
|
|
2633
2633
|
.knobs-group-head .knobs-group-die[disabled]{opacity:var(--k-disabled-opacity);cursor:default}
|
|
2634
2634
|
.knobs-panel .knobs-group-head button:where(:not(.knobs-group-reset)){transition:background var(--k-hover-t),color var(--k-hover-t),opacity .2s,transform .12s}
|
|
2635
|
+
/* A panel card's header: its title, a folded card's summary and its buttons on one line, and its
|
|
2636
|
+
description on the next. The title takes its room first; the summary gets what's left. */
|
|
2637
|
+
.knobs-panel:not(.knobs-page) .knobs-group-head{flex-wrap:wrap;row-gap:0}
|
|
2638
|
+
.knobs-panel:not(.knobs-page) .knobs-group-head>.knobs-group-label{flex:1000 1 0;max-width:max-content;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
2639
|
+
.knobs-group-head>.knobs-group-desc{order:1;flex:0 0 100%;padding:0;line-height:16px;white-space:normal;overflow:visible;text-wrap:pretty}
|
|
2640
|
+
.knobs-group-head>.knobs-group-summary{flex:1 1 0;max-width:max-content;margin-left:auto;font-size:var(--_k-text-meta);text-transform:none}
|
|
2641
|
+
.knobs-group-head[data-folded]>.knobs-group-summary:not(:empty){display:block}
|
|
2635
2642
|
|
|
2636
2643
|
/* Grouped cards: rows share one surface, split by hairlines that stay put whatever a row is doing */
|
|
2637
2644
|
.knobs-card{position:relative;margin-bottom:var(--k-section-gap);border-radius:var(--_k-radius);background:var(--_k-fill);overflow:hidden}
|
|
@@ -3338,7 +3345,7 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
|
|
|
3338
3345
|
.knobs-group-summary{display:none;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_k-fg-3);font-size:var(--_k-text-control);font-weight:500;letter-spacing:0;font-variant-numeric:tabular-nums}
|
|
3339
3346
|
.knobs-tile[data-folded]>.knobs-group-head .knobs-group-summary:not(:empty){display:block}
|
|
3340
3347
|
.knobs-tile[data-folded]>.knobs-group-head .knobs-group-ctl{display:none}
|
|
3341
|
-
.knobs-tile[data-fold-motion]>.knobs-group-head :is(.knobs-group-summary,.knobs-group-ctl){animation:knobs-fade .2s var(--k-ease) both}
|
|
3348
|
+
.knobs-tile[data-fold-motion]>.knobs-group-head :is(.knobs-group-summary,.knobs-group-ctl),.knobs-group-head[data-fold-motion]>.knobs-group-summary{animation:knobs-fade .2s var(--k-ease) both}
|
|
3342
3349
|
/* A highlight (page.highlight): the rest dims under a veil of the page's colour (--k-page-dim at
|
|
3343
3350
|
full), and what's highlighted on its own gets a dot in the gutter by its title. The veil and
|
|
3344
3351
|
dot are written each frame on the panel spring (see page.ts). */
|
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
createHintController,
|
|
120
120
|
pageHints,
|
|
121
121
|
toggleLayer
|
|
122
|
-
} from "./chunk-
|
|
122
|
+
} from "./chunk-9y93net8.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 = {
|
|
@@ -4734,6 +4734,33 @@ class GpuOverlayManager {
|
|
|
4734
4734
|
let clipped = width > 0 && el.scrollWidth > el.clientWidth;
|
|
4735
4735
|
return this.clipCache.set(el, { key, text, width, clipped }), clipped;
|
|
4736
4736
|
}
|
|
4737
|
+
lineCache = /* @__PURE__ */ new WeakMap;
|
|
4738
|
+
linesOf(el, width) {
|
|
4739
|
+
let text = el.textContent ?? "", hit = this.lineCache.get(el);
|
|
4740
|
+
if (hit && hit.text === text && hit.width === width)
|
|
4741
|
+
return hit.lines;
|
|
4742
|
+
let lines = [], node = el.firstChild;
|
|
4743
|
+
if (node?.nodeType === Node.TEXT_NODE) {
|
|
4744
|
+
let top = el.getBoundingClientRect().top, range = el.ownerDocument.createRange(), line, flush = () => {
|
|
4745
|
+
if (line)
|
|
4746
|
+
lines.push({
|
|
4747
|
+
text: text.slice(line.from, line.to),
|
|
4748
|
+
y: (line.top + line.bottom) / 2 - top
|
|
4749
|
+
});
|
|
4750
|
+
};
|
|
4751
|
+
for (let word of text.matchAll(/\S+/g)) {
|
|
4752
|
+
let from = word.index, to = from + word[0].length;
|
|
4753
|
+
range.setStart(node, from), range.setEnd(node, to);
|
|
4754
|
+
let r = range.getBoundingClientRect();
|
|
4755
|
+
if (line && Math.abs(r.top - line.top) < 1)
|
|
4756
|
+
line.to = to, line.bottom = Math.max(line.bottom, r.bottom);
|
|
4757
|
+
else
|
|
4758
|
+
flush(), line = { from, to, top: r.top, bottom: r.bottom };
|
|
4759
|
+
}
|
|
4760
|
+
flush();
|
|
4761
|
+
}
|
|
4762
|
+
return this.lineCache.set(el, { text, width, lines }), lines;
|
|
4763
|
+
}
|
|
4737
4764
|
curReduced = !1;
|
|
4738
4765
|
curLocks;
|
|
4739
4766
|
curSelected = 0;
|
|
@@ -6146,12 +6173,10 @@ class GpuOverlayManager {
|
|
|
6146
6173
|
this.drawText(this.fit(g.labelEl.textContent ?? "", lr.width, px, tokens.titleWeight), lr.left, cy, withAlpha(tokens.text, 1 - peek), { px, weight: tokens.titleWeight, track: -0.01 });
|
|
6147
6174
|
if (g.peek && peek > 0.005)
|
|
6148
6175
|
this.drawText(this.fit(g.peek.textContent ?? "", lr.width, px, tokens.titleWeight), lr.left, cy, withAlpha(tokens.text, peek), { px, weight: tokens.titleWeight, track: -0.01 });
|
|
6149
|
-
} else
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
track: tokens.groupTracking
|
|
6154
|
-
});
|
|
6176
|
+
} else {
|
|
6177
|
+
let label = tokens.groupUpper ? (g.labelEl.textContent ?? "").toUpperCase() : g.labelEl.textContent ?? "", px = Math.max(9, tokens.groupSize);
|
|
6178
|
+
this.drawText(this.domClipped(g.labelEl, label, lr.width) ? this.fit(label, lr.width, px, tokens.titleWeight, void 0, tokens.groupTracking, !0) : label, lr.left, lr.top + lr.height * 0.5, tokens.groupColor, { px, weight: tokens.titleWeight, track: tokens.groupTracking });
|
|
6179
|
+
}
|
|
6155
6180
|
if (!g.title && tokens.groupDivider[3] > 0 && g.element.previousElementSibling)
|
|
6156
6181
|
this.pushPrim({
|
|
6157
6182
|
x: gr.left,
|
|
@@ -6162,13 +6187,14 @@ class GpuOverlayManager {
|
|
|
6162
6187
|
kind: 1
|
|
6163
6188
|
});
|
|
6164
6189
|
if (g.desc && !g.desc.hidden) {
|
|
6165
|
-
let r = this.rectOf(g.desc), cs = getComputedStyle(g.desc), left = r.left + (parseFloat(cs.paddingLeft) || 0), w = r.width - (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0);
|
|
6166
|
-
|
|
6190
|
+
let r = this.rectOf(g.desc), cs = getComputedStyle(g.desc), left = r.left + (parseFloat(cs.paddingLeft) || 0), w = r.width - (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0), lines = cs.whiteSpace.includes("nowrap") ? [{ text: g.desc.textContent ?? "", y: r.height * 0.5 }] : this.linesOf(g.desc, r.width);
|
|
6191
|
+
for (let line of lines)
|
|
6192
|
+
this.drawText(this.fit(line.text, w + 0.5, tokens.textMeta, tokens.labelWeight), left, r.top + line.y, tokens.dim, { px: tokens.textMeta, weight: tokens.labelWeight });
|
|
6167
6193
|
}
|
|
6168
6194
|
let fadeIn = (node) => node.parentElement?.closest("[data-fold-motion]") ? Number(getComputedStyle(node).opacity) || 0 : 1;
|
|
6169
6195
|
if (g.summary && g.summary.getClientRects().length) {
|
|
6170
|
-
let r = this.rectOf(g.summary), a = fadeIn(g.summary);
|
|
6171
|
-
this.drawText(this.fit(g.summary.textContent ?? "", r.width + 0.5,
|
|
6196
|
+
let r = this.rectOf(g.summary), a = fadeIn(g.summary), px = g.title ? tokens.textControl : tokens.textMeta;
|
|
6197
|
+
this.drawText(this.fit(g.summary.textContent ?? "", r.width + 0.5, px, 500), r.left, r.top + r.height * 0.5, withAlpha(tokens.dim, a), { px, weight: 500 });
|
|
6172
6198
|
}
|
|
6173
6199
|
let ctlShown = !g.controls || g.controls.getClientRects().length > 0, headAlpha = this.curAlpha;
|
|
6174
6200
|
if (g.controls && ctlShown)
|
|
@@ -14439,6 +14465,79 @@ function createLens(o) {
|
|
|
14439
14465
|
};
|
|
14440
14466
|
}
|
|
14441
14467
|
|
|
14468
|
+
// src/panel/active.ts
|
|
14469
|
+
var same2 = (a, b) => a === b || a !== null && b !== null && a.group === b.group && a.key === b.key;
|
|
14470
|
+
function activeReporter(resolve, report, schedule) {
|
|
14471
|
+
let hover = null, focus = null, lead = "pointer", last = null, cancel, flush = () => {
|
|
14472
|
+
cancel = void 0;
|
|
14473
|
+
let [first, second] = lead === "pointer" ? [hover, focus] : [focus, hover], at = resolve(first) ?? resolve(second);
|
|
14474
|
+
if (same2(at, last))
|
|
14475
|
+
return;
|
|
14476
|
+
last = at, report(at);
|
|
14477
|
+
}, touch = () => {
|
|
14478
|
+
cancel ??= schedule(flush);
|
|
14479
|
+
};
|
|
14480
|
+
return {
|
|
14481
|
+
pointer(target) {
|
|
14482
|
+
if (hover = target, target !== null)
|
|
14483
|
+
lead = "pointer";
|
|
14484
|
+
touch();
|
|
14485
|
+
},
|
|
14486
|
+
focus(target) {
|
|
14487
|
+
if (focus = target, target !== null)
|
|
14488
|
+
lead = "focus";
|
|
14489
|
+
touch();
|
|
14490
|
+
},
|
|
14491
|
+
dispose() {
|
|
14492
|
+
if (cancel?.(), cancel = void 0, hover = focus = null, last !== null)
|
|
14493
|
+
report(last = null);
|
|
14494
|
+
}
|
|
14495
|
+
};
|
|
14496
|
+
}
|
|
14497
|
+
function activeEvents(reporter, where) {
|
|
14498
|
+
let inMenu = !1;
|
|
14499
|
+
return {
|
|
14500
|
+
over(e) {
|
|
14501
|
+
reporter.pointer(e.target);
|
|
14502
|
+
},
|
|
14503
|
+
move(e) {
|
|
14504
|
+
if (e.movementX || e.movementY)
|
|
14505
|
+
reporter.pointer(e.target);
|
|
14506
|
+
},
|
|
14507
|
+
out(e) {
|
|
14508
|
+
if (where(e.relatedTarget) !== "inside")
|
|
14509
|
+
reporter.pointer(null);
|
|
14510
|
+
},
|
|
14511
|
+
focusIn(e) {
|
|
14512
|
+
let at = where(e.target);
|
|
14513
|
+
if (inMenu = at === "menu", at === "inside")
|
|
14514
|
+
reporter.focus(e.target);
|
|
14515
|
+
else if (at === "outside")
|
|
14516
|
+
reporter.focus(null);
|
|
14517
|
+
},
|
|
14518
|
+
focusOut(e) {
|
|
14519
|
+
if (e.relatedTarget !== null)
|
|
14520
|
+
return;
|
|
14521
|
+
let at = where(e.target);
|
|
14522
|
+
if (at === "inside" || at === "menu" && inMenu)
|
|
14523
|
+
inMenu = !1, reporter.focus(null);
|
|
14524
|
+
}
|
|
14525
|
+
};
|
|
14526
|
+
}
|
|
14527
|
+
function watchActive(root, resolve, report) {
|
|
14528
|
+
let doc = root.ownerDocument, win = doc.defaultView, reporter = activeReporter(resolve, report, (flush) => {
|
|
14529
|
+
let id = win.requestAnimationFrame(flush);
|
|
14530
|
+
return () => win.cancelAnimationFrame(id);
|
|
14531
|
+
}), events = activeEvents(reporter, (node) => node instanceof Node && root.contains(node) ? "inside" : node instanceof Element && node.closest(".knobs-menu") ? "menu" : "outside"), el = (node) => node, over = (e) => events.over({ target: el(e.target) }), move = (e) => events.move({ target: el(e.target), movementX: e.movementX, movementY: e.movementY }), out = (e) => events.out({ relatedTarget: el(e.relatedTarget) }), focusIn = (e) => events.focusIn({ target: el(e.target) }), focusOut = (e) => events.focusOut({ target: el(e.target), relatedTarget: el(e.relatedTarget) });
|
|
14532
|
+
if (root.addEventListener("pointerover", over), root.addEventListener("pointermove", move), root.addEventListener("pointerout", out), doc.addEventListener("focusin", focusIn), doc.addEventListener("focusout", focusOut), doc.activeElement && root.contains(doc.activeElement))
|
|
14533
|
+
reporter.focus(doc.activeElement);
|
|
14534
|
+
return {
|
|
14535
|
+
dispose() {
|
|
14536
|
+
root.removeEventListener("pointerover", over), root.removeEventListener("pointermove", move), root.removeEventListener("pointerout", out), doc.removeEventListener("focusin", focusIn), doc.removeEventListener("focusout", focusOut), reporter.dispose();
|
|
14537
|
+
}
|
|
14538
|
+
};
|
|
14539
|
+
}
|
|
14540
|
+
|
|
14442
14541
|
// src/panel/has.ts
|
|
14443
14542
|
function trackHas(root) {
|
|
14444
14543
|
let doc = root.ownerDocument, win = doc.defaultView, marked = { fv: [], active: [] }, mark = (list, name, nodes, value = "") => {
|
|
@@ -14915,6 +15014,19 @@ function createPresence(root, options) {
|
|
|
14915
15014
|
};
|
|
14916
15015
|
}
|
|
14917
15016
|
|
|
15017
|
+
// src/panel/summary.ts
|
|
15018
|
+
function panelFoldSummary(rows, preset) {
|
|
15019
|
+
if (rows.length === 1 && rows[0].value)
|
|
15020
|
+
return rows[0].value;
|
|
15021
|
+
let lead = preset ?? rows.find((r) => r.changed && r.value)?.value ?? rows.find((r) => r.choice && r.value)?.value, changed = rows.filter((r) => r.changed).length;
|
|
15022
|
+
return [lead, changed ? `${changed} changed` : ""].filter(Boolean).join(" · ");
|
|
15023
|
+
}
|
|
15024
|
+
var baseSignature = (controls) => JSON.stringify([
|
|
15025
|
+
controls.activePreset(),
|
|
15026
|
+
Object.keys(controls.scopes).map((s) => controls.activePreset(s)),
|
|
15027
|
+
Object.keys(controls.groupPresets).map((g) => controls.activeGroupPreset(g))
|
|
15028
|
+
]);
|
|
15029
|
+
|
|
14918
15030
|
// src/panel/panel.ts
|
|
14919
15031
|
var layer = 2147480001, COPIED_MS = 1000, lastTab = /* @__PURE__ */ new Map, REVEAL_QUIET_MS = 2000, NUDGE_BURST_MS = 300;
|
|
14920
15032
|
function readableTitle(id) {
|
|
@@ -15339,16 +15451,17 @@ function buildPanel(controls, host, options) {
|
|
|
15339
15451
|
foldTile(card, next && !filterEl.value.trim(), animate);
|
|
15340
15452
|
else
|
|
15341
15453
|
applyVisibility(animate ? "reveal" : "instant");
|
|
15454
|
+
let marked = page ? card.parentElement : head;
|
|
15455
|
+
if (marked) {
|
|
15456
|
+
if (marked.toggleAttribute("data-folded", next), animate)
|
|
15457
|
+
marked.setAttribute("data-fold-motion", ""), clearTimeout(foldMotions.get(marked)), foldMotions.set(marked, setTimeout(() => marked.removeAttribute("data-fold-motion"), 400));
|
|
15458
|
+
}
|
|
15342
15459
|
if (page) {
|
|
15343
|
-
let tile = card.parentElement;
|
|
15344
|
-
if (tile.toggleAttribute("data-folded", next), animate)
|
|
15345
|
-
tile.setAttribute("data-fold-motion", ""), clearTimeout(foldMotions.get(tile)), foldMotions.set(tile, setTimeout(() => tile.removeAttribute("data-fold-motion"), 400));
|
|
15346
15460
|
if (remember)
|
|
15347
15461
|
foldsKept?.set("groups", group, next);
|
|
15348
|
-
refreshFolds();
|
|
15349
15462
|
} else if (sessionKept)
|
|
15350
15463
|
writeSession(controls.id, { groups: { ...readSession(controls.id).groups, [group]: next } });
|
|
15351
|
-
positioned?.constrain(), wakeGpu();
|
|
15464
|
+
refreshFolds(), positioned?.constrain(), wakeGpu();
|
|
15352
15465
|
}
|
|
15353
15466
|
function foldTile(card, shut, animate) {
|
|
15354
15467
|
if ((!card.hidden && card.dataset.leaving !== "true") !== shut)
|
|
@@ -15444,11 +15557,13 @@ function buildPanel(controls, host, options) {
|
|
|
15444
15557
|
desc.className = "knobs-group-desc", groupDescs.set(groupName, desc), describeGroup(groupName);
|
|
15445
15558
|
}
|
|
15446
15559
|
if (foldAt !== void 0) {
|
|
15447
|
-
if (page)
|
|
15448
|
-
|
|
15449
|
-
caret = caretSvg(), gRight.append(caret), gHead.dataset.foldable = "", gHead.tabIndex = -1, gHead.setAttribute("role", "button"), gHead.setAttribute("aria-label", title);
|
|
15560
|
+
if (summary = el("span"), summary.className = "knobs-group-summary", page)
|
|
15561
|
+
gRight.append(summary);
|
|
15562
|
+
groupSummaries.set(groupName, summary), caret = caretSvg(), gRight.append(caret), gHead.dataset.foldable = "", gHead.tabIndex = -1, gHead.setAttribute("role", "button"), gHead.setAttribute("aria-label", title);
|
|
15450
15563
|
let folded = (page ? foldsKept?.get("groups", groupName) : session.groups?.[groupName]) ?? foldAt;
|
|
15451
|
-
folds.set(groupName, folded), gHead.setAttribute("aria-expanded", String(!folded)),
|
|
15564
|
+
if (folds.set(groupName, folded), gHead.setAttribute("aria-expanded", String(!folded)), !page)
|
|
15565
|
+
gHead.toggleAttribute("data-folded", folded);
|
|
15566
|
+
gHead.addEventListener("click", (event) => {
|
|
15452
15567
|
if (event.detail > 1 || event.target.closest("button"))
|
|
15453
15568
|
return;
|
|
15454
15569
|
byUser(() => fold(groupName));
|
|
@@ -15457,8 +15572,10 @@ function buildPanel(controls, host, options) {
|
|
|
15457
15572
|
if (page) {
|
|
15458
15573
|
let gTitle = el("span");
|
|
15459
15574
|
gTitle.className = "knobs-group-title", gTitle.append(gLabel, gPeek), gHead.append(gTitle, gRight);
|
|
15460
|
-
} else
|
|
15461
|
-
|
|
15575
|
+
} else {
|
|
15576
|
+
let desc = el("span");
|
|
15577
|
+
desc.className = "knobs-group-desc", groupDescs.set(groupName, desc), describeGroup(groupName), gHead.append(gLabel, desc, ...summary ? [summary] : [], gRight);
|
|
15578
|
+
}
|
|
15462
15579
|
return gpuGroups.push({
|
|
15463
15580
|
element: gHead,
|
|
15464
15581
|
labelEl: gLabel,
|
|
@@ -16116,6 +16233,8 @@ function buildPanel(controls, host, options) {
|
|
|
16116
16233
|
motion.push({ node: r.row, show: !0 });
|
|
16117
16234
|
}
|
|
16118
16235
|
}
|
|
16236
|
+
for (let group of groupDescs.keys())
|
|
16237
|
+
describeGroup(group);
|
|
16119
16238
|
for (let [id, sh] of subheads)
|
|
16120
16239
|
if (!usedSubheads.has(id)) {
|
|
16121
16240
|
sh.el.remove();
|
|
@@ -16166,9 +16285,7 @@ function buildPanel(controls, host, options) {
|
|
|
16166
16285
|
let stagger = cascade(motion.length);
|
|
16167
16286
|
motion.forEach((m, i) => present(m.node, m.show, i * stagger, m.after));
|
|
16168
16287
|
}
|
|
16169
|
-
if (applyVisibility(animate ? "reveal" : "instant"),
|
|
16170
|
-
refreshFolds();
|
|
16171
|
-
if (focused && root.contains(focused) && doc.activeElement !== focused && focused.isConnected)
|
|
16288
|
+
if (applyVisibility(animate ? "reveal" : "instant"), refreshFolds(), focused && root.contains(focused) && doc.activeElement !== focused && focused.isConnected)
|
|
16172
16289
|
focused.focus({ preventScroll: !0 });
|
|
16173
16290
|
let gone = focused ? leaving.find((r) => r.row.contains(focused)) : void 0;
|
|
16174
16291
|
if (gone) {
|
|
@@ -16293,7 +16410,7 @@ function buildPanel(controls, host, options) {
|
|
|
16293
16410
|
return selectTab(list[(at + direction + list.length) % list.length].id, !1), !0;
|
|
16294
16411
|
}, searching = () => search?.filtering() ?? !1, rowMatches = (r) => {
|
|
16295
16412
|
let query = search.query();
|
|
16296
|
-
if (query && !matchesFilter(query, r.display, r.key, r.group, r.group && groupTitle(r.group), r.field.subsection))
|
|
16413
|
+
if (query && !matchesFilter(query, r.display, r.key, r.group, r.group && groupTitle(r.group), r.group && groupDescription(r.group), r.field.subsection))
|
|
16297
16414
|
return !1;
|
|
16298
16415
|
for (let chip of search.active())
|
|
16299
16416
|
if (!r.keys.some((k) => chipTests.get(chip)?.(k)))
|
|
@@ -16585,11 +16702,7 @@ function buildPanel(controls, host, options) {
|
|
|
16585
16702
|
if (changed)
|
|
16586
16703
|
wakeGpu();
|
|
16587
16704
|
}
|
|
16588
|
-
let shownBase = ""
|
|
16589
|
-
controls.activePreset(),
|
|
16590
|
-
Object.keys(controls.scopes).map((s) => controls.activePreset(s)),
|
|
16591
|
-
Object.keys(controls.groupPresets).map((g) => controls.activeGroupPreset(g))
|
|
16592
|
-
]);
|
|
16705
|
+
let shownBase = "";
|
|
16593
16706
|
function update() {
|
|
16594
16707
|
if (!controls.enabled) {
|
|
16595
16708
|
close?.();
|
|
@@ -16608,7 +16721,7 @@ function buildPanel(controls, host, options) {
|
|
|
16608
16721
|
if (!fresh(r) && changed(r))
|
|
16609
16722
|
changedCount++;
|
|
16610
16723
|
}
|
|
16611
|
-
let stagger = initialized ? cascade(changedCount) : 0, moving = initialized ? rows.filter(changed) : rows;
|
|
16724
|
+
let stagger = initialized ? cascade(changedCount) : 0, moving = initialized ? rows.filter(changed) : rows, rebasing = baseSignature(controls) !== shownBase;
|
|
16612
16725
|
if (updateStrips(), batchMeasures(() => {
|
|
16613
16726
|
for (let row of moving)
|
|
16614
16727
|
if (row.slider?.prime(), row.editor?.prime?.(), row.choice)
|
|
@@ -16616,7 +16729,7 @@ function buildPanel(controls, host, options) {
|
|
|
16616
16729
|
for (let chips of groupChips.values())
|
|
16617
16730
|
primeSegPill(chips.seg, chips.buttons);
|
|
16618
16731
|
showValues(values, moving, stagger, landing);
|
|
16619
|
-
}), moving.length || !initialized)
|
|
16732
|
+
}), moving.length || !initialized || rebasing)
|
|
16620
16733
|
refreshFolds();
|
|
16621
16734
|
shown = values, initialized = !0, wakeGpu();
|
|
16622
16735
|
}
|
|
@@ -16636,7 +16749,7 @@ function buildPanel(controls, host, options) {
|
|
|
16636
16749
|
}
|
|
16637
16750
|
}
|
|
16638
16751
|
function showValues(values, moving, stagger, landing) {
|
|
16639
|
-
let baseline = controls.baseValues(), fresh = (r) => r.row.dataset.shown === void 0, now = performance.now(), turn = 0, based = baseSignature(), rebased = based !== shownBase;
|
|
16752
|
+
let baseline = controls.baseValues(), fresh = (r) => r.row.dataset.shown === void 0, now = performance.now(), turn = 0, based = baseSignature(controls), rebased = based !== shownBase;
|
|
16640
16753
|
shownBase = based;
|
|
16641
16754
|
for (let row of moving) {
|
|
16642
16755
|
let instant = !initialized || landing || fresh(row);
|
|
@@ -16889,16 +17002,45 @@ function buildPanel(controls, host, options) {
|
|
|
16889
17002
|
preset: null,
|
|
16890
17003
|
changed: changedRows((r) => r.section === section),
|
|
16891
17004
|
keys: rows.filter((r) => r.section === section).flatMap((r) => r.keys)
|
|
16892
|
-
}), changedText = (n) => n && marks ? `${n} changed` : ""
|
|
17005
|
+
}), changedText = (n) => n && marks ? `${n} changed` : "", valueText = (r, values) => {
|
|
17006
|
+
let f = r.field, v = r.value(values), num = (n) => String(Number(n.toFixed(2)));
|
|
17007
|
+
switch (f.kind) {
|
|
17008
|
+
case "choice":
|
|
17009
|
+
return f.labels?.[v] ?? String(v);
|
|
17010
|
+
case "toggle":
|
|
17011
|
+
return v ? "On" : "Off";
|
|
17012
|
+
case "knob":
|
|
17013
|
+
return `${v.toFixed(f.decimals ?? decimals(f.step))}${f.unit ? ` ${f.unit}` : ""}`;
|
|
17014
|
+
case "spring": {
|
|
17015
|
+
let { duration, bounce } = v;
|
|
17016
|
+
return `${num(duration)} s${bounce ? `, bounce ${num(bounce)}` : ""}`;
|
|
17017
|
+
}
|
|
17018
|
+
case "color":
|
|
17019
|
+
return colorHex2(v);
|
|
17020
|
+
case "text":
|
|
17021
|
+
return String(v);
|
|
17022
|
+
default:
|
|
17023
|
+
return "";
|
|
17024
|
+
}
|
|
17025
|
+
}, groupSummary = (group) => {
|
|
17026
|
+
let info = groupInfo(group);
|
|
17027
|
+
if (options.groupSummary)
|
|
17028
|
+
return options.groupSummary(group, info);
|
|
17029
|
+
if (page)
|
|
17030
|
+
return [info.preset, changedText(info.changed)].filter(Boolean).join(" · ") || null;
|
|
17031
|
+
let values = controls.get(), own = rows.filter((r) => r.group === group && !r.leaving && r.keys.length);
|
|
17032
|
+
return panelFoldSummary(own.map((r) => ({
|
|
17033
|
+
value: valueText(r, values),
|
|
17034
|
+
changed: marks && r.keys.some((k) => controls.isModified(k)),
|
|
17035
|
+
choice: r.field.kind === "choice"
|
|
17036
|
+
})), info.preset) || null;
|
|
17037
|
+
};
|
|
16893
17038
|
function refreshFolds() {
|
|
17039
|
+
for (let [group, node] of groupSummaries)
|
|
17040
|
+
if (folds.get(group))
|
|
17041
|
+
putSummary(node, groupSummary(group));
|
|
16894
17042
|
if (!layout || !page)
|
|
16895
17043
|
return;
|
|
16896
|
-
for (let [group, node] of groupSummaries) {
|
|
16897
|
-
if (!folds.get(group))
|
|
16898
|
-
continue;
|
|
16899
|
-
let info = groupInfo(group);
|
|
16900
|
-
putSummary(node, page.groupSummary ? page.groupSummary(group, info) : [info.preset, changedText(info.changed)].filter(Boolean).join(" · ") || null);
|
|
16901
|
-
}
|
|
16902
17044
|
for (let s of layout.sectionList()) {
|
|
16903
17045
|
let node = s.label?.querySelector(":scope > .knobs-section-summary");
|
|
16904
17046
|
if (!node || !s.folded)
|
|
@@ -18050,7 +18192,15 @@ function buildPanel(controls, host, options) {
|
|
|
18050
18192
|
attributes: !0,
|
|
18051
18193
|
attributeFilter: ["data-knobs-renderer", "data-theme", "data-appearance", "class", "style"]
|
|
18052
18194
|
});
|
|
18053
|
-
let
|
|
18195
|
+
let onActive = options.onActive, active = onActive ? watchActive(root, (node) => {
|
|
18196
|
+
if (!node || !root.contains(node))
|
|
18197
|
+
return null;
|
|
18198
|
+
let r = rows.find((r) => !r.leaving && r.row.contains(node));
|
|
18199
|
+
if (r)
|
|
18200
|
+
return r.group === void 0 ? null : { group: r.group, key: r.key };
|
|
18201
|
+
let card = node.closest("[data-group]");
|
|
18202
|
+
return card && root.contains(card) ? { group: card.dataset.group } : null;
|
|
18203
|
+
}, onActive) : void 0, panel = {
|
|
18054
18204
|
element: root,
|
|
18055
18205
|
presetStrip,
|
|
18056
18206
|
focus() {
|
|
@@ -18113,7 +18263,7 @@ function buildPanel(controls, host, options) {
|
|
|
18113
18263
|
doc.removeEventListener(type, onPointerUp, !0);
|
|
18114
18264
|
for (let type of ["pointerup", "pointercancel"])
|
|
18115
18265
|
doc.removeEventListener(type, endScrub, !0);
|
|
18116
|
-
doc.defaultView?.removeEventListener("blur", endScrub), endScrub(), unregisterHints?.(), hintCtrl.stop(), modeObserver.disconnect(), unregisterGpu?.(), lens.dispose(), win?.cancelAnimationFrame(lensFrame), doc.removeEventListener("pointerdown", onPress, !0), doc.removeEventListener("pointerup", onRelease, !0), doc.removeEventListener("pointercancel", onRelease, !0), crumbs?.dispose(), clearTimeout(escTimer), win?.removeEventListener("blur", queueScope), win?.removeEventListener("focus", queueScope), unsubscribe(), unlisten?.(), rowsObserver.disconnect(), gpuObserver.disconnect(), doc.defaultView?.cancelAnimationFrame(landTimer);
|
|
18266
|
+
doc.defaultView?.removeEventListener("blur", endScrub), endScrub(), unregisterHints?.(), hintCtrl.stop(), modeObserver.disconnect(), unregisterGpu?.(), lens.dispose(), win?.cancelAnimationFrame(lensFrame), doc.removeEventListener("pointerdown", onPress, !0), doc.removeEventListener("pointerup", onRelease, !0), doc.removeEventListener("pointercancel", onRelease, !0), crumbs?.dispose(), active?.dispose(), clearTimeout(escTimer), win?.removeEventListener("blur", queueScope), win?.removeEventListener("focus", queueScope), unsubscribe(), unlisten?.(), rowsObserver.disconnect(), gpuObserver.disconnect(), doc.defaultView?.cancelAnimationFrame(landTimer);
|
|
18117
18267
|
for (let k of inHand)
|
|
18118
18268
|
controls.hold(k, !1);
|
|
18119
18269
|
inHand.clear(), positioned?.dispose(), die.dispose();
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
writeSession,
|
|
7
7
|
injectStyles,
|
|
8
8
|
toggleLayer
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-9y93net8.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-e5hbs8fe.js");
|
|
86
86
|
if (disposed || !controls.enabled || panel)
|
|
87
87
|
return;
|
|
88
88
|
if (panel = createPanel(controls, element, close, { restored, chip }), remember)
|
package/dist/dev/index.js
CHANGED
|
@@ -1526,11 +1526,11 @@ function defineControls(id, input, options = {}) {
|
|
|
1526
1526
|
code: (format, values) => formatCode(values ?? get(), format),
|
|
1527
1527
|
variants,
|
|
1528
1528
|
attach(element, chip) {
|
|
1529
|
-
let load = options.ship ? () => options.ship().then((m) => m.attach) : () => import("./chunk-
|
|
1529
|
+
let load = options.ship ? () => options.ship().then((m) => m.attach) : () => import("./chunk-exaya50m.js").then((m) => m.attach);
|
|
1530
1530
|
return live(load, (attach) => attach(controls, element, chip), (detach) => detach());
|
|
1531
1531
|
},
|
|
1532
1532
|
mount(container, mountOptions) {
|
|
1533
|
-
let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => import("./chunk-
|
|
1533
|
+
let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => import("./chunk-e5hbs8fe.js").then((m) => m.mountPanel);
|
|
1534
1534
|
return live(load, (mountPanel) => {
|
|
1535
1535
|
let panel = mountPanel(controls, container, mountOptions);
|
|
1536
1536
|
return mountOptions?.onReady?.(panel), panel;
|
package/dist/panel.js
CHANGED
|
@@ -3335,6 +3335,13 @@ ${GRAD_PROPS}
|
|
|
3335
3335
|
.knobs-group-head .knobs-group-die{width:20px;padding:0;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--_k-round);color:var(--_k-fg-3)}
|
|
3336
3336
|
.knobs-group-head .knobs-group-die[disabled]{opacity:var(--k-disabled-opacity);cursor:default}
|
|
3337
3337
|
.knobs-panel .knobs-group-head button:where(:not(.knobs-group-reset)){transition:background var(--k-hover-t),color var(--k-hover-t),opacity .2s,transform .12s}
|
|
3338
|
+
/* A panel card's header: its title, a folded card's summary and its buttons on one line, and its
|
|
3339
|
+
description on the next. The title takes its room first; the summary gets what's left. */
|
|
3340
|
+
.knobs-panel:not(.knobs-page) .knobs-group-head{flex-wrap:wrap;row-gap:0}
|
|
3341
|
+
.knobs-panel:not(.knobs-page) .knobs-group-head>.knobs-group-label{flex:1000 1 0;max-width:max-content;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3342
|
+
.knobs-group-head>.knobs-group-desc{order:1;flex:0 0 100%;padding:0;line-height:16px;white-space:normal;overflow:visible;text-wrap:pretty}
|
|
3343
|
+
.knobs-group-head>.knobs-group-summary{flex:1 1 0;max-width:max-content;margin-left:auto;font-size:var(--_k-text-meta);text-transform:none}
|
|
3344
|
+
.knobs-group-head[data-folded]>.knobs-group-summary:not(:empty){display:block}
|
|
3338
3345
|
|
|
3339
3346
|
/* Grouped cards: rows share one surface, split by hairlines that stay put whatever a row is doing */
|
|
3340
3347
|
.knobs-card{position:relative;margin-bottom:var(--k-section-gap);border-radius:var(--_k-radius);background:var(--_k-fill);overflow:hidden}
|
|
@@ -4041,7 +4048,7 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
|
|
|
4041
4048
|
.knobs-group-summary{display:none;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_k-fg-3);font-size:var(--_k-text-control);font-weight:500;letter-spacing:0;font-variant-numeric:tabular-nums}
|
|
4042
4049
|
.knobs-tile[data-folded]>.knobs-group-head .knobs-group-summary:not(:empty){display:block}
|
|
4043
4050
|
.knobs-tile[data-folded]>.knobs-group-head .knobs-group-ctl{display:none}
|
|
4044
|
-
.knobs-tile[data-fold-motion]>.knobs-group-head :is(.knobs-group-summary,.knobs-group-ctl){animation:knobs-fade .2s var(--k-ease) both}
|
|
4051
|
+
.knobs-tile[data-fold-motion]>.knobs-group-head :is(.knobs-group-summary,.knobs-group-ctl),.knobs-group-head[data-fold-motion]>.knobs-group-summary{animation:knobs-fade .2s var(--k-ease) both}
|
|
4045
4052
|
/* A highlight (page.highlight): the rest dims under a veil of the page's colour (--k-page-dim at
|
|
4046
4053
|
full), and what's highlighted on its own gets a dot in the gutter by its title. The veil and
|
|
4047
4054
|
dot are written each frame on the panel spring (see page.ts). */
|
|
@@ -11521,6 +11528,44 @@ class GpuOverlayManager {
|
|
|
11521
11528
|
this.clipCache.set(el, { key, text, width, clipped });
|
|
11522
11529
|
return clipped;
|
|
11523
11530
|
}
|
|
11531
|
+
lineCache = new WeakMap;
|
|
11532
|
+
linesOf(el, width) {
|
|
11533
|
+
const text = el.textContent ?? "";
|
|
11534
|
+
const hit = this.lineCache.get(el);
|
|
11535
|
+
if (hit && hit.text === text && hit.width === width)
|
|
11536
|
+
return hit.lines;
|
|
11537
|
+
const lines = [];
|
|
11538
|
+
const node = el.firstChild;
|
|
11539
|
+
if (node?.nodeType === Node.TEXT_NODE) {
|
|
11540
|
+
const top = el.getBoundingClientRect().top;
|
|
11541
|
+
const range = el.ownerDocument.createRange();
|
|
11542
|
+
let line;
|
|
11543
|
+
const flush = () => {
|
|
11544
|
+
if (line)
|
|
11545
|
+
lines.push({
|
|
11546
|
+
text: text.slice(line.from, line.to),
|
|
11547
|
+
y: (line.top + line.bottom) / 2 - top
|
|
11548
|
+
});
|
|
11549
|
+
};
|
|
11550
|
+
for (const word of text.matchAll(/\S+/g)) {
|
|
11551
|
+
const from = word.index;
|
|
11552
|
+
const to = from + word[0].length;
|
|
11553
|
+
range.setStart(node, from);
|
|
11554
|
+
range.setEnd(node, to);
|
|
11555
|
+
const r = range.getBoundingClientRect();
|
|
11556
|
+
if (line && Math.abs(r.top - line.top) < 1) {
|
|
11557
|
+
line.to = to;
|
|
11558
|
+
line.bottom = Math.max(line.bottom, r.bottom);
|
|
11559
|
+
} else {
|
|
11560
|
+
flush();
|
|
11561
|
+
line = { from, to, top: r.top, bottom: r.bottom };
|
|
11562
|
+
}
|
|
11563
|
+
}
|
|
11564
|
+
flush();
|
|
11565
|
+
}
|
|
11566
|
+
this.lineCache.set(el, { text, width, lines });
|
|
11567
|
+
return lines;
|
|
11568
|
+
}
|
|
11524
11569
|
curReduced = false;
|
|
11525
11570
|
curLocks;
|
|
11526
11571
|
curSelected = 0;
|
|
@@ -13320,12 +13365,11 @@ class GpuOverlayManager {
|
|
|
13320
13365
|
this.drawText(this.fit(g.labelEl.textContent ?? "", lr.width, px, tokens.titleWeight), lr.left, cy, withAlpha2(tokens.text, 1 - peek), { px, weight: tokens.titleWeight, track: -0.01 });
|
|
13321
13366
|
if (g.peek && peek > 0.005)
|
|
13322
13367
|
this.drawText(this.fit(g.peek.textContent ?? "", lr.width, px, tokens.titleWeight), lr.left, cy, withAlpha2(tokens.text, peek), { px, weight: tokens.titleWeight, track: -0.01 });
|
|
13323
|
-
} else
|
|
13324
|
-
|
|
13325
|
-
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
});
|
|
13368
|
+
} else {
|
|
13369
|
+
const label = tokens.groupUpper ? (g.labelEl.textContent ?? "").toUpperCase() : g.labelEl.textContent ?? "";
|
|
13370
|
+
const px = Math.max(9, tokens.groupSize);
|
|
13371
|
+
this.drawText(this.domClipped(g.labelEl, label, lr.width) ? this.fit(label, lr.width, px, tokens.titleWeight, undefined, tokens.groupTracking, true) : label, lr.left, lr.top + lr.height * 0.5, tokens.groupColor, { px, weight: tokens.titleWeight, track: tokens.groupTracking });
|
|
13372
|
+
}
|
|
13329
13373
|
if (!g.title && tokens.groupDivider[3] > 0 && g.element.previousElementSibling)
|
|
13330
13374
|
this.pushPrim({
|
|
13331
13375
|
x: gr.left,
|
|
@@ -13340,13 +13384,16 @@ class GpuOverlayManager {
|
|
|
13340
13384
|
const cs = getComputedStyle(g.desc);
|
|
13341
13385
|
const left = r.left + (parseFloat(cs.paddingLeft) || 0);
|
|
13342
13386
|
const w = r.width - (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0);
|
|
13343
|
-
|
|
13387
|
+
const lines = cs.whiteSpace.includes("nowrap") ? [{ text: g.desc.textContent ?? "", y: r.height * 0.5 }] : this.linesOf(g.desc, r.width);
|
|
13388
|
+
for (const line of lines)
|
|
13389
|
+
this.drawText(this.fit(line.text, w + 0.5, tokens.textMeta, tokens.labelWeight), left, r.top + line.y, tokens.dim, { px: tokens.textMeta, weight: tokens.labelWeight });
|
|
13344
13390
|
}
|
|
13345
13391
|
const fadeIn = (node) => node.parentElement?.closest("[data-fold-motion]") ? Number(getComputedStyle(node).opacity) || 0 : 1;
|
|
13346
13392
|
if (g.summary && g.summary.getClientRects().length) {
|
|
13347
13393
|
const r = this.rectOf(g.summary);
|
|
13348
13394
|
const a = fadeIn(g.summary);
|
|
13349
|
-
|
|
13395
|
+
const px = g.title ? tokens.textControl : tokens.textMeta;
|
|
13396
|
+
this.drawText(this.fit(g.summary.textContent ?? "", r.width + 0.5, px, 500), r.left, r.top + r.height * 0.5, withAlpha2(tokens.dim, a), { px, weight: 500 });
|
|
13350
13397
|
}
|
|
13351
13398
|
const ctlShown = !g.controls || g.controls.getClientRects().length > 0;
|
|
13352
13399
|
const headAlpha = this.curAlpha;
|
|
@@ -25131,6 +25178,114 @@ var init_lens = __esm(() => {
|
|
|
25131
25178
|
};
|
|
25132
25179
|
});
|
|
25133
25180
|
|
|
25181
|
+
// src/panel/active.ts
|
|
25182
|
+
function activeReporter(resolve, report, schedule) {
|
|
25183
|
+
let hover = null;
|
|
25184
|
+
let focus = null;
|
|
25185
|
+
let lead = "pointer";
|
|
25186
|
+
let last = null;
|
|
25187
|
+
let cancel;
|
|
25188
|
+
const flush = () => {
|
|
25189
|
+
cancel = undefined;
|
|
25190
|
+
const [first, second] = lead === "pointer" ? [hover, focus] : [focus, hover];
|
|
25191
|
+
const at = resolve(first) ?? resolve(second);
|
|
25192
|
+
if (same3(at, last))
|
|
25193
|
+
return;
|
|
25194
|
+
last = at;
|
|
25195
|
+
report(at);
|
|
25196
|
+
};
|
|
25197
|
+
const touch = () => {
|
|
25198
|
+
cancel ??= schedule(flush);
|
|
25199
|
+
};
|
|
25200
|
+
return {
|
|
25201
|
+
pointer(target) {
|
|
25202
|
+
hover = target;
|
|
25203
|
+
if (target !== null)
|
|
25204
|
+
lead = "pointer";
|
|
25205
|
+
touch();
|
|
25206
|
+
},
|
|
25207
|
+
focus(target) {
|
|
25208
|
+
focus = target;
|
|
25209
|
+
if (target !== null)
|
|
25210
|
+
lead = "focus";
|
|
25211
|
+
touch();
|
|
25212
|
+
},
|
|
25213
|
+
dispose() {
|
|
25214
|
+
cancel?.();
|
|
25215
|
+
cancel = undefined;
|
|
25216
|
+
hover = focus = null;
|
|
25217
|
+
if (last !== null)
|
|
25218
|
+
report(last = null);
|
|
25219
|
+
}
|
|
25220
|
+
};
|
|
25221
|
+
}
|
|
25222
|
+
function activeEvents(reporter, where) {
|
|
25223
|
+
let inMenu = false;
|
|
25224
|
+
return {
|
|
25225
|
+
over(e) {
|
|
25226
|
+
reporter.pointer(e.target);
|
|
25227
|
+
},
|
|
25228
|
+
move(e) {
|
|
25229
|
+
if (e.movementX || e.movementY)
|
|
25230
|
+
reporter.pointer(e.target);
|
|
25231
|
+
},
|
|
25232
|
+
out(e) {
|
|
25233
|
+
if (where(e.relatedTarget) !== "inside")
|
|
25234
|
+
reporter.pointer(null);
|
|
25235
|
+
},
|
|
25236
|
+
focusIn(e) {
|
|
25237
|
+
const at = where(e.target);
|
|
25238
|
+
inMenu = at === "menu";
|
|
25239
|
+
if (at === "inside")
|
|
25240
|
+
reporter.focus(e.target);
|
|
25241
|
+
else if (at === "outside")
|
|
25242
|
+
reporter.focus(null);
|
|
25243
|
+
},
|
|
25244
|
+
focusOut(e) {
|
|
25245
|
+
if (e.relatedTarget !== null)
|
|
25246
|
+
return;
|
|
25247
|
+
const at = where(e.target);
|
|
25248
|
+
if (at === "inside" || at === "menu" && inMenu) {
|
|
25249
|
+
inMenu = false;
|
|
25250
|
+
reporter.focus(null);
|
|
25251
|
+
}
|
|
25252
|
+
}
|
|
25253
|
+
};
|
|
25254
|
+
}
|
|
25255
|
+
function watchActive(root, resolve, report) {
|
|
25256
|
+
const doc = root.ownerDocument;
|
|
25257
|
+
const win = doc.defaultView;
|
|
25258
|
+
const reporter = activeReporter(resolve, report, (flush) => {
|
|
25259
|
+
const id = win.requestAnimationFrame(flush);
|
|
25260
|
+
return () => win.cancelAnimationFrame(id);
|
|
25261
|
+
});
|
|
25262
|
+
const events = activeEvents(reporter, (node) => node instanceof Node && root.contains(node) ? "inside" : node instanceof Element && node.closest(".knobs-menu") ? "menu" : "outside");
|
|
25263
|
+
const el = (node) => node;
|
|
25264
|
+
const over = (e) => events.over({ target: el(e.target) });
|
|
25265
|
+
const move = (e) => events.move({ target: el(e.target), movementX: e.movementX, movementY: e.movementY });
|
|
25266
|
+
const out = (e) => events.out({ relatedTarget: el(e.relatedTarget) });
|
|
25267
|
+
const focusIn = (e) => events.focusIn({ target: el(e.target) });
|
|
25268
|
+
const focusOut = (e) => events.focusOut({ target: el(e.target), relatedTarget: el(e.relatedTarget) });
|
|
25269
|
+
root.addEventListener("pointerover", over);
|
|
25270
|
+
root.addEventListener("pointermove", move);
|
|
25271
|
+
root.addEventListener("pointerout", out);
|
|
25272
|
+
doc.addEventListener("focusin", focusIn);
|
|
25273
|
+
doc.addEventListener("focusout", focusOut);
|
|
25274
|
+
if (doc.activeElement && root.contains(doc.activeElement))
|
|
25275
|
+
reporter.focus(doc.activeElement);
|
|
25276
|
+
return {
|
|
25277
|
+
dispose() {
|
|
25278
|
+
root.removeEventListener("pointerover", over);
|
|
25279
|
+
root.removeEventListener("pointermove", move);
|
|
25280
|
+
root.removeEventListener("pointerout", out);
|
|
25281
|
+
doc.removeEventListener("focusin", focusIn);
|
|
25282
|
+
doc.removeEventListener("focusout", focusOut);
|
|
25283
|
+
reporter.dispose();
|
|
25284
|
+
}
|
|
25285
|
+
};
|
|
25286
|
+
}
|
|
25287
|
+
var same3 = (a, b) => a === b || a !== null && b !== null && a.group === b.group && a.key === b.key;
|
|
25288
|
+
|
|
25134
25289
|
// src/panel/has.ts
|
|
25135
25290
|
function trackHas(root) {
|
|
25136
25291
|
const doc = root.ownerDocument;
|
|
@@ -25879,6 +26034,20 @@ var init_presence = __esm(() => {
|
|
|
25879
26034
|
LOOK = ["opacity", "filter", "scale", "transform-origin"];
|
|
25880
26035
|
});
|
|
25881
26036
|
|
|
26037
|
+
// src/panel/summary.ts
|
|
26038
|
+
function panelFoldSummary(rows, preset) {
|
|
26039
|
+
if (rows.length === 1 && rows[0].value)
|
|
26040
|
+
return rows[0].value;
|
|
26041
|
+
const lead = preset ?? rows.find((r) => r.changed && r.value)?.value ?? rows.find((r) => r.choice && r.value)?.value;
|
|
26042
|
+
const changed = rows.filter((r) => r.changed).length;
|
|
26043
|
+
return [lead, changed ? `${changed} changed` : ""].filter(Boolean).join(" · ");
|
|
26044
|
+
}
|
|
26045
|
+
var baseSignature = (controls) => JSON.stringify([
|
|
26046
|
+
controls.activePreset(),
|
|
26047
|
+
Object.keys(controls.scopes).map((s) => controls.activePreset(s)),
|
|
26048
|
+
Object.keys(controls.groupPresets).map((g) => controls.activeGroupPreset(g))
|
|
26049
|
+
]);
|
|
26050
|
+
|
|
25882
26051
|
// src/panel/panel.ts
|
|
25883
26052
|
function readableTitle(id) {
|
|
25884
26053
|
if (/\s/.test(id))
|
|
@@ -26526,19 +26695,21 @@ function buildPanel(controls, host, options) {
|
|
|
26526
26695
|
foldTile(card, next && !filterEl.value.trim(), animate);
|
|
26527
26696
|
else
|
|
26528
26697
|
applyVisibility(animate ? "reveal" : "instant");
|
|
26529
|
-
|
|
26530
|
-
|
|
26531
|
-
|
|
26698
|
+
const marked = page ? card.parentElement : head;
|
|
26699
|
+
if (marked) {
|
|
26700
|
+
marked.toggleAttribute("data-folded", next);
|
|
26532
26701
|
if (animate) {
|
|
26533
|
-
|
|
26534
|
-
clearTimeout(foldMotions.get(
|
|
26535
|
-
foldMotions.set(
|
|
26702
|
+
marked.setAttribute("data-fold-motion", "");
|
|
26703
|
+
clearTimeout(foldMotions.get(marked));
|
|
26704
|
+
foldMotions.set(marked, setTimeout(() => marked.removeAttribute("data-fold-motion"), 400));
|
|
26536
26705
|
}
|
|
26706
|
+
}
|
|
26707
|
+
if (page) {
|
|
26537
26708
|
if (remember)
|
|
26538
26709
|
foldsKept?.set("groups", group, next);
|
|
26539
|
-
refreshFolds();
|
|
26540
26710
|
} else if (sessionKept)
|
|
26541
26711
|
writeSession(controls.id, { groups: { ...readSession(controls.id).groups, [group]: next } });
|
|
26712
|
+
refreshFolds();
|
|
26542
26713
|
positioned?.constrain();
|
|
26543
26714
|
wakeGpu();
|
|
26544
26715
|
}
|
|
@@ -26695,12 +26866,11 @@ function buildPanel(controls, host, options) {
|
|
|
26695
26866
|
describeGroup(groupName);
|
|
26696
26867
|
}
|
|
26697
26868
|
if (foldAt !== undefined) {
|
|
26698
|
-
|
|
26699
|
-
|
|
26700
|
-
|
|
26869
|
+
summary = el("span");
|
|
26870
|
+
summary.className = "knobs-group-summary";
|
|
26871
|
+
if (page)
|
|
26701
26872
|
gRight.append(summary);
|
|
26702
|
-
|
|
26703
|
-
}
|
|
26873
|
+
groupSummaries.set(groupName, summary);
|
|
26704
26874
|
caret = caretSvg();
|
|
26705
26875
|
gRight.append(caret);
|
|
26706
26876
|
gHead.dataset.foldable = "";
|
|
@@ -26710,6 +26880,8 @@ function buildPanel(controls, host, options) {
|
|
|
26710
26880
|
const folded = (page ? foldsKept?.get("groups", groupName) : session.groups?.[groupName]) ?? foldAt;
|
|
26711
26881
|
folds.set(groupName, folded);
|
|
26712
26882
|
gHead.setAttribute("aria-expanded", String(!folded));
|
|
26883
|
+
if (!page)
|
|
26884
|
+
gHead.toggleAttribute("data-folded", folded);
|
|
26713
26885
|
gHead.addEventListener("click", (event) => {
|
|
26714
26886
|
if (event.detail > 1 || event.target.closest("button"))
|
|
26715
26887
|
return;
|
|
@@ -26721,8 +26893,13 @@ function buildPanel(controls, host, options) {
|
|
|
26721
26893
|
gTitle.className = "knobs-group-title";
|
|
26722
26894
|
gTitle.append(gLabel, gPeek);
|
|
26723
26895
|
gHead.append(gTitle, gRight);
|
|
26724
|
-
} else
|
|
26725
|
-
|
|
26896
|
+
} else {
|
|
26897
|
+
const desc = el("span");
|
|
26898
|
+
desc.className = "knobs-group-desc";
|
|
26899
|
+
groupDescs.set(groupName, desc);
|
|
26900
|
+
describeGroup(groupName);
|
|
26901
|
+
gHead.append(gLabel, desc, ...summary ? [summary] : [], gRight);
|
|
26902
|
+
}
|
|
26726
26903
|
gpuGroups.push({
|
|
26727
26904
|
element: gHead,
|
|
26728
26905
|
labelEl: gLabel,
|
|
@@ -27540,6 +27717,8 @@ function buildPanel(controls, host, options) {
|
|
|
27540
27717
|
motion.push({ node: r.row, show: true });
|
|
27541
27718
|
}
|
|
27542
27719
|
}
|
|
27720
|
+
for (const group of groupDescs.keys())
|
|
27721
|
+
describeGroup(group);
|
|
27543
27722
|
for (const [id, sh] of subheads)
|
|
27544
27723
|
if (!usedSubheads.has(id)) {
|
|
27545
27724
|
sh.el.remove();
|
|
@@ -27598,8 +27777,7 @@ function buildPanel(controls, host, options) {
|
|
|
27598
27777
|
motion.forEach((m, i) => present(m.node, m.show, i * stagger, m.after));
|
|
27599
27778
|
}
|
|
27600
27779
|
applyVisibility(animate ? "reveal" : "instant");
|
|
27601
|
-
|
|
27602
|
-
refreshFolds();
|
|
27780
|
+
refreshFolds();
|
|
27603
27781
|
if (focused && root.contains(focused) && doc.activeElement !== focused && focused.isConnected)
|
|
27604
27782
|
focused.focus({ preventScroll: true });
|
|
27605
27783
|
const gone = focused ? leaving.find((r) => r.row.contains(focused)) : undefined;
|
|
@@ -27759,7 +27937,7 @@ function buildPanel(controls, host, options) {
|
|
|
27759
27937
|
const searching = () => search?.filtering() ?? false;
|
|
27760
27938
|
const rowMatches = (r) => {
|
|
27761
27939
|
const query = search.query();
|
|
27762
|
-
if (query && !matchesFilter(query, r.display, r.key, r.group, r.group && groupTitle(r.group), r.field.subsection))
|
|
27940
|
+
if (query && !matchesFilter(query, r.display, r.key, r.group, r.group && groupTitle(r.group), r.group && groupDescription(r.group), r.field.subsection))
|
|
27763
27941
|
return false;
|
|
27764
27942
|
for (const chip of search.active())
|
|
27765
27943
|
if (!r.keys.some((k) => chipTests.get(chip)?.(k)))
|
|
@@ -28157,11 +28335,6 @@ function buildPanel(controls, host, options) {
|
|
|
28157
28335
|
wakeGpu();
|
|
28158
28336
|
}
|
|
28159
28337
|
let shownBase = "";
|
|
28160
|
-
const baseSignature = () => JSON.stringify([
|
|
28161
|
-
controls.activePreset(),
|
|
28162
|
-
Object.keys(controls.scopes).map((s) => controls.activePreset(s)),
|
|
28163
|
-
Object.keys(controls.groupPresets).map((g) => controls.activeGroupPreset(g))
|
|
28164
|
-
]);
|
|
28165
28338
|
function update() {
|
|
28166
28339
|
if (!controls.enabled) {
|
|
28167
28340
|
close?.();
|
|
@@ -28188,6 +28361,7 @@ function buildPanel(controls, host, options) {
|
|
|
28188
28361
|
}
|
|
28189
28362
|
const stagger = initialized ? cascade(changedCount) : 0;
|
|
28190
28363
|
const moving = initialized ? rows.filter(changed) : rows;
|
|
28364
|
+
const rebasing = baseSignature(controls) !== shownBase;
|
|
28191
28365
|
updateStrips();
|
|
28192
28366
|
batchMeasures(() => {
|
|
28193
28367
|
for (const row of moving) {
|
|
@@ -28200,7 +28374,7 @@ function buildPanel(controls, host, options) {
|
|
|
28200
28374
|
primeSegPill(chips.seg, chips.buttons);
|
|
28201
28375
|
showValues(values, moving, stagger, landing);
|
|
28202
28376
|
});
|
|
28203
|
-
if (moving.length || !initialized)
|
|
28377
|
+
if (moving.length || !initialized || rebasing)
|
|
28204
28378
|
refreshFolds();
|
|
28205
28379
|
shown = values;
|
|
28206
28380
|
initialized = true;
|
|
@@ -28226,7 +28400,7 @@ function buildPanel(controls, host, options) {
|
|
|
28226
28400
|
const fresh = (r) => r.row.dataset.shown === undefined;
|
|
28227
28401
|
const now = performance.now();
|
|
28228
28402
|
let turn = 0;
|
|
28229
|
-
const based = baseSignature();
|
|
28403
|
+
const based = baseSignature(controls);
|
|
28230
28404
|
const rebased = based !== shownBase;
|
|
28231
28405
|
shownBase = based;
|
|
28232
28406
|
for (const row of moving) {
|
|
@@ -28531,15 +28705,49 @@ function buildPanel(controls, host, options) {
|
|
|
28531
28705
|
keys: rows.filter((r) => r.section === section).flatMap((r) => r.keys)
|
|
28532
28706
|
});
|
|
28533
28707
|
const changedText = (n) => n && marks ? `${n} changed` : "";
|
|
28708
|
+
const valueText = (r, values) => {
|
|
28709
|
+
const f = r.field;
|
|
28710
|
+
const v = r.value(values);
|
|
28711
|
+
const num = (n) => String(Number(n.toFixed(2)));
|
|
28712
|
+
switch (f.kind) {
|
|
28713
|
+
case "choice":
|
|
28714
|
+
return f.labels?.[v] ?? String(v);
|
|
28715
|
+
case "toggle":
|
|
28716
|
+
return v ? "On" : "Off";
|
|
28717
|
+
case "knob":
|
|
28718
|
+
return `${v.toFixed(f.decimals ?? decimals(f.step))}${f.unit ? ` ${f.unit}` : ""}`;
|
|
28719
|
+
case "spring": {
|
|
28720
|
+
const { duration, bounce } = v;
|
|
28721
|
+
return `${num(duration)} s${bounce ? `, bounce ${num(bounce)}` : ""}`;
|
|
28722
|
+
}
|
|
28723
|
+
case "color":
|
|
28724
|
+
return colorHex(v);
|
|
28725
|
+
case "text":
|
|
28726
|
+
return String(v);
|
|
28727
|
+
default:
|
|
28728
|
+
return "";
|
|
28729
|
+
}
|
|
28730
|
+
};
|
|
28731
|
+
const groupSummary = (group) => {
|
|
28732
|
+
const info = groupInfo(group);
|
|
28733
|
+
if (options.groupSummary)
|
|
28734
|
+
return options.groupSummary(group, info);
|
|
28735
|
+
if (page)
|
|
28736
|
+
return [info.preset, changedText(info.changed)].filter(Boolean).join(" · ") || null;
|
|
28737
|
+
const values = controls.get();
|
|
28738
|
+
const own = rows.filter((r) => r.group === group && !r.leaving && r.keys.length);
|
|
28739
|
+
return panelFoldSummary(own.map((r) => ({
|
|
28740
|
+
value: valueText(r, values),
|
|
28741
|
+
changed: marks && r.keys.some((k) => controls.isModified(k)),
|
|
28742
|
+
choice: r.field.kind === "choice"
|
|
28743
|
+
})), info.preset) || null;
|
|
28744
|
+
};
|
|
28534
28745
|
function refreshFolds() {
|
|
28746
|
+
for (const [group, node] of groupSummaries)
|
|
28747
|
+
if (folds.get(group))
|
|
28748
|
+
putSummary(node, groupSummary(group));
|
|
28535
28749
|
if (!layout || !page)
|
|
28536
28750
|
return;
|
|
28537
|
-
for (const [group, node] of groupSummaries) {
|
|
28538
|
-
if (!folds.get(group))
|
|
28539
|
-
continue;
|
|
28540
|
-
const info = groupInfo(group);
|
|
28541
|
-
putSummary(node, page.groupSummary ? page.groupSummary(group, info) : [info.preset, changedText(info.changed)].filter(Boolean).join(" · ") || null);
|
|
28542
|
-
}
|
|
28543
28751
|
for (const s of layout.sectionList()) {
|
|
28544
28752
|
const node = s.label?.querySelector(":scope > .knobs-section-summary");
|
|
28545
28753
|
if (!node || !s.folded)
|
|
@@ -30057,6 +30265,16 @@ function buildPanel(controls, host, options) {
|
|
|
30057
30265
|
attributes: true,
|
|
30058
30266
|
attributeFilter: ["data-knobs-renderer", "data-theme", "data-appearance", "class", "style"]
|
|
30059
30267
|
});
|
|
30268
|
+
const onActive = options.onActive;
|
|
30269
|
+
const active = onActive ? watchActive(root, (node) => {
|
|
30270
|
+
if (!node || !root.contains(node))
|
|
30271
|
+
return null;
|
|
30272
|
+
const r = rows.find((r) => !r.leaving && r.row.contains(node));
|
|
30273
|
+
if (r)
|
|
30274
|
+
return r.group === undefined ? null : { group: r.group, key: r.key };
|
|
30275
|
+
const card = node.closest("[data-group]");
|
|
30276
|
+
return card && root.contains(card) ? { group: card.dataset.group } : null;
|
|
30277
|
+
}, onActive) : undefined;
|
|
30060
30278
|
const panel = {
|
|
30061
30279
|
element: root,
|
|
30062
30280
|
presetStrip,
|
|
@@ -30147,6 +30365,7 @@ function buildPanel(controls, host, options) {
|
|
|
30147
30365
|
doc.removeEventListener("pointerup", onRelease, true);
|
|
30148
30366
|
doc.removeEventListener("pointercancel", onRelease, true);
|
|
30149
30367
|
crumbs?.dispose();
|
|
30368
|
+
active?.dispose();
|
|
30150
30369
|
clearTimeout(escTimer);
|
|
30151
30370
|
win?.removeEventListener("blur", queueScope);
|
|
30152
30371
|
win?.removeEventListener("focus", queueScope);
|
|
@@ -15,7 +15,10 @@ export interface FieldMeta {
|
|
|
15
15
|
readonly section?: string;
|
|
16
16
|
/** What the section's heading shows (the first field that gives one wins); `section` by default. */
|
|
17
17
|
readonly sectionLabel?: string;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* One muted line under the group's card title, in a panel or a page (the first field that gives
|
|
20
|
+
* one wins).
|
|
21
|
+
*/
|
|
19
22
|
readonly groupDescription?: string;
|
|
20
23
|
/** One muted line under the section's heading on a page (the first field that gives one wins). */
|
|
21
24
|
readonly sectionDescription?: string;
|
|
@@ -96,7 +96,7 @@ export interface LabelOptions {
|
|
|
96
96
|
readonly defaultLabel?: string | Readonly<Record<string, string>>;
|
|
97
97
|
/** How a page card shows its group presets (see `GroupPresetDisplay`). */
|
|
98
98
|
readonly groupPresetDisplay?: GroupPresetDisplay;
|
|
99
|
-
/** By group id: one muted line under its card's title
|
|
99
|
+
/** By group id: one muted line under its card's title, over fields' `groupDescription`. */
|
|
100
100
|
readonly groupDescriptions?: Readonly<Record<string, string>>;
|
|
101
101
|
/** By section id: one muted line under its heading on a page, over fields' `sectionDescription`. */
|
|
102
102
|
readonly sectionDescriptions?: Readonly<Record<string, string>>;
|
|
@@ -146,6 +146,20 @@ export interface HeaderOptions {
|
|
|
146
146
|
*/
|
|
147
147
|
readonly buttons?: readonly HeaderButton[];
|
|
148
148
|
}
|
|
149
|
+
/** What's active in a panel (see `MountPanelOptions.onActive`): a card, or one of its rows. */
|
|
150
|
+
export interface PanelActive {
|
|
151
|
+
readonly group: string;
|
|
152
|
+
readonly key?: string;
|
|
153
|
+
}
|
|
154
|
+
/** What a folded card's or section's summary is made of (see `MountPanelOptions.groupSummary`). */
|
|
155
|
+
export interface FoldSummaryInfo {
|
|
156
|
+
/** The group preset its values match (its name), "Custom" when none does; null without any. */
|
|
157
|
+
readonly preset: string | null;
|
|
158
|
+
/** How many of its rows differ from their defaults (a spring's two keys count once). */
|
|
159
|
+
readonly changed: number;
|
|
160
|
+
/** Its value keys. */
|
|
161
|
+
readonly keys: readonly string[];
|
|
162
|
+
}
|
|
149
163
|
export interface MountPanelOptions extends LabelOptions {
|
|
150
164
|
readonly title?: string | false;
|
|
151
165
|
/**
|
|
@@ -253,6 +267,20 @@ export interface MountPanelOptions extends LabelOptions {
|
|
|
253
267
|
readonly sound?: boolean | SoundOptions;
|
|
254
268
|
/** Called once the panel is mounted, with a handle to focus it (a host's "Tune" key). */
|
|
255
269
|
readonly onReady?: (panel: PanelHandle) => void;
|
|
270
|
+
/**
|
|
271
|
+
* Called when the card or row under the pointer or focus changes: `{ group }` for a card's
|
|
272
|
+
* header or body, `{ group, key }` for one of its rows, `null` for nothing (and once more when
|
|
273
|
+
* the panel goes). Whichever of pointer and focus moved last wins; at most once a frame, never
|
|
274
|
+
* the same value twice. For outlining the part of the app a card changes.
|
|
275
|
+
*/
|
|
276
|
+
readonly onActive?: (at: PanelActive | null) => void;
|
|
277
|
+
/**
|
|
278
|
+
* What a folded group card's header says on its right. In a panel, by default, a one-row card's
|
|
279
|
+
* value ("CSS orb"); otherwise its matching group preset, else its first changed value, else
|
|
280
|
+
* its first choice, then "· N changed". On a page, "<matching preset, or Custom> · N changed".
|
|
281
|
+
* Each part shows only where it applies; `null` shows nothing.
|
|
282
|
+
*/
|
|
283
|
+
readonly groupSummary?: (group: string, info: FoldSummaryInfo) => string | Node | null;
|
|
256
284
|
/** What Copy (and ⇧C) puts on the clipboard; `controls.code()` by default. */
|
|
257
285
|
readonly copy?: () => string;
|
|
258
286
|
/**
|
package/dist/types/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { colorHex, parseColor, type Oklch } from "./core/color.js";
|
|
|
5
5
|
export { sampleGradient, sampleCurve, gradientCss, CURVE_SHAPES, type GradientStop, type GradientSpace, type CurvePoint, type CurveInterp, type SampleGradientOptions, type SampleCurveOptions, } from "./core/ramp.js";
|
|
6
6
|
export { bezierCss, bezierPoint, EASINGS, type Bezier, type EasingName } from "./core/easing.js";
|
|
7
7
|
export { AROUND } from "./core/random.js";
|
|
8
|
-
export { defineControls, type ChangeMeta, type GestureEvent, type ChangeSource, type ChipOptions, type ControlAction, type Controls, type ControlsOptions, type DensityMode, type GroupPreset, type GroupPresetDisplay, type PresetRef, type HeaderButton, type HeaderOptions, type HostHistory, type LabelOptions, type MountPanelOptions, type PanelCommand, type PanelHandle, type PanelModule, type PanelKeys, type PersistMode, type SearchChip, type SearchOptions, type SyncOptions, type RendererMode, type RevealMode, type SectionFolded, type SoundOptions, type SoundTheme, type ThemeMode, type Appearance, } from "./core/store.js";
|
|
8
|
+
export { defineControls, type ChangeMeta, type GestureEvent, type ChangeSource, type ChipOptions, type ControlAction, type Controls, type ControlsOptions, type DensityMode, type FoldSummaryInfo, type GroupPreset, type GroupPresetDisplay, type PresetRef, type HeaderButton, type HeaderOptions, type HostHistory, type LabelOptions, type MountPanelOptions, type PanelActive, type PanelCommand, type PanelHandle, type PanelModule, type PanelKeys, type PersistMode, type SearchChip, type SearchOptions, type SyncOptions, type RendererMode, type RevealMode, type SectionFolded, type SoundOptions, type SoundTheme, type ThemeMode, type Appearance, } from "./core/store.js";
|
|
9
9
|
export { createRemoteControls, descriptorToField, formatSwift, fromDescriptors, fromSettings, type SettingsGroup, type SettingsItem, type ActionDescriptor, type ChoiceOption, type ControlDescriptor, type SpringBindingDescriptor, type ToolbarDescriptor, type ReadoutDescriptor, type ValueDescriptor, type RemoteControls, type RemoteControlsOptions, } from "./core/remote.js";
|
|
10
10
|
export type { LogEntry, LogLevel, TableRows, WatchValue } from "./core/readout.js";
|
|
11
11
|
export { memoryStorage, urlFlag, type StoragePort } from "./core/storage.js";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { PanelActive } from "../core/store.js";
|
|
2
|
+
/**
|
|
3
|
+
* What's active in a panel, from where the pointer is and where focus is, each told as it moves.
|
|
4
|
+
* Whichever last moved onto something leads; while it's on nothing (or off the panel), the
|
|
5
|
+
* other's target stands. `report` hears the result at most once a frame (`schedule`), and never
|
|
6
|
+
* the same value twice in a row.
|
|
7
|
+
*/
|
|
8
|
+
export declare function activeReporter<T>(resolve: (target: T | null) => PanelActive | null, report: (at: PanelActive | null) => void, schedule: (flush: () => void) => () => void): {
|
|
9
|
+
pointer(target: T | null): void;
|
|
10
|
+
focus(target: T | null): void;
|
|
11
|
+
/** Stops listening; what was active is no longer, so a last `null` is reported. */
|
|
12
|
+
dispose(): void;
|
|
13
|
+
};
|
|
14
|
+
/** Where an event's node is: in the panel, in one of its menus (on the body), or elsewhere. */
|
|
15
|
+
export type ActiveWhere = "inside" | "menu" | "outside";
|
|
16
|
+
/**
|
|
17
|
+
* The panel's pointer and focus events, told to `reporter` (see `activeReporter`). Focus that
|
|
18
|
+
* moves into one of the panel's menus keeps the row that opened it until it comes back, moves
|
|
19
|
+
* elsewhere, or leaves the menu for nowhere (a blur, the menu closing without handing it back).
|
|
20
|
+
* A pointer that moves (not just one the page scrolled under) takes the lead back from focus.
|
|
21
|
+
*/
|
|
22
|
+
export declare function activeEvents<T>(reporter: {
|
|
23
|
+
pointer(target: T | null): void;
|
|
24
|
+
focus(target: T | null): void;
|
|
25
|
+
}, where: (node: T | null) => ActiveWhere): {
|
|
26
|
+
over(e: {
|
|
27
|
+
readonly target: T | null;
|
|
28
|
+
}): void;
|
|
29
|
+
move(e: {
|
|
30
|
+
readonly target: T | null;
|
|
31
|
+
readonly movementX: number;
|
|
32
|
+
readonly movementY: number;
|
|
33
|
+
}): void;
|
|
34
|
+
out(e: {
|
|
35
|
+
readonly relatedTarget: T | null;
|
|
36
|
+
}): void;
|
|
37
|
+
/** On the document: focus arriving anywhere. */
|
|
38
|
+
focusIn(e: {
|
|
39
|
+
readonly target: T | null;
|
|
40
|
+
}): void;
|
|
41
|
+
/** On the document: focus going nowhere (the window losing it, a blur) fires no focusin. */
|
|
42
|
+
focusOut(e: {
|
|
43
|
+
readonly target: T | null;
|
|
44
|
+
readonly relatedTarget: T | null;
|
|
45
|
+
}): void;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Tells `report` what the pointer or focus is on in `root` (see `activeReporter` and
|
|
49
|
+
* `activeEvents`), resolving the element under them with `resolve`. Returns a cleanup.
|
|
50
|
+
*/
|
|
51
|
+
export declare function watchActive(root: HTMLElement, resolve: (node: Element | null) => PanelActive | null, report: (at: PanelActive | null) => void): {
|
|
52
|
+
dispose(): void;
|
|
53
|
+
};
|
|
@@ -399,9 +399,9 @@ export interface GpuGroupBinding {
|
|
|
399
399
|
readonly peek?: HTMLElement;
|
|
400
400
|
/** On a page: the header's controls (presets, reset, die), hidden while the card is folded. */
|
|
401
401
|
readonly controls?: HTMLElement;
|
|
402
|
-
/**
|
|
402
|
+
/** What a folded card's header says (on a page, in place of its controls). */
|
|
403
403
|
readonly summary?: HTMLElement;
|
|
404
|
-
/**
|
|
404
|
+
/** The muted line under the card's title. */
|
|
405
405
|
readonly desc?: HTMLElement;
|
|
406
406
|
/** On a page: the group's presets, as named or numbered segments, or a compact menu. */
|
|
407
407
|
readonly presets?: {
|
|
@@ -732,6 +732,12 @@ declare class GpuOverlayManager {
|
|
|
732
732
|
private clipCache;
|
|
733
733
|
/** Whether the DOM lays `el`'s text out wider than its box (so text-overflow shows "…"). */
|
|
734
734
|
private domClipped;
|
|
735
|
+
private lineCache;
|
|
736
|
+
/**
|
|
737
|
+
* The lines the DOM wraps `el`'s text into, each with its middle's offset from `el`'s top, read
|
|
738
|
+
* back word by word (kept until the text or the width changes).
|
|
739
|
+
*/
|
|
740
|
+
private linesOf;
|
|
735
741
|
private curReduced;
|
|
736
742
|
private curLocks;
|
|
737
743
|
/** The current row's selection wash (0…1), which its hover wash gives way to. */
|
|
@@ -5,6 +5,7 @@ export { computeHintPrefixes, type HintBadgeState, type HintCandidate, type Hint
|
|
|
5
5
|
export { assignPageCodes, hints, pageHints, type HintPainter, type HintSourceOptions, type PageHints, type PageHintTarget, } from "./page-hints.js";
|
|
6
6
|
export { Spring, VelocityTracker } from "./motion.js";
|
|
7
7
|
export { createPanel, mountPage, mountPanel, type Panel, type PanelSearch } from "./panel.js";
|
|
8
|
+
export type { PanelActive } from "../core/store.js";
|
|
8
9
|
export { balancedRuns, type FoldSummaryInfo, type MountPageOptions, type Page, type PageAction, type PageCard, type PageCardOptions, type PageControl, type PageFoldOptions, type PageFoldSource, type PageGroupInfo, type PageHighlight, type PageSectionInfo, } from "./page.js";
|
|
9
10
|
export { createPresetStrip, nextPresetIndex, resolveActivePresetEntry, type ActivePresetState, type PresetEntry, type PresetStrip, } from "./presets.js";
|
|
10
11
|
export { sound, type CueDetail, type Sound, type SoundCue, type SoundLayer, type SoundOptions, type SoundTheme, } from "./sound.js";
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import type { StoragePort } from "../core/storage.js";
|
|
2
|
-
import type { MountPanelOptions, SectionFolded } from "../core/store.js";
|
|
2
|
+
import type { FoldSummaryInfo, MountPanelOptions, SectionFolded } from "../core/store.js";
|
|
3
3
|
import type { Panel } from "./panel.js";
|
|
4
|
-
|
|
5
|
-
export interface FoldSummaryInfo {
|
|
6
|
-
/** The group preset its values match (its name), "Custom" when none does; null without any. */
|
|
7
|
-
readonly preset: string | null;
|
|
8
|
-
/** How many of its rows differ from their defaults (a spring's two keys count once). */
|
|
9
|
-
readonly changed: number;
|
|
10
|
-
/** Its value keys. */
|
|
11
|
-
readonly keys: readonly string[];
|
|
12
|
-
}
|
|
4
|
+
export type { FoldSummaryInfo };
|
|
13
5
|
export interface MountPageOptions extends MountPanelOptions {
|
|
14
6
|
/** Section order; sections not listed follow in the order they first appear. */
|
|
15
7
|
readonly sections?: readonly string[];
|
|
@@ -39,11 +31,6 @@ export interface MountPageOptions extends MountPanelOptions {
|
|
|
39
31
|
* opening it if it's folded.
|
|
40
32
|
*/
|
|
41
33
|
readonly nav?: boolean;
|
|
42
|
-
/**
|
|
43
|
-
* What a folded group card's header says. By default "<matching preset, or Custom> · N
|
|
44
|
-
* changed", leaving out what doesn't apply. `null` shows nothing.
|
|
45
|
-
*/
|
|
46
|
-
readonly groupSummary?: (group: string, info: FoldSummaryInfo) => string | Node | null;
|
|
47
34
|
/** What a folded section's heading says ("N changed · M cards" by default; `preset` is null). */
|
|
48
35
|
readonly sectionSummary?: (section: string, info: FoldSummaryInfo) => string | Node | null;
|
|
49
36
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Controls } from "../core/store.js";
|
|
2
|
+
/** A row of a folded panel card, as its summary reads it. */
|
|
3
|
+
export interface SummaryRow {
|
|
4
|
+
/** Its value as text ("CSS orb", "12 px"), or "" where it has no short form. */
|
|
5
|
+
readonly value: string;
|
|
6
|
+
/** Whether it differs from its default. */
|
|
7
|
+
readonly changed: boolean;
|
|
8
|
+
/** Whether it's a choice (the likeliest to say what the card is set to). */
|
|
9
|
+
readonly choice: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A folded panel card's summary, by default: a one-row card's value; otherwise the most telling
|
|
13
|
+
* value (its matching group preset, else its first changed value, else its first choice) and
|
|
14
|
+
* "N changed", each only where it applies. `preset` is null for a card without group presets.
|
|
15
|
+
*/
|
|
16
|
+
export declare function panelFoldSummary(rows: readonly SummaryRow[], preset: string | null): string;
|
|
17
|
+
/**
|
|
18
|
+
* Which presets the values are measured against (the panel's, each scope's and each group's).
|
|
19
|
+
* It changes when one is chosen or saved even if no value moves, which still changes what counts
|
|
20
|
+
* as changed: marks, resets and folded summaries follow it.
|
|
21
|
+
*/
|
|
22
|
+
export declare const baseSignature: (controls: Pick<Controls<any>, "activePreset" | "activeGroupPreset" | "scopes" | "groupPresets">) => string;
|