@kitlangton/knobs 0.0.0-stage → 0.1.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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1270 -2
  3. package/dist/dev/chunk-e13znr7c.js +155 -0
  4. package/dist/dev/chunk-kdewv0mt.js +1334 -0
  5. package/dist/dev/chunk-ktsn42p7.js +15668 -0
  6. package/dist/dev/chunk-qtt698hp.js +1118 -0
  7. package/dist/dev/index.js +1389 -0
  8. package/dist/index.js +2367 -0
  9. package/dist/panel.js +23635 -0
  10. package/dist/react.js +326 -0
  11. package/dist/solid.js +336 -0
  12. package/dist/themes.js +235 -0
  13. package/dist/types/core/adapter.d.ts +21 -0
  14. package/dist/types/core/code.d.ts +3 -0
  15. package/dist/types/core/color.d.ts +27 -0
  16. package/dist/types/core/easing.d.ts +21 -0
  17. package/dist/types/core/internal.d.ts +18 -0
  18. package/dist/types/core/keys.d.ts +21 -0
  19. package/dist/types/core/mutate.d.ts +21 -0
  20. package/dist/types/core/ramp.d.ts +150 -0
  21. package/dist/types/core/random.d.ts +2 -0
  22. package/dist/types/core/readout.d.ts +73 -0
  23. package/dist/types/core/remote.d.ts +258 -0
  24. package/dist/types/core/schema.d.ts +526 -0
  25. package/dist/types/core/spring.d.ts +38 -0
  26. package/dist/types/core/storage.d.ts +8 -0
  27. package/dist/types/core/store.d.ts +474 -0
  28. package/dist/types/core/theme.d.ts +27 -0
  29. package/dist/types/core/variants.d.ts +17 -0
  30. package/dist/types/index.d.ts +13 -0
  31. package/dist/types/panel/attach.d.ts +5 -0
  32. package/dist/types/panel/choice.d.ts +56 -0
  33. package/dist/types/panel/die.d.ts +121 -0
  34. package/dist/types/panel/editors.d.ts +159 -0
  35. package/dist/types/panel/folds.d.ts +14 -0
  36. package/dist/types/panel/give.d.ts +73 -0
  37. package/dist/types/panel/gpu/atlas.d.ts +40 -0
  38. package/dist/types/panel/gpu/color.d.ts +128 -0
  39. package/dist/types/panel/gpu/die.wgsl.d.ts +1 -0
  40. package/dist/types/panel/gpu/renderer.d.ts +712 -0
  41. package/dist/types/panel/gpu/shader.wgsl.d.ts +2 -0
  42. package/dist/types/panel/haptics.d.ts +8 -0
  43. package/dist/types/panel/has.d.ts +14 -0
  44. package/dist/types/panel/hints.d.ts +45 -0
  45. package/dist/types/panel/icons.d.ts +30 -0
  46. package/dist/types/panel/index.d.ts +10 -0
  47. package/dist/types/panel/inspector.d.ts +20 -0
  48. package/dist/types/panel/measure.d.ts +6 -0
  49. package/dist/types/panel/menu.d.ts +38 -0
  50. package/dist/types/panel/morph.d.ts +38 -0
  51. package/dist/types/panel/motion.d.ts +77 -0
  52. package/dist/types/panel/page-hints.d.ts +82 -0
  53. package/dist/types/panel/page.d.ts +423 -0
  54. package/dist/types/panel/panel.d.ts +43 -0
  55. package/dist/types/panel/position.d.ts +5 -0
  56. package/dist/types/panel/prefs.d.ts +28 -0
  57. package/dist/types/panel/presets.d.ts +48 -0
  58. package/dist/types/panel/ramps.d.ts +14 -0
  59. package/dist/types/panel/readout-row.d.ts +21 -0
  60. package/dist/types/panel/reveal.d.ts +15 -0
  61. package/dist/types/panel/rolling-css.d.ts +2 -0
  62. package/dist/types/panel/search.d.ts +36 -0
  63. package/dist/types/panel/seg.d.ts +15 -0
  64. package/dist/types/panel/session.d.ts +21 -0
  65. package/dist/types/panel/slider.d.ts +92 -0
  66. package/dist/types/panel/smear.d.ts +7 -0
  67. package/dist/types/panel/spin.d.ts +13 -0
  68. package/dist/types/panel/styles.d.ts +11 -0
  69. package/dist/types/panel/swipe.d.ts +114 -0
  70. package/dist/types/panel/switcher.d.ts +20 -0
  71. package/dist/types/panel/tabs.d.ts +31 -0
  72. package/dist/types/panel/thumb.d.ts +36 -0
  73. package/dist/types/react/Knobs.d.ts +15 -0
  74. package/dist/types/react/VariantSwitcher.d.ts +10 -0
  75. package/dist/types/react/index.d.ts +3 -0
  76. package/dist/types/react/useControls.d.ts +11 -0
  77. package/dist/types/solid/index.d.ts +50 -0
  78. package/dist/types/themes/index.d.ts +1 -0
  79. package/dist/types/ui/bento-css.d.ts +1 -0
  80. package/dist/types/ui/bento-model.d.ts +96 -0
  81. package/dist/types/ui/bento.d.ts +77 -0
  82. package/dist/types/ui/commands.d.ts +11 -0
  83. package/dist/types/ui/confirm.d.ts +13 -0
  84. package/dist/types/ui/index.d.ts +20 -0
  85. package/dist/types/ui/layer.d.ts +70 -0
  86. package/dist/types/ui/look.d.ts +53 -0
  87. package/dist/types/ui/menu.d.ts +63 -0
  88. package/dist/types/ui/overlay-css.d.ts +1 -0
  89. package/dist/types/ui/overlay-model.d.ts +87 -0
  90. package/dist/types/ui/palette.d.ts +38 -0
  91. package/dist/types/ui/popover.d.ts +35 -0
  92. package/dist/types/ui/react.d.ts +45 -0
  93. package/dist/types/ui/readout-css.d.ts +1 -0
  94. package/dist/types/ui/readout-model.d.ts +137 -0
  95. package/dist/types/ui/readouts.d.ts +85 -0
  96. package/dist/types/ui/rgba.d.ts +6 -0
  97. package/dist/types/ui/solid.d.ts +40 -0
  98. package/dist/types/ui/split-model.d.ts +81 -0
  99. package/dist/types/ui/split.d.ts +75 -0
  100. package/dist/types/ui/timeline-model.d.ts +83 -0
  101. package/dist/types/ui/timeline.d.ts +76 -0
  102. package/dist/types/ui/toast.d.ts +19 -0
  103. package/dist/types/ui/tooltip.d.ts +19 -0
  104. package/dist/types/ui/tree-model.d.ts +125 -0
  105. package/dist/types/ui/tree.d.ts +95 -0
  106. package/dist/ui-react.js +6558 -0
  107. package/dist/ui-solid.js +6511 -0
  108. package/dist/ui.js +9238 -0
  109. package/package.json +99 -4
