@kitlangton/knobs 0.2.0 → 0.4.0-next.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 (62) hide show
  1. package/README.md +197 -19
  2. package/dist/dev/{chunk-gezhc24g.js → chunk-7xgkerd2.js} +14 -5
  3. package/dist/dev/{chunk-693jd1gj.js → chunk-pmkvb9ep.js} +1544 -160
  4. package/dist/dev/{chunk-5cr85vv6.js → chunk-stbcgz1y.js} +7924 -7931
  5. package/dist/dev/{chunk-4km87zfx.js → chunk-t68smqkc.js} +18 -7
  6. package/dist/dev/index.js +187 -31
  7. package/dist/index.js +197 -33
  8. package/dist/panel.js +20342 -18461
  9. package/dist/types/core/keys.d.ts +2 -0
  10. package/dist/types/core/remote.d.ts +2 -0
  11. package/dist/types/core/schema.d.ts +5 -0
  12. package/dist/types/core/store.d.ts +29 -2
  13. package/dist/types/core/unlock.d.ts +77 -0
  14. package/dist/types/index.d.ts +1 -0
  15. package/dist/types/panel/attach.d.ts +2 -0
  16. package/dist/types/panel/crumbs.d.ts +19 -0
  17. package/dist/types/panel/editors.d.ts +9 -0
  18. package/dist/types/panel/feel.d.ts +103 -0
  19. package/dist/types/panel/give.d.ts +34 -1
  20. package/dist/types/panel/gpu/color.d.ts +4 -4
  21. package/dist/types/panel/gpu/renderer.d.ts +32 -0
  22. package/dist/types/panel/gpu/shader.wgsl.d.ts +1 -1
  23. package/dist/types/panel/index.d.ts +2 -1
  24. package/dist/types/panel/inspector.d.ts +1 -1
  25. package/dist/types/panel/lens.d.ts +113 -0
  26. package/dist/types/panel/motion.d.ts +2 -0
  27. package/dist/types/panel/page-hints.d.ts +2 -1
  28. package/dist/types/panel/prefs.d.ts +2 -2
  29. package/dist/types/panel/presence.d.ts +20 -0
  30. package/dist/types/panel/scope.d.ts +29 -0
  31. package/dist/types/panel/slider.d.ts +21 -4
  32. package/dist/types/panel/tab-transition.d.ts +15 -0
  33. package/dist/types/panel/tabs.d.ts +1 -1
  34. package/dist/types/panel/toggle.d.ts +27 -0
  35. package/dist/types/ui/compare-css.d.ts +1 -0
  36. package/dist/types/ui/compare-frames.d.ts +41 -0
  37. package/dist/types/ui/compare-gpu.d.ts +58 -0
  38. package/dist/types/ui/compare-model.d.ts +169 -0
  39. package/dist/types/ui/compare-mp4.d.ts +29 -0
  40. package/dist/types/ui/compare-strip.d.ts +28 -0
  41. package/dist/types/ui/compare.d.ts +103 -0
  42. package/dist/types/ui/diff-css.d.ts +1 -0
  43. package/dist/types/ui/diff-model.d.ts +128 -0
  44. package/dist/types/ui/diff-scroll.d.ts +29 -0
  45. package/dist/types/ui/diff.d.ts +158 -0
  46. package/dist/types/ui/index.d.ts +9 -1
  47. package/dist/types/ui/look.d.ts +1 -1
  48. package/dist/types/ui/react.d.ts +66 -0
  49. package/dist/types/ui/scrollbar.d.ts +12 -0
  50. package/dist/types/ui/solid.d.ts +52 -0
  51. package/dist/types/ui/split-model.d.ts +4 -3
  52. package/dist/types/ui/split.d.ts +8 -6
  53. package/dist/types/ui/timeline.d.ts +35 -2
  54. package/dist/types/ui/tree.d.ts +7 -2
  55. package/dist/types/ui/walkthrough-css.d.ts +1 -0
  56. package/dist/types/ui/walkthrough-media.d.ts +29 -0
  57. package/dist/types/ui/walkthrough-model.d.ts +447 -0
  58. package/dist/types/ui/walkthrough.d.ts +63 -0
  59. package/dist/ui-react.js +16486 -8140
  60. package/dist/ui-solid.js +16030 -7775
  61. package/dist/ui.js +17510 -9506
  62. package/package.json +6 -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;
