@kitlangton/knobs 0.1.0 → 0.3.0

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.
Files changed (65) hide show
  1. package/README.md +303 -41
  2. package/dist/dev/{chunk-qtt698hp.js → chunk-7xgkerd2.js} +34 -11
  3. package/dist/dev/{chunk-ktsn42p7.js → chunk-jx1ktap3.js} +13762 -11794
  4. package/dist/dev/{chunk-kdewv0mt.js → chunk-pmkvb9ep.js} +1692 -207
  5. package/dist/dev/{chunk-e13znr7c.js → chunk-v8ssbcbk.js} +18 -7
  6. package/dist/dev/index.js +557 -130
  7. package/dist/index.js +589 -141
  8. package/dist/panel.js +21615 -17048
  9. package/dist/types/core/keys.d.ts +2 -0
  10. package/dist/types/core/random.d.ts +61 -1
  11. package/dist/types/core/remote.d.ts +5 -1
  12. package/dist/types/core/schema.d.ts +35 -6
  13. package/dist/types/core/store.d.ts +97 -8
  14. package/dist/types/core/unlock.d.ts +77 -0
  15. package/dist/types/core/variants.d.ts +10 -2
  16. package/dist/types/index.d.ts +4 -2
  17. package/dist/types/panel/attach.d.ts +2 -0
  18. package/dist/types/panel/crumbs.d.ts +19 -0
  19. package/dist/types/panel/editors.d.ts +20 -0
  20. package/dist/types/panel/feel.d.ts +103 -0
  21. package/dist/types/panel/flash.d.ts +13 -0
  22. package/dist/types/panel/give.d.ts +34 -1
  23. package/dist/types/panel/gpu/color.d.ts +4 -4
  24. package/dist/types/panel/gpu/renderer.d.ts +110 -6
  25. package/dist/types/panel/gpu/shader.wgsl.d.ts +1 -1
  26. package/dist/types/panel/haptics.d.ts +6 -2
  27. package/dist/types/panel/has.d.ts +0 -1
  28. package/dist/types/panel/index.d.ts +3 -1
  29. package/dist/types/panel/inspector.d.ts +33 -3
  30. package/dist/types/panel/lens.d.ts +113 -0
  31. package/dist/types/panel/motion.d.ts +2 -0
  32. package/dist/types/panel/optkeys.d.ts +7 -0
  33. package/dist/types/panel/page-hints.d.ts +2 -1
  34. package/dist/types/panel/prefs.d.ts +2 -2
  35. package/dist/types/panel/presence.d.ts +20 -0
  36. package/dist/types/panel/presets.d.ts +17 -5
  37. package/dist/types/panel/ramp-morph.d.ts +117 -0
  38. package/dist/types/panel/ramps.d.ts +1 -1
  39. package/dist/types/panel/reel.d.ts +43 -0
  40. package/dist/types/panel/scope.d.ts +48 -0
  41. package/dist/types/panel/slider.d.ts +22 -5
  42. package/dist/types/panel/sound.d.ts +137 -0
  43. package/dist/types/panel/styles.d.ts +1 -1
  44. package/dist/types/panel/tab-transition.d.ts +15 -0
  45. package/dist/types/panel/tabs.d.ts +1 -1
  46. package/dist/types/panel/toggle.d.ts +27 -0
  47. package/dist/types/panel/toolbar-grid.d.ts +6 -0
  48. package/dist/types/ui/activity-css.d.ts +1 -0
  49. package/dist/types/ui/activity-model.d.ts +122 -0
  50. package/dist/types/ui/activity.d.ts +67 -0
  51. package/dist/types/ui/follow.d.ts +17 -0
  52. package/dist/types/ui/index.d.ts +5 -1
  53. package/dist/types/ui/look.d.ts +9 -0
  54. package/dist/types/ui/picker-css.d.ts +1 -0
  55. package/dist/types/ui/picker-model.d.ts +53 -0
  56. package/dist/types/ui/picker.d.ts +67 -0
  57. package/dist/types/ui/react.d.ts +27 -0
  58. package/dist/types/ui/scrollbar.d.ts +12 -0
  59. package/dist/types/ui/solid.d.ts +23 -0
  60. package/dist/types/ui/split-model.d.ts +55 -0
  61. package/dist/types/ui/split.d.ts +22 -3
  62. package/dist/ui-react.js +3462 -690
  63. package/dist/ui-solid.js +3440 -689
  64. package/dist/ui.js +2963 -264
  65. package/package.json +1 -1
@@ -19,3 +19,5 @@ export declare function matchesCombo(combo: string, event: KeyboardEvent): boole
19
19
  */
20
20
  export declare function comboParts(combo: string, apple?: boolean): string[];
21
21
  export declare function comboLabel(combo: string, apple?: boolean): string;