package/dist/themes.js ADDED
@@ -0,0 +1,235 @@
1
+ // src/core/spring.ts
2
+ class Spring {
3
+ x;
4
+ omega;
5
+ zeta;
6
+ eps;
7
+ v = 0;
8
+ target;
9
+ next;
10
+ constructor(x, omega = 24, zeta = 0.86, eps = 0.005) {
11
+ this.x = x;
12
+ this.omega = omega;
13
+ this.zeta = zeta;
14
+ this.eps = eps;
15
+ this.target = x;
16
+ }
17
+ set(target, delay = 0) {
18
+ if (delay > 0) {
19
+ if (target !== (this.next?.target ?? this.target))
20
+ this.next = { target, at: performance.now() + delay };
21
+ return this;
22
+ }
23
+ this.next = undefined;
24
+ this.target = target;
25
+ return this;
26
+ }
27
+ snap(x) {
28
+ this.next = undefined;
29
+ this.x = this.target = x;
30
+ this.v = 0;
31
+ return this;
32
+ }
33
+ step(dt) {
34
+ if (this.next && performance.now() >= this.next.at) {
35
+ this.target = this.next.target;
36
+ this.next = undefined;
37
+ }
38
+ if (this.x === this.target && this.v === 0)
39
+ return this.x;
40
+ const clampedDt = Math.min(dt, 0.1);
41
+ const h = 1 / 480;
42
+ for (let t = 0;t < clampedDt; t += h) {
43
+ const k = Math.min(h, clampedDt - t);
44
+ const a = -this.omega * this.omega * (this.x - this.target) - 2 * this.zeta * this.omega * this.v;
45
+ this.v += a * k;
46
+ this.x += this.v * k;
47
+ }
48
+ if (this.resting) {
49
+ this.x = this.target;
50
+ this.v = 0;
51
+ }
52
+ return this.x;
53
+ }
54
+ get resting() {
55
+ return !this.next && Math.abs(this.x - this.target) < this.eps && Math.abs(this.v) < this.eps * 4;
56
+ }
57
+ }
58
+ function springMotion({ duration, bounce }) {
59
+ return {
60
+ omega: 2 * Math.PI / Math.max(0.01, duration),
61
+ zeta: bounce >= 0 ? 1 - bounce : 1 / (1 + bounce)
62
+ };
63
+ }
64
+ function springEasing(omega, zeta) {
65
+ const s = new Spring(0, omega, zeta).set(1);
66
+ const points = ["0"];
67
+ const dt = 1 / 60;
68
+ let t = 0;
69
+ while (t < 4 && !(Math.abs(s.x - 1) < 0.002 && Math.abs(s.v) < 0.05)) {
70
+ s.step(dt);
71
+ t += dt;
72
+ points.push(String(Math.round(s.x * 1000) / 1000));
73
+ }
74
+ points[points.length - 1] = "1";
75
+ return { easing: `linear(${points.join(",")})`, ms: Math.round(t * 1000) };
76
+ }
77
+ function springCss(value) {
78
+ const { omega, zeta } = springMotion(value);
79
+ const { easing, ms } = springEasing(omega, zeta);
80
+ return { easing, duration: ms };
81
+ }
82
+
83
+ // src/core/theme.ts
84
+ var block = (tokens) => Object.entries(tokens).map(([k, v]) => `${k.startsWith("--") || k === "color-scheme" ? k : `--k-${k}`}:${v}`).join(";");
85
+ function presetCss(preset) {
86
+ if (!/^[\w-]+$/.test(preset.name))
87
+ throw new Error(`Invalid preset name: ${preset.name}`);
88
+ const at = `.knobs-panel[data-preset="${preset.name}"]`;
89
+ const light = preset.light ? `:where(.knobs-panel)[data-preset="${preset.name}"][data-theme="light"],` + `:where([data-knobs-theme="light"]) ${at}:not([data-theme="dark"]),` + `:where(:root[data-theme="light"]) ${at}[data-theme="auto"]{${block(preset.light)}}` + `@media(prefers-color-scheme:light){:where(:root:not([data-theme="dark"])) ${at}[data-theme="auto"]{${block(preset.light)}}}` : "";
90
+ return `${at}{${block(preset.dark)}}${light}${(preset.css ?? "").replaceAll("&", at)}`;
91
+ }
92
+ var glide = /* @__PURE__ */ springCss({ duration: 0.185, bounce: 0.14 });
93
+ var labPreset = {
94
+ name: "lab",
95
+ dark: {
96
+ bg: "#191a1e",
97
+ page: "#141518",
98
+ surface: "rgba(216,219,224,.045)",
99
+ "surface-hi": "rgba(216,219,224,.08)",
100
+ line: "#23252a",
101
+ "line-hi": "#2c2f35",
102
+ text: "#d8dbe0",
103
+ label: "#8b9098",
104
+ dim: "#5b6068",
105
+ accent: "#4d8dff",
106
+ modified: "#4d8dff",
107
+ "seg-bg": "#0e0f11",
108
+ "seg-active": "#2c3747",
109
+ "toggle-track": "#2c2f35",
110
+ "toggle-thumb": "#fff",
111
+ thumb: "#c4c8ce",
112
+ rim: "transparent",
113
+ font: '-apple-system,BlinkMacSystemFont,"SF Pro Text",system-ui,sans-serif',
114
+ mono: 'ui-monospace,"SF Mono",SFMono-Regular,Menlo,monospace',
115
+ "font-size": "11.5px",
116
+ "font-weight": 400,
117
+ "label-weight": 400,
118
+ "title-weight": 600,
119
+ row: "26px",
120
+ pad: "0px",
121
+ inset: "8px",
122
+ gap: "3px",
123
+ radius: "0px",
124
+ "panel-radius": "0px",
125
+ frame: "flat",
126
+ chrome: 0,
127
+ glass: 0,
128
+ glow: 0,
129
+ lift: 0,
130
+ blur: 0.6,
131
+ "hover-in": 0,
132
+ "hover-out": 120,
133
+ "fill-rest-mix": "22%",
134
+ "fill-hover-mix": "28%",
135
+ "fill-active-mix": "34%",
136
+ "handle-rest": 0,
137
+ "value-font": "var(--k-mono)",
138
+ "unit-gap": ".3em",
139
+ "readout-mark": 1,
140
+ "group-size": "10px",
141
+ "group-color": "#8b9098",
142
+ "group-tracking": ".06em",
143
+ "group-divider": "#23252a",
144
+ "group-divider-gap": "6px",
145
+ "disabled-opacity": 0.38,
146
+ "curve-guide": "dashed",
147
+ "curve-dot": 0
148
+ }
149
+ };
150
+ var problemsPreset = {
151
+ name: "problems",
152
+ dark: {
153
+ bg: "#161618",
154
+ surface: "rgba(255,255,255,.055)",
155
+ "surface-hi": "rgba(255,255,255,.11)",
156
+ line: "rgba(255,255,255,.075)",
157
+ "line-hi": "rgba(255,255,255,.12)",
158
+ text: "#f5f5f7",
159
+ label: "rgba(235,235,245,.62)",
160
+ dim: "rgba(235,235,245,.36)",
161
+ accent: "#0091ff",
162
+ modified: "#0091ff",
163
+ "seg-bg": "rgba(120,120,128,.16)",
164
+ "seg-active": "rgba(120,120,128,.24)",
165
+ "toggle-track": "rgba(120,120,128,.32)",
166
+ "toggle-thumb": "#fff",
167
+ rim: "transparent",
168
+ font: 'system-ui,-apple-system,BlinkMacSystemFont,"Helvetica Neue",sans-serif',
169
+ mono: '"Kit SF Mono",ui-monospace,Menlo,monospace',
170
+ "font-size": "13px",
171
+ "label-weight": 500,
172
+ row: "34px",
173
+ radius: "8px",
174
+ "panel-radius": "12px",
175
+ pad: "0px",
176
+ inset: "10px",
177
+ gap: "4px",
178
+ "row-gap": "4px",
179
+ glass: 0,
180
+ glow: 0,
181
+ blur: 0.6,
182
+ "hover-in": 0,
183
+ "hover-out": 100,
184
+ "handle-rest": 0.55,
185
+ handle: "var(--k-text)",
186
+ "handle-inset": "7.5px",
187
+ "fill-base": "transparent",
188
+ "fill-rest-mix": "24%",
189
+ "fill-hover-mix": "36%",
190
+ "fill-active-mix": "36%",
191
+ chrome: 0,
192
+ "group-transform": "none",
193
+ "value-font": "var(--k-mono)",
194
+ "unit-gap": "0px",
195
+ "unit-color": "currentColor",
196
+ "readout-mark": 1,
197
+ "group-size": "12px",
198
+ "group-color": "rgba(235,235,245,.62)",
199
+ "group-inset": "0px",
200
+ "group-tracking": "-.01em",
201
+ "group-divider": "rgba(255,255,255,.075)",
202
+ "group-divider-gap": "8px",
203
+ "curve-inset": "0px",
204
+ "curve-guide": "dashed",
205
+ "curve-dot": 0,
206
+ spring: "0.185 0.14",
207
+ "fill-ease": glide.easing,
208
+ "fill-ms": glide.duration
209
+ },
210
+ light: {
211
+ bg: "#fff",
212
+ surface: "rgba(0,0,0,.045)",
213
+ "surface-hi": "rgba(0,0,0,.09)",
214
+ line: "rgba(0,0,0,.07)",
215
+ "line-hi": "rgba(0,0,0,.11)",
216
+ text: "#000",
217
+ label: "rgba(60,60,67,.66)",
218
+ dim: "rgba(60,60,67,.38)",
219
+ accent: "#0088ff",
220
+ modified: "#0088ff",
221
+ "seg-bg": "rgba(120,120,128,.11)",
222
+ "seg-active": "#fff",
223
+ "toggle-track": "rgba(120,120,128,.2)",
224
+ "toggle-thumb": "#fff",
225
+ rim: "transparent",
226
+ "group-color": "rgba(60,60,67,.66)",
227
+ "group-divider": "rgba(0,0,0,.07)",
228
+ "color-scheme": "light"
229
+ }
230
+ };
231
+ export {
232
+ labPreset,
233
+ presetCss,
234
+ problemsPreset
235
+ };
@@ -0,0 +1,21 @@
1
+ import type { ControlDescriptor } from "./remote.js";
2
+ import type { Choice, Schema, State } from "./schema.js";
3
+ import type { Controls, DensityMode, MountPanelOptions, ThemeMode } from "./store.js";
4
+ export type ChoiceKey<S extends Schema> = {
5
+ [K in keyof S]: S[K] extends Choice ? K : never;
6
+ }[keyof S] & string;
7
+ export type KnobsPanelProps<S extends Schema = Schema> = MountPanelOptions & {
8
+ controls: Controls<S>;
9
+ /**
10
+ * Remote controls only: the descriptors to show. A new array swaps rows in place
11
+ * (`remote.update`); the same array with new `values` just syncs them.
12
+ */
13
+ descriptors?: readonly ControlDescriptor[];
14
+ /** The values to show, from your own state. Keys in hand keep theirs until let go. */
15
+ values?: State;
16
+ /** While true, a different item is arriving: values land without springing or rolling. */
17
+ land?: boolean;
18
+ };
19
+ export declare function syncPanelLook<S extends Schema>(host: HTMLElement | null | undefined, controls: Controls<S>, theme?: ThemeMode, density?: DensityMode): void;
20
+ export declare function syncPanelValues<S extends Schema>(controls: Controls<S>, descriptors?: readonly ControlDescriptor[], values?: State, land?: boolean): void;
21
+ export declare function mountPanelAdapter<S extends Schema>(host: HTMLElement, controls: Controls<S>, getProps: () => KnobsPanelProps<S>): () => void;
@@ -0,0 +1,3 @@
1
+ import type { State } from "./schema.js";
2
+ export type CodeFormat = "ts" | "json" | "jsx";
3
+ export declare function formatCode(values: State, format?: CodeFormat): string;
@@ -0,0 +1,27 @@
1
+ /** An OKLCH colour: lightness 0…1, chroma 0…0.37, hue in degrees. */
2
+ export interface Oklch {
3
+ readonly l: number;
4
+ readonly c: number;
5
+ readonly h: number;
6
+ }
7
+ export declare const CHROMA_MAX = 0.37;
8
+ /** OKLCH → linear-light sRGB (unclamped). */
9
+ export declare function oklchToLinear({ l, c, h }: Oklch): [number, number, number];
10
+ /** The most chroma this lightness and hue can carry inside sRGB. */
11
+ export declare function maxChroma(l: number, h: number): number;
12
+ /**
13
+ * Gamut-mapped sRGB channels 0…1: a colour inside sRGB (any hex, as the string keeps it) converts
14
+ * as it is; one outside drops chroma until it fits, keeping lightness and hue.
15
+ */
16
+ export declare function oklchToRgb(color: Oklch): [number, number, number];
17
+ export declare function rgbToOklch(r: number, g: number, b: number): Oklch;
18
+ /** Reads `oklch(L C H)` (L as 0…1 or a percentage), `#rgb` / `#rrggbb`, or `{ l, c, h }`. */
19
+ export declare function parseColor(input: unknown): Oklch | undefined;
20
+ /**
21
+ * Clamps into range and into sRGB (by chroma), rounded to what the string keeps: five places for
22
+ * lightness and chroma and three for hue, enough for every hex to come back exactly.
23
+ */
24
+ export declare function fitColor({ l, c, h }: Oklch): Oklch;
25
+ export declare function formatColor({ l, c, h }: Oklch): string;
26
+ /** A colour field's value as `#rrggbb` (sRGB). */
27
+ export declare function colorHex(value: string | Oklch): string;
@@ -0,0 +1,21 @@
1
+ /** A CSS `cubic-bezier(x1, y1, x2, y2)`. */
2
+ export type Bezier = readonly [number, number, number, number];
3
+ /** Named curves offered as chips in the easing editor. */
4
+ export declare const EASINGS: {
5
+ readonly ease: readonly [0.25, 0.1, 0.25, 1];
6
+ readonly in: readonly [0.42, 0, 1, 1];
7
+ readonly out: readonly [0, 0, 0.58, 1];
8
+ readonly "in-out": readonly [0.42, 0, 0.58, 1];
9
+ readonly linear: readonly [0, 0, 1, 1];
10
+ readonly smooth: readonly [0.65, 0, 0.35, 1];
11
+ readonly snappy: readonly [0.32, 0.72, 0, 1];
12
+ readonly back: readonly [0.34, 1.56, 0.64, 1];
13
+ };
14
+ export type EasingName = keyof typeof EASINGS | "ease-in" | "ease-out" | "ease-in-out";
15
+ export declare const EASING_Y: readonly [number, number];
16
+ export declare function namedEasing(name: string): Bezier | undefined;
17
+ /** The chip name whose curve this is, if any. */
18
+ export declare function easingName(value: Bezier): string | undefined;
19
+ /** The curve's point at parameter `t` (not time): (0,0) → (1,1). */
20
+ export declare function bezierPoint([x1, y1, x2, y2]: Bezier, t: number): [number, number];
21
+ export declare const bezierCss: (b: Bezier) => string;
@@ -0,0 +1,18 @@
1
+ import type { Schema, State } from "./schema.js";
2
+ /**
3
+ * Swaps a store's schema in place (see `RemoteControls.update`). Shared through the global symbol
4
+ * registry, so the core and panel bundles agree on it.
5
+ */
6
+ export declare const RESHAPE: unique symbol;
7
+ export type Reshape = (schema: Schema, values: State, options?: {
8
+ land?: boolean;
9
+ mixed?: readonly string[];
10
+ }) => void;
11
+ /**
12
+ * What a remote store hears from `reset(keys)` in place of an `onReset({ key })` per key, so it
13
+ * can send the keys' resets together (see `RemoteControlsOptions.onSetBatch`).
14
+ */
15
+ export declare const RESET_KEYS: unique symbol;
16
+ export type ResetKeys = (keys: readonly string[]) => void;
17
+ /** How many times `controls.refresh()` was called: panels ask their accessories again when it moves. */
18
+ export declare const REFRESHES: unique symbol;
@@ -0,0 +1,21 @@
1
+ export declare const isApple: () => boolean;
2
+ /** The canonical spelling of a combination, or `""` when it names no key. */
3
+ export declare function normalizeCombo(combo: string): string;
4
+ /**
5
+ * The letter a keydown means in the user's layout, or `""` when it isn't a letter key. The typed
6
+ * character when it's a plain letter (Dvorak's K is K); with ⌥, which changes the character on
7
+ * macOS, the layout's letter for that physical key (QWERTY's when the layout can't be read).
8
+ */
9
+ export declare function letterOf(event: KeyboardEvent): string;
10
+ /** The combination a keydown names, or `""` for a bare modifier. */
11
+ export declare function comboFromEvent(event: KeyboardEvent): string;
12
+ /** The modifiers held in an event, as a combination's prefix (for a live capture preview). */
13
+ export declare function heldMods(event: KeyboardEvent): string[];
14
+ /** Whether a keydown presses `combo` (normalized or not). */
15
+ export declare function matchesCombo(combo: string, event: KeyboardEvent): boolean;
16
+ /**
17
+ * How a combination reads: Apple glyphs on Apple platforms (`⌃⌥⇧⌘K`), words elsewhere
18
+ * (`Ctrl+Shift+K`). Returns the parts so a chip can space them.
19
+ */
20
+ export declare function comboParts(combo: string, apple?: boolean): string[];
21
+ export declare function comboLabel(combo: string, apple?: boolean): string;
@@ -0,0 +1,21 @@
1
+ import { type Schema, type Value } from "./schema.js";
2
+ /**
3
+ * How far a mutation moves things. Numbers and points move 8–12% of their range either way;
4
+ * springs the same share of the range Random uses; choices and switches change only `flip` of the
5
+ * time. Colours turn a few degrees and shift chroma and lightness slightly.
6
+ */
7
+ export declare const MUTATE: {
8
+ readonly min: 0.08;
9
+ readonly max: 0.12;
10
+ readonly flip: 0.15;
11
+ readonly hue: readonly [4, 12];
12
+ readonly chroma: 0.02;
13
+ readonly lightness: readonly [0.015, 0.045];
14
+ readonly bezier: readonly [0.03, 0.06];
15
+ };
16
+ /**
17
+ * Small changes around `current` for `keys` (every field when omitted): a nudge rather than a
18
+ * re-roll. When only switches and choices are asked for and none happened to change, one flips,
19
+ * so a mutation always does something.
20
+ */
21
+ export declare function mutateValues<S extends Schema>(schema: S, current: Readonly<Record<string, Value>>, keys?: readonly string[], random?: () => number): Record<string, Value>;
@@ -0,0 +1,150 @@
1
+ /** A gradient stop: where it sits (0…1) and its colour, as an `oklch(L C H)` string. */
2
+ export interface GradientStop {
3
+ readonly at: number;
4
+ readonly color: string;
5
+ }
6
+ /** How a gradient blends between its stops: perceptually (`oklab`) or as CSS does (`srgb`). */
7
+ export type GradientSpace = "oklab" | "srgb";
8
+ /** A curve point: x across 0…1, y in the curve's range. */
9
+ export interface CurvePoint {
10
+ readonly x: number;
11
+ readonly y: number;
12
+ }
13
+ /** `smooth` is a monotone cubic: it never overshoots its points, so a falloff stays a falloff. */
14
+ export type CurveInterp = "smooth" | "linear" | "step";
15
+ export declare const MAX_STOPS = 32;
16
+ export declare const MAX_POINTS = 64;
17
+ /** How many evenly spaced samples a gradient strip and a curve are drawn from (both renderers). */
18
+ export declare const GRADIENT_SAMPLES = 64;
19
+ export declare const CURVE_SAMPLES = 129;
20
+ /** Stops as given, sorted by position (ties keep their order) and clamped; undefined if fewer than two. */
21
+ export declare function normalizeStops(input: unknown): readonly GradientStop[] | undefined;
22
+ /**
23
+ * Points sorted by x and clamped into `range`, the first at x 0 and the last at x 1 (the ends are
24
+ * pinned); undefined if fewer than two.
25
+ */
26
+ export declare function normalizePoints(input: unknown, range: readonly [number, number]): readonly CurvePoint[] | undefined;
27
+ export interface SampleGradientOptions {
28
+ /** How the stops blend (the field's own `space`); `oklab` by default. Pass the field itself. */
29
+ readonly space?: GradientSpace;
30
+ }
31
+ /**
32
+ * The gradient's colour at `t` (0…1, clamped): an `oklch()` string by default, `"hex"` for
33
+ * `#rrggbb`, `"rgb"` for gamma-encoded sRGB channels (0…1) or `"linear"` for linear-light ones (a
34
+ * shader's). Blends in the field's `space`: `sampleGradient(v.smoke, age, { ...field, as: "linear" })`.
35
+ */
36
+ export declare function sampleGradient(stops: readonly GradientStop[], t: number, options?: SampleGradientOptions & {
37
+ readonly as?: "css" | "hex";
38
+ }): string;
39
+ export declare function sampleGradient(stops: readonly GradientStop[], t: number, options: SampleGradientOptions & {
40
+ readonly as: "rgb" | "linear";
41
+ }): [number, number, number];
42
+ /** `n` evenly spaced samples (t = i / (n − 1)) as gamma-encoded sRGB triples, flat. */
43
+ export declare function gradientSamples(stops: readonly GradientStop[], space: GradientSpace, n?: number): number[];
44
+ /** A CSS `linear-gradient(…)` of the gradient (sampled, so `oklab` blends match). */
45
+ export declare function gradientCss(stops: readonly GradientStop[], options?: SampleGradientOptions & {
46
+ readonly direction?: string;
47
+ }): string;
48
+ /** The colour halfway between two stops, for a stop added between them. */
49
+ export declare function stopColorAt(stops: readonly GradientStop[], space: GradientSpace, t: number): string;
50
+ export interface SampleCurveOptions {
51
+ /** How the points join (the field's own `interp`); `smooth` by default. Pass the field itself. */
52
+ readonly interp?: CurveInterp;
53
+ }
54
+ /** The curve's y at `x` (clamped to 0…1): `sampleCurve(v.falloff, distance / radius, field)`. */
55
+ export declare function sampleCurve(points: readonly CurvePoint[], x: number, options?: SampleCurveOptions): number;
56
+ /**
57
+ * `n` samples of the curve as x, y pairs: evenly spaced in x, except that the sample nearest each
58
+ * point lands on it exactly (corners stay sharp; a step rises straight up). The count never
59
+ * changes, so two curves' samples can be blended point for point.
60
+ */
61
+ export declare function curveSamples(points: readonly CurvePoint[], interp: CurveInterp, n?: number): number[];
62
+ /** Built-in curve shapes, y over 0…1 (mapped onto the field's range). */
63
+ export declare const CURVE_SHAPES: {
64
+ readonly linear: readonly [{
65
+ readonly x: 0;
66
+ readonly y: 0;
67
+ }, {
68
+ readonly x: 1;
69
+ readonly y: 1;
70
+ }];
71
+ readonly "ease-out": readonly [{
72
+ readonly x: 0;
73
+ readonly y: 0;
74
+ }, {
75
+ readonly x: 0.35;
76
+ readonly y: 0.62;
77
+ }, {
78
+ readonly x: 1;
79
+ readonly y: 1;
80
+ }];
81
+ readonly "inverse\u00B2": readonly [{
82
+ readonly x: 0;
83
+ readonly y: 1;
84
+ }, {
85
+ readonly x: 0.1;
86
+ readonly y: 0.85;
87
+ }, {
88
+ readonly x: 0.25;
89
+ readonly y: 0.47;
90
+ }, {
91
+ readonly x: 0.5;
92
+ readonly y: 0.15;
93
+ }, {
94
+ readonly x: 1;
95
+ readonly y: 0;
96
+ }];
97
+ readonly smoothstep: readonly [{
98
+ readonly x: 0;
99
+ readonly y: 0;
100
+ }, {
101
+ readonly x: 0.25;
102
+ readonly y: 0.156;
103
+ }, {
104
+ readonly x: 0.5;
105
+ readonly y: 0.5;
106
+ }, {
107
+ readonly x: 0.75;
108
+ readonly y: 0.844;
109
+ }, {
110
+ readonly x: 1;
111
+ readonly y: 1;
112
+ }];
113
+ readonly bell: readonly [{
114
+ readonly x: 0;
115
+ readonly y: 0;
116
+ }, {
117
+ readonly x: 0.25;
118
+ readonly y: 0.25;
119
+ }, {
120
+ readonly x: 0.5;
121
+ readonly y: 1;
122
+ }, {
123
+ readonly x: 0.75;
124
+ readonly y: 0.25;
125
+ }, {
126
+ readonly x: 1;
127
+ readonly y: 0;
128
+ }];
129
+ };
130
+ /** A 0…1 shape in a y range. */
131
+ export declare const inRange: (shape: readonly CurvePoint[], range: readonly [number, number]) => {
132
+ x: number;
133
+ y: number;
134
+ }[];
135
+ /** Whether two point or stop lists are the same, to a rounding. */
136
+ export declare function sameRamp(a: readonly (CurvePoint | GradientStop)[], b: readonly (CurvePoint | GradientStop)[]): boolean;
137
+ /**
138
+ * A fresh gradient with as many stops: positions spread evenly with a little play, colours a
139
+ * walk through a few neighbouring hues from dark to light (or back), so it reads as one ramp.
140
+ */
141
+ export declare function randomStops(count: number, random: () => number): GradientStop[];
142
+ /**
143
+ * The stops nudged: each colour turns a few degrees and shifts lightness and chroma a touch (as a
144
+ * colour field mutates), and each position moves a little without passing its neighbours.
145
+ */
146
+ export declare function mutateStops(stops: readonly GradientStop[], random: () => number, hue: readonly [number, number], lightness: readonly [number, number]): GradientStop[];
147
+ /** A built-in shape, picked at random, with its inner points shifted a touch. */
148
+ export declare function randomPoints(range: readonly [number, number], random: () => number): CurvePoint[];
149
+ /** Each point nudged a little in y (and the inner ones in x, between their neighbours). */
150
+ export declare function mutatePoints(points: readonly CurvePoint[], range: readonly [number, number], random: () => number, share: readonly [number, number]): CurvePoint[];
@@ -0,0 +1,2 @@
1
+ import { type Schema, type Values } from "./schema.js";
2
+ export declare function randomValues<S extends Schema>(schema: S, random?: () => number): Values<S>;
@@ -0,0 +1,73 @@
1
+ /** A number or text that changes on its own: FPS, a count, a status. */
2
+ export type WatchValue = number | string;
3
+ export type LogLevel = "debug" | "info" | "warn" | "error";
4
+ export interface LogEntry {
5
+ readonly text: string;
6
+ readonly level?: LogLevel;
7
+ /** When it happened (ms since the epoch); when it was pushed by default. */
8
+ readonly time?: number;
9
+ }
10
+ /** A table's rows (cells by column), or a key-value record. */
11
+ export type TableRows = readonly (readonly (string | number)[])[] | Readonly<Record<string, unknown>>;
12
+ /** A fixed-size history of numbers, oldest first. */
13
+ export declare class Ring {
14
+ readonly capacity: number;
15
+ private data;
16
+ private start;
17
+ length: number;
18
+ constructor(capacity: number);
19
+ push(v: number): void;
20
+ /** The `i`th sample, 0 the oldest. */
21
+ at(i: number): number;
22
+ last(): number;
23
+ clear(): void;
24
+ /** The smallest and largest finite samples (NaN when there are none). */
25
+ extent(): [number, number];
26
+ toArray(): number[];
27
+ }
28
+ /** A log's entries, oldest first, the oldest dropped past `max`; `total` counts every one pushed. */
29
+ export declare class EntryLog {
30
+ readonly max: number;
31
+ entries: (LogEntry & {
32
+ readonly seq: number;
33
+ readonly time: number;
34
+ })[];
35
+ total: number;
36
+ constructor(max: number);
37
+ push(entry: LogEntry): void;
38
+ clear(): void;
39
+ }
40
+ /** What a readout has received, kept by the store per key. */
41
+ export interface ReadoutState {
42
+ /** Bumps on every push, so a row can tell it has something new. */
43
+ seq: number;
44
+ /** The latest value as pushed (`watch`, `meter`, `table`), else the last sample. */
45
+ value: unknown;
46
+ /** `sparkline`: one ring; `graph`: one per series id. */
47
+ readonly series?: Map<string, Ring>;
48
+ readonly log?: EntryLog;
49
+ }
50
+ /** A readout field's own options, as far as the history goes. */
51
+ export interface ReadoutShape {
52
+ readonly kind: string;
53
+ readonly history?: number;
54
+ readonly series?: readonly {
55
+ readonly id: string;
56
+ }[];
57
+ readonly max?: number;
58
+ }
59
+ export declare function readoutState(field: ReadoutShape): ReadoutState;
60
+ /**
61
+ * Takes a pushed value into a readout's state. `null` clears a history (and blanks a value);
62
+ * a sparkline takes a number or an array that replaces its history, a graph a number (its first
63
+ * series) or `{ [series]: number }`, a log a string, an entry or an array of them.
64
+ */
65
+ export declare function pushReadout(field: ReadoutShape, state: ReadoutState, value: unknown): void;
66
+ /** Shared by the store and the panel bundles (the global symbol registry). */
67
+ export declare const READOUT: unique symbol;
68
+ /** What panels use to follow a store's readouts. */
69
+ export interface ReadoutPort {
70
+ state(key: string): ReadoutState | undefined;
71
+ /** Calls `cb(key)` after each push; returns the unsubscribe. */
72
+ listen(cb: (key: string) => void): () => void;
73
+ }