@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.
- package/README.md +303 -41
- package/dist/dev/{chunk-qtt698hp.js → chunk-7xgkerd2.js} +34 -11
- package/dist/dev/{chunk-ktsn42p7.js → chunk-jx1ktap3.js} +13762 -11794
- package/dist/dev/{chunk-kdewv0mt.js → chunk-pmkvb9ep.js} +1692 -207
- package/dist/dev/{chunk-e13znr7c.js → chunk-v8ssbcbk.js} +18 -7
- package/dist/dev/index.js +557 -130
- package/dist/index.js +589 -141
- package/dist/panel.js +21615 -17048
- package/dist/types/core/keys.d.ts +2 -0
- package/dist/types/core/random.d.ts +61 -1
- package/dist/types/core/remote.d.ts +5 -1
- package/dist/types/core/schema.d.ts +35 -6
- package/dist/types/core/store.d.ts +97 -8
- package/dist/types/core/unlock.d.ts +77 -0
- package/dist/types/core/variants.d.ts +10 -2
- package/dist/types/index.d.ts +4 -2
- package/dist/types/panel/attach.d.ts +2 -0
- package/dist/types/panel/crumbs.d.ts +19 -0
- package/dist/types/panel/editors.d.ts +20 -0
- package/dist/types/panel/feel.d.ts +103 -0
- package/dist/types/panel/flash.d.ts +13 -0
- package/dist/types/panel/give.d.ts +34 -1
- package/dist/types/panel/gpu/color.d.ts +4 -4
- package/dist/types/panel/gpu/renderer.d.ts +110 -6
- package/dist/types/panel/gpu/shader.wgsl.d.ts +1 -1
- package/dist/types/panel/haptics.d.ts +6 -2
- package/dist/types/panel/has.d.ts +0 -1
- package/dist/types/panel/index.d.ts +3 -1
- package/dist/types/panel/inspector.d.ts +33 -3
- package/dist/types/panel/lens.d.ts +113 -0
- package/dist/types/panel/motion.d.ts +2 -0
- package/dist/types/panel/optkeys.d.ts +7 -0
- package/dist/types/panel/page-hints.d.ts +2 -1
- package/dist/types/panel/prefs.d.ts +2 -2
- package/dist/types/panel/presence.d.ts +20 -0
- package/dist/types/panel/presets.d.ts +17 -5
- package/dist/types/panel/ramp-morph.d.ts +117 -0
- package/dist/types/panel/ramps.d.ts +1 -1
- package/dist/types/panel/reel.d.ts +43 -0
- package/dist/types/panel/scope.d.ts +48 -0
- package/dist/types/panel/slider.d.ts +22 -5
- package/dist/types/panel/sound.d.ts +137 -0
- package/dist/types/panel/styles.d.ts +1 -1
- package/dist/types/panel/tab-transition.d.ts +15 -0
- package/dist/types/panel/tabs.d.ts +1 -1
- package/dist/types/panel/toggle.d.ts +27 -0
- package/dist/types/panel/toolbar-grid.d.ts +6 -0
- package/dist/types/ui/activity-css.d.ts +1 -0
- package/dist/types/ui/activity-model.d.ts +122 -0
- package/dist/types/ui/activity.d.ts +67 -0
- package/dist/types/ui/follow.d.ts +17 -0
- package/dist/types/ui/index.d.ts +5 -1
- package/dist/types/ui/look.d.ts +9 -0
- package/dist/types/ui/picker-css.d.ts +1 -0
- package/dist/types/ui/picker-model.d.ts +53 -0
- package/dist/types/ui/picker.d.ts +67 -0
- package/dist/types/ui/react.d.ts +27 -0
- package/dist/types/ui/scrollbar.d.ts +12 -0
- package/dist/types/ui/solid.d.ts +23 -0
- package/dist/types/ui/split-model.d.ts +55 -0
- package/dist/types/ui/split.d.ts +22 -3
- package/dist/ui-react.js +3462 -690
- package/dist/ui-solid.js +3440 -689
- package/dist/ui.js +2963 -264
- package/package.json +1 -1
|
@@ -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:
|
|
83
|
+
export declare const isTyping: typeof isTypingTarget;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PanelCommand, PanelKeys } from "../core/store.js";
|
|
2
2
|
/** How a panel reads keys that aren't commands (see `PanelKeys`): resolved once per panel. */
|
|
3
3
|
export interface KeyPrefs {
|
|
4
|
-
/** j/k
|
|
4
|
+
/** j/k as aliases for ↑/↓ (l is Lock, so ←/→ have no letters). */
|
|
5
5
|
readonly vim: boolean;
|
|
6
6
|
/** Digits, `.`, `-` and Enter on a slider open its inline number editor. */
|
|
7
7
|
readonly edit: boolean;
|
|
@@ -14,7 +14,7 @@ export declare const DEFAULT_PREFS: KeyPrefs;
|
|
|
14
14
|
export declare const setKeyPrefs: (root: Element, prefs: KeyPrefs) => WeakMap<Element, KeyPrefs>;
|
|
15
15
|
/** The preferences of the panel `node` sits in. */
|
|
16
16
|
export declare function keyPrefs(node: EventTarget | null): KeyPrefs;
|
|
17
|
-
/** -1, 1 or 0 for
|
|
17
|
+
/** -1, 1 or 0 for ←/→. */
|
|
18
18
|
export declare function arrowOf(event: KeyboardEvent): -1 | 0 | 1;
|
|
19
19
|
/** ⇧'s multiplier for this event (1 without ⇧). */
|
|
20
20
|
export declare const bigOf: (event: KeyboardEvent) => number;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MorphFrame } from "./morph.js";
|
|
2
|
+
export interface Presence {
|
|
3
|
+
readonly shown: boolean;
|
|
4
|
+
/** Shows or hides the panel; `animate: false` puts it there at once (a reload, a first paint). */
|
|
5
|
+
set(shown: boolean, animate: boolean): void;
|
|
6
|
+
/** The shape the GPU overlay draws while it shows or hides (see `MorphFrame`), else undefined. */
|
|
7
|
+
frame(now: number): MorphFrame | undefined;
|
|
8
|
+
dispose(): void;
|
|
9
|
+
}
|
|
10
|
+
/** What the panel looks like at `x` (0 hidden, 1 shown) on the way in or out. */
|
|
11
|
+
export declare function presenceLook(x: number, inline: boolean, reduced: boolean): {
|
|
12
|
+
opacity: number;
|
|
13
|
+
blur: number;
|
|
14
|
+
scale: number;
|
|
15
|
+
fold: number;
|
|
16
|
+
};
|
|
17
|
+
export declare function createPresence(root: HTMLElement, options: {
|
|
18
|
+
readonly inline: boolean;
|
|
19
|
+
readonly wake: () => void;
|
|
20
|
+
}): Presence;
|
|
@@ -7,19 +7,27 @@ export interface PresetEntry<V = Record<string, unknown>> {
|
|
|
7
7
|
readonly rawId: string;
|
|
8
8
|
readonly label: string;
|
|
9
9
|
readonly values: V;
|
|
10
|
-
|
|
10
|
+
/** Applies it (making it the active preset); `coalesce` folds it into the last undo step. */
|
|
11
|
+
apply(coalesce?: boolean): void;
|
|
11
12
|
}
|
|
12
13
|
export interface ActivePresetState {
|
|
13
14
|
readonly activeId: string;
|
|
14
15
|
readonly activeIndex: number;
|
|
15
16
|
readonly isCustom: boolean;
|
|
17
|
+
/** The active preset's chip, while the values have moved off it (its modified mark). */
|
|
18
|
+
readonly drifted?: boolean;
|
|
16
19
|
}
|
|
20
|
+
/** How strong the selection pill stays on the active preset once the values drift off it. */
|
|
21
|
+
export declare const DRIFTED_PILL = 0.5;
|
|
22
|
+
/** The strip's id for the store's active preset (`p:` a preset, `v:` a saved variant), if any. */
|
|
23
|
+
export declare function activeEntryId(controls: Controls<any>): string | null;
|
|
17
24
|
/**
|
|
18
25
|
* Resolves which preset or variant is currently active.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
26
|
+
* The store's active preset (`baseId`) holds the selection however far the values drift from it.
|
|
27
|
+
* Without one, `lastAppliedId` is honoured when its values still match `currentValues`, so
|
|
28
|
+
* cycling never gets stuck when a saved variant has the exact same values as a built-in preset.
|
|
21
29
|
*/
|
|
22
|
-
export declare function resolveActivePresetEntry<V extends Record<string, any>>(entries: readonly PresetEntry<V>[], currentValues: V, lastAppliedId: string | null): ActivePresetState;
|
|
30
|
+
export declare function resolveActivePresetEntry<V extends Record<string, any>>(entries: readonly PresetEntry<V>[], currentValues: V, lastAppliedId: string | null, baseId?: string | null): ActivePresetState;
|
|
23
31
|
export declare function nextPresetIndex(count: number, activeIndex: number, direction: number): number;
|
|
24
32
|
export interface PresetStrip {
|
|
25
33
|
readonly element: HTMLElement;
|
|
@@ -36,8 +44,12 @@ export interface PresetStrip {
|
|
|
36
44
|
getActiveState(): ActivePresetState;
|
|
37
45
|
cycle(direction: -1 | 1): void;
|
|
38
46
|
selectById(id: string): void;
|
|
39
|
-
/** Saves the current values as "Variant N"; returns a status message. */
|
|
47
|
+
/** Saves the current values as "Variant N" and opens its name to typing; returns a status message. */
|
|
40
48
|
save(): string;
|
|
49
|
+
/** Opens the active chip's name to typing, when it's a saved variant; returns a status message. */
|
|
50
|
+
rename(): string | undefined;
|
|
51
|
+
/** The name field while a chip's name is being typed. */
|
|
52
|
+
readonly nameField: () => HTMLInputElement | null;
|
|
41
53
|
/** Deletes the selected chip when it's a saved variant; returns a status message. */
|
|
42
54
|
deleteActive(): string | undefined;
|
|
43
55
|
update(): void;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { type SpringValue } from "../core/spring.js";
|
|
2
|
+
import type { RowContext } from "./editors.js";
|
|
3
|
+
export interface Pt {
|
|
4
|
+
readonly x: number;
|
|
5
|
+
readonly y: number;
|
|
6
|
+
}
|
|
7
|
+
export interface MorphPlan {
|
|
8
|
+
/** For each new point, the old point it travels from, or -1: it buds. */
|
|
9
|
+
readonly from: readonly number[];
|
|
10
|
+
/** Each old point without a partner, and the new point it merges into (-1: none, it shrinks). */
|
|
11
|
+
readonly merges: readonly {
|
|
12
|
+
readonly old: number;
|
|
13
|
+
readonly into: number;
|
|
14
|
+
}[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Pairs `prev`'s points with `next`'s. The first stays the first and the last the last; the inner
|
|
18
|
+
* ones pair in order, as many as both have, choosing the pairing that travels least (in the 0…1
|
|
19
|
+
* box). Only the surplus is left over: merging when there are fewer, budding when more.
|
|
20
|
+
*/
|
|
21
|
+
export declare function matchPoints(prev: readonly Pt[], next: readonly Pt[]): MorphPlan;
|
|
22
|
+
/** `flat` (`stride` numbers per sample) resampled to `n` samples evenly along its index. */
|
|
23
|
+
export declare function resample(flat: readonly number[], stride: number, n: number): number[];
|
|
24
|
+
/** The y of an x, y polyline (x not decreasing) at `x`, holding its ends past them. */
|
|
25
|
+
export declare function sampleAt(flat: readonly number[], x: number): number;
|
|
26
|
+
/** A point on its way: its channels (x, y, then any extras, then its size), their speeds and goals. */
|
|
27
|
+
export interface MorphDot {
|
|
28
|
+
readonly id: number;
|
|
29
|
+
readonly at: number[];
|
|
30
|
+
readonly vel: number[];
|
|
31
|
+
readonly to: number[];
|
|
32
|
+
/** Merging: the dot it shrinks into (-1 shrinks where it is); undefined while it's one of the points. */
|
|
33
|
+
into?: number;
|
|
34
|
+
}
|
|
35
|
+
/** The default travel: .3 s with no bounce. */
|
|
36
|
+
export declare const MORPH_SPRING: SpringValue;
|
|
37
|
+
/** How long a point merging into its neighbour takes to shrink away (s). */
|
|
38
|
+
export declare const MERGE_SPRING: SpringValue;
|
|
39
|
+
export interface MorphSet {
|
|
40
|
+
/** Snap there: nothing moves (a drag, a different item, reduced motion). */
|
|
41
|
+
readonly instant?: boolean;
|
|
42
|
+
/** Hold the current motion this long (ms) first, as a cascade of rows does. */
|
|
43
|
+
readonly delay?: number;
|
|
44
|
+
readonly now: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The shown samples and points of a ramp between shapes: `stride` numbers per sample, `dims`
|
|
48
|
+
* channels per point (x, y and any extras; the dot's size is kept after them). Every number has
|
|
49
|
+
* its own spring, so a change mid-flight keeps each one's speed.
|
|
50
|
+
*/
|
|
51
|
+
export declare class RampMorph {
|
|
52
|
+
readonly stride: number;
|
|
53
|
+
readonly dims: number;
|
|
54
|
+
/** Where a new point buds from: on the line under it as it's shown now (no size channel). */
|
|
55
|
+
private readonly origin;
|
|
56
|
+
samples: number[];
|
|
57
|
+
private sampleVel;
|
|
58
|
+
private sampleTo;
|
|
59
|
+
/** The points, in order. */
|
|
60
|
+
live: MorphDot[];
|
|
61
|
+
/** Points merging away into others. */
|
|
62
|
+
gone: MorphDot[];
|
|
63
|
+
private ids;
|
|
64
|
+
private last;
|
|
65
|
+
private pending;
|
|
66
|
+
private resting;
|
|
67
|
+
omega: number;
|
|
68
|
+
zeta: number;
|
|
69
|
+
private readonly shrink;
|
|
70
|
+
constructor(stride: number, dims: number, spring?: SpringValue,
|
|
71
|
+
/** Where a new point buds from: on the line under it as it's shown now (no size channel). */
|
|
72
|
+
origin?: (to: readonly number[], samples: readonly number[]) => number[]);
|
|
73
|
+
get moving(): boolean;
|
|
74
|
+
/** The points' channels as the shape stands (where the live dots are going). */
|
|
75
|
+
private points;
|
|
76
|
+
private dot;
|
|
77
|
+
/** Moves toward `samples` and `points` (each `dims` channels, without the size). */
|
|
78
|
+
set(samples: readonly number[], points: readonly (readonly number[])[], o: MorphSet): void;
|
|
79
|
+
private snap;
|
|
80
|
+
private retarget;
|
|
81
|
+
/** Where a merging dot is headed: the dot it merges into, followed if that one is merging too. */
|
|
82
|
+
private target;
|
|
83
|
+
/** Advances to `now` (ms); whether anything still moves. A time already reached is a no-op. */
|
|
84
|
+
step(now: number): boolean;
|
|
85
|
+
/** The line's fastest sample (px/s), given px per unit across and up. */
|
|
86
|
+
sampleSpeed(sx: number, sy: number): number;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* How wide (px) a moving line or point smears at `speed` px/s, by `strength` (--k-curve-blur, 0
|
|
90
|
+
* to 1): at 1, about what a half-open shutter catches in a 60 Hz frame. Under a quarter pixel it
|
|
91
|
+
* is 0, so it rests crisp.
|
|
92
|
+
*/
|
|
93
|
+
export declare function curveSmear(speed: number, strength: number, cap?: number): number;
|
|
94
|
+
/** The widest a moving curve's line smears (px): it's thin, so less than a point does. */
|
|
95
|
+
export declare const LINE_SMEAR = 4;
|
|
96
|
+
/** How strongly a ramp's moving line and points smear (--k-curve-blur, 0 off to 1). */
|
|
97
|
+
export declare const curveBlur: (row: HTMLElement) => number;
|
|
98
|
+
/**
|
|
99
|
+
* Runs a morph on one frame loop while it moves, drawing each frame; `wake` asks the GPU for a
|
|
100
|
+
* frame too, which reads the same morph (stepping it to its own frame time first: a time already
|
|
101
|
+
* reached is a no-op, so the two never step it twice).
|
|
102
|
+
*/
|
|
103
|
+
export declare function morphLoop(c: RowContext<unknown>, morph: RampMorph, draw: () => void): {
|
|
104
|
+
kick(): void;
|
|
105
|
+
dispose(): void;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* The DOM's motion blur for a ramp's line and points: an SVG Gaussian per element, its deviation
|
|
109
|
+
* the GPU smear's box width over √12 (the same spread). Off (no filter at all) at rest.
|
|
110
|
+
*/
|
|
111
|
+
export declare function smearFilters(doc: Document, plot: SVGSVGElement): {
|
|
112
|
+
/** The line's smear: `set(px)` blurs `path` across (0 takes the filter off). */
|
|
113
|
+
line(path: SVGPathElement): (px: number) => void;
|
|
114
|
+
/** A point's smear along its travel (px across, px down); 0, 0 takes it off. */
|
|
115
|
+
dot(node: HTMLElement, sx: number, sy: number): void;
|
|
116
|
+
drop(node: HTMLElement): void;
|
|
117
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CurveField, GradientField } from "../core/schema.js";
|
|
2
2
|
import { type EditorRow, type RowContext } from "./editors.js";
|
|
3
|
-
/** The custom properties a gradient strip's samples ride on (
|
|
3
|
+
/** The custom properties a gradient strip's samples ride on (written each frame of a morph). */
|
|
4
4
|
export declare const GRADIENT_VARS: string[];
|
|
5
5
|
/**
|
|
6
6
|
* A gradient: a swatch strip with stop ticks in the head; a wide strip with a handle per stop, the
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { DigitPlace } from "./slider.js";
|
|
2
|
+
/**
|
|
3
|
+
* Brings a readout's places to `text` (a place per character, ones first), as rolling-number
|
|
4
|
+
* does: each digit a wheel rolling through every face in the value's direction (98 → 108 rolls
|
|
5
|
+
* the tens 9 → 0 forwards), new places rising in after a short hold, cascading outward, and
|
|
6
|
+
* leaving places fading over ~65% of `ms`. `ms` 0 places it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function syncPlaces(places: DigitPlace[], text: string, trend: number, ms: number, delay: number, entering?: boolean): void;
|
|
9
|
+
/** How long a readout rolls (ms): the panel's --k-roll, 320 by default. */
|
|
10
|
+
export declare function rollMs(el: Element): number;
|
|
11
|
+
/** A readout's parts: text, or a number shown to `decimals` places. */
|
|
12
|
+
export type ReadoutPart = string | {
|
|
13
|
+
readonly value: number;
|
|
14
|
+
readonly decimals: number;
|
|
15
|
+
};
|
|
16
|
+
/** A part as laid out: its element, its text, and a number's places for the canvas. */
|
|
17
|
+
export interface ReelSegment {
|
|
18
|
+
readonly el: HTMLElement;
|
|
19
|
+
text: string;
|
|
20
|
+
readonly places?: DigitPlace[];
|
|
21
|
+
}
|
|
22
|
+
export interface RollingReadout {
|
|
23
|
+
readonly host: HTMLElement;
|
|
24
|
+
readonly segments: readonly ReelSegment[];
|
|
25
|
+
/** Everything it shows, as one string. */
|
|
26
|
+
readonly text: string;
|
|
27
|
+
/**
|
|
28
|
+
* Shows `parts`. Numbers roll over `ms` (0 places them: the first paint, the user's own hand,
|
|
29
|
+
* a landing), after `delay` ms for the row's turn in a cascade.
|
|
30
|
+
*/
|
|
31
|
+
show(parts: readonly ReadoutPart[], ms: number, delay?: number): void;
|
|
32
|
+
dispose(): void;
|
|
33
|
+
}
|
|
34
|
+
/** The rolling readout an element holds, if it holds one (the canvas draws its segments). */
|
|
35
|
+
export declare const rollingReadoutOf: (el: Element) => RollingReadout | undefined;
|
|
36
|
+
/** Makes `host`'s content a rolling readout (its children are the readout's from now on). */
|
|
37
|
+
export declare function rollingReadout(host: HTMLElement): RollingReadout;
|
|
38
|
+
/**
|
|
39
|
+
* Sets a name-like readout (an easing's or curve's name, a hex colour). `fade` crossfades the old
|
|
40
|
+
* text out over the new, where it was; without it (the user's own hand) the text just changes.
|
|
41
|
+
* The canvas does the same, reading `data-text` and `data-fade`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function fadeText(el: HTMLElement, text: string, fade: boolean): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** How deep focus sits in the panel. */
|
|
2
|
+
export type FocusLevel = "row" | "card" | "panel" | "none";
|
|
3
|
+
/** Where Esc takes focus from `level`: up one, past a card when the row isn't in one. */
|
|
4
|
+
export declare function escapeTo(level: FocusLevel, inCard?: boolean): FocusLevel;
|
|
5
|
+
/** What R and ⇧R act on: a selection always wins, then the level focused. */
|
|
6
|
+
export type CommandScope = "selection" | "row" | "group" | "all";
|
|
7
|
+
export declare function commandScope(level: FocusLevel, selection?: boolean): CommandScope;
|
|
8
|
+
/** How a command reads from where it would land: "Random this", "Reset group", "Random all". */
|
|
9
|
+
export declare function scopeLabel(verb: string, scope: CommandScope): string;
|
|
10
|
+
/** A level below the panel that the header's crumbs name: a card, a row, an open editor's part. */
|
|
11
|
+
export type CrumbLevel = "card" | "row" | "editor";
|
|
12
|
+
export interface Crumb {
|
|
13
|
+
readonly level: CrumbLevel;
|
|
14
|
+
readonly label: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Where keys go, as the header reads after the panel's title: `Shape › Cohesion`. A level that
|
|
18
|
+
* isn't focused (or has no name) is left out; the last crumb is the deepest, the one focused.
|
|
19
|
+
*/
|
|
20
|
+
export declare function crumbTrail(path: {
|
|
21
|
+
readonly card?: string;
|
|
22
|
+
readonly row?: string;
|
|
23
|
+
readonly editor?: string;
|
|
24
|
+
}): Crumb[];
|
|
25
|
+
/**
|
|
26
|
+
* Where Esc goes from the deepest crumb: the crumb before it, else the panel (`title`). Nothing
|
|
27
|
+
* at the panel itself (no crumbs), where Esc leaves.
|
|
28
|
+
*/
|
|
29
|
+
export declare function escTarget(trail: readonly Crumb[], title: string): string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* How much of a trail fits `room` px: crumbs from `from` on show (the ones before it fold into
|
|
32
|
+
* one "…"), and the Esc hint shows only when it fits too. The deepest crumb always shows.
|
|
33
|
+
* `widths` are the crumbs' own; `sep` a separator's (with its gaps), `more` the "…"'s.
|
|
34
|
+
*/
|
|
35
|
+
export declare function fitCrumbs(widths: readonly number[], room: number, sep: number, more: number, hint?: number): {
|
|
36
|
+
from: number;
|
|
37
|
+
hint: boolean;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The option a digit picks among `count` (0-based), or undefined when it names none: 1–9 pick
|
|
41
|
+
* the first nine, as they read.
|
|
42
|
+
*/
|
|
43
|
+
export declare function digitOption(key: string, count: number): number | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The digit hints for a row's options: one per option that shows (up to nine), or, while they
|
|
46
|
+
* don't (a menu, a folded editor), one standing for them all ("1–5").
|
|
47
|
+
*/
|
|
48
|
+
export declare function digitHints(count: number, showing: boolean): string[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Knob as KnobField } from "../core/schema.js";
|
|
2
|
-
import {
|
|
2
|
+
import { type DetentKind } from "./feel.js";
|
|
3
|
+
import { type Roll, Spring, VelocityTracker } from "./motion.js";
|
|
3
4
|
export interface DigitPlace {
|
|
4
5
|
ch: string;
|
|
5
6
|
/** Unbounded wheel position for digits (… 9, 10, 11 …); face = position mod 10. */
|
|
@@ -39,6 +40,17 @@ export interface Slider {
|
|
|
39
40
|
readonly notchRatio: number;
|
|
40
41
|
/** Whether a drag is currently held on the default by the notch's magnet. */
|
|
41
42
|
isMagnetized(): boolean;
|
|
43
|
+
/** The detent holding a drag right now, if any. */
|
|
44
|
+
heldDetent(): DetentKind | undefined;
|
|
45
|
+
/** Where the active preset's value sits [0..1], when it differs from the default. */
|
|
46
|
+
readonly baseRatio: number | undefined;
|
|
47
|
+
/** When the handle last caught a detent (see `settleScale`). */
|
|
48
|
+
readonly settledAt: number;
|
|
49
|
+
/**
|
|
50
|
+
* How many halvings the drag in hand moves at, for its ruler (0 at 1:1 or at rest, up to 3; each
|
|
51
|
+
* level halves the tenths again). Each level shows `rulerAlpha(depth, level)` of the way.
|
|
52
|
+
*/
|
|
53
|
+
rulerDepth(): number;
|
|
42
54
|
/** When the readout last popped (a click landing on a decile, or the default's magnet). */
|
|
43
55
|
poppedAt: number;
|
|
44
56
|
/** Formatted numeric string without unit (e.g. "0.45" or "98"). */
|
|
@@ -83,10 +95,15 @@ export declare function stepValue(field: {
|
|
|
83
95
|
/**
|
|
84
96
|
* A DialKit-style slider: the whole row is the track, the fill is a full-height block,
|
|
85
97
|
* the handle is a hairline that only shows on hover, and the label + value sit on the
|
|
86
|
-
* track itself. Dragging follows the pointer exactly
|
|
87
|
-
*
|
|
88
|
-
*
|
|
98
|
+
* track itself. Dragging follows the pointer exactly, slower the further the pointer strays off
|
|
99
|
+
* the row (and ⌥ for 1/10 speed); detents at the default, the active preset's value and round
|
|
100
|
+
* values hold it for a few px, and past an end the row stretches like rubber. A click snaps
|
|
101
|
+
* to the nearest step (or nearest 10% when there are many steps) with a short spring-like ease.
|
|
102
|
+
* A sideways trackpad pan moves it like a drag.
|
|
89
103
|
*/
|
|
90
104
|
export declare function createSlider(doc: Document, key: string, initialField: KnobField, set: (value: number, options?: {
|
|
91
105
|
coalesce?: boolean;
|
|
92
|
-
}) => void, onMotionWake?: () => void
|
|
106
|
+
}) => void, onMotionWake?: () => void, options?: {
|
|
107
|
+
/** The active preset's value for this key (what Reset goes back to first). */
|
|
108
|
+
readonly base?: () => number | undefined;
|
|
109
|
+
}): Slider;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { SoundOptions, SoundTheme } from "../core/store.js";
|
|
2
|
+
export type { SoundOptions, SoundTheme };
|
|
3
|
+
/** A cue knobs plays for a moment in a panel (see README, Sound). */
|
|
4
|
+
export type SoundCue = "step" | "limit" | "toggle" | "select" | "preset" | "random" | "mutate" | "reset" | "copy" | "undo" | "redo" | "open" | "close" | "hint" | "type" | "press" | "capture";
|
|
5
|
+
/** What a play knows about its moment. */
|
|
6
|
+
export interface CueDetail {
|
|
7
|
+
/** Where the value now sits in its range, 0–1: a step's pitch. */
|
|
8
|
+
readonly position?: number;
|
|
9
|
+
/** Forwards or on (1), back or off (-1). */
|
|
10
|
+
readonly direction?: number;
|
|
11
|
+
/** A firmer play: the default's notch catching. */
|
|
12
|
+
readonly strong?: boolean;
|
|
13
|
+
readonly volume?: number;
|
|
14
|
+
readonly theme?: SoundTheme;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* One layer of a sound: a tone (`wave`) or a burst of noise through a filter. Times are seconds;
|
|
18
|
+
* `d` is how long it takes to die away by 60 dB.
|
|
19
|
+
*/
|
|
20
|
+
export interface SoundLayer {
|
|
21
|
+
readonly wave?: OscillatorType;
|
|
22
|
+
/** The noise's filter; `bandpass` by default. */
|
|
23
|
+
readonly filter?: BiquadFilterType;
|
|
24
|
+
/** The tone's pitch, or the noise filter's frequency (Hz). */
|
|
25
|
+
readonly f: number;
|
|
26
|
+
/** Glides `f` here over `glide` seconds. */
|
|
27
|
+
readonly to?: number;
|
|
28
|
+
readonly glide?: number;
|
|
29
|
+
readonly q?: number;
|
|
30
|
+
/** Starts this long after the play. */
|
|
31
|
+
readonly at?: number;
|
|
32
|
+
readonly a: number;
|
|
33
|
+
readonly d: number;
|
|
34
|
+
readonly p: number;
|
|
35
|
+
}
|
|
36
|
+
interface Recipe {
|
|
37
|
+
readonly layers: readonly SoundLayer[];
|
|
38
|
+
/** The play's level, matched across cues by ear and by loudness (1 by default). */
|
|
39
|
+
readonly gain?: number;
|
|
40
|
+
/** How much rings into the shared room (0 by default: dry). */
|
|
41
|
+
readonly room?: number;
|
|
42
|
+
}
|
|
43
|
+
/** 0 (unhurried) to 1 (as fast as it goes), from the time since the cue last played. */
|
|
44
|
+
export declare const cadence: (sinceMs: number) => number;
|
|
45
|
+
/**
|
|
46
|
+
* A cue's layers for this play: `speed` from `cadence`, `random` for its small variations (a die's
|
|
47
|
+
* clicks, a key's pitch). Pure, so tests and an offline render read the same sound.
|
|
48
|
+
*/
|
|
49
|
+
export declare function recipe(cue: SoundCue, detail?: CueDetail, speed?: number, random?: () => number): Recipe;
|
|
50
|
+
/** The `mech` theme: the same cues with the rings all but gone and the clicks tighter. */
|
|
51
|
+
export declare const themed: (layers: readonly SoundLayer[], theme: SoundTheme) => SoundLayer[];
|
|
52
|
+
/** The shortest gap (ms) between two plays of a cue: closer ones are dropped. */
|
|
53
|
+
export declare const GAPS: Readonly<Record<SoundCue, number>>;
|
|
54
|
+
/** How a cue's plays are let through: too close are dropped; the rest report their cadence. */
|
|
55
|
+
export declare function createLimiter(gaps?: Readonly<Record<string, number>>): (cue: string, now: number) => number | null;
|
|
56
|
+
/** A change this long (ms) after the last one on its slider is a fresh one (a key, a click). */
|
|
57
|
+
export declare const FRESH_MS = 150;
|
|
58
|
+
/** A continuous slider ticks this many times across its range while it's dragged. */
|
|
59
|
+
export declare const DETENTS = 24;
|
|
60
|
+
/**
|
|
61
|
+
* Whether a slider's change (positions 0–1) earns a tick: a fresh change always does; within a
|
|
62
|
+
* drag, only crossing into another of its `detents` (each stop of a stepped slider, else
|
|
63
|
+
* DETENTS), so a slow drag ratchets evenly instead of ticking every pixel.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createDetents(): (key: string, before: number, after: number, detents: number, now: number) => boolean;
|
|
66
|
+
/** The parts of a field a change's cue reads. */
|
|
67
|
+
interface CueField {
|
|
68
|
+
readonly kind: string;
|
|
69
|
+
readonly value?: unknown;
|
|
70
|
+
readonly min?: unknown;
|
|
71
|
+
readonly max?: unknown;
|
|
72
|
+
readonly step?: unknown;
|
|
73
|
+
readonly options?: readonly unknown[];
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The cue for a row's value changing by hand from `before` to `after`: a step for numbers (pitched
|
|
77
|
+
* by position, firmer landing on the default), a switch's throw, a choice's detent (by direction),
|
|
78
|
+
* a captured key. `detents` is how many ticks a drag across the whole range may make.
|
|
79
|
+
*/
|
|
80
|
+
export declare function changeCue(field: CueField, before: unknown, after: unknown): {
|
|
81
|
+
cue: SoundCue;
|
|
82
|
+
detail: CueDetail;
|
|
83
|
+
detents: number;
|
|
84
|
+
at: number;
|
|
85
|
+
was: number;
|
|
86
|
+
} | null;
|
|
87
|
+
/** A mount's `sound` option as what the panel plays with, or null for silence. */
|
|
88
|
+
export declare function resolveSound(option: boolean | SoundOptions | undefined): SoundOptions | null;
|
|
89
|
+
/** At most this many plays sound at once; the oldest fades for a new one. */
|
|
90
|
+
export declare const MAX_VOICES = 10;
|
|
91
|
+
/** What the engine needs from the page, so tests can hand it a fake. */
|
|
92
|
+
export interface SoundEnv {
|
|
93
|
+
readonly AudioContext?: {
|
|
94
|
+
new (): AudioContext;
|
|
95
|
+
};
|
|
96
|
+
/** Whether the page has had a user gesture yet (no context is made before one). */
|
|
97
|
+
readonly activated?: () => boolean;
|
|
98
|
+
readonly now?: () => number;
|
|
99
|
+
readonly random?: () => number;
|
|
100
|
+
}
|
|
101
|
+
export interface Sound {
|
|
102
|
+
/** The page's switch: on unless turned off. Panels only play with `sound` on their mount. */
|
|
103
|
+
readonly enabled: boolean;
|
|
104
|
+
enable(on?: boolean): void;
|
|
105
|
+
/** 0–1, 1 by default. */
|
|
106
|
+
readonly volume: number;
|
|
107
|
+
setVolume(volume: number): void;
|
|
108
|
+
readonly theme: SoundTheme;
|
|
109
|
+
setTheme(theme: SoundTheme): void;
|
|
110
|
+
/** Plays a cue, or layers of your own, unless sound is off or the page hasn't been touched. */
|
|
111
|
+
play(cue: SoundCue | readonly SoundLayer[], detail?: CueDetail): void;
|
|
112
|
+
/**
|
|
113
|
+
* The shared context and the bus to connect your own nodes to (volume, mute and the compressor
|
|
114
|
+
* apply), once there is one: after the first user gesture with sound wanted.
|
|
115
|
+
*/
|
|
116
|
+
output(): {
|
|
117
|
+
readonly context: AudioContext;
|
|
118
|
+
readonly destination: AudioNode;
|
|
119
|
+
} | null;
|
|
120
|
+
/** Makes the context on the page's next gesture, so the first cue sounds at once. */
|
|
121
|
+
prime(doc?: Document): void;
|
|
122
|
+
/** Called when `enabled`, `volume` or `theme` change; returns an unsubscribe. */
|
|
123
|
+
subscribe(listener: () => void): () => void;
|
|
124
|
+
}
|
|
125
|
+
export declare function createSound(env?: SoundEnv): Sound;
|
|
126
|
+
/** The page's sound: one switch, volume and theme for every panel (and the host's own cues). */
|
|
127
|
+
export declare const sound: Sound;
|
|
128
|
+
/** Where the page keeps the user's choice, in a store's storage. */
|
|
129
|
+
export declare const SOUND_PREF = "knobs:sound:v1";
|
|
130
|
+
/** Marks `root` as a panel that plays (a mount's resolved `sound`). */
|
|
131
|
+
export declare function soundPanel(root: Element, options: SoundOptions): void;
|
|
132
|
+
/** Whether the panel `from` sits in plays sounds. */
|
|
133
|
+
export declare const sounds: (from: Element | null | undefined) => boolean;
|
|
134
|
+
/** A row's value changing by hand in the panel `from` sits in (see `changeCue`). */
|
|
135
|
+
export declare function changed(from: Element | null | undefined, key: string, field: CueField, before: unknown, after: unknown): void;
|
|
136
|
+
/** Plays `cue` for a moment in the panel `from` sits in, if that panel plays sounds. */
|
|
137
|
+
export declare function cue(from: Element | null | undefined, name: SoundCue, detail?: CueDetail): void;
|
|
@@ -3,7 +3,7 @@ import { type TokenPreset } from "../core/theme.js";
|
|
|
3
3
|
* What DELAY times: a row's turn in a preset's cascade is set on these (see `showValues` in
|
|
4
4
|
* panel.ts) rather than on the row, where the custom property would restyle all of the row.
|
|
5
5
|
*/
|
|
6
|
-
export declare const DELAYED = ".knobs-fill,.knobs-handle,.knobs-seg-pill,.knobs-toggle,.knobs-toggle i,.knobs-arm,.knobs-curve,.knobs-swatch,.knobs-strip-thumb,.knobs-pad>i,.knobs-ease-handle,.knobs-range-fill,.knobs-range-thumb,.knobs-dial-hand,.knobs-grad,.knobs-grad-mini,.knobs-grad-mini>i,.knobs-grad-stop,.knobs-graph-pt";
|
|
6
|
+
export declare const DELAYED = ".knobs-fill,.knobs-handle,.knobs-seg-pill,.knobs-toggle,.knobs-toggle i,.knobs-arm,.knobs-curve,.knobs-swatch,.knobs-strip-thumb,.knobs-pad>i,.knobs-ease-handle,.knobs-range-track,.knobs-range-fill,.knobs-range-thumb,.knobs-dial-hand,.knobs-grad,.knobs-grad-mini,.knobs-grad-mini>i,.knobs-grad-stop,.knobs-graph-pt";
|
|
7
7
|
/** Adds a token preset's stylesheet once per document. */
|
|
8
8
|
export declare function injectPreset(doc: Document, preset: TokenPreset): void;
|
|
9
9
|
/** The rules' `:has()` as the attributes has.ts keeps (see there for why). */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tab change, as one layout animation: what leaves fades out where it stood (lifted out of the
|
|
3
|
+
* flow, so it takes no room), what arrives fades in block by block with a small rise, what stays
|
|
4
|
+
* glides to its new place, and the surface (and any card whose rows change) springs to its new
|
|
5
|
+
* height. Every spring keeps its position and velocity when another tab is picked mid-flight.
|
|
6
|
+
*
|
|
7
|
+
* While the pointer stays on the panel the surface never shrinks: a panel anchored at its foot
|
|
8
|
+
* (or centred) would otherwise move its tabs out from under the pointer between two clicks. It
|
|
9
|
+
* settles to its size once the pointer leaves.
|
|
10
|
+
*/
|
|
11
|
+
export declare function tabTransition(body: HTMLElement, root: HTMLElement, wake: () => void): {
|
|
12
|
+
readonly running: boolean;
|
|
13
|
+
finish: () => void;
|
|
14
|
+
to(want: Map<HTMLElement, boolean>): void;
|
|
15
|
+
};
|
|
@@ -28,4 +28,4 @@ export interface TabStrip {
|
|
|
28
28
|
focus(options?: FocusOptions): void;
|
|
29
29
|
dispose(): void;
|
|
30
30
|
}
|
|
31
|
-
export declare function createTabStrip(doc: Document, choose: (id: string) => void): TabStrip;
|
|
31
|
+
export declare function createTabStrip(doc: Document, choose: (id: string, animate?: boolean) => void): TabStrip;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type StoragePort } from "../core/storage.js";
|
|
2
|
+
type KeyEventLike = Pick<KeyboardEvent, "key" | "code" | "ctrlKey" | "metaKey" | "altKey" | "shiftKey" | "repeat" | "isComposing" | "defaultPrevented" | "target">;
|
|
3
|
+
/** Whether a keydown is `key` for the toggle: outside a text field, with no modifier held. */
|
|
4
|
+
export declare function isToggleKey(event: KeyEventLike, key: string): boolean;
|
|
5
|
+
/** A panel or chip that comes and goes with the toggle key. */
|
|
6
|
+
export interface ToggleSurface {
|
|
7
|
+
/** Its store's id: what's remembered. */
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly key: string;
|
|
10
|
+
readonly storage: () => StoragePort | null;
|
|
11
|
+
/** Where focus may be when it hides, to give back when it shows. */
|
|
12
|
+
readonly root: HTMLElement;
|
|
13
|
+
/** Shows or hides it; `animate: false` puts it there at once. */
|
|
14
|
+
show(shown: boolean, animate: boolean): void;
|
|
15
|
+
}
|
|
16
|
+
export interface ToggleLayer {
|
|
17
|
+
/** Joins the layer; `shown` is how it should start (what its store remembers). */
|
|
18
|
+
register(surface: ToggleSurface): {
|
|
19
|
+
readonly shown: boolean;
|
|
20
|
+
dispose(): void;
|
|
21
|
+
};
|
|
22
|
+
/** Shows or hides everything listening for `key`; false when nothing does. */
|
|
23
|
+
toggle(key: string): boolean;
|
|
24
|
+
}
|
|
25
|
+
/** The page's toggle layer, for `doc`. */
|
|
26
|
+
export declare function toggleLayer(doc: Document): ToggleLayer;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many equal columns a toolbar's buttons take in `avail` px: as many as fit at the widest
|
|
3
|
+
* button's width, then balanced so the rows hold counts as even as they can (four buttons wrap as
|
|
4
|
+
* two and two, never three and one; five as three and two).
|
|
5
|
+
*/
|
|
6
|
+
export declare function toolbarColumns(count: number, widest: number, avail: number, gap: number): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const activityCss: string;
|