@@ -118,6 +118,8 @@ export interface RemoteControlsOptions {
118
118
  readonly enabled?: boolean | (() => boolean);
119
119
  /** Ships the panel as product UI, live in production: `() => import("knobs/panel")` (see `ControlsOptions.ship`). */
120
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;
121
123
  /** How long (ms) a key changed here outranks incoming values for it; 600 by default. */
122
124
  readonly echo?: number;
123
125
  /**
@@ -78,6 +78,11 @@ export interface Knob extends FieldMeta {
78
78
  /** Digits after the point in the readout; the step's own by default. */
79
79
  readonly decimals?: number;
80
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;
81
86
  }
82
87
  export interface Choice<T extends string = string> extends FieldMeta {
83
88
  readonly kind: "choice";
@@ -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. */
@@ -96,7 +97,7 @@ export type PanelCommand = "random" | "mutate" | "reset" | "resetAll" | "compare
96
97
  export type PanelKeys = {
97
98
  readonly [C in PanelCommand]?: string | boolean;
98
99
  } & {
99
- /** `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. */
100
101
  readonly vim?: boolean;
101
102
  /**
102
103
  * `false` stops digits, `.`, `-` and Enter from opening a slider's inline number editor (they
@@ -144,6 +145,12 @@ export interface MountPanelOptions extends LabelOptions {
144
145
  * don't join the page's. `keys: { hint: false }` turns hint mode off here either way.
145
146
  */
146
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;
147
154
  /**
148
155
  * `false` hides what says a value differs from its default: modified dots and readout marks,
149
156
  * group reset icons and the header's Reset. For panels whose values belong to someone else
@@ -159,6 +166,11 @@ export interface MountPanelOptions extends LabelOptions {
159
166
  * list, every button but `help` shows (see `HeaderOptions`).
160
167
  */
161
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;
162
174
  /**
163
175
  * `false`: no per-tab session, so a rebuilt panel doesn't take focus back to the row it last
164
176
  * had or reopen editors and folded groups. On while the store has storage.
@@ -203,6 +215,11 @@ export interface MountPanelOptions extends LabelOptions {
203
215
  * Reduced motion turns them off too.
204
216
  */
205
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;
206
223
  /**
207
224
  * Sound effects: quiet, short ticks and clicks synthesized live for the panel's moments (a
208
225
  * slider's detents, a switch, Random, Copy…). Off by default; `true` turns them on, or
@@ -280,6 +297,8 @@ export interface PanelHandle {
280
297
  export interface PanelModule {
281
298
  mountPanel(controls: Controls<any>, container: HTMLElement, options?: MountPanelOptions): PanelHandle;
282
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;
283
302
  }
284
303
  export interface ControlsOptions<S extends Schema> extends LabelOptions {
285
304
  enabled?: boolean | (() => boolean);
@@ -292,6 +311,12 @@ export interface ControlsOptions<S extends Schema> extends LabelOptions {
292
311
  ship?: () => Promise<PanelModule>;
293
312
  /** The chip `attach` shows: its label, when it shows and the knobs-mode key (see `ChipOptions`). */
294
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;
295
320
  /**
296
321
  * The longest (ms) a key changed here outranks incoming values for it (see `sync`), so an echo
297
322
  * of an older value still in flight can't pull it back. The hold ends early once the remote
@@ -370,6 +395,8 @@ export interface Controls<S extends Schema = Schema> {
370
395
  readonly reveal: RevealMode;
371
396
  readonly shortcut: string;
372
397
  };
398
+ /** The key that shows and hides its panels and chips, or `false` (see `ControlsOptions.toggleKey`). */
399
+ readonly toggleKey: string | false;
373
400
  /** How page cards show group presets (`groupPresetDisplay`, `auto` by default). */
374
401
  readonly groupPresetDisplay: GroupPresetDisplay;
375
402
  /** A group's display label (`groupLabels`, else its fields' `groupLabel`), if it has one. */
@@ -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;
@@ -8,6 +8,7 @@ export { defineControls, type ChangeMeta, type GestureEvent, type ChangeSource,
8
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";
9
9
  export type { LogEntry, LogLevel, TableRows, WatchValue } from "./core/readout.js";
10
10
  export { memoryStorage, urlFlag, type StoragePort } from "./core/storage.js";
11
+ export { KONAMI, unlock, type Corner, type Unlock, type UnlockOptions } from "./core/unlock.js";
11
12
  export type { TokenPreset } from "./core/theme.js";
12
13
  export { formatCode, type CodeFormat } from "./core/code.js";
13
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;
@@ -165,6 +165,15 @@ export declare function panPresets(c: RowContext<unknown>, head: HTMLElement, nu
165
165
  at(): number;
166
166
  choose(i: number, coalesce: boolean): void;
167
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;
168
177
  /** A mini curve in the head; an editor with two handles and named chips in the body. */
169
178
  export declare function easingRow(c: RowContext<EasingField>): EditorRow;
170
179
  export {};
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Where each halving of the rate happens (px off the row: from, to). ×1 within 24 px, ×½ around
3
+ * 60, ×¼ around 110, ×⅛ beyond 146.
4
+ */
5
+ export declare const PRECISION: readonly (readonly [number, number])[];
6
+ /** How far (px) a pointer at `y` is off a row spanning `top`..`bottom` (0 on it). */
7
+ export declare const offRow: (y: number, top: number, bottom: number) => number;
8
+ /** How many halvings a drag `off` px from its row moves at: 0 near it, up to 3, smoothly. */
9
+ export declare const precisionDepth: (off: number) => number;
10
+ /** The rate (value per px of the hand, relative to 1:1) a drag `off` px from its row moves at. */
11
+ export declare const precisionRate: (off: number) => number;
12
+ /** The ruler's levels: intervals across the track, tick height (px) and the accent's alpha. */
13
+ export declare const RULER: readonly (readonly [number, number, number])[];
14
+ /** The ruler shows in full within `RULER_NEAR` px of the handle, fading out by `RULER_FADE`. */
15
+ export declare const RULER_NEAR = 36;
16
+ export declare const RULER_FADE = 120;
17
+ /** The ruler's finest level a slider of `steps` steps shows (each halves the last, from tenths). */
18
+ export declare const rulerLevels: (steps: number) => number;
19
+ /** How much of ruler level `level` (1–3) shows at `depth` halvings. */
20
+ export declare const rulerAlpha: (depth: number, level: number) => number;
21
+ /** The depth a ruler shows for a drag at `rate` (with ⌥'s tenth folded in), at most `levels`. */
22
+ export declare const rulerDepth: (rate: number, levels: number) => number;
23
+ /** How much a stretched control thins per unit of strain (a row ~300 px long, 14 px over: ~.92). */
24
+ export declare const THIN = 1.7;
25
+ /** The thinnest a stretched control gets. */
26
+ export declare const THINNEST = 0.85;
27
+ /** How thin a control stretched to `scale` times its length draws, across it. */
28
+ export declare const thinOf: (scale: number) => number;
29
+ /** Over how many px of give a control's stretching corners round off fully. */
30
+ export declare const ROUND_IN = 4;
31
+ /** How far (0–1) the corners at a stretching end have rounded off. */
32
+ export declare const roundOf: (give: number) => number;
33
+ /**
34
+ * A box `length` px long, stretched `give` px past one end (positive: past its far end, negative:
35
+ * past its start): its scale along it, about the end that stays put (0 its start, 1 its far end).
36
+ */
37
+ export declare const stretchOf: (give: number, length: number) => {
38
+ scale: number;
39
+ anchor: number;
40
+ };
41
+ /**
42
+ * Where a point `at` px along a box `length` long draws while it's stretched `give` px: points near
43
+ * the anchored end barely move, the stretching end moves by the whole give.
44
+ */
45
+ export declare const stretchAt: (at: number, give: number, length: number) => number;
46
+ /**
47
+ * The most a control gives where it has `room` px before something clips it: `most`, or less so
48
+ * it never draws cut off (and a hair at least, so meeting the limit still ticks).
49
+ */
50
+ export declare const giveRoom: (room: number, most: number) => number;
51
+ export type DetentKind = "default" | "base" | "round";
52
+ /** A detent's hold (px either side where the value sticks) and the ramp past it (px). */
53
+ export declare const DETENTS: Readonly<Record<DetentKind, {
54
+ readonly hold: number;
55
+ readonly ramp: number;
56
+ }>>;
57
+ /** How strong detents are at a drag's rate: full at 1:1, weaker faster than the rate slows. */
58
+ export declare const detentScale: (rate: number) => number;
59
+ export interface Detent {
60
+ /** Where it sits along the track (px). */
61
+ readonly at: number;
62
+ /** The value it holds. */
63
+ readonly value: number;
64
+ readonly kind: DetentKind;
65
+ }
66
+ /**
67
+ * The detents of a track `width` px wide: its default first, then the active preset's value,
68
+ * then the round values given, each dropped where it would crowd one before it.
69
+ */
70
+ export declare function detentsOf(width: number, min: number, max: number, o: {
71
+ readonly value: number;
72
+ readonly base?: number;
73
+ readonly round?: readonly number[];
74
+ }): Detent[];
75
+ /** A range's round values worth a detent: its middle, and zero when it lies inside. */
76
+ export declare const roundValues: (min: number, max: number) => number[];
77
+ /**
78
+ * Where the value shows for a hand at `x` (px): on a detent within its hold, catching up a little
79
+ * faster than the hand just past it, and the hand's own position beyond. `k` scales the detents.
80
+ */
81
+ export declare function warp(x: number, list: readonly Detent[], k?: number): number;
82
+ /**
83
+ * The hand position that shows `shown` with detents at strength `k` (`warp`'s inverse); on a
84
+ * detent, the one nearest `x`. Re-anchoring here when the strength changes keeps the value still.
85
+ */
86
+ export declare function unwarp(shown: number, x: number, list: readonly Detent[], k?: number): number;
87
+ /**
88
+ * A drag's detents: where the value shows, which detent holds it, and the moment one catches (to
89
+ * tick once). Once caught, a detent catches again only after the hand has left its reach, so a
90
+ * hand resting on its edge doesn't chatter.
91
+ */
92
+ export declare class Notches {
93
+ readonly list: readonly Detent[];
94
+ /** The detent holding the value, or -1. */
95
+ held: number;
96
+ private readonly armed;
97
+ constructor(list: readonly Detent[], x: number);
98
+ /** The shown position for a hand at `x` with detents at strength `k`, and any detent caught now. */
99
+ move(x: number, k?: number): {
100
+ shown: number;
101
+ caught?: Detent;
102
+ };
103
+ }
@@ -1,5 +1,10 @@
1
1
  /** The most a control gives past an end (px). */
2
2
  export declare const GIVE = 6;
3
+ /** The most a slider's row stretches past an end (px), dragged or panned alike. */
4
+ export declare const STRETCH = 14;
5
+ /** How long (s) a give takes to spring home, with no bounce: a readout's, and a stretch's. */
6
+ export declare const HOME_S = 0.25;
7
+ export declare const STRETCH_HOME_S = 0.35;
3
8
  /** The give `over` px of push past an end makes: 1:1 at first, never reaching `max`. */
4
9
  export declare const rubber: (over: number, max?: number) => number;
5
10
  /** The push (px) that makes `give` (`rubber`'s inverse). */
@@ -62,7 +67,12 @@ export declare const giveOf: (el: Element) => number;
62
67
  * pan puts it; let go, it springs home on one loop that both renderers read: the DOM as a
63
68
  * `translate`, the GPU through `giveOf`.
64
69
  */
65
- export declare function textGive(el: HTMLElement | SVGElement, wake: () => void): {
70
+ export declare function textGive(el: HTMLElement | SVGElement, wake: () => void, o?: {
71
+ /** Seconds to spring home (`HOME_S` by default). */
72
+ readonly home?: number;
73
+ /** Draws the give in the DOM (a `translate` by default; not called while the GPU draws). */
74
+ readonly draw?: (px: number) => void;
75
+ }): {
66
76
  /** The give as last held (0 once let go). */
67
77
  readonly give: number;
68
78
  /** Holds the give at `px` (signed), ticking as it starts. */
@@ -71,3 +81,26 @@ export declare function textGive(el: HTMLElement | SVGElement, wake: () => void)
71
81
  release(): void;
72
82
  };
73
83
  export type TextGive = ReturnType<typeof textGive>;
84
+ /**
85
+ * Draws `el` stretched like rubber, `x` px past an end along it (and `y` px down or up, for a pad):
86
+ * scaled about the end that stays put, thinner across it as it lengthens (`thin`), its stretching
87
+ * corners rounding off. CSS reads the vars (.knobs-stretching); `unclip` is a parent whose clip it
88
+ * may draw past meanwhile (its card).
89
+ */
90
+ export declare function drawStretch(el: HTMLElement, x: number, y: number, size: {
91
+ readonly w: number;
92
+ readonly h: number;
93
+ }, o?: {
94
+ readonly thin?: boolean;
95
+ readonly unclip?: Element | null;
96
+ }): void;
97
+ /**
98
+ * How far (px) `el` can stretch each way before an ancestor clips it (its overflow isn't
99
+ * visible), passing over `skip` (a card it unclips while it stretches).
100
+ */
101
+ export declare function roomOf(el: Element, skip?: Element | null): {
102
+ left: number;
103
+ right: number;
104
+ top: number;
105
+ bottom: number;
106
+ };
@@ -1,7 +1,5 @@
1
- export type Rgba = readonly [number, number, number, number];
2
- export declare function parseCssColor(raw: string, fallback?: Rgba): Rgba;
3
- export declare function withAlpha(c: Rgba, alphaFactor: number): Rgba;
4
- export declare function mixColor(a: Rgba, b: Rgba, t: number): Rgba;
1
+ import { mixRgba as mixColor, parseCssColor, type Rgba, withAlpha } from "../../ui/rgba.js";
2
+ export { mixColor, parseCssColor, type Rgba, withAlpha };
5
3
  /** CSS `color-mix(in srgb, a p, b)`, interpolated with premultiplied alpha like the browser. */
6
4
  export declare function cssMix(a: Rgba, b: Rgba, p: number): Rgba;
7
5
  export interface PanelThemeTokens {
@@ -124,5 +122,7 @@ export interface PanelThemeTokens {
124
122
  readonly thumb: Rgba;
125
123
  /** How strongly small drop shadows show under thumbs and pills (--k-lift). */
126
124
  readonly lift: number;
125
+ /** How dark the panel's drop shadow is (--k-shadow-strength): .48 on dark panels, .16 on light. */
126
+ readonly shadowStrength: number;
127
127
  }
128
128
  export declare function readPanelTokens(panel: HTMLElement, version?: number): PanelThemeTokens;
@@ -3,6 +3,7 @@ import { type HeaderDie } from "../die.js";
3
3
  import { type IconName } from "../icons.js";
4
4
  import { Edges } from "../motion.js";
5
5
  import type { MorphFrame } from "../morph.js";
6
+ import type { LensShape } from "../lens.js";
6
7
  import { type PresetStrip } from "../presets.js";
7
8
  import type { DigitPlace, Slider } from "../slider.js";
8
9
  import type { TabStrip } from "../tabs.js";
@@ -111,8 +112,11 @@ export interface GpuPointRow {
111
112
  readonly x: GpuExpander;
112
113
  readonly read: HTMLElement;
113
114
  readonly pad: HTMLElement;
115
+ /** Where the puck and the default sit at rest (px in the pad); a stretch moves them with it. */
114
116
  puck(): readonly [number, number];
115
117
  home(): readonly [number, number];
118
+ /** How far (px) the pad stretches past an edge across and down (see drawStretch). */
119
+ stretch(): readonly [number, number];
116
120
  dragging(): boolean;
117
121
  readonly wave: RowWave;
118
122
  isModified(): boolean;
@@ -425,6 +429,13 @@ export interface GpuPanelBinding {
425
429
  readonly rows: readonly GpuRowBinding[];
426
430
  readonly actionButtons: readonly HTMLButtonElement[];
427
431
  isPopoverOpen(): boolean;
432
+ /** The focus lens (lens.ts): its shapes each frame, drawn as the DOM's `.knobs-lens`. */
433
+ readonly lens?: {
434
+ frame(now: number): readonly LensShape[];
435
+ moving(now: number): boolean;
436
+ };
437
+ /** The scope line beside the title (crumbs.ts): its spans are drawn where they sit. */
438
+ readonly crumbs?: HTMLElement;
428
439
  /** While the panel grows out of (or into) its chip: the shape at a frame time. */
429
440
  readonly morph?: (now: number) => MorphFrame | undefined;
430
441
  /** True while rows, cards or group heads arrive or leave (a descriptor swap). */
@@ -545,6 +556,10 @@ declare class GpuOverlayManager {
545
556
  private curSubHovered;
546
557
  /** Whether the pointer has one of the panel's rows (and keys haven't taken over since). */
547
558
  private curPointerRow;
559
+ /** The panel being drawn marks focus with its lens: the per-level washes step aside. */
560
+ private curLens;
561
+ /** Each panel's armed look (--k-armed-*, --k-title-rest), read again when styles change. */
562
+ private readonly armedLooks;
548
563
  private curPanelRect;
549
564
  private curPanelScrollTop;
550
565
  private curLayoutKey;
@@ -632,6 +647,17 @@ declare class GpuOverlayManager {
632
647
  private rowCorners;
633
648
  /** A row's hover wash, drawn beneath its control like the DOM row background. */
634
649
  private drawRowHover;
650
+ /**
651
+ * A row stretched `give` px past an end, as drawStretch scales it in the DOM: its rect (about
652
+ * the end that stays put, thinner across), its scale along, its corners rounding off at the
653
+ * stretching end, and its clip let out to the panel's on that side (past its card).
654
+ */
655
+ /**
656
+ * A box stretched `x` px past an end across and `y` px down or up, about the far ends, and
657
+ * (`thin`, stretched along x) thinner down, as drawStretch scales it in the DOM.
658
+ */
659
+ private stretchRect;
660
+ private stretch;
635
661
  /** The hairline above every row but a card's first, and the focus wash. */
636
662
  private drawRowOverlay;
637
663
  /** Whether `el` shows its focus wash: one wash at a time, the pointer's row's while it has one. */
@@ -722,6 +748,12 @@ declare class GpuOverlayManager {
722
748
  * advance, and a place on its way out keeps its last spot. True while any place moves.
723
749
  */
724
750
  private drawPlaces;
751
+ /** The armed look's tokens for a panel (see the styles' --k-armed-*). */
752
+ private armedLook;
753
+ /** The focus lens's shapes (lens.ts), as the DOM's `.knobs-lens`: a wash and a hairline. */
754
+ private drawLens;
755
+ /** The scope line's spans (crumbs.ts) where the DOM placed them, at their opacity. */
756
+ private drawCrumbs;
725
757
  /** The soft wash on the card focus is on (`data-k-scope`), as the DOM's ::after. */
726
758
  private drawScope;
727
759
  /** A disclosure head (hover, label and changed dot, caret, focus); returns the body's clip, if open. */
@@ -1,2 +1,2 @@
1
- export declare const FLOATS_PER_PRIM = 40;
1
+ export declare const FLOATS_PER_PRIM = 44;
2
2
  export declare const panelWgsl: string;
@@ -1,4 +1,5 @@
1
- export { attach } from "./attach.js";
1
+ export { attach, status } from "./attach.js";
2
+ export { toggleLayer, type ToggleLayer, type ToggleSurface } from "./toggle.js";
2
3
  export { getGpuOverlayManager, resolveRendererMode, type KnobsGpuDiagnostics, } from "./gpu/renderer.js";
3
4
  export { computeHintPrefixes, type HintBadgeState, type HintCandidate, type HintResolution, } from "./hints.js";
4
5
  export { assignPageCodes, hints, pageHints, type HintPainter, type HintSourceOptions, type PageHints, type PageHintTarget, } from "./page-hints.js";
@@ -1,5 +1,5 @@
1
1
  import { toolbarShortcut, type AngleField, type KeybindField, type Point, type RangeField, type SeedField, type ToolbarField, type ToolbarItem, type Vec3, type VecField } from "../core/schema.js";
2
- import type { EditorRow, RowContext } from "./editors.js";
2
+ import { type EditorRow, type RowContext } from "./editors.js";
3
3
  type Vec = Point & {
4
4
  readonly z?: number;
5
5
  };
@@ -0,0 +1,113 @@
1
+ /** Corner radii: top left, top right, bottom right, bottom left. */
2
+ export type LensCorners = [number, number, number, number];
3
+ /** A box with its corners, in viewport px. */
4
+ export interface LensBox {
5
+ x: number;
6
+ y: number;
7
+ w: number;
8
+ h: number;
9
+ r: LensCorners;
10
+ }
11
+ export type LensLevel = "editor" | "row" | "card" | "panel";
12
+ /**
13
+ * One shape to draw: its box, how present it is (0–1), and its wash and edge (shares of
14
+ * --k-surface-hi and --k-line-hi).
15
+ */
16
+ export interface LensShape extends LensBox {
17
+ alpha: number;
18
+ wash: number;
19
+ line: number;
20
+ }
21
+ /** The look, from the panel's `--k-lens-*` tokens (see the README). */
22
+ export interface LensTune {
23
+ /** The morph's duration (ms), a spring with no bounce. */
24
+ ms: number;
25
+ /** How much quicker the size settles than the position (1: together). */
26
+ lead: number;
27
+ /** The wash: a share of --k-surface-hi. */
28
+ wash: number;
29
+ /** The hairline edge: a share of --k-line-hi. */
30
+ line: number;
31
+ /** The wash at the panel's level, where the shape covers everything (a share of `wash`). */
32
+ panelWash: number;
33
+ /** A row's (or an editor part's) shape sits this far inside it (px). */
34
+ inset: number;
35
+ /** The panel's shape sits this far inside its edge (px). */
36
+ panelInset: number;
37
+ }
38
+ export declare const LENS_DEFAULTS: LensTune;
39
+ export declare function readLensTune(style: CSSStyleDeclaration): LensTune;
40
+ /**
41
+ * Offsets that decay to zero on a critically damped spring (ω = 2π / duration), sampled by time:
42
+ * x(t) = (x₀ + (v₀ + ωx₀)t)·e^(−ωt). Each channel has its own ω.
43
+ */
44
+ export declare class Glide {
45
+ omegas: readonly number[];
46
+ private readonly eps;
47
+ private x0;
48
+ private v0;
49
+ private t0;
50
+ constructor(omegas: readonly number[], eps?: number);
51
+ sample(now: number): {
52
+ x: number[];
53
+ v: number[];
54
+ };
55
+ /** From `now`, the offsets are `x` moving at `v` (px/s). */
56
+ set(x: readonly number[], v: readonly number[], now: number, omegas?: readonly number[]): void;
57
+ zero(): void;
58
+ resting(now: number): boolean;
59
+ }
60
+ /** The ω for a spring that settles in `ms` with no bounce (knobs' mapping: ω = 2π / duration). */
61
+ export declare const omegaOf: (ms: number) => number;
62
+ /** A box as glide channels (x, y, w, h, then the four radii), and back. */
63
+ export declare const boxChannels: (b: LensBox) => number[];
64
+ export declare const boxPlus: (b: LensBox, d: readonly number[]) => LensBox;
65
+ /**
66
+ * The corners a box inside a rounded container shows: where one of its corners sits on the
67
+ * container's (within half a pixel), the container's radius there, else its own. A row in a card
68
+ * is square but for the card's corners it reaches; so is an editor's part.
69
+ */
70
+ export declare function cornersWithin(box: {
71
+ left: number;
72
+ top: number;
73
+ right: number;
74
+ bottom: number;
75
+ }, own: LensCorners, outer?: {
76
+ left: number;
77
+ top: number;
78
+ right: number;
79
+ bottom: number;
80
+ r: LensCorners;
81
+ }): LensCorners;
82
+ export interface LensTarget {
83
+ readonly el: HTMLElement;
84
+ readonly level: LensLevel;
85
+ }
86
+ export interface LensOptions {
87
+ readonly root: HTMLElement;
88
+ /** A page: the panel's level is what shows of it under its header. */
89
+ readonly header?: HTMLElement;
90
+ readonly page?: boolean;
91
+ /** Whether a target shows now (a row's only while keys are in use, say). */
92
+ readonly shown: (target: LensTarget) => boolean;
93
+ readonly reduced: () => boolean;
94
+ /** Asks the GPU overlay for a frame. */
95
+ readonly wake: () => void;
96
+ /** While the layout glides (a page's cards): the DOM shape is re-placed every frame. */
97
+ readonly follow?: () => boolean;
98
+ }
99
+ export interface Lens {
100
+ /** The element the DOM draws it with. */
101
+ readonly element: HTMLElement;
102
+ /** Where focus is: a target, `null` to fade where it is, or "out" to grow out and fade. */
103
+ set(target: LensTarget | null | "out"): void;
104
+ readonly target: LensTarget | null;
105
+ /** The shapes to draw at `now` (viewport px). */
106
+ frame(now: number): LensShape[];
107
+ moving(now: number): boolean;
108
+ tune(): LensTune;
109
+ /** Re-place it (a scroll, a resize, a change in what shows). */
110
+ poke(followMs?: number): void;
111
+ dispose(): void;
112
+ }
113
+ export declare function createLens(o: LensOptions): Lens;
@@ -40,6 +40,8 @@ export declare class Edges {
40
40
  export declare const POP_MS = 360;
41
41
  /** Decile pop: a critically damped kick that swells to `peak` at 55 ms and settles (t in ms). */
42
42
  export declare function popScale(t: number, peak?: number): number;
43
+ /** A slider's handle settling on a detent: its height swells like the pop, a little more (t in ms). */
44
+ export declare const settleScale: (t: number) => number;
43
45
  export declare class VelocityTracker {
44
46
  private samples;
45
47
  add(x: number, y: number, t?: number): void;
@@ -1,3 +1,4 @@
1
+ import { isTypingTarget } from "../core/keys.js";
1
2
  import { type HintSpot } from "./hints.js";
2
3
  /** Code letters, home row first: groups take the first letters, their targets the second. */
3
4
  export declare const HINT_LETTERS = "asdfghjkleruiowtyqpcvnmxzb";
@@ -79,4 +80,4 @@ export declare const hints: {
79
80
  * type-ahead): it's the hint key and something on the page has turned hint mode on.
80
81
  */
81
82
  export declare function claimsHintKey(doc: Document, key: string): boolean;
82
- export declare const isTyping: (target: EventTarget | null) => boolean;
83
+ export declare const isTyping: typeof isTypingTarget;