22
+ /** Whether a key goes to what has focus: a text field, a select or editable text. */
23
+ export declare function isTypingTarget(target: EventTarget | null): boolean;
@@ -1,2 +1,62 @@
1
- import { type Schema, type Values } from "./schema.js";
1
+ import { type CurvePoint, type GradientStop } from "./ramp.js";
2
+ import { type Schema, type ValueField, type Values } from "./schema.js";
3
+ /** One field's roll across its whole range (see `randomValues`). */
4
+ export declare function pick(field: ValueField, random: () => number): string | number | boolean | number[] | readonly [0.25, 0.1, 0.25, 1] | readonly [0.42, 0, 1, 1] | readonly [0, 0, 0.58, 1] | readonly [0.42, 0, 0.58, 1] | readonly [0, 0, 1, 1] | readonly [0.65, 0, 0.35, 1] | readonly [0.32, 0.72, 0, 1] | readonly [0.34, 1.56, 0.64, 1] | GradientStop[] | CurvePoint[] | {
5
+ duration: number;
6
+ bounce: number;
7
+ l?: undefined;
8
+ c?: undefined;
9
+ h?: undefined;
10
+ x?: undefined;
11
+ y?: undefined;
12
+ z?: undefined;
13
+ } | {
14
+ l: number;
15
+ c: number;
16
+ h: number;
17
+ duration?: undefined;
18
+ bounce?: undefined;
19
+ x?: undefined;
20
+ y?: undefined;
21
+ z?: undefined;
22
+ } | {
23
+ x: number;
24
+ y: number;
25
+ duration?: undefined;
26
+ bounce?: undefined;
27
+ l?: undefined;
28
+ c?: undefined;
29
+ h?: undefined;
30
+ z?: undefined;
31
+ } | {
32
+ x: number;
33
+ y: number;
34
+ z: number;
35
+ duration?: undefined;
36
+ bounce?: undefined;
37
+ l?: undefined;
38
+ c?: undefined;
39
+ h?: undefined;
40
+ };
41
+ /**
42
+ * How far Random strays from an active preset. A number lands in a window 35% of its range wide,
43
+ * centred on the preset's value and slid inward at an end so it keeps its width: far enough to
44
+ * be a new take, near enough to stay recognisably that preset (Mutate moves 8–12% from the
45
+ * current values; a free roll covers 100%). Choices and opted-in switches keep the preset's
46
+ * pick, changing `switch` of the time. Colours keep their family: hue within ±`hue`°,
47
+ * lightness ±`lightness`, chroma ±`chroma` of what that hue can hold. Seeds roll freely: a new
48
+ * pattern is what a seed is for.
49
+ */
50
+ export declare const AROUND: {
51
+ readonly spread: 0.35;
52
+ readonly switch: 0.3;
53
+ readonly hue: 20;
54
+ readonly lightness: 0.06;
55
+ readonly chroma: 0.15;
56
+ };
57
+ /**
58
+ * One field's roll around `base` (an active preset's value): see `AROUND`. Fields with nothing
59
+ * to vary around (keys, seeds) roll as `pick` does.
60
+ */
61
+ export declare function around(field: ValueField, base: unknown, random: () => number): unknown;
2
62
  export declare function randomValues<S extends Schema>(schema: S, random?: () => number): Values<S>;
@@ -26,9 +26,11 @@ export interface SpringBindingDescriptor extends DescriptorMeta {
26
26
  };
27
27
  readonly default?: SpringValue;
28
28
  }
29
- /** A button row: pressing it calls `onAction(key)`. */
29
+ /** A button row: pressing it calls `onAction(key)`. `shortcut` takes a combination here. */
30
30
  export interface ActionDescriptor extends DescriptorMeta {
31
31
  readonly kind: "action";
32
+ readonly global?: boolean;
33
+ readonly rebind?: boolean;
32
34
  readonly default?: undefined;
33
35
  readonly bind?: undefined;
34
36
  }
@@ -116,6 +118,8 @@ export interface RemoteControlsOptions {
116
118
  readonly enabled?: boolean | (() => boolean);
117
119
  /** Ships the panel as product UI, live in production: `() => import("knobs/panel")` (see `ControlsOptions.ship`). */
118
120
  readonly ship?: () => Promise<PanelModule>;
121
+ /** The key that shows and hides its panels with the page's others (see `ControlsOptions.toggleKey`). */
122
+ readonly toggleKey?: string | false;
119
123
  /** How long (ms) a key changed here outranks incoming values for it; 600 by default. */
120
124
  readonly echo?: number;
121
125
  /**
@@ -20,7 +20,10 @@ export interface FieldMeta {
20
20
  /** One muted line under the section's heading on a page (the first field that gives one wins). */
21
21
  readonly sectionDescription?: string;
22
22
  readonly shortcut?: string;
23
- /** `false` keeps the field out of Random and Mutate, as a lock does, but by declaration. */
23
+ /**
24
+ * `false` keeps the field out of Random and Mutate, as a lock does, but by declaration. Switches,
25
+ * seeds and key combinations start out; `true` brings them in.
26
+ */
24
27
  readonly random?: boolean;
25
28
  /**
26
29
  * The row dims and takes no edits from the pointer or keys (focus can still reach it, to read
@@ -75,6 +78,11 @@ export interface Knob extends FieldMeta {
75
78
  /** Digits after the point in the readout; the step's own by default. */
76
79
  readonly decimals?: number;
77
80
  readonly spring?: SpringPart;
81
+ /**
82
+ * Whether a drag sticks for a moment at the default, the active preset's value and the middle
83
+ * (and zero); `false` lets it glide straight through.
84
+ */
85
+ readonly detents?: boolean;
78
86
  }
