@kitlangton/knobs 0.1.0 → 0.2.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 +174 -30
- package/dist/dev/{chunk-e13znr7c.js → chunk-4km87zfx.js} +3 -3
- package/dist/dev/{chunk-ktsn42p7.js → chunk-5cr85vv6.js} +7448 -5390
- package/dist/dev/{chunk-kdewv0mt.js → chunk-693jd1gj.js} +171 -70
- package/dist/dev/{chunk-qtt698hp.js → chunk-gezhc24g.js} +21 -7
- package/dist/dev/index.js +377 -106
- package/dist/index.js +392 -108
- package/dist/panel.js +9518 -6749
- package/dist/types/core/random.d.ts +61 -1
- package/dist/types/core/remote.d.ts +3 -1
- package/dist/types/core/schema.d.ts +30 -6
- package/dist/types/core/store.d.ts +68 -6
- package/dist/types/core/variants.d.ts +10 -2
- package/dist/types/index.d.ts +3 -2
- package/dist/types/panel/editors.d.ts +11 -0
- package/dist/types/panel/flash.d.ts +13 -0
- package/dist/types/panel/gpu/renderer.d.ts +78 -6
- 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 +1 -0
- package/dist/types/panel/inspector.d.ts +32 -2
- package/dist/types/panel/optkeys.d.ts +7 -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 +19 -0
- package/dist/types/panel/slider.d.ts +1 -1
- package/dist/types/panel/sound.d.ts +137 -0
- package/dist/types/panel/styles.d.ts +1 -1
- 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/solid.d.ts +23 -0
- package/dist/types/ui/split-model.d.ts +54 -0
- package/dist/types/ui/split.d.ts +17 -0
- package/dist/ui-react.js +3239 -567
- package/dist/ui-solid.js +3123 -472
- package/dist/ui.js +2688 -100
- package/package.json +1 -1
|
@@ -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,19 @@
|
|
|
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
|
+
/**
|
|
11
|
+
* The option a digit picks among `count` (0-based), or undefined when it names none: 1–9 pick
|
|
12
|
+
* the first nine, as they read.
|
|
13
|
+
*/
|
|
14
|
+
export declare function digitOption(key: string, count: number): number | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* The digit hints for a row's options: one per option that shows (up to nine), or, while they
|
|
17
|
+
* don't (a menu, a folded editor), one standing for them all ("1–5").
|
|
18
|
+
*/
|
|
19
|
+
export declare function digitHints(count: number, showing: boolean): string[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Knob as KnobField } from "../core/schema.js";
|
|
2
|
-
import { Roll, Spring, VelocityTracker } from "./motion.js";
|
|
2
|
+
import { type Roll, Spring, VelocityTracker } from "./motion.js";
|
|
3
3
|
export interface DigitPlace {
|
|
4
4
|
ch: string;
|
|
5
5
|
/** Unbounded wheel position for digits (… 9, 10, 11 …); face = position mod 10. */
|
|
@@ -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,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;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** The built-in kinds, each with its icon and tint. */
|
|
2
|
+
export type ActivityKind = "you" | "agent" | "proposal" | "review" | "pr" | "build" | "blocked" | "shipped";
|
|
3
|
+
export interface ActivityEvent {
|
|
4
|
+
/** Unique, and the same across `set()`: rows keep their identity and glide. */
|
|
5
|
+
readonly id: string;
|
|
6
|
+
/** A built-in kind (its icon and tint), or a name of your own with `icon` and `tint`. */
|
|
7
|
+
readonly kind?: ActivityKind | (string & {});
|
|
8
|
+
/** Who: "You", "Agent", "Ryan". */
|
|
9
|
+
readonly actor?: string;
|
|
10
|
+
/** One line; it fades at its end when cut off. A group without one says "3 updates". */
|
|
11
|
+
readonly title?: string;
|
|
12
|
+
/** Trailing, already formatted ("2m", "9:41"); it rolls when it changes. */
|
|
13
|
+
readonly time?: string;
|
|
14
|
+
/** A duration chip, "▶ 0:32": seconds, or text as it should read. */
|
|
15
|
+
readonly duration?: number | string;
|
|
16
|
+
/** Small thumbnails before the time (image URLs; four at most show). */
|
|
17
|
+
readonly thumbs?: readonly string[];
|
|
18
|
+
/** SVG markup, a glyph or a node in place of the kind's icon. */
|
|
19
|
+
readonly icon?: string | Node;
|
|
20
|
+
/** A CSS colour in place of the kind's tint. */
|
|
21
|
+
readonly tint?: string;
|
|
22
|
+
/** Makes a group that opens in place ("Agent · 3 updates"). One level deep. */
|
|
23
|
+
readonly children?: readonly ActivityEvent[];
|
|
24
|
+
/** The live or latest step: its node takes a ring. */
|
|
25
|
+
readonly current?: boolean;
|
|
26
|
+
/** Quieter: past steps, minor updates. */
|
|
27
|
+
readonly dimmed?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface ActivityRow {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly event: ActivityEvent;
|
|
32
|
+
/** 0 for an event, 1 for one inside an open group. */
|
|
33
|
+
readonly depth: 0 | 1;
|
|
34
|
+
readonly parent: string | null;
|
|
35
|
+
/** Has children: it opens and closes. */
|
|
36
|
+
readonly group: boolean;
|
|
37
|
+
readonly expanded: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** The rows on show: every event, and the children of open groups right after their group. */
|
|
40
|
+
export declare function flattenActivity(events: readonly ActivityEvent[], expanded: ReadonlySet<string>): ActivityRow[];
|
|
41
|
+
/** Every event by id, children included, with its group. */
|
|
42
|
+
export declare function indexActivity(events: readonly ActivityEvent[]): Map<string, {
|
|
43
|
+
event: ActivityEvent;
|
|
44
|
+
parent: string | null;
|
|
45
|
+
}>;
|
|
46
|
+
export type ActivityKey = "ArrowUp" | "ArrowDown" | "ArrowLeft" | "ArrowRight" | "Home" | "End";
|
|
47
|
+
export declare const isActivityKey: (key: string) => key is ActivityKey;
|
|
48
|
+
/**
|
|
49
|
+
* What a key does from the selected row: ↑/↓ move (from nothing, to the first or the last),
|
|
50
|
+
* Home/End go to the ends, → opens a closed group and steps into an open one, ← closes an open
|
|
51
|
+
* group and steps out of a child to its group.
|
|
52
|
+
*/
|
|
53
|
+
export declare function navigateActivity(rows: readonly ActivityRow[], selected: string | null, key: ActivityKey): {
|
|
54
|
+
select?: string;
|
|
55
|
+
toggle?: {
|
|
56
|
+
id: string;
|
|
57
|
+
expanded: boolean;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Whether the rail takes a key it navigated: ↑/↓ and Home/End whenever there are rows (at an end
|
|
62
|
+
* they hold the selection), ← and → only when they open, close or step. A ← or → that does
|
|
63
|
+
* nothing is left to the page (a review stage stepping frames, say).
|
|
64
|
+
*/
|
|
65
|
+
export declare function takesActivityKey(key: ActivityKey, result: {
|
|
66
|
+
select?: string;
|
|
67
|
+
toggle?: unknown;
|
|
68
|
+
}, rows: number): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Where the selection goes when its row leaves the rows on show: to its group when that closed
|
|
71
|
+
* over it, else nowhere.
|
|
72
|
+
*/
|
|
73
|
+
export declare function keepSelection(rows: readonly ActivityRow[], selected: string | null, parentOf: (id: string) => string | null | undefined): string | null;
|
|
74
|
+
/** What last moved the selection: keys show it as a tint, a pointer hides it. */
|
|
75
|
+
export type ActivityInput = "key" | "pointer" | "blur" | "code";
|
|
76
|
+
/**
|
|
77
|
+
* Whether the selection's tint shows: keys turn it on, a pointer turns it off (a click selects
|
|
78
|
+
* with no ring), losing focus turns it off unless the page's keys drive it, and a host's
|
|
79
|
+
* `select()` leaves it as it was.
|
|
80
|
+
*/
|
|
81
|
+
export declare function showsSelection(showing: boolean, input: ActivityInput, keys?: "focus" | "page"): boolean;
|
|
82
|
+
/** Which ids came, went and stayed between two orders of rows. */
|
|
83
|
+
export declare function diffActivity(before: readonly string[], after: readonly string[]): {
|
|
84
|
+
entered: string[];
|
|
85
|
+
left: string[];
|
|
86
|
+
kept: string[];
|
|
87
|
+
};
|
|
88
|
+
/** Rows stacked top to bottom from `top`: each one's y, and the bottom of the last. */
|
|
89
|
+
export declare function stackRows(heights: readonly number[], top?: number, gap?: number): {
|
|
90
|
+
y: number[];
|
|
91
|
+
bottom: number;
|
|
92
|
+
};
|
|
93
|
+
export interface RailNode {
|
|
94
|
+
/** The node's centre. */
|
|
95
|
+
readonly y: number;
|
|
96
|
+
/** How far the line keeps clear of it on each side. */
|
|
97
|
+
readonly r: number;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The line through the nodes, as segments between neighbours that stop clear of each node, and
|
|
101
|
+
* only within `[from, to]` (the line's ends, which glide as rows come and go). One continuous
|
|
102
|
+
* thread at rest: nothing past the last node, nothing before the first.
|
|
103
|
+
*/
|
|
104
|
+
export declare function railSegments(nodes: readonly RailNode[], from: number, to: number): [number, number][];
|
|
105
|
+
/**
|
|
106
|
+
* The scroll while a change takes the content from height `h0` to `h1` (now at `height`): it goes
|
|
107
|
+
* from `from` to where it has to end (no further down than the shorter content allows) in step
|
|
108
|
+
* with the height, and the space stays tall enough for it meanwhile. Closing a group at the
|
|
109
|
+
* scroll's end, the rows above glide down with the drawer instead of dropping in a frame.
|
|
110
|
+
*/
|
|
111
|
+
export declare function holdScroll(hold: {
|
|
112
|
+
readonly from: number;
|
|
113
|
+
readonly h0: number;
|
|
114
|
+
readonly h1: number;
|
|
115
|
+
}, height: number, view: number): {
|
|
116
|
+
scroll: number;
|
|
117
|
+
space: number;
|
|
118
|
+
};
|
|
119
|
+
/** "0:32", "4:05", "1:02:03"; text passes through as it is. */
|
|
120
|
+
export declare function formatDuration(duration: number | string): string;
|
|
121
|
+
/** A group's title when it has none of its own: "1 update", "3 updates". */
|
|
122
|
+
export declare function groupTitle(count: number): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type ActivityEvent } from "./activity-model.js";
|
|
2
|
+
import { type UiLook } from "./look.js";
|
|
3
|
+
export interface ActivityOptions extends UiLook {
|
|
4
|
+
readonly events: readonly ActivityEvent[];
|
|
5
|
+
/** The selected event's id. */
|
|
6
|
+
readonly selected?: string | null;
|
|
7
|
+
/** Groups open from the start. */
|
|
8
|
+
readonly expanded?: Iterable<string>;
|
|
9
|
+
/** Keys and clicks moved the selection (a host's `select()` doesn't call it). */
|
|
10
|
+
readonly onSelect?: (id: string, event: ActivityEvent, source: "key" | "pointer") => void;
|
|
11
|
+
/** Enter on the selected row (on a group with no `onActivate`, Enter opens or closes it). */
|
|
12
|
+
readonly onActivate?: (id: string, event: ActivityEvent) => void;
|
|
13
|
+
readonly onToggle?: (id: string, expanded: boolean) => void;
|
|
14
|
+
/** Makes the duration chip a button. */
|
|
15
|
+
readonly onPlay?: (id: string, event: ActivityEvent) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Where the newest events are: `"bottom"` (the default; oldest first) or `"top"`. `add()` puts
|
|
18
|
+
* an event there, and new events slide in from that side.
|
|
19
|
+
*/
|
|
20
|
+
readonly newest?: "top" | "bottom";
|
|
21
|
+
/** Stay pinned to the newest end when it was in view as events arrive (default true). */
|
|
22
|
+
readonly follow?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Where the keys come from: `"focus"` (the default) while the rail has focus, or `"page"` from
|
|
25
|
+
* anywhere a focused field or widget doesn't take them.
|
|
26
|
+
*/
|
|
27
|
+
readonly keys?: "focus" | "page";
|
|
28
|
+
/** Icons (SVG markup or a glyph) for your own kinds, or in place of built-in ones. */
|
|
29
|
+
readonly icons?: Readonly<Record<string, string>>;
|
|
30
|
+
/** Tints (CSS colours) for your own kinds, or in place of built-in ones. */
|
|
31
|
+
readonly tints?: Readonly<Record<string, string>>;
|
|
32
|
+
/** The rail's accessible name. */
|
|
33
|
+
readonly label?: string;
|
|
34
|
+
/** Shown with no events. */
|
|
35
|
+
readonly empty?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface Activity {
|
|
38
|
+
readonly element: HTMLElement;
|
|
39
|
+
/** New events: those that stay glide to their places, new ones bloom in, gone ones soften out. */
|
|
40
|
+
set(events: readonly ActivityEvent[]): void;
|
|
41
|
+
/** One more event at the newest end. */
|
|
42
|
+
add(event: ActivityEvent): void;
|
|
43
|
+
/**
|
|
44
|
+
* New events and where the newest are, in one change: rows glide to their new places, as with
|
|
45
|
+
* `set` (flipping `newest` with the events reversed reverses the rail in place).
|
|
46
|
+
*/
|
|
47
|
+
update(next: {
|
|
48
|
+
events?: readonly ActivityEvent[];
|
|
49
|
+
newest?: "top" | "bottom";
|
|
50
|
+
}): void;
|
|
51
|
+
/** Selects (opening its group); `null` clears. Doesn't call `onSelect`. */
|
|
52
|
+
select(id: string | null): void;
|
|
53
|
+
readonly selected: string | null;
|
|
54
|
+
expand(id: string, expanded?: boolean): void;
|
|
55
|
+
isExpanded(id: string): boolean;
|
|
56
|
+
focus(): void;
|
|
57
|
+
destroy(): void;
|
|
58
|
+
/** The same as `destroy`, as the other pieces name it. */
|
|
59
|
+
dispose(): void;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A vertical rail of events: who did what, one line each, with a kind's icon on one continuous
|
|
63
|
+
* line. Groups open in place with their children on the same line. ↑/↓, Home/End move the
|
|
64
|
+
* selection (a tint that shows only once keys move it), ←/→ close and open groups, Enter
|
|
65
|
+
* activates. New events bloom in while the rest glide; every move is an interruptible spring.
|
|
66
|
+
*/
|
|
67
|
+
export declare function activity(container: HTMLElement, options: ActivityOptions): Activity;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A diff for each set of prop values, given the values the piece was made with: the props that
|
|
3
|
+
* differ from the last values seen (at first, those at creation).
|
|
4
|
+
*/
|
|
5
|
+
export declare function propChanges<P extends object>(initial: P, same?: (a: unknown, b: unknown) => boolean): (next: P) => Partial<P>;
|
|
6
|
+
/** Whether a diff from `propChanges` has anything in it. */
|
|
7
|
+
export declare const anyChanged: (changed: object) => boolean;
|
|
8
|
+
/**
|
|
9
|
+
* A timeline's changed props as one `update()`: unset ones dropped, and so are values it already
|
|
10
|
+
* has (a `time` or `playing` it reported itself, markers it handed out).
|
|
11
|
+
*/
|
|
12
|
+
export declare function timelineChanges<C extends Record<string, unknown>>(now: {
|
|
13
|
+
readonly time: number;
|
|
14
|
+
readonly playing: boolean;
|
|
15
|
+
readonly markers: unknown;
|
|
16
|
+
readonly ranges: unknown;
|
|
17
|
+
}, changed: C): Partial<C>;
|
package/dist/types/ui/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { popover, type Popover, type PopoverCloseReason, type PopoverOptions } f
|
|
|
10
10
|
export { readout, type Readout } from "./readouts.js";
|
|
11
11
|
export { toast, type Toast, type ToastOptions } from "./toast.js";
|
|
12
12
|
export { tooltip, tooltips, type TooltipContent, type TooltipOptions } from "./tooltip.js";
|
|
13
|
-
export { split, type PaneAction, type PaneOptions, type Split, type SplitOptions, type SplitPane, } from "./split.js";
|
|
13
|
+
export { split, type PaneAction, type PaneOptions, type Split, type SplitFold, type SplitOptions, type SplitPane, } from "./split.js";
|
|
14
14
|
export { COLLAPSE_PAST, drag as dragDivider, parseLayout, softClamp, solve as solveSplit, type PaneSpec, type SplitDirection, type SplitLayout, } from "./split-model.js";
|
|
15
15
|
export { timeline, type SeekSource, type Timeline, type TimelineMarker, type TimelineOptions, type TimelineRange, } from "./timeline.js";
|
|
16
16
|
export { adjacentMarker, advance, clampView, followView, frameTime, magnet, rulerTicks, scrubTime, timeLabel, toFrame, zoomView, type Tick, type Ticks, type TimeUnits, type TimeView, } from "./timeline-model.js";
|
|
@@ -18,3 +18,7 @@ export { tree, type Tree, type TreeOptions, type TreeRowContext, type TreeToggle
|
|
|
18
18
|
export { applyMove, plainAccess, dropTarget, flatten, indexTree, matchLabel, navigate, select, stepMove, topmost, typeahead, validMove, virtualWindow, type DropTarget, type FlatRow, type Selection, type TreeAccess, type TreeFilter, type TreeMove, } from "./tree-model.js";
|
|
19
19
|
export { bento, type Bento, type BentoItem, type BentoOptions } from "./bento.js";
|
|
20
20
|
export { fitRect, FIT_MAX, motionBlur, neighbour as bentoNeighbour, planBento, rowTarget, windowOf as bentoWindow, type BentoDirection, type BentoLayout, type BentoMode, type BentoPlan, type BentoRect, } from "./bento-model.js";
|
|
21
|
+
export { activity, type Activity, type ActivityOptions, } from "./activity.js";
|
|
22
|
+
export { diffActivity, flattenActivity, formatDuration, indexActivity, keepSelection as keepActivitySelection, navigateActivity, railSegments, showsSelection, stackRows, type ActivityEvent, type ActivityInput, type ActivityKey, type ActivityKind, type ActivityRow, type RailNode, } from "./activity-model.js";
|
|
23
|
+
export { optionPicker, type OptionPicker, type OptionPickerOptions, type PickerOption, } from "./picker.js";
|
|
24
|
+
export { claimsDigit, digitPick, keepPick, stepPick } from "./picker-model.js";
|
package/dist/types/ui/look.d.ts
CHANGED
|
@@ -31,6 +31,15 @@ export declare const lookDefaults: {
|
|
|
31
31
|
current: UiLook;
|
|
32
32
|
};
|
|
33
33
|
export declare const reducedMotion: (win: Window | null) => boolean;
|
|
34
|
+
export declare const tune: <T extends {
|
|
35
|
+
omega: number;
|
|
36
|
+
zeta: number;
|
|
37
|
+
}>(s: T, m: {
|
|
38
|
+
readonly omega: number;
|
|
39
|
+
readonly zeta: number;
|
|
40
|
+
}) => T;
|
|
41
|
+
/** Whether `target` is an editable field or select that takes typed keys. */
|
|
42
|
+
export declare const isTypingTarget: (t: EventTarget | null) => boolean;
|
|
34
43
|
/**
|
|
35
44
|
* Tells the page's GPU overlay that layout moved (a pane resizing, collapsing or sliding): it
|
|
36
45
|
* reads rects afresh on its next frame, so a panel inside a pane keeps up.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const pickerCss: string;
|