79
87
  export interface Choice<T extends string = string> extends FieldMeta {
80
88
  readonly kind: "choice";
@@ -141,6 +149,12 @@ export interface CurveField extends FieldMeta {
141
149
  export interface ActionField extends FieldMeta {
142
150
  readonly kind: "action";
143
151
  readonly run?: () => void;
152
+ /** The combination that presses it (`"Shift+K"`), shown as a key cap at the row's end. */
153
+ readonly shortcut?: string;
154
+ /** Where `shortcut` works, as a toolbar button's (see `ToolbarShortcut`); `"panel"` by default. */
155
+ readonly scope?: "page" | "panel" | "none";
156
+ /** The user can rebind it in place: a click or Enter on the key cap listens for a new one. */
157
+ readonly rebind?: boolean;
144
158
  }
145
159
  export interface Vec3 {
146
160
  readonly x: number;
@@ -350,9 +364,17 @@ export declare function choice<const T extends readonly [string, ...string[]]>(v
350
364
  readonly labels?: Readonly<Partial<Record<T[number], string>>>;
351
365
  readonly display?: "menu" | "segmented";
352
366
  }): Choice<T[number]>;
353
- /** `action("Replay", { run })`: a button row. Its key also reaches the store's `onAction`. */
354
- export declare function action(label?: string, meta?: Omit<FieldMeta, "label"> & {
367
+ /**
368
+ * `action("Replay", { run, shortcut: "Shift+K", global: true, rebind: true })`: a button row. Its
369
+ * key also reaches the store's `onAction`. `shortcut` presses it as a toolbar button's does (a
370
+ * combination, or `{ key, scope }`), and `rebind` lets the user change it from its key cap.
371
+ */
372
+ export declare function action(label?: string, meta?: Omit<FieldMeta, "label" | "shortcut"> & {
355
373
  run?: () => void;
374
+ shortcut?: string | ToolbarShortcut;
375
+ /** A plain-string shortcut works anywhere on the page (as `scope: "page"`). */
376
+ global?: boolean;
377
+ rebind?: boolean;
356
378
  }): ActionField;
357
379
  type SpringHalf = number | Knob;
358
380
  /**
@@ -369,6 +391,10 @@ export declare function flatSpring(parts?: {
369
391
  readonly duration: Knob;
370
392
  readonly bounce: Knob;
371
393
  };
394
+ /**
395
+ * A switch. Switches usually show or hide things (a view, a scene's parts), so Random and Mutate
396
+ * leave them alone unless `random: true`.
397
+ */
372
398
  export declare function toggle(value: boolean, meta?: FieldMeta): Toggle;
373
399
  /** `spring({ duration: 0.4, bounce: 0.2 })`, like SwiftUI's `.spring(duration:bounce:)`. */
374
400
  export declare function spring(value?: Partial<SpringValue>, meta?: FieldMeta): SpringField;
@@ -469,11 +495,14 @@ export declare function curve(points: readonly CurvePoint[] | keyof typeof CURVE
469
495
  readonly presets?: Readonly<Record<string, readonly CurvePoint[]>>;
470
496
  readonly shapes?: boolean;
471
497
  }): CurveField;
472
- /** A toolbar item's shortcut as `{ key, scope }`, or undefined without one. */
473
- export declare function toolbarShortcut(item: ToolbarItem): {
498
+ type ResolvedShortcut = {
474
499
  readonly key: string;
475
500
  readonly scope: "page" | "panel" | "none";
476
- } | undefined;
501
+ };
502
+ /** A toolbar item's shortcut as `{ key, scope }`, or undefined without one. */
503
+ export declare function toolbarShortcut(item: ToolbarItem): ResolvedShortcut | undefined;
504
+ /** An action row's shortcut as `{ key, scope }`, or undefined without one. */
505
+ export declare function actionShortcut(field: ActionField): ResolvedShortcut | undefined;
477
506
  /** A wrapping row of buttons with icons and shortcut chips; holds no value. */
478
507
  export declare function toolbar(items: readonly ToolbarItem[], meta?: FieldMeta): ToolbarField;
479
508
  type ReadoutOptions = Omit<ReadoutMeta, "shortcut" | "random" | "readOnly">;
@@ -15,7 +15,8 @@ export interface ChipOptions {
15
15
  /**
16
16
  * When the chip shows. `"hover"`: while the element is hovered or focused. `"shortcut"`: hidden
17
17
  * until `shortcut` turns on knobs mode (remembered for the tab), then on hover. `"always"`:
18
- * whenever its panel is closed. `"hover"` in development, `"shortcut"` in production builds.
18
+ * whenever its panel is closed. `"hover"` in development (and on a page someone unlocked),
19
+ * `"shortcut"` for a shipped store in production builds.
19
20
  */
20
21
  readonly reveal?: RevealMode;
21
22
  /** The key that toggles knobs mode for `"shortcut"` chips: `"Alt+Shift+D"` (⌥⇧D) by default. */
@@ -36,6 +37,16 @@ export interface ChangeMeta {
36
37
  readonly coalesce?: boolean;
37
38
  readonly key?: string;
38
39
  readonly group?: string;
40
+ /** A reset that went back to the active preset (see `Controls.activePreset`), not the defaults. */
41
+ readonly base?: boolean;
42
+ }
43
+ /**
44
+ * The preset last applied from the strip: a schema preset (by id) or a saved variant. It's the
45
+ * base Reset returns to and Random explores around.
46
+ */
47
+ export interface PresetRef {
48
+ readonly kind: "preset" | "variant";
49
+ readonly id: string;
39
50
  }
40
51
  export interface ControlAction<V = State> {
41
52
  readonly id?: string;
@@ -86,7 +97,7 @@ export type PanelCommand = "random" | "mutate" | "reset" | "resetAll" | "compare
86
97
  export type PanelKeys = {
87
98
  readonly [C in PanelCommand]?: string | boolean;
88
99
  } & {
89
- /** `false` drops the j/k and h/l aliases: only the arrows move and adjust; the letters reach the page. */
100
+ /** `false` drops the j/k aliases: only the arrows move; the letters reach the page. */
90
101
  readonly vim?: boolean;
91
102
  /**
92
103
  * `false` stops digits, `.`, `-` and Enter from opening a slider's inline number editor (they
@@ -98,6 +109,14 @@ export type PanelKeys = {
98
109
  /** `false` stops ↑/↓ at the first and last rows instead of wrapping around. */
99
110
  readonly wrap?: boolean;
100
111
  };
112
+ /** `glass` (the default): ticks with a faint ring. `mech`: drier, the rings all but gone. */
113
+ export type SoundTheme = "glass" | "mech";
114
+ /** A mount's `sound`, beyond `true`. */
115
+ export interface SoundOptions {
116
+ /** This panel's level, 0–1 (times `sound.volume`). */
117
+ readonly volume?: number;
118
+ readonly theme?: SoundTheme;
119
+ }
101
120
  /** A header button (see `MountPanelOptions.header`). */
102
121
  export type HeaderButton = "random" | "reset" | "copy" | "undo" | "redo" | "help" | "close";
103
122
  export interface HeaderOptions {
@@ -126,6 +145,12 @@ export interface MountPanelOptions extends LabelOptions {
126
145
  * don't join the page's. `keys: { hint: false }` turns hint mode off here either way.
127
146
  */
128
147
  readonly hints?: "page" | "panel";
148
+ /**
149
+ * The key that shows and hides every panel and chip on the page that listens for it, outside
150
+ * text fields and without modifiers; remembered per store. The store's `toggleKey` by default
151
+ * (a backtick), `false` on a full page (`mountPage`). `false`: this panel always shows.
152
+ */
153
+ readonly toggleKey?: string | false;
129
154
  /**
130
155
  * `false` hides what says a value differs from its default: modified dots and readout marks,
131
156
  * group reset icons and the header's Reset. For panels whose values belong to someone else
@@ -141,6 +166,11 @@ export interface MountPanelOptions extends LabelOptions {
141
166
  * list, every button but `help` shows (see `HeaderOptions`).
142
167
  */
143
168
  readonly header?: boolean | HeaderOptions;
169
+ /**
170
+ * `false` drops the scope line beside the title (`Shape › Cohesion`, where keys go, and a faint
171
+ * `esc ↑ Shape` for where Esc goes). The focus lens and the armed frame stay.
172
+ */
173
+ readonly crumbs?: boolean;
144
174
  /**
145
175
  * `false`: no per-tab session, so a rebuilt panel doesn't take focus back to the row it last
146
176
  * had or reopen editors and folded groups. On while the store has storage.
@@ -185,6 +215,18 @@ export interface MountPanelOptions extends LabelOptions {
185
215
  * Reduced motion turns them off too.
186
216
  */
187
217
  readonly haptics?: boolean;
218
+ /**
219
+ * Magnetic detents on slider drags and pans (the default, the active preset's value, the middle
220
+ * and zero); on by default, `false` turns them off for the whole panel.
221
+ */
222
+ readonly detents?: boolean;
223
+ /**
224
+ * Sound effects: quiet, short ticks and clicks synthesized live for the panel's moments (a
225
+ * slider's detents, a switch, Random, Copy…). Off by default; `true` turns them on, or
226
+ * `{ volume, theme }`. The page's switch (`sound.enable(false)` from `knobs/panel`) mutes every
227
+ * panel, and the `?` popover offers it as Sounds, kept in the store's storage.
228
+ */
229
+ readonly sound?: boolean | SoundOptions;
188
230
  /** Called once the panel is mounted, with a handle to focus it (a host's "Tune" key). */
189
231
  readonly onReady?: (panel: PanelHandle) => void;
190
232
  /** What Copy (and ⇧C) puts on the clipboard; `controls.code()` by default. */
@@ -255,6 +297,8 @@ export interface PanelHandle {
255
297
  export interface PanelModule {
256
298
  mountPanel(controls: Controls<any>, container: HTMLElement, options?: MountPanelOptions): PanelHandle;
257
299
  attach(controls: Controls<any>, element: HTMLElement, chip?: ChipOptions): () => void;
300
+ /** A brief status at the bottom of the page ("knobs unlocked"). */
301
+ status?(text: string): void;
258
302
  }
259
303
  export interface ControlsOptions<S extends Schema> extends LabelOptions {
260
304
  enabled?: boolean | (() => boolean);
@@ -267,6 +311,12 @@ export interface ControlsOptions<S extends Schema> extends LabelOptions {
267
311
  ship?: () => Promise<PanelModule>;
268
312
  /** The chip `attach` shows: its label, when it shows and the knobs-mode key (see `ChipOptions`). */
269
313
  chip?: ChipOptions;
314
+ /**
315
+ * The key that shows and hides this store's panels and chips, with every other panel on the
316
+ * page that listens for it (see `MountPanelOptions.toggleKey`). A backtick by default; `false`
317
+ * for a shipped store (`ship`), whose panels are product UI. `false`: always shown.
318
+ */
319
+ toggleKey?: string | false;
270
320
  /**
271
321
  * The longest (ms) a key changed here outranks incoming values for it (see `sync`), so an echo
272
322
  * of an older value still in flight can't pull it back. The hold ends early once the remote
@@ -345,6 +395,8 @@ export interface Controls<S extends Schema = Schema> {
345
395
  readonly reveal: RevealMode;
346
396
  readonly shortcut: string;
347
397
  };
398
+ /** The key that shows and hides its panels and chips, or `false` (see `ControlsOptions.toggleKey`). */
399
+ readonly toggleKey: string | false;
348
400
  /** How page cards show group presets (`groupPresetDisplay`, `auto` by default). */
349
401
  readonly groupPresetDisplay: GroupPresetDisplay;
350
402
  /** A group's display label (`groupLabels`, else its fields' `groupLabel`), if it has one. */
@@ -406,17 +458,28 @@ export interface Controls<S extends Schema = Schema> {
406
458
  readonly canRedo: boolean;
407
459
  subscribe(cb: () => void): () => void;
408
460
  /**
409
- * Back to the defaults: every field, or only `keys`. Locks don't apply; disabled and read-only
410
- * fields keep their values.
461
+ * Back to the active preset (see `activePreset`) while anything differs from it, else to the
462
+ * defaults: every field, or only `keys`. Reaching the defaults with every field also forgets
463
+ * the active preset. Locks don't apply; disabled and read-only fields keep their values.
411
464
  */
412
465
  reset(keys?: readonly (keyof S & string)[]): void;
466
+ /** One key, as `reset` does: to the active preset's value, else (already there) its default. */
413
467
  resetKey(key: keyof S & string): void;
468
+ /**
469
+ * A group, as `reset` does: to its active group preset (or the panel's active preset) while it
470
+ * differs from it, else to its defaults, which forgets the group's preset.
471
+ */
414
472
  resetGroup(group: string): void;
473
+ /** Whether the key (or anything) differs from the active preset; the defaults without one. */
415
474
  isModified(key?: keyof S & string): boolean;
475
+ /** Whether `reset` (of every field, or `keys`) would change anything. */
476
+ canReset(keys?: readonly (keyof S & string)[]): boolean;
477
+ /** What differs from the defaults (the values `code()` and Swift export care about). */
416
478
  modified(): Partial<Values<S>>;
417
479
  /**
418
480
  * Random values for every field that can roll (see `canRoll`), or those among `keys`, drawn
419
- * from `random`. One undo step.
481
+ * from `random`. With an active preset, they land near its values (see `AROUND`) instead of
482
+ * anywhere in range. One undo step.
420
483
  */
421
484
  randomize(keys?: readonly (keyof S & string)[]): void;
422
485
  /** Small changes around the current values instead of a re-roll; skips what can't roll. */
@@ -424,6 +487,17 @@ export interface Controls<S extends Schema = Schema> {
424
487
  /** Keeps fields out of Random and Mutate (or lets them back in). Saved with the values. */
425
488
  lock(keys: (keyof S & string) | readonly (keyof S & string)[], locked?: boolean): void;
426
489
  isLocked(key: keyof S & string): boolean;
490
+ /**
491
+ * The combination that presses an action row now, or a toolbar's button (`item`, its id or
492
+ * label): the user's rebinding, else the declared `shortcut`; `""` when cleared, undefined
493
+ * when there's none.
494
+ */
495
+ shortcut(key: keyof S & string, item?: string): string | undefined;
496
+ /**
497
+ * Rebinds an action row's shortcut (or a toolbar button's): a combination, `""` for none, or
498
+ * undefined to restore the declared one. Not undoable; saved with the values, as locks are.
499
+ */
500
+ rebind(key: keyof S & string, combo: string | undefined, item?: string): void;
427
501
  /**
428
502
  * Whether Random and Mutate may change `key`: it holds a value and isn't locked, `random:
429
503
  * false`, refused by `tunable`, disabled or read-only.
@@ -451,13 +525,28 @@ export interface Controls<S extends Schema = Schema> {
451
525
  */
452
526
  compare(hold: boolean): void;
453
527
  readonly comparing: boolean;
454
- preset(id: string): void;
528
+ /**
529
+ * Applies a preset and makes it the active one (`"default"` clears it); one undo step, or
530
+ * folded into the last with `coalesce` (a pan across the strip).
531
+ */
532
+ preset(id: string, options?: {
533
+ coalesce?: boolean;
534
+ }): void;
455
535
  matchingPreset(): string;
536
+ /**
537
+ * The preset (or saved variant) last applied, which Reset returns to and Random explores
538
+ * around, or null. Kept with the values; forgotten by a reset to the defaults.
539
+ */
540
+ activePreset(): PresetRef | null;
541
+ /** The group preset last applied to `group` (0-based), or -1. */
542
+ activeGroupPreset(group: string): number;
543
+ /** The values Reset returns to first: the active presets over the defaults. */
544
+ baseValues(): Values<S>;
456
545
  /** Each group's numbered presets, complete over the keys they cover. */
457
546
  readonly groupPresets: Readonly<Record<string, readonly GroupPreset<Values<S>>[]>>;
458
547
  /**
459
- * Applies a group's preset (0-based), one undo step; `coalesce` folds it into the previous
460
- * one (a pan across a card's presets).
548
+ * Applies a group's preset (0-based) and makes it the group's active one, one undo step;
549
+ * `coalesce` folds it into the previous one (a pan across a card's presets).
461
550
  */
462
551
  groupPreset(group: string, index: number, options?: {
463
552
  coalesce?: boolean;
@@ -0,0 +1,77 @@
1
+ import type { PanelModule } from "./store.js";
2
+ /** The Konami code: ↑ ↑ ↓ ↓ ← → ← → B A, as key names for `unlock({ sequence })`. */
3
+ export declare const KONAMI: readonly string[];
4
+ export type Corner = "top-left" | "top-right" | "bottom-left" | "bottom-right";
5
+ export interface UnlockOptions {
6
+ /**
7
+ * The panel code, loaded only once someone unlocks: `() => import("knobs/panel")`. Your bundler
8
+ * splits it into its own chunk, so visitors who never unlock download none of it.
9
+ */
10
+ readonly panel: () => Promise<PanelModule>;
11
+ /**
12
+ * Typed anywhere outside a text field: a word (`"knobs"` by default) or a list of key names
13
+ * (`KONAMI`). Typing it again locks. `false`: none.
14
+ */
15
+ readonly sequence?: string | readonly string[] | false;
16
+ /** `?knobs` in the URL unlocks and `?knobs=off` locks (`"knobs"` by default). `false`: none. */
17
+ readonly query?: string | false;
18
+ /**
19
+ * Three quick taps or clicks in this corner of the window (`"top-right"` by default), for
20
+ * phones. Three more lock. `false`: none.
21
+ */
22
+ readonly corner?: Corner | false;
23
+ /** Remember being unlocked in localStorage until it's locked again (default true). */
24
+ readonly persist?: boolean;
25
+ }
26
+ /** A page's secret unlock (see `unlock`). */
27
+ export interface Unlock {
28
+ readonly unlocked: boolean;
29
+ unlock(): void;
30
+ lock(): void;
31
+ /** Stops listening for the sequence and the corner; the state stays as it is. */
32
+ dispose(): void;
33
+ }
34
+ /** Whether this page has been unlocked (see `unlock`). */
35
+ export declare const isUnlocked: () => boolean;
36
+ /** The panel module an unlocked page mounts from, if `unlock` named one. */
37
+ export declare const unlockedPanel: () => (() => Promise<PanelModule>) | undefined;
38
+ /** Calls `listener` whenever the page locks or unlocks. */
39
+ export declare function onUnlock(listener: () => void): () => void;
40
+ export declare const UNLOCK_KEY = "knobs:unlocked";
41
+ /** The keys a sequence is typed with: a word's letters, or key names as given. */
42
+ export declare function sequenceKeys(sequence: string | readonly string[]): string[];
43
+ /**
44
+ * Watches keys go by for `sequence`: true on the key that completes it. Letters match in either
45
+ * case, a wrong key starts over (keeping what it could begin), and a pause longer than `gap` ms
46
+ * forgets what was typed.
47
+ */
48
+ export declare function sequenceMatcher(sequence: string | readonly string[], gap?: number): (key: string, at: number) => boolean;
49
+ /**
50
+ * What the URL says: `?knobs` (or `=1`, `=on`, `=true`) unlocks, `?knobs=off` (or `=0`, `=false`,
51
+ * `=lock`) locks, and without it, undefined.
52
+ */
53
+ export declare function queryUnlock(search: string, name: string): boolean | undefined;
54
+ /**
55
+ * Whether a page view starts unlocked: the URL decides when it says, else what was remembered
56
+ * (only when `persist`). `remember` is what to keep afterwards (undefined: leave it).
57
+ */
58
+ export declare function resolveUnlock(input: {
59
+ readonly query: boolean | undefined;
60
+ readonly stored: boolean;
61
+ readonly persist: boolean;
62
+ }): {
63
+ readonly unlocked: boolean;
64
+ readonly remember: boolean | undefined;
65
+ };
66
+ /** Whether a point is in `corner` of a `width` × `height` window, within `size` px. */
67
+ export declare function inCorner(x: number, y: number, width: number, height: number, corner: Corner, size?: number): boolean;
68
+ /** Counts quick taps: true on the `count`th, each within `within` ms of the last. */
69
+ export declare function tapCounter(count?: number, within?: number): (at: number) => boolean;
70
+ /**
71
+ * Lets this deploy be unlocked: stores stay locked to their shipped values and mount nothing
72
+ * until someone types the sequence, opens the page with `?knobs`, or taps the corner three
73
+ * times. Then they come alive (values from this browser's storage) and the panel code loads.
74
+ * The same again locks, back to the shipped values. In development it changes nothing: stores
75
+ * are live there anyway.
76
+ */
77
+ export declare function unlock(options: UnlockOptions): Unlock;
@@ -9,9 +9,17 @@ export interface Variants<V> {
9
9
  readonly ready: boolean;
10
10
  list(): readonly Variant<V>[];
11
11
  save(name?: string): Variant<V>;
12
- load(id: string): void;
12
+ /**
13
+ * Applies a saved variant, making it the active preset (see `Controls.activePreset`); one undo
14
+ * step, or folded into the last with `coalesce` (a pan across the strip).
15
+ */
16
+ load(id: string, options?: {
17
+ coalesce?: boolean;
18
+ }): void;
19
+ /** Renames a saved variant (an empty name clears it); kept in storage with the variants. */
20
+ rename(id: string, name: string): void;
13
21
  remove(id: string): void;
14
22
  export(): string;
15
23
  import(json: string): void;
16
24
  }
17
- export declare function createVariants<S extends Schema>(schema: () => S, id: string, key: string, storage: () => StoragePort | null, get: () => Values<S>, set: (v: Values<S>) => void, notify: () => void, enabled: () => boolean): Variants<Values<S>>;
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): Variants<Values<S>>;
@@ -1,12 +1,14 @@
1
- export { knob, choice, toggle, spring, color, point, easing, action, flatSpring, vec2, vec3, range, angle, seed, keybind, gradient, curve, toolbar, toolbarShortcut, watch, sparkline, graph, meter, table, log, hasValue, isReadout, wrapAngle, SPRING, type FieldMeta, type ActionField, type SpringPart, type ValueField, type Knob, type Choice, type Toggle, type SpringField, type ColorField, type PointField, type EasingField, type VecField, type Vec3, type RangeField, type AngleField, type SeedField, type KeybindField, type GradientField, type CurveField, type ToolbarField, type ToolbarItem, type ToolbarShortcut, type WatchField, type SparklineField, type GraphField, type GraphSeries, type GraphThreshold, type MeterField, type TableField, type LogField, type ReadoutField, type ReadoutKeys, type ReadoutInput, type Point, type Field, type Schema, type Values, type Value, type State, } from "./core/schema.js";
1
+ export { knob, choice, toggle, spring, color, point, easing, action, flatSpring, vec2, vec3, range, angle, seed, keybind, gradient, curve, toolbar, toolbarShortcut, actionShortcut, watch, sparkline, graph, meter, table, log, hasValue, isReadout, wrapAngle, SPRING, type FieldMeta, type ActionField, type SpringPart, type ValueField, type Knob, type Choice, type Toggle, type SpringField, type ColorField, type PointField, type EasingField, type VecField, type Vec3, type RangeField, type AngleField, type SeedField, type KeybindField, type GradientField, type CurveField, type ToolbarField, type ToolbarItem, type ToolbarShortcut, type WatchField, type SparklineField, type GraphField, type GraphSeries, type GraphThreshold, type MeterField, type TableField, type LogField, type ReadoutField, type ReadoutKeys, type ReadoutInput, type Point, type Field, type Schema, type Values, type Value, type State, } from "./core/schema.js";
2
2
  export { springCss, springMotion, type SpringValue } from "./core/spring.js";
3
3
  export { colorHex, parseColor, type Oklch } from "./core/color.js";
4
4
  export { sampleGradient, sampleCurve, gradientCss, CURVE_SHAPES, type GradientStop, type GradientSpace, type CurvePoint, type CurveInterp, type SampleGradientOptions, type SampleCurveOptions, } from "./core/ramp.js";
5
5
  export { bezierCss, bezierPoint, EASINGS, type Bezier, type EasingName } from "./core/easing.js";
6
- export { defineControls, type ChangeMeta, type GestureEvent, type ChangeSource, type ChipOptions, type ControlAction, type Controls, type ControlsOptions, type DensityMode, type GroupPreset, type GroupPresetDisplay, 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 ThemeMode, } from "./core/store.js";
6
+ export { AROUND } from "./core/random.js";
7
+ 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, } from "./core/store.js";
7
8
  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";
8
9
  export type { LogEntry, LogLevel, TableRows, WatchValue } from "./core/readout.js";
9
10
  export { memoryStorage, urlFlag, type StoragePort } from "./core/storage.js";
11
+ export { KONAMI, unlock, type Corner, type Unlock, type UnlockOptions } from "./core/unlock.js";
10
12
  export type { TokenPreset } from "./core/theme.js";
11
13
  export { formatCode, type CodeFormat } from "./core/code.js";
12
14
  export { comboFromEvent, letterOf, comboLabel, comboParts, matchesCombo, normalizeCombo, } from "./core/keys.js";
@@ -3,3 +3,5 @@ import type { Schema } from "../core/schema.js";
3
3
  export declare function attach<S extends Schema>(controls: Controls<S>, element: HTMLElement, options?: ChipOptions): () => void;
4
4
  /** Parses `"Alt+Shift+D"` into a test for keydown events. Letters and digits match by key position. */
5
5
  export declare function matchShortcut(shortcut: string): (event: KeyboardEvent) => boolean;
6
+ /** A brief status at the bottom of the page, as knobs mode's "knobs on" ("knobs unlocked"). */
7
+ export declare function status(text: string, doc?: Document): void;
@@ -0,0 +1,19 @@
1
+ import { type Crumb, type CrumbLevel } from "./scope.js";
2
+ export declare const CRUMB_GLIDE: string;
3
+ export interface CrumbsView {
4
+ readonly element: HTMLElement;
5
+ /** The trail for where focus is, and where Esc goes from there (none at the panel). */
6
+ set(trail: readonly Crumb[], esc?: string): void;
7
+ /** Shows or quiets the Esc hint. */
8
+ hint(on: boolean): void;
9
+ /** Re-place it after the header's layout changed. */
10
+ layout(): void;
11
+ dispose(): void;
12
+ }
13
+ export declare function createCrumbs(o: {
14
+ header: HTMLElement;
15
+ title: HTMLElement;
16
+ jump: (level: CrumbLevel | "panel") => void;
17
+ reduced: () => boolean;
18
+ wake: () => void;
19
+ }): CrumbsView;
@@ -54,6 +54,17 @@ export interface EditorRow {
54
54
  dispose(): void;
55
55
  readonly tracks: readonly SubTrack[];
56
56
  readonly gpu: readonly GpuRowBinding[];
57
+ /** Named options the digits pick while the row is focused (a ramp's or easing's presets). */
58
+ readonly options?: RowOptions;
59
+ }
60
+ /** A row's options as the digits 1–9 pick them (see scope.ts). */
61
+ export interface RowOptions {
62
+ readonly count: number;
63
+ choose(i: number): void;
64
+ /** The options while they show (segments, chips), for a digit on each; empty while hidden. */
65
+ shown(): readonly HTMLElement[];
66
+ /** What stands for them while they're hidden (a menu's button, a head's mini curve). */
67
+ readonly anchor: Element;
57
68
  }
58
69
  export declare const reducedMotion: (doc: Document) => boolean;
59
70
  export declare const rgbCss: (rgb: readonly number[]) => string;
@@ -154,6 +165,15 @@ export declare function panPresets(c: RowContext<unknown>, head: HTMLElement, nu
154
165
  at(): number;
155
166
  choose(i: number, coalesce: boolean): void;
156
167
  }): void;
168
+ /** Named presets as chips in a radiogroup (the easing editor's look); ←/→ cycle them. */
169
+ export declare function presetChips(c: RowContext<unknown>, names: readonly string[], choose: (i: number) => void, cls?: string): {
170
+ el: HTMLDivElement;
171
+ buttons: HTMLButtonElement[];
172
+ cycle: (dir: number) => void;
173
+ check(i: number): void;
174
+ };
175
+ /** ←/→ on `target` itself cycle the presets (a row's head, the chips). */
176
+ export declare function cycleKeys(targets: readonly HTMLElement[], row: HTMLElement, cycle: (dir: number) => void): void;
157
177
  /** A mini curve in the head; an editor with two handles and named chips in the body. */
158
178
  export declare function easingRow(c: RowContext<EasingField>): EditorRow;
159
179
  export {};