@kitlangton/knobs 0.0.0-stage → 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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1414 -2
  3. package/dist/dev/chunk-4km87zfx.js +155 -0
  4. package/dist/dev/chunk-5cr85vv6.js +17726 -0
  5. package/dist/dev/chunk-693jd1gj.js +1435 -0
  6. package/dist/dev/chunk-gezhc24g.js +1132 -0
  7. package/dist/dev/index.js +1660 -0
  8. package/dist/index.js +2651 -0
  9. package/dist/panel.js +26404 -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 +62 -0
  22. package/dist/types/core/readout.d.ts +73 -0
  23. package/dist/types/core/remote.d.ts +260 -0
  24. package/dist/types/core/schema.d.ts +550 -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 +536 -0
  28. package/dist/types/core/theme.d.ts +27 -0
  29. package/dist/types/core/variants.d.ts +25 -0
  30. package/dist/types/index.d.ts +14 -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 +170 -0
  35. package/dist/types/panel/flash.d.ts +13 -0
  36. package/dist/types/panel/folds.d.ts +14 -0
  37. package/dist/types/panel/give.d.ts +73 -0
  38. package/dist/types/panel/gpu/atlas.d.ts +40 -0
  39. package/dist/types/panel/gpu/color.d.ts +128 -0
  40. package/dist/types/panel/gpu/die.wgsl.d.ts +1 -0
  41. package/dist/types/panel/gpu/renderer.d.ts +784 -0
  42. package/dist/types/panel/gpu/shader.wgsl.d.ts +2 -0
  43. package/dist/types/panel/haptics.d.ts +12 -0
  44. package/dist/types/panel/has.d.ts +13 -0
  45. package/dist/types/panel/hints.d.ts +45 -0
  46. package/dist/types/panel/icons.d.ts +30 -0
  47. package/dist/types/panel/index.d.ts +11 -0
  48. package/dist/types/panel/inspector.d.ts +50 -0
  49. package/dist/types/panel/measure.d.ts +6 -0
  50. package/dist/types/panel/menu.d.ts +38 -0
  51. package/dist/types/panel/morph.d.ts +38 -0
  52. package/dist/types/panel/motion.d.ts +77 -0
  53. package/dist/types/panel/optkeys.d.ts +7 -0
  54. package/dist/types/panel/page-hints.d.ts +82 -0
  55. package/dist/types/panel/page.d.ts +423 -0
  56. package/dist/types/panel/panel.d.ts +43 -0
  57. package/dist/types/panel/position.d.ts +5 -0
  58. package/dist/types/panel/prefs.d.ts +28 -0
  59. package/dist/types/panel/presets.d.ts +60 -0
  60. package/dist/types/panel/ramp-morph.d.ts +117 -0
  61. package/dist/types/panel/ramps.d.ts +14 -0
  62. package/dist/types/panel/readout-row.d.ts +21 -0
  63. package/dist/types/panel/reel.d.ts +43 -0
  64. package/dist/types/panel/reveal.d.ts +15 -0
  65. package/dist/types/panel/rolling-css.d.ts +2 -0
  66. package/dist/types/panel/scope.d.ts +19 -0
  67. package/dist/types/panel/search.d.ts +36 -0
  68. package/dist/types/panel/seg.d.ts +15 -0
  69. package/dist/types/panel/session.d.ts +21 -0
  70. package/dist/types/panel/slider.d.ts +92 -0
  71. package/dist/types/panel/smear.d.ts +7 -0
  72. package/dist/types/panel/sound.d.ts +137 -0
  73. package/dist/types/panel/spin.d.ts +13 -0
  74. package/dist/types/panel/styles.d.ts +11 -0
  75. package/dist/types/panel/swipe.d.ts +114 -0
  76. package/dist/types/panel/switcher.d.ts +20 -0
  77. package/dist/types/panel/tabs.d.ts +31 -0
  78. package/dist/types/panel/thumb.d.ts +36 -0
  79. package/dist/types/panel/toolbar-grid.d.ts +6 -0
  80. package/dist/types/react/Knobs.d.ts +15 -0
  81. package/dist/types/react/VariantSwitcher.d.ts +10 -0
  82. package/dist/types/react/index.d.ts +3 -0
  83. package/dist/types/react/useControls.d.ts +11 -0
  84. package/dist/types/solid/index.d.ts +50 -0
  85. package/dist/types/themes/index.d.ts +1 -0
  86. package/dist/types/ui/activity-css.d.ts +1 -0
  87. package/dist/types/ui/activity-model.d.ts +122 -0
  88. package/dist/types/ui/activity.d.ts +67 -0
  89. package/dist/types/ui/bento-css.d.ts +1 -0
  90. package/dist/types/ui/bento-model.d.ts +96 -0
  91. package/dist/types/ui/bento.d.ts +77 -0
  92. package/dist/types/ui/commands.d.ts +11 -0
  93. package/dist/types/ui/confirm.d.ts +13 -0
  94. package/dist/types/ui/follow.d.ts +17 -0
  95. package/dist/types/ui/index.d.ts +24 -0
  96. package/dist/types/ui/layer.d.ts +70 -0
  97. package/dist/types/ui/look.d.ts +62 -0
  98. package/dist/types/ui/menu.d.ts +63 -0
  99. package/dist/types/ui/overlay-css.d.ts +1 -0
  100. package/dist/types/ui/overlay-model.d.ts +87 -0
  101. package/dist/types/ui/palette.d.ts +38 -0
  102. package/dist/types/ui/picker-css.d.ts +1 -0
  103. package/dist/types/ui/picker-model.d.ts +53 -0
  104. package/dist/types/ui/picker.d.ts +67 -0
  105. package/dist/types/ui/popover.d.ts +35 -0
  106. package/dist/types/ui/react.d.ts +72 -0
  107. package/dist/types/ui/readout-css.d.ts +1 -0
  108. package/dist/types/ui/readout-model.d.ts +137 -0
  109. package/dist/types/ui/readouts.d.ts +85 -0
  110. package/dist/types/ui/rgba.d.ts +6 -0
  111. package/dist/types/ui/solid.d.ts +63 -0
  112. package/dist/types/ui/split-model.d.ts +135 -0
  113. package/dist/types/ui/split.d.ts +92 -0
  114. package/dist/types/ui/timeline-model.d.ts +83 -0
  115. package/dist/types/ui/timeline.d.ts +76 -0
  116. package/dist/types/ui/toast.d.ts +19 -0
  117. package/dist/types/ui/tooltip.d.ts +19 -0
  118. package/dist/types/ui/tree-model.d.ts +125 -0
  119. package/dist/types/ui/tree.d.ts +95 -0
  120. package/dist/ui-react.js +9230 -0
  121. package/dist/ui-solid.js +9162 -0
  122. package/dist/ui.js +11826 -0
  123. package/package.json +99 -4
package/dist/index.js ADDED
@@ -0,0 +1,2651 @@
1
+ // src/core/color.ts
2
+ var round = (n, places) => Number(n.toFixed(places)), clamp01 = (n) => Math.min(1, Math.max(0, n)), gamma = (c) => c <= 0.0031308 ? 12.92 * c : 1.055 * c ** 0.4166666666666667 - 0.055, linear = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
3
+ function oklchToLinear({ l, c, h }) {
4
+ let a = c * Math.cos(h * Math.PI / 180), b = c * Math.sin(h * Math.PI / 180), l_ = (l + 0.3963377774 * a + 0.2158037573 * b) ** 3, m_ = (l - 0.1055613458 * a - 0.0638541728 * b) ** 3, s_ = (l - 0.0894841775 * a - 1.291485548 * b) ** 3;
5
+ return [
6
+ 4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_,
7
+ -1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_,
8
+ -0.0041960863 * l_ - 0.7034186147 * m_ + 1.707614701 * s_
9
+ ];
10
+ }
11
+ var inGamut = (rgb) => rgb.every((v) => v >= -0.0001 && v <= 1.0001);
12
+ function maxChroma(l, h) {
13
+ if (l <= 0 || l >= 1)
14
+ return 0;
15
+ let lo = 0, hi = 0.37;
16
+ if (inGamut(oklchToLinear({ l, c: hi, h })))
17
+ return hi;
18
+ for (let i = 0;i < 18; i++) {
19
+ let mid = (lo + hi) / 2;
20
+ if (inGamut(oklchToLinear({ l, c: mid, h })))
21
+ lo = mid;
22
+ else
23
+ hi = mid;
24
+ }
25
+ return lo;
26
+ }
27
+ function oklchToRgb(color) {
28
+ let rgb = oklchToLinear(color);
29
+ if (!inGamut(rgb))
30
+ rgb = oklchToLinear({ ...color, c: Math.min(color.c, maxChroma(color.l, color.h)) });
31
+ let [r, g, b] = rgb;
32
+ return [clamp01(gamma(r)), clamp01(gamma(g)), clamp01(gamma(b))];
33
+ }
34
+ function rgbToOklch(r, g, b) {
35
+ let [lr, lg, lb] = [linear(r), linear(g), linear(b)], l_ = Math.cbrt(0.4122214708 * lr + 0.5363377612 * lg + 0.0514459929 * lb), m_ = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb), s_ = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb), L = 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_, a = 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_, bb = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_, c = Math.hypot(a, bb), h = c < 0.0001 ? 0 : (Math.atan2(bb, a) * 180 / Math.PI + 360) % 360;
36
+ return { l: L, c, h };
37
+ }
38
+ function parseColor(input) {
39
+ if (typeof input === "object" && input !== null) {
40
+ let { l, c, h } = input;
41
+ return [l, c, h].every((n) => typeof n === "number" && Number.isFinite(n)) ? { l, c, h } : void 0;
42
+ }
43
+ if (typeof input !== "string")
44
+ return;
45
+ let s = input.trim().toLowerCase(), hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/.exec(s)?.[1];
46
+ if (hex) {
47
+ let full = hex.length === 3 ? [...hex].map((d) => d + d).join("") : hex, [r, g, b] = [0, 2, 4].map((i) => parseInt(full.slice(i, i + 2), 16) / 255);
48
+ return rgbToOklch(r, g, b);
49
+ }
50
+ let m = /^oklch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(?:deg)?\s*\)$/.exec(s);
51
+ if (!m)
52
+ return;
53
+ let parts = [Number(m[1]) / (m[2] ? 100 : 1), Number(m[3]), Number(m[4])];
54
+ return parts.every(Number.isFinite) ? { l: parts[0], c: parts[1], h: parts[2] } : void 0;
55
+ }
56
+ function fitColor({ l, c, h }) {
57
+ let L = round(clamp01(l), 5), H = round((h % 360 + 360) % 360, 3) % 360, C0 = round(Math.min(0.37, Math.max(0, c)), 5);
58
+ if (inGamut(oklchToLinear({ l: L, c: C0, h: H })))
59
+ return { l: L, c: C0, h: H };
60
+ let max = Math.min(0.37, maxChroma(L, H));
61
+ return { l: L, c: Math.min(C0, Math.floor(max * 1e5) / 1e5), h: H };
62
+ }
63
+ function formatColor({ l, c, h }) {
64
+ return `oklch(${round(l, 5)} ${round(c, 5)} ${round(h, 3)})`;
65
+ }
66
+ function colorHex(value) {
67
+ let color = parseColor(value);
68
+ if (!color)
69
+ return "#000000";
70
+ return `#${oklchToRgb(color).map((v) => Math.round(v * 255).toString(16).padStart(2, "0")).join("")}`;
71
+ }
72
+
73
+ // src/core/easing.ts
74
+ var EASINGS = {
75
+ ease: [0.25, 0.1, 0.25, 1],
76
+ in: [0.42, 0, 1, 1],
77
+ out: [0, 0, 0.58, 1],
78
+ "in-out": [0.42, 0, 0.58, 1],
79
+ linear: [0, 0, 1, 1],
80
+ smooth: [0.65, 0, 0.35, 1],
81
+ snappy: [0.32, 0.72, 0, 1],
82
+ back: [0.34, 1.56, 0.64, 1]
83
+ }, EASING_Y = [-0.6, 1.6];
84
+ function namedEasing(name) {
85
+ let key = name.replace(/^ease-(?=in|out)/, "");
86
+ return Object.hasOwn(EASINGS, key) ? EASINGS[key] : void 0;
87
+ }
88
+ function bezierPoint([x1, y1, x2, y2], t) {
89
+ let u = 1 - t, a = 3 * u * u * t, b = 3 * u * t * t, c = t * t * t;
90
+ return [a * x1 + b * x2 + c, a * y1 + b * y2 + c];
91
+ }
92
+ var bezierCss = (b) => `cubic-bezier(${b.join(", ")})`;
93
+
94
+ // src/core/keys.ts
95
+ var MODS = ["Ctrl", "Alt", "Shift", "Meta"], ALIASES = {
96
+ ctrl: "Ctrl",
97
+ control: "Ctrl",
98
+ "⌃": "Ctrl",
99
+ alt: "Alt",
100
+ option: "Alt",
101
+ opt: "Alt",
102
+ "⌥": "Alt",
103
+ shift: "Shift",
104
+ "⇧": "Shift",
105
+ meta: "Meta",
106
+ cmd: "Meta",
107
+ command: "Meta",
108
+ "⌘": "Meta",
109
+ mod: "Mod"
110
+ }, NAMED = {
111
+ " ": "Space",
112
+ space: "Space",
113
+ spacebar: "Space",
114
+ esc: "Escape",
115
+ escape: "Escape",
116
+ enter: "Enter",
117
+ return: "Enter",
118
+ tab: "Tab",
119
+ backspace: "Backspace",
120
+ delete: "Delete",
121
+ del: "Delete",
122
+ up: "ArrowUp",
123
+ down: "ArrowDown",
124
+ left: "ArrowLeft",
125
+ right: "ArrowRight",
126
+ arrowup: "ArrowUp",
127
+ arrowdown: "ArrowDown",
128
+ arrowleft: "ArrowLeft",
129
+ arrowright: "ArrowRight",
130
+ home: "Home",
131
+ end: "End",
132
+ pageup: "PageUp",
133
+ pagedown: "PageDown"
134
+ }, isApple = () => typeof navigator < "u" && /mac|iphone|ipad|ipod/i.test(navigator.userAgentData?.platform ?? navigator.platform ?? ""), keyName = (raw) => {
135
+ if (!raw)
136
+ return "";
137
+ let named = NAMED[raw.toLowerCase()];
138
+ if (named)
139
+ return named;
140
+ if (raw.length === 1)
141
+ return raw.toUpperCase();
142
+ if (/^f\d{1,2}$/i.test(raw))
143
+ return raw.toUpperCase();
144
+ return raw.charAt(0).toUpperCase() + raw.slice(1);
145
+ };
146
+ function normalizeCombo(combo) {
147
+ let trimmed = combo.trim();
148
+ if (!trimmed)
149
+ return "";
150
+ let parts = trimmed === "+" ? ["+"] : trimmed.replace(/\+\+$/, "+plus").split("+"), mods = /* @__PURE__ */ new Set, key = "";
151
+ for (let part of parts) {
152
+ let alias = ALIASES[part.trim().toLowerCase()] ?? ALIASES[part.trim()];
153
+ if (alias === "Mod")
154
+ mods.add(isApple() ? "Meta" : "Ctrl");
155
+ else if (alias)
156
+ mods.add(alias);
157
+ else
158
+ key = part === "plus" ? "+" : keyName(part.trim() || part);
159
+ }
160
+ if (!key)
161
+ return "";
162
+ return [...MODS.filter((m) => mods.has(m)), key].join("+");
163
+ }
164
+ var layout = null, layoutAsked = !1;
165
+ function askLayout() {
166
+ if (layoutAsked)
167
+ return;
168
+ layoutAsked = !0, globalThis.navigator?.keyboard?.getLayoutMap?.().then((map) => {
169
+ layout = map;
170
+ }).catch(() => {});
171
+ }
172
+ function letterOf(event) {
173
+ askLayout();
174
+ let code = event.code ?? "";
175
+ if (/^[a-z]$/i.test(event.key) && !event.altKey)
176
+ return event.key.toUpperCase();
177
+ let mapped = layout?.get(code);
178
+ if (mapped && /^[a-z]$/i.test(mapped))
179
+ return mapped.toUpperCase();
180
+ return /^Key[A-Z]$/.test(code) ? code.slice(3) : "";
181
+ }
182
+ function comboFromEvent(event) {
183
+ if (["Shift", "Alt", "Meta", "Control", "CapsLock", "Fn", "OS"].includes(event.key))
184
+ return "";
185
+ let code = event.code ?? "", letter = letterOf(event), key = letter ? letter : /^Digit\d$/.test(code) ? code.slice(5) : code === "Space" ? "Space" : keyName(event.key);
186
+ return [
187
+ event.ctrlKey ? "Ctrl" : "",
188
+ event.altKey ? "Alt" : "",
189
+ event.shiftKey ? "Shift" : "",
190
+ event.metaKey ? "Meta" : "",
191
+ key
192
+ ].filter(Boolean).join("+");
193
+ }
194
+ function matchesCombo(combo, event) {
195
+ let want = normalizeCombo(combo);
196
+ return want !== "" && want === comboFromEvent(event);
197
+ }
198
+ var GLYPHS = { Ctrl: "⌃", Alt: "⌥", Shift: "⇧", Meta: "⌘" }, WORDS = { Ctrl: "Ctrl", Alt: "Alt", Shift: "Shift", Meta: "Meta" }, KEY_GLYPHS = {
199
+ ArrowUp: "↑",
200
+ ArrowDown: "↓",
201
+ ArrowLeft: "←",
202
+ ArrowRight: "→",
203
+ Backspace: "⌫",
204
+ Delete: "⌦",
205
+ Enter: "↩",
206
+ Escape: "Esc",
207
+ Tab: "⇥",
208
+ PageUp: "PgUp",
209
+ PageDown: "PgDn"
210
+ };
211
+ function comboParts(combo, apple = isApple()) {
212
+ let norm = normalizeCombo(combo);
213
+ if (!norm)
214
+ return [];
215
+ let parts = norm === "+" ? ["+"] : norm.split("+");
216
+ if (norm.endsWith("++"))
217
+ parts.splice(-2, 2, "+");
218
+ let key = parts.pop(), mods = parts, shown = KEY_GLYPHS[key] ?? key;
219
+ return apple ? [...mods.map((m) => GLYPHS[m]), shown] : [...mods.map((m) => WORDS[m]), shown];
220
+ }
221
+ function comboLabel(combo, apple = isApple()) {
222
+ return comboParts(combo, apple).join(apple ? "" : "+");
223
+ }
224
+
225
+ // src/core/ramp.ts
226
+ var MAX_STOPS = 32, MAX_POINTS = 64, GRADIENT_SAMPLES = 64;
227
+ var clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v)), finite = (v) => typeof v === "number" && Number.isFinite(v), round4 = (v) => Math.round(v * 1e4) / 1e4 || 0;
228
+ function normalizeStops(input) {
229
+ if (!Array.isArray(input))
230
+ return;
231
+ let stops = [];
232
+ for (let s of input.slice(0, MAX_STOPS)) {
233
+ if (typeof s !== "object" || s === null)
234
+ continue;
235
+ let { at, color } = s, parsed = parseColor(color);
236
+ if (!finite(at) || !parsed)
237
+ continue;
238
+ stops.push(Object.freeze({ at: round4(clamp(at, 0, 1)), color: formatColor(fitColor(parsed)) }));
239
+ }
240
+ if (stops.length < 2)
241
+ return;
242
+ return Object.freeze(stops.map((s, i) => [s, i]).sort((a, b) => a[0].at - b[0].at || a[1] - b[1]).map(([s]) => s));
243
+ }
244
+ function normalizePoints(input, range) {
245
+ if (!Array.isArray(input))
246
+ return;
247
+ let pts = [], span = range[1] - range[0], roundY = (y) => span > 0 ? Number((range[0] + Math.round((y - range[0]) / span * 1e5) / 1e5 * span).toPrecision(12)) : range[0];
248
+ for (let p of input.slice(0, MAX_POINTS)) {
249
+ if (typeof p !== "object" || p === null)
250
+ continue;
251
+ let { x, y } = p;
252
+ if (!finite(x) || !finite(y))
253
+ continue;
254
+ pts.push({ x: round4(clamp(x, 0, 1)), y: roundY(clamp(y, range[0], range[1])) });
255
+ }
256
+ if (pts.length < 2)
257
+ return;
258
+ let sorted = pts.map((p, i) => [p, i]).sort((a, b) => a[0].x - b[0].x || a[1] - b[1]).map(([p]) => p);
259
+ return sorted[0] = { ...sorted[0], x: 0 }, sorted[sorted.length - 1] = { ...sorted[sorted.length - 1], x: 1 }, Object.freeze(sorted.map((p) => Object.freeze(p)));
260
+ }
261
+ var toLab = ({ l, c, h }) => [
262
+ l,
263
+ c * Math.cos(h * Math.PI / 180),
264
+ c * Math.sin(h * Math.PI / 180)
265
+ ], fromLab = ([l, a, b]) => {
266
+ let c = Math.hypot(a, b);
267
+ return { l, c, h: c < 0.00001 ? 0 : (Math.atan2(b, a) * 180 / Math.PI + 360) % 360 };
268
+ }, linearOf = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
269
+ function prepare(stops, space) {
270
+ let at = stops.map((s) => s.at), ok = stops.map((s) => parseColor(s.color) ?? { l: 0, c: 0, h: 0 }), lab = ok.map(toLab), rgb = ok.map((c) => oklchToRgb(c));
271
+ return (t) => {
272
+ let n = at.length, i = 0;
273
+ while (i < n && at[i] <= t)
274
+ i++;
275
+ if (i === 0)
276
+ return [...rgb[0]];
277
+ if (i === n)
278
+ return [...rgb[n - 1]];
279
+ let a = i - 1, span = at[i] - at[a], f = span > 0 ? (t - at[a]) / span : 1;
280
+ if (space === "srgb") {
281
+ let p = rgb[a], q = rgb[i];
282
+ return [p[0] + (q[0] - p[0]) * f, p[1] + (q[1] - p[1]) * f, p[2] + (q[2] - p[2]) * f];
283
+ }
284
+ let p = lab[a], q = lab[i];
285
+ return oklchToRgb(fromLab([p[0] + (q[0] - p[0]) * f, p[1] + (q[1] - p[1]) * f, p[2] + (q[2] - p[2]) * f]));
286
+ };
287
+ }
288
+ function sampleGradient(stops, t, options = {}) {
289
+ let valid = normalizeStops(stops) ?? stops;
290
+ if (!valid.length)
291
+ return options.as === "rgb" || options.as === "linear" ? [0, 0, 0] : "#000000";
292
+ let rgb = prepare(valid, options.space ?? "oklab")(clamp(Number.isFinite(t) ? t : 0, 0, 1));
293
+ if (options.as === "rgb")
294
+ return rgb;
295
+ if (options.as === "linear")
296
+ return [linearOf(rgb[0]), linearOf(rgb[1]), linearOf(rgb[2])];
297
+ let color = formatColor(fitColor(rgbToOklch(...rgb)));
298
+ return options.as === "hex" ? colorHex(color) : color;
299
+ }
300
+ function gradientSamples(stops, space, n = GRADIENT_SAMPLES) {
301
+ let at = prepare(stops, space), out = [];
302
+ for (let i = 0;i < n; i++)
303
+ out.push(...at(i / (n - 1)));
304
+ return out;
305
+ }
306
+ function gradientCss(stops, options = {}) {
307
+ let valid = normalizeStops(stops) ?? stops, s = gradientSamples(valid, options.space ?? "oklab", 17), parts = [];
308
+ for (let i = 0;i < 17; i++) {
309
+ let [r, g, b] = [s[i * 3], s[i * 3 + 1], s[i * 3 + 2]].map((v) => Math.round(v * 255));
310
+ parts.push(`rgb(${r} ${g} ${b}) ${round4(i / 16 * 100)}%`);
311
+ }
312
+ return `linear-gradient(${options.direction ?? "to right"},${parts.join(",")})`;
313
+ }
314
+ function tangents(xs, ys) {
315
+ let n = xs.length, h = [], s = [];
316
+ for (let i = 0;i < n - 1; i++)
317
+ h.push(xs[i + 1] - xs[i]), s.push(h[i] > 0 ? (ys[i + 1] - ys[i]) / h[i] : 0);
318
+ let m = Array(n).fill(0);
319
+ if (n === 2)
320
+ return m[0] = m[1] = s[0], m;
321
+ let sign = Math.sign;
322
+ for (let i = 1;i < n - 1; i++) {
323
+ let h0 = h[i - 1], h1 = h[i];
324
+ if (h0 <= 0 || h1 <= 0)
325
+ continue;
326
+ let p = (s[i - 1] * h1 + s[i] * h0) / (h0 + h1);
327
+ m[i] = (sign(s[i - 1]) + sign(s[i])) * Math.min(Math.abs(s[i - 1]), Math.abs(s[i]), 0.5 * Math.abs(p));
328
+ }
329
+ let end = (a, b, ha, hb) => {
330
+ if (ha <= 0)
331
+ return 0;
332
+ let p = a * (1 + ha / (ha + hb)) - b * ha / (ha + hb);
333
+ if (sign(p) !== sign(a))
334
+ return 0;
335
+ return Math.abs(p) > 2 * Math.abs(a) ? 2 * a : p;
336
+ };
337
+ return m[0] = end(s[0], s[1], h[0], h[1]), m[n - 1] = end(s[n - 2], s[n - 3], h[n - 2], h[n - 3]), m;
338
+ }
339
+ function prepareCurve(points, interp) {
340
+ let xs = points.map((p) => p.x), ys = points.map((p) => p.y), m = interp === "smooth" ? tangents(xs, ys) : [], n = xs.length;
341
+ return (x) => {
342
+ if (!n)
343
+ return 0;
344
+ if (x <= xs[0])
345
+ return ys[0];
346
+ if (x >= xs[n - 1])
347
+ return ys[n - 1];
348
+ let i = 0;
349
+ while (i < n - 2 && xs[i + 1] <= x)
350
+ i++;
351
+ let x0 = xs[i], span = xs[i + 1] - x0;
352
+ if (interp === "step" || span <= 0)
353
+ return ys[i];
354
+ let t = (x - x0) / span;
355
+ if (interp === "linear")
356
+ return ys[i] + (ys[i + 1] - ys[i]) * t;
357
+ let t2 = t * t, t3 = t2 * t;
358
+ return (2 * t3 - 3 * t2 + 1) * ys[i] + (t3 - 2 * t2 + t) * span * m[i] + (-2 * t3 + 3 * t2) * ys[i + 1] + (t3 - t2) * span * m[i + 1];
359
+ };
360
+ }
361
+ function sampleCurve(points, x, options = {}) {
362
+ return prepareCurve(points, options.interp ?? "smooth")(Number.isFinite(x) ? x : 0);
363
+ }
364
+ var CURVE_SHAPES = {
365
+ linear: [
366
+ { x: 0, y: 0 },
367
+ { x: 1, y: 1 }
368
+ ],
369
+ "ease-out": [
370
+ { x: 0, y: 0 },
371
+ { x: 0.35, y: 0.62 },
372
+ { x: 1, y: 1 }
373
+ ],
374
+ "inverse²": [
375
+ { x: 0, y: 1 },
376
+ { x: 0.1, y: 0.85 },
377
+ { x: 0.25, y: 0.47 },
378
+ { x: 0.5, y: 0.15 },
379
+ { x: 1, y: 0 }
380
+ ],
381
+ smoothstep: [
382
+ { x: 0, y: 0 },
383
+ { x: 0.25, y: 0.156 },
384
+ { x: 0.5, y: 0.5 },
385
+ { x: 0.75, y: 0.844 },
386
+ { x: 1, y: 1 }
387
+ ],
388
+ bell: [
389
+ { x: 0, y: 0 },
390
+ { x: 0.25, y: 0.25 },
391
+ { x: 0.5, y: 1 },
392
+ { x: 0.75, y: 0.25 },
393
+ { x: 1, y: 0 }
394
+ ]
395
+ }, inRange = (shape, range) => shape.map((p) => ({ x: p.x, y: range[0] + p.y * (range[1] - range[0]) }));
396
+ var between = (min, max, r) => min + (max - min) * r, signed = (random) => random() < 0.5 ? -1 : 1;
397
+ function randomStops(count, random) {
398
+ let n = Math.max(2, count), hue = random() * 360, turn = signed(random) * between(20, 70, random()), rising = random() < 0.5, [l0, l1] = [between(0.2, 0.4, random()), between(0.75, 0.95, random())], vivid = between(0.3, 0.9, random());
399
+ return Array.from({ length: n }, (_, i) => {
400
+ let t = i / (n - 1), jitter = i === 0 || i === n - 1 ? 0 : (random() - 0.5) * (0.5 / (n - 1)), at = clamp(t + jitter, 0, 1), l = rising ? l0 + (l1 - l0) * t : l1 + (l0 - l1) * t, h = (hue + turn * t + 360) % 360, c = vivid * maxChroma(l, h) * between(0.7, 1, random());
401
+ return { at, color: formatColor(fitColor({ l, c, h })) };
402
+ });
403
+ }
404
+ function mutateStops(stops, random, hue, lightness) {
405
+ let out = stops.map((s) => ({ ...s }));
406
+ for (let [i, s] of out.entries()) {
407
+ let c = parseColor(s.color) ?? { l: 0.5, c: 0, h: 0 }, max0 = maxChroma(c.l, c.h), rel = max0 > 0.0001 ? clamp(c.c / max0, 0, 1) : 0.5, h = (c.h + signed(random) * between(hue[0], hue[1], random()) + 360) % 360, l = clamp(c.l + signed(random) * between(lightness[0], lightness[1], random()), 0, 1), r = clamp(rel + signed(random) * between(0.04, 0.1, random()), 0, 1);
408
+ s.color = formatColor(fitColor({ l, c: r * maxChroma(l, h), h }));
409
+ let lo = i > 0 ? out[i - 1].at : 0, hi = i < out.length - 1 ? stops[i + 1].at : 1;
410
+ s.at = round4(clamp(s.at + signed(random) * between(0.02, 0.05, random()), lo, hi));
411
+ }
412
+ return out;
413
+ }
414
+ function randomPoints(range, random) {
415
+ let shapes = Object.values(CURVE_SHAPES), shape = shapes[Math.floor(Math.min(0.999999, random()) * shapes.length)], flip = random() < 0.3;
416
+ return inRange(shape.map((p, i) => ({
417
+ x: p.x,
418
+ y: i === 0 || i === shape.length - 1 ? flip ? 1 - p.y : p.y : clamp((flip ? 1 - p.y : p.y) + (random() - 0.5) * 0.12, 0, 1)
419
+ })), range);
420
+ }
421
+ function mutatePoints(points, range, random, share) {
422
+ let span = range[1] - range[0], out = points.map((p) => ({ ...p }));
423
+ for (let [i, p] of out.entries()) {
424
+ let by = span * between(share[0], share[1], random()) * 0.6, y = p.y + signed(random) * by;
425
+ if (y > range[1] || y < range[0])
426
+ y = p.y - (y - p.y);
427
+ if (p.y = clamp(y, range[0], range[1]), i === 0 || i === out.length - 1)
428
+ continue;
429
+ let lo = out[i - 1].x + 0.01, hi = points[i + 1].x - 0.01;
430
+ if (hi > lo)
431
+ p.x = round4(clamp(p.x + signed(random) * between(0.02, 0.05, random()), lo, hi));
432
+ }
433
+ return out;
434
+ }
435
+
436
+ // src/core/schema.ts
437
+ var META_KEYS = [
438
+ "label",
439
+ "group",
440
+ "groupLabel",
441
+ "section",
442
+ "sectionLabel",
443
+ "groupDescription",
444
+ "sectionDescription",
445
+ "shortcut",
446
+ "random",
447
+ "disabled",
448
+ "readOnly",
449
+ "folded",
450
+ "sectionFolded",
451
+ "tab",
452
+ "subsection"
453
+ ];
454
+ function pickMeta(source, skip = []) {
455
+ let out = {};
456
+ if (source) {
457
+ for (let k of META_KEYS)
458
+ if (source[k] !== void 0 && !skip.includes(k))
459
+ out[k] = source[k];
460
+ }
461
+ return out;
462
+ }
463
+ var READOUT_KINDS = /* @__PURE__ */ new Set(["watch", "sparkline", "graph", "meter", "table", "log"]), isReadout = (field) => READOUT_KINDS.has(field.kind), hasValue = (field) => field.kind !== "action" && field.kind !== "toolbar" && !READOUT_KINDS.has(field.kind), SPRING = {
464
+ duration: { min: 0.05, max: 2, step: 0.01 },
465
+ bounce: { min: -0.5, max: 1, step: 0.01 }
466
+ };
467
+ function checkShortcut(shortcut) {
468
+ if (shortcut !== void 0 && !/^[a-z]$/i.test(shortcut))
469
+ throw Error("Shortcut must be a letter.");
470
+ }
471
+ function valueKeys(schema) {
472
+ return Object.keys(schema).filter((k) => hasValue(schema[k]));
473
+ }
474
+ function withMeta(base, meta) {
475
+ return checkShortcut(meta?.shortcut), Object.freeze({ ...base, ...pickMeta(meta) });
476
+ }
477
+ function knob(value, options) {
478
+ let step = options.step ?? ((options.max - options.min) / 100 || 1);
479
+ if (![value, options.min, options.max, step].every(Number.isFinite) || options.max < options.min || step <= 0)
480
+ throw Error("A knob needs finite bounds and a positive step.");
481
+ if (options.decimals !== void 0 && !(Number.isInteger(options.decimals) && options.decimals >= 0))
482
+ throw Error("A knob's decimals must be a whole number.");
483
+ checkShortcut(options.shortcut);
484
+ let field = { ...options, kind: "knob", value, step };
485
+ return Object.freeze({ ...field, value: normalizeField(field, value) });
486
+ }
487
+ function choice(value, options, meta) {
488
+ if (!options.includes(value) || new Set(options).size !== options.length)
489
+ throw Error("Invalid choice options/default.");
490
+ let labels = meta?.labels ? Object.freeze(Object.fromEntries(Object.entries(meta.labels).filter(([k, v]) => options.includes(k) && typeof v === "string" && v !== k))) : void 0;
491
+ return withMeta({
492
+ kind: "choice",
493
+ value,
494
+ options: Object.freeze([...options]),
495
+ ...labels && Object.keys(labels).length ? { labels } : {},
496
+ ...meta?.display ? { display: meta.display } : {}
497
+ }, meta);
498
+ }
499
+ function action(label, meta) {
500
+ let { shortcut, global, rebind, run, ...rest } = meta ?? {}, key = shortcutOf({ ...shortcut ? { shortcut } : {}, ...global ? { global } : {} }), combo = key ? normalizeCombo(key.key) : "";
501
+ if (key && !combo)
502
+ throw Error("An action's shortcut must name a key.");
503
+ return withMeta({
504
+ kind: "action",
505
+ ...run ? { run } : {},
506
+ ...key ? { shortcut: combo, scope: key.scope } : {},
507
+ ...rebind ? { rebind: !0 } : {}
508
+ }, { ...rest, ...label !== void 0 ? { label } : {} });
509
+ }
510
+ function flatSpring(parts = {}, meta = {}) {
511
+ let id = meta.id ?? meta.label ?? `spring-${++springIds}`, half = (part, input, fallback) => {
512
+ let base = typeof input === "object" ? input : knob(input ?? fallback, {
513
+ ...SPRING[part],
514
+ ...part === "duration" ? { unit: "s" } : {}
515
+ });
516
+ return Object.freeze({
517
+ ...base,
518
+ ...base.label === void 0 ? { label: part === "duration" ? "Duration" : "Bounce" } : {},
519
+ ...pickMeta(meta, ["label", "shortcut"]),
520
+ spring: Object.freeze({
521
+ id,
522
+ part,
523
+ ...meta.label !== void 0 ? { label: meta.label } : {}
524
+ })
525
+ });
526
+ };
527
+ return {
528
+ duration: half("duration", parts.duration, 0.5),
529
+ bounce: half("bounce", parts.bounce, 0)
530
+ };
531
+ }
532
+ var springIds = 0;
533
+ function toggle(value, meta) {
534
+ return withMeta({ kind: "toggle", value }, { ...meta, random: meta?.random ?? !1 });
535
+ }
536
+ function spring(value = {}, meta) {
537
+ let field = { kind: "spring", value: { duration: 0.5, bounce: 0 } };
538
+ return withMeta({ ...field, value: normalizeField(field, value) }, meta);
539
+ }
540
+ function color(value, meta) {
541
+ let parsed = parseColor(value);
542
+ if (!parsed)
543
+ throw Error("A color needs #rgb, #rrggbb, oklch(L C H) or { l, c, h }.");
544
+ return withMeta({ kind: "color", value: formatColor(fitColor(parsed)) }, meta);
545
+ }
546
+ var axes = (v) => typeof v === "number" ? { x: v, y: v } : v;
547
+ function point(value, options) {
548
+ let min = axes(options.min), max = axes(options.max), step = options.step === void 0 ? void 0 : axes(options.step), field = {
549
+ kind: "point",
550
+ value,
551
+ min: Object.freeze({ ...min }),
552
+ max: Object.freeze({ ...max }),
553
+ step: Object.freeze({
554
+ x: step?.x ?? ((max.x - min.x) / 100 || 1),
555
+ y: step?.y ?? ((max.y - min.y) / 100 || 1)
556
+ })
557
+ };
558
+ if (![value.x, value.y, min.x, min.y, max.x, max.y, field.step.x, field.step.y].every(Number.isFinite) || max.x < min.x || max.y < min.y || field.step.x <= 0 || field.step.y <= 0)
559
+ throw Error("A point needs finite bounds and positive steps.");
560
+ return withMeta({ ...field, value: normalizeField(field, value) }, options);
561
+ }
562
+ function easing(value, meta) {
563
+ let curve = typeof value === "string" ? namedEasing(value) : value;
564
+ if (!curve || curve.length !== 4 || !curve.every(Number.isFinite))
565
+ throw Error("An easing needs four finite numbers or a known name.");
566
+ let field = { kind: "easing", value: curve };
567
+ return withMeta({ ...field, value: normalizeField(field, curve) }, meta);
568
+ }
569
+ function bounds(o, what) {
570
+ let step = o.step ?? ((o.max - o.min) / 100 || 1);
571
+ if (![o.min, o.max, step].every(Number.isFinite) || o.max < o.min || step <= 0)
572
+ throw Error(`${what} needs finite bounds and a positive step.`);
573
+ if (o.decimals !== void 0 && !(Number.isInteger(o.decimals) && o.decimals >= 0))
574
+ throw Error(`${what}'s decimals must be a whole number.`);
575
+ return step;
576
+ }
577
+ var numberMeta = (o) => ({
578
+ ...o.unit !== void 0 ? { unit: o.unit } : {},
579
+ ...o.decimals !== void 0 ? { decimals: o.decimals } : {}
580
+ });
581
+ function vec(axes, value, options) {
582
+ let step = bounds(options, "A vector"), names = options.axes;
583
+ if (names && (names.length !== axes || names.some((n) => typeof n !== "string" || !n.trim())))
584
+ throw Error(`A vector's axes need ${axes} names.`);
585
+ let field = {
586
+ kind: "vec",
587
+ axes,
588
+ value,
589
+ min: options.min,
590
+ max: options.max,
591
+ step,
592
+ ...numberMeta(options),
593
+ ...options.link || options.linked ? { link: !0 } : {},
594
+ ...options.linked ? { linked: !0 } : {},
595
+ ...names ? { axisLabels: Object.freeze(names.map((n) => n.trim())) } : {}
596
+ };
597
+ return withMeta({ ...field, value: normalizeField(field, value) }, options);
598
+ }
599
+ function vec2(value, options) {
600
+ return vec(2, value, options);
601
+ }
602
+ function vec3(value, options) {
603
+ return vec(3, value, options);
604
+ }
605
+ function range(value, options) {
606
+ let step = bounds(options, "A range"), field = {
607
+ kind: "range",
608
+ value,
609
+ min: options.min,
610
+ max: options.max,
611
+ step,
612
+ ...numberMeta(options)
613
+ };
614
+ return withMeta({ ...field, value: normalizeField(field, value) }, options);
615
+ }
616
+ function angle(value, options = {}) {
617
+ let min = options.min ?? 0, max = options.max ?? 360, step = options.step ?? 1;
618
+ bounds({ min, max, step }, "An angle");
619
+ let field = {
620
+ kind: "angle",
621
+ value,
622
+ min,
623
+ max,
624
+ step,
625
+ wrap: options.wrap ?? max - min === 360
626
+ };
627
+ return withMeta({ ...field, value: normalizeField(field, value) }, options);
628
+ }
629
+ function seed(value, options = {}) {
630
+ let field = {
631
+ kind: "seed",
632
+ value,
633
+ min: Math.ceil(options.min ?? 0),
634
+ max: Math.floor(options.max ?? 999999)
635
+ };
636
+ return bounds({ min: field.min, max: field.max, step: 1 }, "A seed"), withMeta({ ...field, value: normalizeField(field, value) }, { random: !1, ...options });
637
+ }
638
+ function keybind(value = "", meta) {
639
+ let field = { kind: "keybind", value: normalizeCombo(value) };
640
+ return withMeta(field, { random: !1, ...meta });
641
+ }
642
+ function gradient(stops, options = {}) {
643
+ let value = normalizeStops(stops);
644
+ if (!value)
645
+ throw Error("A gradient needs at least two stops with a position and a colour.");
646
+ let presets = options.presets ? Object.freeze(Object.fromEntries(Object.entries(options.presets).flatMap(([name, p]) => {
647
+ let s = normalizeStops(p);
648
+ return s ? [[name, s]] : [];
649
+ }))) : void 0;
650
+ return withMeta({
651
+ kind: "gradient",
652
+ value,
653
+ space: options.space ?? "oklab",
654
+ ...presets && Object.keys(presets).length ? { presets } : {}
655
+ }, options);
656
+ }
657
+ function curve(points, options = {}) {
658
+ let range = options.range ?? [0, 1];
659
+ if (!(Number.isFinite(range[0]) && Number.isFinite(range[1]) && range[1] > range[0]))
660
+ throw Error("A curve's range needs finite bounds, max above min.");
661
+ let given = typeof points === "string" ? inRange(CURVE_SHAPES[points], range) : points, value = normalizePoints(given, range);
662
+ if (!value)
663
+ throw Error("A curve needs at least two points.");
664
+ let presets = options.presets ? Object.freeze(Object.fromEntries(Object.entries(options.presets).flatMap(([name, p]) => {
665
+ let s = normalizePoints(p, range);
666
+ return s ? [[name, s]] : [];
667
+ }))) : void 0;
668
+ return withMeta({
669
+ kind: "curve",
670
+ value,
671
+ range: Object.freeze([range[0], range[1]]),
672
+ interp: options.interp ?? "smooth",
673
+ shapes: options.shapes ?? !0,
674
+ ...presets && Object.keys(presets).length ? { presets } : {}
675
+ }, options);
676
+ }
677
+ function shortcutOf(o) {
678
+ let s = o.shortcut;
679
+ if (!s)
680
+ return;
681
+ if (typeof s === "string")
682
+ return { key: s, scope: o.global ? "page" : "panel" };
683
+ return s.key ? { key: s.key, scope: s.scope ?? (o.global ? "page" : "panel") } : void 0;
684
+ }
685
+ function toolbarShortcut(item) {
686
+ return shortcutOf(item);
687
+ }
688
+ function actionShortcut(field) {
689
+ return field.shortcut ? { key: field.shortcut, scope: field.scope ?? "panel" } : void 0;
690
+ }
691
+ function toolbar(items, meta) {
692
+ if (!items.length)
693
+ throw Error("A toolbar needs at least one item.");
694
+ return withMeta({
695
+ kind: "toolbar",
696
+ items: Object.freeze(items.map((item) => Object.freeze({ ...item })))
697
+ }, meta);
698
+ }
699
+ var readoutMeta = (o) => {
700
+ if (o.decimals !== void 0 && !(Number.isInteger(o.decimals) && o.decimals >= 0))
701
+ throw Error("A readout's decimals must be a whole number.");
702
+ if (o.hz !== void 0 && !(o.hz > 0))
703
+ throw Error("A readout's hz must be above 0.");
704
+ return {
705
+ ...pickMeta(o),
706
+ ...o.get ? { get: o.get } : {},
707
+ ...o.unit !== void 0 ? { unit: o.unit } : {},
708
+ ...o.decimals !== void 0 ? { decimals: o.decimals } : {},
709
+ ...o.format ? { format: o.format } : {},
710
+ ...o.hz !== void 0 ? { hz: o.hz } : {}
711
+ };
712
+ }, whole = (n, fallback, what) => {
713
+ let v = n ?? fallback;
714
+ if (!(Number.isInteger(v) && v > 0))
715
+ throw Error(`${what} must be a whole number above 0.`);
716
+ return v;
717
+ };
718
+ function watch(options = {}) {
719
+ return Object.freeze({ kind: "watch", ...readoutMeta(options) });
720
+ }
721
+ function sparkline(options = {}) {
722
+ return Object.freeze({
723
+ kind: "sparkline",
724
+ ...readoutMeta(options),
725
+ history: whole(options.history, 60, "A sparkline's history"),
726
+ ...options.min !== void 0 ? { min: options.min } : {},
727
+ ...options.max !== void 0 ? { max: options.max } : {},
728
+ ...options.color ? { color: options.color } : {}
729
+ });
730
+ }
731
+ function graph(options = {}) {
732
+ let series = (options.series ?? [{ id: "value" }]).map((s) => Object.freeze(typeof s === "string" ? { id: s } : { ...s }));
733
+ if (!series.length || new Set(series.map((s) => s.id)).size !== series.length)
734
+ throw Error("A graph needs series with distinct ids.");
735
+ return Object.freeze({
736
+ kind: "graph",
737
+ ...readoutMeta(options),
738
+ series: Object.freeze(series),
739
+ history: whole(options.history, 120, "A graph's history"),
740
+ height: whole(options.height, 64, "A graph's height"),
741
+ ...options.min !== void 0 ? { min: options.min } : {},
742
+ ...options.max !== void 0 ? { max: options.max } : {},
743
+ thresholds: Object.freeze((options.thresholds ?? []).map((t) => Object.freeze(typeof t === "number" ? { value: t } : t)))
744
+ });
745
+ }
746
+ function meter(options = {}) {
747
+ let min = options.min ?? 0, max = options.max ?? 1;
748
+ if (!(Number.isFinite(min) && Number.isFinite(max) && max > min))
749
+ throw Error("A meter needs finite bounds, max above min.");
750
+ return Object.freeze({
751
+ kind: "meter",
752
+ ...readoutMeta(options),
753
+ min,
754
+ max,
755
+ ...options.segments !== void 0 ? { segments: whole(options.segments, 1, "A meter's segments") } : {},
756
+ ...options.warn !== void 0 ? { warn: options.warn } : {},
757
+ ...options.critical !== void 0 ? { critical: options.critical } : {}
758
+ });
759
+ }
760
+ function table(options = {}) {
761
+ if (options.columns && (options.columns.length < 1 || options.columns.length > 6))
762
+ throw Error("A table takes 1–6 columns.");
763
+ return Object.freeze({
764
+ kind: "table",
765
+ ...readoutMeta(options),
766
+ ...options.columns ? { columns: Object.freeze([...options.columns]) } : {}
767
+ });
768
+ }
769
+ function log(options = {}) {
770
+ return Object.freeze({
771
+ kind: "log",
772
+ ...readoutMeta(options),
773
+ lines: whole(options.lines, 8, "A log's lines"),
774
+ max: whole(options.max, 1000, "A log's max")
775
+ });
776
+ }
777
+ var keep = (v, min, max, step) => {
778
+ let clamped = Math.min(max, Math.max(min, v));
779
+ return Number.isInteger(step) && Number.isInteger(min) ? Math.round(clamped) : Number(clamped.toPrecision(14));
780
+ }, wrapAngle = (v, min, max) => {
781
+ let span = max - min;
782
+ return span > 0 ? ((v - min) % span + span) % span + min : min;
783
+ }, snap = (v, min, max, step) => {
784
+ let bounded = Math.max(min, Math.min(max, v)), steps = Math.min(Math.floor((max - min) / step + 0.000000001), Math.round((bounded - min) / step));
785
+ return Number((min + steps * step).toPrecision(14));
786
+ }, num = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? v : fallback;
787
+ function normalizeField(field, input) {
788
+ switch (field.kind) {
789
+ case "knob":
790
+ if (typeof input !== "number" || !Number.isFinite(input))
791
+ return field.value;
792
+ {
793
+ let clamped = Math.min(field.max, Math.max(field.min, input));
794
+ return Number.isInteger(field.step) && Number.isInteger(field.min) ? Math.round(clamped) : Number(clamped.toPrecision(14));
795
+ }
796
+ case "choice":
797
+ return typeof input === "string" && field.options.includes(input) ? input : field.value;
798
+ case "toggle":
799
+ return typeof input === "boolean" ? input : field.value;
800
+ case "spring": {
801
+ let v = isRecord(input) ? input : {}, { duration: d, bounce: b } = SPRING;
802
+ return Object.freeze({
803
+ duration: snap(num(v.duration, field.value.duration), d.min, d.max, d.step),
804
+ bounce: snap(num(v.bounce, field.value.bounce), b.min, b.max, b.step)
805
+ });
806
+ }
807
+ case "color": {
808
+ let parsed = parseColor(input);
809
+ return parsed ? formatColor(fitColor(parsed)) : field.value;
810
+ }
811
+ case "point": {
812
+ let v = isRecord(input) ? input : {};
813
+ return Object.freeze({
814
+ x: snap(num(v.x, field.value.x), field.min.x, field.max.x, field.step.x),
815
+ y: snap(num(v.y, field.value.y), field.min.y, field.max.y, field.step.y)
816
+ });
817
+ }
818
+ case "easing": {
819
+ let v = Array.isArray(input) && input.length === 4 ? input : field.value;
820
+ return Object.freeze([0, 1, 2, 3].map((i) => i % 2 === 0 ? snap(num(v[i], field.value[i]), 0, 1, 0.01) : snap(num(v[i], field.value[i]), EASING_Y[0], EASING_Y[1], 0.01)));
821
+ }
822
+ case "vec": {
823
+ let v = isRecord(input) ? input : {}, base = field.value, axis = (k) => keep(num(v[k], base[k] ?? 0), field.min, field.max, field.step);
824
+ return Object.freeze(field.axes === 3 ? { x: axis("x"), y: axis("y"), z: axis("z") } : { x: axis("x"), y: axis("y") });
825
+ }
826
+ case "range": {
827
+ let v = Array.isArray(input) && input.length === 2 ? input : field.value, a = keep(num(v[0], field.value[0]), field.min, field.max, field.step), b = keep(num(v[1], field.value[1]), field.min, field.max, field.step);
828
+ return Object.freeze([Math.min(a, b), Math.max(a, b)]);
829
+ }
830
+ case "angle": {
831
+ if (typeof input !== "number" || !Number.isFinite(input))
832
+ return field.value;
833
+ let v = field.wrap ? wrapAngle(input, field.min, field.max) : input, kept = keep(v, field.min, field.max, field.step);
834
+ return field.wrap && kept >= field.max ? field.min : kept;
835
+ }
836
+ case "seed":
837
+ return typeof input === "number" && Number.isFinite(input) ? Math.round(Math.min(field.max, Math.max(field.min, input))) : field.value;
838
+ case "keybind":
839
+ return typeof input === "string" ? normalizeCombo(input) : field.value;
840
+ case "gradient":
841
+ return normalizeStops(input) ?? field.value;
842
+ case "curve":
843
+ return normalizePoints(input, field.range) ?? field.value;
844
+ }
845
+ }
846
+ function normalize(schema, input) {
847
+ let record = isRecord(input) ? input : {};
848
+ return Object.freeze(Object.fromEntries(Object.entries(schema).flatMap(([key, field]) => hasValue(field) ? [[key, normalizeField(field, record[key])]] : [])));
849
+ }
850
+ function isRecord(value) {
851
+ return typeof value === "object" && value !== null && !Array.isArray(value);
852
+ }
853
+ function same(a, b) {
854
+ if (a === b)
855
+ return !0;
856
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
857
+ return !1;
858
+ if (Array.isArray(a) !== Array.isArray(b))
859
+ return !1;
860
+ let ka = Object.keys(a);
861
+ return ka.length === Object.keys(b).length && ka.every((k) => same(a[k], b[k]));
862
+ }
863
+ function equal(a, b) {
864
+ return Object.keys(a).length === Object.keys(b).length && Object.keys(a).every((key) => same(a[key], b[key]));
865
+ }
866
+ // src/core/spring.ts
867
+ class Spring {
868
+ x;
869
+ omega;
870
+ zeta;
871
+ eps;
872
+ v = 0;
873
+ target;
874
+ next;
875
+ constructor(x, omega = 24, zeta = 0.86, eps = 0.005) {
876
+ this.x = x;
877
+ this.omega = omega;
878
+ this.zeta = zeta;
879
+ this.eps = eps;
880
+ this.target = x;
881
+ }
882
+ set(target, delay = 0) {
883
+ if (delay > 0) {
884
+ if (target !== (this.next?.target ?? this.target))
885
+ this.next = { target, at: performance.now() + delay };
886
+ return this;
887
+ }
888
+ return this.next = void 0, this.target = target, this;
889
+ }
890
+ snap(x) {
891
+ return this.next = void 0, this.x = this.target = x, this.v = 0, this;
892
+ }
893
+ step(dt) {
894
+ if (this.next && performance.now() >= this.next.at)
895
+ this.target = this.next.target, this.next = void 0;
896
+ if (this.x === this.target && this.v === 0)
897
+ return this.x;
898
+ let clampedDt = Math.min(dt, 0.1), h = 0.0020833333333333333;
899
+ for (let t = 0;t < clampedDt; t += h) {
900
+ let k = Math.min(h, clampedDt - t), a = -this.omega * this.omega * (this.x - this.target) - 2 * this.zeta * this.omega * this.v;
901
+ this.v += a * k, this.x += this.v * k;
902
+ }
903
+ if (this.resting)
904
+ this.x = this.target, this.v = 0;
905
+ return this.x;
906
+ }
907
+ get resting() {
908
+ return !this.next && Math.abs(this.x - this.target) < this.eps && Math.abs(this.v) < this.eps * 4;
909
+ }
910
+ }
911
+ function springMotion({ duration, bounce }) {
912
+ return {
913
+ omega: 2 * Math.PI / Math.max(0.01, duration),
914
+ zeta: bounce >= 0 ? 1 - bounce : 1 / (1 + bounce)
915
+ };
916
+ }
917
+ function springEasing(omega, zeta) {
918
+ let s = new Spring(0, omega, zeta).set(1), points = ["0"], dt = 0.016666666666666666, t = 0;
919
+ while (t < 4 && !(Math.abs(s.x - 1) < 0.002 && Math.abs(s.v) < 0.05))
920
+ s.step(0.016666666666666666), t += 0.016666666666666666, points.push(String(Math.round(s.x * 1000) / 1000));
921
+ return points[points.length - 1] = "1", { easing: `linear(${points.join(",")})`, ms: Math.round(t * 1000) };
922
+ }
923
+ function springCss(value) {
924
+ let { omega, zeta } = springMotion(value), { easing, ms } = springEasing(omega, zeta);
925
+ return { easing, duration: ms };
926
+ }
927
+ // src/core/random.ts
928
+ var lattice = (min, max, step, r) => min + Math.floor(r * (Math.floor((max - min) / step + 0.000000001) + 1)) * step, between2 = (min, max, r) => min + (max - min) * r;
929
+ function pick(field, random) {
930
+ let r = () => Math.max(0, Math.min(1 - Number.EPSILON, random()));
931
+ switch (field.kind) {
932
+ case "knob":
933
+ return lattice(field.min, field.max, field.step, r());
934
+ case "choice":
935
+ return field.options[Math.floor(r() * field.options.length)];
936
+ case "toggle":
937
+ return r() >= 0.5;
938
+ case "spring":
939
+ return { duration: between2(0.2, 0.9, r()), bounce: between2(0, 0.45, r()) };
940
+ case "color": {
941
+ let l = between2(0.5, 0.85, r()), h = between2(0, 360, r());
942
+ return { l, c: between2(0.25, 0.85, r()) * maxChroma(l, h), h };
943
+ }
944
+ case "point":
945
+ return {
946
+ x: lattice(field.min.x, field.max.x, field.step.x, r()),
947
+ y: lattice(field.min.y, field.max.y, field.step.y, r())
948
+ };
949
+ case "easing": {
950
+ let curves = Object.values(EASINGS);
951
+ return curves[Math.floor(r() * curves.length)];
952
+ }
953
+ case "vec": {
954
+ let axis = () => lattice(field.min, field.max, field.step, r());
955
+ return field.axes === 3 ? { x: axis(), y: axis(), z: axis() } : { x: axis(), y: axis() };
956
+ }
957
+ case "range":
958
+ return [
959
+ lattice(field.min, field.max, field.step, r()),
960
+ lattice(field.min, field.max, field.step, r())
961
+ ];
962
+ case "angle": {
963
+ let top = field.wrap ? field.max - field.step : field.max;
964
+ return lattice(field.min, Math.max(field.min, top), field.step, r());
965
+ }
966
+ case "seed":
967
+ return lattice(field.min, field.max, 1, r());
968
+ case "keybind":
969
+ return field.value;
970
+ case "gradient":
971
+ return randomStops(field.value.length, r);
972
+ case "curve":
973
+ return randomPoints(field.range, r);
974
+ }
975
+ }
976
+ var AROUND = {
977
+ spread: 0.35,
978
+ switch: 0.3,
979
+ hue: 20,
980
+ lightness: 0.06,
981
+ chroma: 0.15
982
+ };
983
+ function near(v, min, max, step, r, reach = max - min) {
984
+ let span = max - min;
985
+ if (!(span > 0))
986
+ return v;
987
+ let width = Math.min(span, reach * AROUND.spread), lo = Math.max(min, Math.min(max - width, v - width / 2)), top = Math.floor(span / step + 0.000000001), a = Math.max(0, Math.ceil((lo - min) / step - 0.000000001)), b = Math.min(top, Math.floor((lo + width - min) / step + 0.000000001)), n = b < a ? Math.round((v - min) / step) : a + Math.floor(r * (b - a + 1));
988
+ return Number((min + Math.max(0, Math.min(top, n)) * step).toPrecision(14));
989
+ }
990
+ var unit = (r) => r * 2 - 1;
991
+ function around(field, base, random) {
992
+ let r = () => Math.max(0, Math.min(1 - Number.EPSILON, random()));
993
+ switch (field.kind) {
994
+ case "knob":
995
+ return near(base, field.min, field.max, field.step, r());
996
+ case "choice": {
997
+ if (field.options.length < 2 || r() >= AROUND.switch)
998
+ return base;
999
+ let rest = field.options.filter((o) => o !== base);
1000
+ return rest[Math.floor(r() * rest.length)];
1001
+ }
1002
+ case "toggle":
1003
+ return r() < AROUND.switch ? !base : base;
1004
+ case "spring": {
1005
+ let s = base, d = SPRING.duration, b = SPRING.bounce;
1006
+ return {
1007
+ duration: near(s.duration, d.min, d.max, d.step, r(), 0.7),
1008
+ bounce: near(s.bounce, b.min, b.max, b.step, r(), 0.45)
1009
+ };
1010
+ }
1011
+ case "color": {
1012
+ let c = parseColor(base) ?? { l: 0.7, c: 0.1, h: 0 }, max0 = maxChroma(c.l, c.h), rel = max0 > 0.0001 ? Math.min(1, Math.max(0, c.c / max0)) : 0.5, h = (c.h + unit(r()) * AROUND.hue + 360) % 360, l = Math.min(1, Math.max(0, c.l + unit(r()) * AROUND.lightness)), k = Math.min(1, Math.max(0, rel + unit(r()) * AROUND.chroma));
1013
+ return { l, c: k * maxChroma(l, h), h };
1014
+ }
1015
+ case "point": {
1016
+ let p = base;
1017
+ return {
1018
+ x: near(p.x, field.min.x, field.max.x, field.step.x, r()),
1019
+ y: near(p.y, field.min.y, field.max.y, field.step.y, r())
1020
+ };
1021
+ }
1022
+ case "easing":
1023
+ return base.map((v, i) => {
1024
+ let [lo, hi] = i % 2 === 0 ? [0, 1] : EASING_Y;
1025
+ return near(v, lo, hi, 0.001, r());
1026
+ });
1027
+ case "vec": {
1028
+ let v = base, axis = (n) => near(n, field.min, field.max, field.step, r());
1029
+ return field.axes === 3 ? { x: axis(v.x), y: axis(v.y), z: axis(v.z ?? 0) } : { x: axis(v.x), y: axis(v.y) };
1030
+ }
1031
+ case "range": {
1032
+ let [a, b] = base;
1033
+ return [
1034
+ near(a, field.min, field.max, field.step, r()),
1035
+ near(b, field.min, field.max, field.step, r())
1036
+ ];
1037
+ }
1038
+ case "angle": {
1039
+ let v = base;
1040
+ if (!field.wrap)
1041
+ return near(v, field.min, field.max, field.step, r());
1042
+ let span = field.max - field.min;
1043
+ return v + unit(r()) * span * (AROUND.spread / 2);
1044
+ }
1045
+ case "gradient":
1046
+ return mutateStops(base, r, [AROUND.hue / 2, AROUND.hue], [AROUND.lightness / 2, AROUND.lightness]);
1047
+ case "curve":
1048
+ return mutatePoints(base, field.range, r, [
1049
+ AROUND.spread / 2,
1050
+ AROUND.spread
1051
+ ]);
1052
+ case "seed":
1053
+ case "keybind":
1054
+ return pick(field, random);
1055
+ }
1056
+ }
1057
+ function randomValues(schema, random = Math.random) {
1058
+ return normalize(schema, Object.fromEntries(Object.entries(schema).flatMap(([key, field]) => hasValue(field) ? [[key, pick(field, random)]] : [])));
1059
+ }
1060
+ // src/core/code.ts
1061
+ var literal = (value) => Array.isArray(value) ? `[${value.map(literal).join(", ")}]` : typeof value === "object" && value !== null ? `{ ${Object.entries(value).map(([k, v]) => `${k}: ${literal(v)}`).join(", ")} }` : JSON.stringify(value);
1062
+ function formatCode(values, format = "ts") {
1063
+ if (format === "json")
1064
+ return JSON.stringify(values, null, 2);
1065
+ if (format === "jsx")
1066
+ return Object.entries(values).map(([key, value]) => {
1067
+ if (!/^[A-Za-z_$][\w$-]*$/.test(key))
1068
+ throw Error(`Cannot serialize JSX prop: ${key}`);
1069
+ return `${key}={${literal(value)}}`;
1070
+ }).join(" ");
1071
+ return `{ ${Object.entries(values).map(([key, value]) => `${/^[A-Za-z_$][\w$]*$/.test(key) && key !== "__proto__" ? key : `[${JSON.stringify(key)}]`}: ${literal(value)}`).join(", ")} }`;
1072
+ }
1073
+
1074
+ // src/core/mutate.ts
1075
+ var MUTATE = {
1076
+ min: 0.08,
1077
+ max: 0.12,
1078
+ flip: 0.15,
1079
+ hue: [4, 12],
1080
+ chroma: 0.02,
1081
+ lightness: [0.015, 0.045],
1082
+ bezier: [0.03, 0.06]
1083
+ }, between3 = (min, max, r) => min + (max - min) * r, signed2 = (random) => random() < 0.5 ? -1 : 1;
1084
+ function nudge(v, min, max, step, random, reach = max - min) {
1085
+ let span = max - min;
1086
+ if (!(span > 0))
1087
+ return v;
1088
+ let sign = signed2(random), by = reach * between3(MUTATE.min, MUTATE.max, random()), next = v + sign * by;
1089
+ if (next > max || next < min)
1090
+ next = v - sign * by;
1091
+ let top = Math.floor(span / step + 0.000000001), at = (n) => Number((min + n * step).toPrecision(14)), steps = Math.max(0, Math.min(top, Math.round((next - min) / step)));
1092
+ if (at(steps) === v) {
1093
+ let dir = next >= v ? 1 : -1;
1094
+ steps = Math.max(0, Math.min(top, steps + (steps + dir > top || steps + dir < 0 ? -dir : dir)));
1095
+ }
1096
+ return at(steps);
1097
+ }
1098
+ function shift(field, value, random) {
1099
+ switch (field.kind) {
1100
+ case "knob":
1101
+ return nudge(value, field.min, field.max, field.step, random);
1102
+ case "point": {
1103
+ let p = value;
1104
+ return {
1105
+ x: nudge(p.x, field.min.x, field.max.x, field.step.x, random),
1106
+ y: nudge(p.y, field.min.y, field.max.y, field.step.y, random)
1107
+ };
1108
+ }
1109
+ case "spring": {
1110
+ let s = value, d = SPRING.duration, b = SPRING.bounce;
1111
+ return {
1112
+ duration: nudge(s.duration, d.min, d.max, d.step, random, 0.7),
1113
+ bounce: nudge(s.bounce, b.min, b.max, b.step, random, 0.45)
1114
+ };
1115
+ }
1116
+ case "color": {
1117
+ let c = parseColor(value) ?? { l: 0.7, c: 0.1, h: 0 }, max0 = maxChroma(c.l, c.h), rel0 = max0 > 0.0001 ? Math.min(1, Math.max(0, c.c / max0)) : 0.5, h = (c.h + signed2(random) * between3(MUTATE.hue[0], MUTATE.hue[1], random()) + 360) % 360, l = Math.min(1, Math.max(0, c.l + signed2(random) * between3(MUTATE.lightness[0], MUTATE.lightness[1], random())));
1118
+ return { l, c: nudge(rel0, 0, 1, 0.01, random) * maxChroma(l, h), h };
1119
+ }
1120
+ case "easing":
1121
+ return value.map((v, i) => {
1122
+ let [lo, hi] = i % 2 === 0 ? [0, 1] : EASING_Y, next = v + signed2(random) * between3(MUTATE.bezier[0], MUTATE.bezier[1], random());
1123
+ return Math.min(hi, Math.max(lo, next));
1124
+ });
1125
+ case "choice": {
1126
+ if (random() >= MUTATE.flip || field.options.length < 2)
1127
+ return value;
1128
+ return other(field.options, value, random);
1129
+ }
1130
+ case "toggle":
1131
+ return random() < MUTATE.flip ? !value : value;
1132
+ case "vec": {
1133
+ let v = value, axis = (n) => nudge(n, field.min, field.max, field.step, random);
1134
+ return field.axes === 3 ? { x: axis(v.x), y: axis(v.y), z: axis(v.z ?? 0) } : { x: axis(v.x), y: axis(v.y) };
1135
+ }
1136
+ case "range": {
1137
+ let [a, b] = value;
1138
+ return [
1139
+ nudge(a, field.min, field.max, field.step, random),
1140
+ nudge(b, field.min, field.max, field.step, random)
1141
+ ];
1142
+ }
1143
+ case "angle": {
1144
+ let v = value;
1145
+ if (!field.wrap)
1146
+ return nudge(v, field.min, field.max, field.step, random);
1147
+ let span = field.max - field.min;
1148
+ return v + signed2(random) * span * between3(MUTATE.min, MUTATE.max, random());
1149
+ }
1150
+ case "seed":
1151
+ return nudge(value, field.min, field.max, 1, random);
1152
+ case "keybind":
1153
+ return value;
1154
+ case "gradient":
1155
+ return mutateStops(value, random, MUTATE.hue, MUTATE.lightness);
1156
+ case "curve":
1157
+ return mutatePoints(value, field.range, random, [
1158
+ MUTATE.min,
1159
+ MUTATE.max
1160
+ ]);
1161
+ }
1162
+ }
1163
+ var other = (options, value, random) => {
1164
+ let rest = options.filter((o) => o !== value);
1165
+ return rest[Math.floor(Math.min(0.999999, random()) * rest.length)] ?? value;
1166
+ };
1167
+ function mutateValues(schema, current, keys = valueKeys(schema), random = Math.random) {
1168
+ let out = {}, changed = !1;
1169
+ for (let key of keys) {
1170
+ let field = schema[key];
1171
+ if (!field || !hasValue(field))
1172
+ continue;
1173
+ let before = current[key] ?? field.value, next = normalizeField(field, shift(field, before, random));
1174
+ if (out[key] = next, !same(before, next))
1175
+ changed = !0;
1176
+ }
1177
+ if (!changed) {
1178
+ let discrete = keys.filter((k) => {
1179
+ let f = schema[k];
1180
+ return f?.kind === "toggle" || f?.kind === "choice" && f.options.length > 1;
1181
+ }), key = discrete[Math.floor(Math.min(0.999999, random()) * discrete.length)], field = key ? schema[key] : void 0;
1182
+ if (key && field?.kind === "toggle")
1183
+ out[key] = !(current[key] ?? field.value);
1184
+ else if (key && field?.kind === "choice")
1185
+ out[key] = other(field.options, String(current[key] ?? field.value), random);
1186
+ }
1187
+ return out;
1188
+ }
1189
+
1190
+ // src/core/internal.ts
1191
+ var RESHAPE = Symbol.for("knobs.reshape"), RESET_KEYS = Symbol.for("knobs.resetKeys"), REFRESHES = Symbol.for("knobs.refreshes");
1192
+
1193
+ // src/core/readout.ts
1194
+ class Ring {
1195
+ capacity;
1196
+ data;
1197
+ start = 0;
1198
+ length = 0;
1199
+ constructor(capacity) {
1200
+ this.capacity = capacity;
1201
+ this.data = new Float64Array(Math.max(1, capacity));
1202
+ }
1203
+ push(v) {
1204
+ let cap = this.data.length;
1205
+ if (this.length < cap)
1206
+ this.data[(this.start + this.length++) % cap] = v;
1207
+ else
1208
+ this.data[this.start] = v, this.start = (this.start + 1) % cap;
1209
+ }
1210
+ at(i) {
1211
+ return this.data[(this.start + i) % this.data.length];
1212
+ }
1213
+ last() {
1214
+ return this.length ? this.at(this.length - 1) : Number.NaN;
1215
+ }
1216
+ clear() {
1217
+ this.start = this.length = 0;
1218
+ }
1219
+ extent() {
1220
+ let lo = 1 / 0, hi = -1 / 0;
1221
+ for (let i = 0;i < this.length; i++) {
1222
+ let v = this.at(i);
1223
+ if (!Number.isFinite(v))
1224
+ continue;
1225
+ if (v < lo)
1226
+ lo = v;
1227
+ if (v > hi)
1228
+ hi = v;
1229
+ }
1230
+ return lo <= hi ? [lo, hi] : [Number.NaN, Number.NaN];
1231
+ }
1232
+ toArray() {
1233
+ return Array.from({ length: this.length }, (_, i) => this.at(i));
1234
+ }
1235
+ }
1236
+
1237
+ class EntryLog {
1238
+ max;
1239
+ entries = [];
1240
+ total = 0;
1241
+ constructor(max) {
1242
+ this.max = max;
1243
+ }
1244
+ push(entry) {
1245
+ let time = entry.time ?? Date.now();
1246
+ if (this.entries.push({ ...entry, level: entry.level ?? "info", time, seq: this.total++ }), this.entries.length > this.max)
1247
+ this.entries.splice(0, this.entries.length - this.max);
1248
+ }
1249
+ clear() {
1250
+ this.entries = [];
1251
+ }
1252
+ }
1253
+ function readoutState(field) {
1254
+ if (field.kind === "sparkline")
1255
+ return { seq: 0, value: null, series: /* @__PURE__ */ new Map([["", new Ring(field.history ?? 60)]]) };
1256
+ if (field.kind === "graph")
1257
+ return {
1258
+ seq: 0,
1259
+ value: null,
1260
+ series: new Map((field.series ?? []).map((s) => [s.id, new Ring(field.history ?? 120)]))
1261
+ };
1262
+ if (field.kind === "log")
1263
+ return { seq: 0, value: null, log: new EntryLog(field.max ?? 1000) };
1264
+ return { seq: 0, value: null };
1265
+ }
1266
+ var levels = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]), asEntry = (v) => typeof v === "string" ? { text: v } : v && typeof v === "object" && typeof v.text === "string" ? {
1267
+ text: v.text,
1268
+ ...levels.has(v.level) ? { level: v.level } : {},
1269
+ ...typeof v.time === "number" ? { time: v.time } : {}
1270
+ } : void 0;
1271
+ function pushReadout(field, state, value) {
1272
+ if (state.seq++, value === null || value === void 0) {
1273
+ state.value = null;
1274
+ for (let ring of state.series?.values() ?? [])
1275
+ ring.clear();
1276
+ state.log?.clear();
1277
+ return;
1278
+ }
1279
+ if (field.kind === "sparkline") {
1280
+ let ring = state.series.get("");
1281
+ if (Array.isArray(value)) {
1282
+ ring.clear();
1283
+ for (let v of value)
1284
+ ring.push(Number(v));
1285
+ } else
1286
+ ring.push(Number(value));
1287
+ state.value = ring.last();
1288
+ return;
1289
+ }
1290
+ if (field.kind === "graph") {
1291
+ let record = typeof value === "number" ? { [field.series?.[0]?.id ?? ""]: value } : value;
1292
+ for (let [id, ring] of state.series) {
1293
+ let v = record[id];
1294
+ ring.push(typeof v === "number" ? v : Number.NaN);
1295
+ }
1296
+ state.value = record;
1297
+ return;
1298
+ }
1299
+ if (field.kind === "log") {
1300
+ let list = Array.isArray(value) ? value : [value];
1301
+ for (let item of list) {
1302
+ let entry = asEntry(item);
1303
+ if (entry)
1304
+ state.log.push(entry);
1305
+ }
1306
+ state.value = state.log.entries.at(-1) ?? null;
1307
+ return;
1308
+ }
1309
+ state.value = value;
1310
+ }
1311
+ var READOUT = Symbol.for("knobs.readout");
1312
+
1313
+ // src/core/storage.ts
1314
+ function memoryStorage() {
1315
+ let data = /* @__PURE__ */ new Map;
1316
+ return {
1317
+ getItem: (key) => data.get(key) ?? null,
1318
+ setItem: (key, value) => {
1319
+ data.set(key, value);
1320
+ }
1321
+ };
1322
+ }
1323
+ function browserStorage() {
1324
+ return typeof window > "u" ? null : window.localStorage;
1325
+ }
1326
+ function urlFlag(name) {
1327
+ return () => typeof location < "u" && new URLSearchParams(location.search).has(name);
1328
+ }
1329
+ var storageKey = (id, part) => `knobs:${encodeURIComponent(id)}:${part}:v1`;
1330
+
1331
+ // src/core/variants.ts
1332
+ function createVariants(schema, id, key, storage, get, set, notify, enabled) {
1333
+ let ready = !1, list = Object.freeze([]);
1334
+ function parse(json) {
1335
+ let data = JSON.parse(json);
1336
+ if (!isRecord(data) || data.version !== 1 || data.controls !== id || !Array.isArray(data.variants))
1337
+ throw Error("Expected a version 1 export for these controls.");
1338
+ let ids = /* @__PURE__ */ new Set;
1339
+ return Object.freeze(data.variants.map((item) => {
1340
+ if (!isRecord(item) || typeof item.id !== "string" || !item.id || ids.has(item.id))
1341
+ throw Error("Variant IDs must be unique nonempty strings.");
1342
+ if (ids.add(item.id), item.name !== void 0 && (typeof item.name !== "string" || item.name.length > 100))
1343
+ throw Error("Variant names must be at most 100 characters.");
1344
+ if (!isRecord(item.values))
1345
+ throw Error("Invalid variant values.");
1346
+ let values = normalize(schema(), item.values);
1347
+ if (!equal(values, item.values))
1348
+ throw Error("Variant values do not match the schema, bounds, or step.");
1349
+ return Object.freeze({
1350
+ id: item.id,
1351
+ ...typeof item.name === "string" ? { name: item.name } : {},
1352
+ values
1353
+ });
1354
+ }));
1355
+ }
1356
+ let encode = (variants) => JSON.stringify({ version: 1, controls: id, variants }, null, 2);
1357
+ function read() {
1358
+ if (ready || !enabled())
1359
+ return;
1360
+ let json = storage()?.getItem(key);
1361
+ list = json ? parse(json) : Object.freeze([]), ready = !0;
1362
+ }
1363
+ function persist(next) {
1364
+ if (!enabled())
1365
+ throw Error("Controls are disabled.");
1366
+ if (!ready)
1367
+ throw Error("Read variants with list() before writing; unread storage will not be overwritten.");
1368
+ storage()?.setItem(key, encode(next)), list = Object.freeze(next), notify();
1369
+ }
1370
+ return {
1371
+ get ready() {
1372
+ return ready;
1373
+ },
1374
+ list() {
1375
+ return read(), list;
1376
+ },
1377
+ save(name) {
1378
+ if (read(), name !== void 0 && name.length > 100)
1379
+ throw Error("Variant names must be at most 100 characters.");
1380
+ let variant = Object.freeze({
1381
+ id: crypto.randomUUID(),
1382
+ ...name?.trim() ? { name: name.trim() } : {},
1383
+ values: get()
1384
+ });
1385
+ return persist([...list, variant]), variant;
1386
+ },
1387
+ load(id, options) {
1388
+ read();
1389
+ let variant = list.find((v) => v.id === id);
1390
+ if (!variant)
1391
+ throw Error(`Unknown variant: ${id}`);
1392
+ set(variant.values, id, Boolean(options?.coalesce));
1393
+ },
1394
+ rename(id, name) {
1395
+ if (read(), name.length > 100)
1396
+ throw Error("Variant names must be at most 100 characters.");
1397
+ let at = list.findIndex((v) => v.id === id);
1398
+ if (at < 0)
1399
+ throw Error(`Unknown variant: ${id}`);
1400
+ let was = list[at], trimmed = name.trim();
1401
+ if ((was.name ?? "") === trimmed)
1402
+ return;
1403
+ let next = [...list];
1404
+ next[at] = Object.freeze({
1405
+ id: was.id,
1406
+ ...trimmed ? { name: trimmed } : {},
1407
+ values: was.values
1408
+ }), persist(next);
1409
+ },
1410
+ remove(id) {
1411
+ read(), persist(list.filter((v) => v.id !== id));
1412
+ },
1413
+ export() {
1414
+ return read(), encode(list);
1415
+ },
1416
+ import(json) {
1417
+ if (!ready)
1418
+ throw Error("Read variants with list() before importing; unread storage will not be overwritten.");
1419
+ let incoming = parse(json), ids = new Set(list.map((v) => v.id));
1420
+ persist([...list, ...incoming.filter((v) => !ids.has(v.id))]);
1421
+ }
1422
+ };
1423
+ }
1424
+
1425
+ // src/core/store.ts
1426
+ var devBuild = () => !1;
1427
+ function sameField(a, b) {
1428
+ if (a === b)
1429
+ return !0;
1430
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
1431
+ return !1;
1432
+ let ka = Object.keys(a).filter((k) => k !== "run"), kb = Object.keys(b).filter((k) => k !== "run");
1433
+ if (ka.length !== kb.length)
1434
+ return !1;
1435
+ return ka.every((k) => {
1436
+ let x = a[k], y = b[k];
1437
+ if (typeof x === "function" && typeof y === "function")
1438
+ return !0;
1439
+ return Array.isArray(x) && Array.isArray(y) ? x.length === y.length && x.every((v, i) => same(v, y[i]) || sameField(v, y[i])) : same(x, y) || typeof x === "object" && sameField(x, y);
1440
+ });
1441
+ }
1442
+ function defineControls(id, input, options = {}) {
1443
+ if (!id.trim())
1444
+ throw Error("Controls need a stable, nonempty id.");
1445
+ let schema = Object.freeze({ ...input }), locked = normalize(schema, {}), actions = Object.freeze([...options.actions ?? []]), theme = options.theme ?? "dark", density = options.density ?? "default", renderer = options.renderer ?? "auto", enabled = () => Boolean(options.ship) && (typeof options.enabled === "function" ? options.enabled() : options.enabled ?? (Boolean(options.ship) || typeof location < "u" && ["localhost", "127.0.0.1", "[::1]"].includes(location.hostname))), storage = () => options.storage === void 0 ? browserStorage() : options.storage, persistMode = options.persist ?? "all", valueStorage = () => persistMode === "all" ? storage() : null, variantStorage = () => persistMode === "none" ? null : storage(), draw = options.random ?? Math.random, hostHistory = options.history, unsubscribeHistory, listeners = /* @__PURE__ */ new Set, refreshes = 0, notify = () => {
1446
+ for (let cb of [...listeners])
1447
+ cb();
1448
+ }, mixed = /* @__PURE__ */ new Set, current = locked, read = !1, error = null, locks = /* @__PURE__ */ new Set, bindings = /* @__PURE__ */ new Map, comparing = null, landing = !1, echo = Math.max(0, options.echo ?? 0), grabbed = /* @__PURE__ */ new Set, changedAt = /* @__PURE__ */ new Map, sent = /* @__PURE__ */ new Map, pending = /* @__PURE__ */ new Map, echoTimer, now = () => typeof performance < "u" ? performance.now() : Date.now(), isHeld = (key) => grabbed.has(key) || echo > 0 && now() - (changedAt.get(key) ?? -1 / 0) < echo, NO_BASE = Object.freeze({ preset: null, groups: Object.freeze({}) }), base = NO_BASE, past = [], future = [], HISTORY = 100, stateKey = storageKey(id, "state"), buildPresets = () => Object.freeze(Object.fromEntries([
1449
+ ["default", locked],
1450
+ ...Object.entries(options.presets ?? {}).map(([id, patch]) => {
1451
+ if (id === "default" || id === "custom")
1452
+ throw Error(`Reserved preset id: ${id}`);
1453
+ return [id, normalize(schema, { ...locked, ...patch })];
1454
+ })
1455
+ ])), presets = buildPresets(), buildGroupPresets = () => Object.freeze(Object.fromEntries(Object.entries(options.groupPresets ?? {}).map(([group, list]) => {
1456
+ let named = list.flatMap((p) => Object.keys(p.values).filter((k) => Object.hasOwn(schema, k))), covered = new Set((named.length ? named : Object.keys(schema).filter((k) => schema[k].group === group)).filter((k) => hasValue(schema[k])));
1457
+ return [
1458
+ group,
1459
+ Object.freeze(list.map((p) => Object.freeze({
1460
+ name: p.name,
1461
+ values: Object.freeze(Object.fromEntries([...covered].map((k) => [
1462
+ k,
1463
+ normalizeField(schema[k], p.values[k] ?? locked[k])
1464
+ ])))
1465
+ })))
1466
+ ];
1467
+ }))), groupPresets = buildGroupPresets(), labelsOf, fieldLabels = () => {
1468
+ if (labelsOf?.schema === schema)
1469
+ return labelsOf;
1470
+ let group = /* @__PURE__ */ new Map, section = /* @__PURE__ */ new Map, groupText = /* @__PURE__ */ new Map, sectionText = /* @__PURE__ */ new Map, first = (map, id, text) => {
1471
+ if (id !== void 0 && text !== void 0 && !map.has(id))
1472
+ map.set(id, text);
1473
+ };
1474
+ for (let f of Object.values(schema))
1475
+ first(group, f.group, f.groupLabel), first(section, f.section, f.sectionLabel), first(groupText, f.group, f.groupDescription), first(sectionText, f.section, f.sectionDescription);
1476
+ return labelsOf = { schema, group, section, groupText, sectionText }, labelsOf;
1477
+ };
1478
+ function load() {
1479
+ if (!read) {
1480
+ read = !0;
1481
+ try {
1482
+ let raw = valueStorage()?.getItem(stateKey);
1483
+ if (raw) {
1484
+ let data = JSON.parse(raw);
1485
+ if (data?.version === 1) {
1486
+ if (current = normalize(schema, data.values), Array.isArray(data.locks))
1487
+ locks = new Set(data.locks.filter((k) => typeof k === "string" && Object.hasOwn(schema, k)));
1488
+ if (data.bindings && typeof data.bindings === "object")
1489
+ bindings = new Map(Object.entries(data.bindings).flatMap(([id, combo]) => typeof combo === "string" && bindable(id) ? [[id, normalizeCombo(combo)]] : []));
1490
+ base = readBase(data.base);
1491
+ }
1492
+ }
1493
+ } catch (cause) {
1494
+ error = String(cause);
1495
+ }
1496
+ }
1497
+ return current;
1498
+ }
1499
+ function get() {
1500
+ if (!enabled())
1501
+ return locked;
1502
+ return comparing ?? load();
1503
+ }
1504
+ function persist() {
1505
+ try {
1506
+ valueStorage()?.setItem(stateKey, JSON.stringify({
1507
+ version: 1,
1508
+ values: current,
1509
+ ...locks.size ? { locks: [...locks] } : {},
1510
+ ...bindings.size ? { bindings: Object.fromEntries(bindings) } : {},
1511
+ ...base !== NO_BASE ? { base } : {}
1512
+ })), error = null;
1513
+ } catch (cause) {
1514
+ error = String(cause);
1515
+ }
1516
+ }
1517
+ function commit(next) {
1518
+ current = next, persist(), notify();
1519
+ }
1520
+ let makeBase = (preset, groups) => preset === null && Object.keys(groups).length === 0 ? NO_BASE : Object.freeze({ preset, groups: Object.freeze({ ...groups }) }), sameBase = (a, b) => a === b || a.preset?.kind === b.preset?.kind && a.preset?.id === b.preset?.id && Object.keys(a.groups).length === Object.keys(b.groups).length && Object.entries(a.groups).every(([g, i]) => b.groups[g] === i);
1521
+ function validBase(b) {
1522
+ let preset = b.preset?.kind === "preset" && !(b.preset.id !== "default" && Object.hasOwn(presets, b.preset.id)) ? null : b.preset, groups = Object.fromEntries(Object.entries(b.groups).filter(([g, i]) => groupPresets[g]?.[i] !== void 0)), next = makeBase(preset, groups);
1523
+ return sameBase(next, b) ? b : next;
1524
+ }
1525
+ function readBase(raw) {
1526
+ if (!raw || typeof raw !== "object")
1527
+ return NO_BASE;
1528
+ let { preset, groups } = raw, ref = preset && typeof preset === "object" && (preset.kind === "preset" || preset.kind === "variant") && typeof preset.id === "string" ? Object.freeze({ kind: preset.kind, id: preset.id }) : null, byGroup = groups && typeof groups === "object" ? Object.fromEntries(Object.entries(groups).filter((e) => Number.isInteger(e[1]))) : {};
1529
+ return validBase(makeBase(ref, byGroup));
1530
+ }
1531
+ function activeBase() {
1532
+ load();
1533
+ let ref = base.preset;
1534
+ if (ref?.kind === "variant" && !variants.list().some((v) => v.id === ref.id))
1535
+ base = makeBase(null, base.groups);
1536
+ return base;
1537
+ }
1538
+ let hasBase = () => activeBase() !== NO_BASE, baseCache;
1539
+ function baseValues() {
1540
+ if (!enabled())
1541
+ return locked;
1542
+ let b = activeBase();
1543
+ if (b === NO_BASE)
1544
+ return locked;
1545
+ let list = b.preset?.kind === "variant" ? variants.list() : null, c = baseCache;
1546
+ if (c && c.base === b && c.schema === schema && c.presets === presets && c.groupPresets === groupPresets && c.variants === list)
1547
+ return c.values;
1548
+ let out = locked, ref = b.preset;
1549
+ if (ref?.kind === "preset" && Object.hasOwn(presets, ref.id))
1550
+ out = presets[ref.id];
1551
+ else if (ref?.kind === "variant") {
1552
+ let found = list?.find((v) => v.id === ref.id);
1553
+ if (found)
1554
+ out = normalize(schema, found.values);
1555
+ }
1556
+ let groups = Object.entries(b.groups);
1557
+ if (groups.length) {
1558
+ out = { ...out };
1559
+ for (let [g, i] of groups)
1560
+ Object.assign(out, groupPresets[g]?.[i]?.values);
1561
+ }
1562
+ let values = Object.freeze(out);
1563
+ return baseCache = { base: b, schema, presets, groupPresets, variants: list, values }, values;
1564
+ }
1565
+ let covered = (key) => {
1566
+ let b = activeBase();
1567
+ if (b.preset)
1568
+ return !0;
1569
+ let group = schema[key]?.group;
1570
+ return group !== void 0 && Object.hasOwn(b.groups, group);
1571
+ };
1572
+ function declaredShortcut(id) {
1573
+ let slash = id.indexOf("/"), key = slash < 0 ? id : id.slice(0, slash), field = Object.hasOwn(schema, key) ? schema[key] : void 0;
1574
+ if (field?.kind === "action" && slash < 0)
1575
+ return { found: !0, ...field.shortcut ? { combo: field.shortcut } : {} };
1576
+ if (field?.kind === "toolbar" && slash >= 0) {
1577
+ let item = field.items.find((i) => (i.id ?? i.label) === id.slice(slash + 1));
1578
+ if (!item)
1579
+ return { found: !1 };
1580
+ let key = toolbarShortcut(item)?.key;
1581
+ return { found: !0, ...key ? { combo: normalizeCombo(key) } : {} };
1582
+ }
1583
+ return { found: !1 };
1584
+ }
1585
+ function bindable(id) {
1586
+ return declaredShortcut(id).found;
1587
+ }
1588
+ let fieldKeys = () => valueKeys(schema), valueField = (k) => {
1589
+ let field = Object.hasOwn(schema, k) ? schema[k] : void 0;
1590
+ return field && hasValue(field) ? field : void 0;
1591
+ };
1592
+ function changes(from, to) {
1593
+ let diff = {};
1594
+ for (let k of fieldKeys())
1595
+ if (!same(from[k], to[k]))
1596
+ diff[k] = to[k];
1597
+ return diff;
1598
+ }
1599
+ function release() {
1600
+ if (!comparing)
1601
+ return;
1602
+ let shown = comparing;
1603
+ comparing = null, options.onChange?.(changes(shown, current), current, { source: "compare" }), notify();
1604
+ }
1605
+ let flag = (k, name) => {
1606
+ let v = valueField(k)?.[name];
1607
+ return typeof v === "function" ? Boolean(v(get())) : Boolean(v);
1608
+ }, blocked = (k) => flag(k, "disabled") || flag(k, "readOnly"), rollable = (k) => {
1609
+ let field = valueField(k);
1610
+ return Boolean(field && !locks.has(k) && field.random !== !1 && !blocked(k) && (!options.tunable || options.tunable(k, get())));
1611
+ }, pool = (keys) => (load(), (keys ?? fieldKeys()).filter(rollable));
1612
+ function applyPatch(base, previous, patch) {
1613
+ let out = { ...base }, diff = {}, changed = !1;
1614
+ if (base === previous) {
1615
+ for (let k of Object.keys(patch)) {
1616
+ let field = valueField(k);
1617
+ if (!field)
1618
+ continue;
1619
+ let raw = patch[k], val = raw === void 0 ? previous[k] : normalizeField(field, raw);
1620
+ if (!same(previous[k], val))
1621
+ out[k] = val, diff[k] = val, changed = !0;
1622
+ }
1623
+ return changed ? { next: Object.freeze(out), diff } : null;
1624
+ }
1625
+ for (let k of Object.keys(patch)) {
1626
+ let field = valueField(k);
1627
+ if (!field)
1628
+ continue;
1629
+ let raw = patch[k], val = raw === void 0 ? base[k] : normalizeField(field, raw);
1630
+ out[k] = same(previous[k], val) ? previous[k] : val;
1631
+ }
1632
+ for (let k of fieldKeys())
1633
+ if (!same(previous[k], out[k]))
1634
+ diff[k] = out[k], changed = !0;
1635
+ else
1636
+ out[k] = previous[k];
1637
+ return changed ? { next: Object.freeze(out), diff } : null;
1638
+ }
1639
+ let markChanged = (diff) => {
1640
+ if (echo > 0) {
1641
+ let at = now();
1642
+ for (let k of Object.keys(diff))
1643
+ changedAt.set(k, at), sent.set(k, diff[k]);
1644
+ scheduleEcho();
1645
+ }
1646
+ for (let k of Object.keys(diff))
1647
+ pending.delete(k);
1648
+ };
1649
+ function set(patch, setOpts) {
1650
+ write(patch, setOpts);
1651
+ }
1652
+ function write(patch, setOpts, nextBase) {
1653
+ if (!enabled())
1654
+ return;
1655
+ release();
1656
+ let previous = load(), was = activeBase(), moves = nextBase !== void 0 && !sameBase(nextBase, was), forced = mixed.size ? Object.keys(patch).filter((k) => mixed.has(k) && valueField(k)) : [];
1657
+ for (let k of forced)
1658
+ mixed.delete(k);
1659
+ let applied = applyPatch(previous, previous, patch) ?? {
1660
+ next: previous,
1661
+ diff: {}
1662
+ }, { next, diff } = applied;
1663
+ for (let k of forced)
1664
+ if (!(k in diff))
1665
+ diff[k] = next[k];
1666
+ let changed = Object.keys(diff).length > 0;
1667
+ if (!changed && !moves)
1668
+ return;
1669
+ if (next === previous && !moves) {
1670
+ notify(), options.onChange?.(diff, next, {
1671
+ source: setOpts?.source ?? "local",
1672
+ ...setOpts?.key ? { key: setOpts.key } : {}
1673
+ });
1674
+ return;
1675
+ }
1676
+ if (!setOpts?.coalesce || past.length === 0) {
1677
+ if (past.push({ values: previous, base: was }), past.length > HISTORY)
1678
+ past.shift();
1679
+ }
1680
+ if (future.length = 0, moves)
1681
+ base = nextBase;
1682
+ if (markChanged(diff), commit(next), !changed)
1683
+ return;
1684
+ options.onChange?.(diff, next, {
1685
+ source: setOpts?.source ?? "local",
1686
+ ...setOpts?.coalesce ? { coalesce: !0 } : {},
1687
+ ...setOpts?.key ? { key: setOpts.key } : {},
1688
+ ...setOpts?.group ? { group: setOpts.group } : {},
1689
+ ...setOpts?.base ? { base: !0 } : {}
1690
+ });
1691
+ }
1692
+ function step(to, source) {
1693
+ let before = load(), left = { values: before, base: activeBase() }, diff = changes(before, to.values);
1694
+ if (base = validBase(to.base), markChanged(diff), commit(to.values), Object.keys(diff).length)
1695
+ options.onChange?.(diff, to.values, { source });
1696
+ return left;
1697
+ }
1698
+ function place(values, replace, land) {
1699
+ let previous = load(), applied = applyPatch(replace ? locked : previous, previous, values);
1700
+ if (!applied)
1701
+ return;
1702
+ landing = land;
1703
+ try {
1704
+ commit(applied.next), options.onChange?.(applied.diff, applied.next, { source: "remote" });
1705
+ } finally {
1706
+ landing = !1;
1707
+ }
1708
+ }
1709
+ function acknowledges(key, value) {
1710
+ if (!sent.has(key))
1711
+ return !1;
1712
+ let was = sent.get(key), field = schema[key];
1713
+ if (field?.kind === "knob" && typeof value === "number" && typeof was === "number")
1714
+ return Math.abs(value - was) <= (field.step ?? 0) / 2 + 0.000000001;
1715
+ return same(value, was);
1716
+ }
1717
+ function incoming(values, replace) {
1718
+ let current = load(), out = {};
1719
+ for (let k of replace ? fieldKeys() : Object.keys(values)) {
1720
+ if (!valueField(k))
1721
+ continue;
1722
+ let target = Object.hasOwn(values, k) ? values[k] : locked[k];
1723
+ if (!grabbed.has(k) && changedAt.has(k) && acknowledges(k, target))
1724
+ changedAt.delete(k), sent.delete(k);
1725
+ if (isHeld(k))
1726
+ pending.set(k, target), out[k] = current[k];
1727
+ else
1728
+ pending.delete(k), out[k] = target;
1729
+ }
1730
+ if (pending.size)
1731
+ scheduleEcho();
1732
+ return out;
1733
+ }
1734
+ function replaceMixed(keys) {
1735
+ if (!keys)
1736
+ return !1;
1737
+ let next = new Set(keys.filter((k) => valueField(k)));
1738
+ if (next.size === mixed.size && [...next].every((k) => mixed.has(k)))
1739
+ return !1;
1740
+ mixed.clear();
1741
+ for (let k of next)
1742
+ mixed.add(k);
1743
+ return !0;
1744
+ }
1745
+ function sync(values, syncOpts) {
1746
+ if (!enabled())
1747
+ return;
1748
+ release();
1749
+ let land = Boolean(syncOpts?.land);
1750
+ if (land)
1751
+ settle();
1752
+ let before = current, remixed = replaceMixed(syncOpts?.mixed);
1753
+ if (place(incoming(values, Boolean(syncOpts?.replace)), !1, land), (land || remixed) && current === before)
1754
+ announce(land);
1755
+ }
1756
+ function settle() {
1757
+ past.length = 0, future.length = 0, changedAt.clear(), sent.clear();
1758
+ for (let k of [...pending.keys()])
1759
+ if (!grabbed.has(k))
1760
+ pending.delete(k);
1761
+ scheduleEcho();
1762
+ }
1763
+ function announce(land) {
1764
+ landing = land;
1765
+ try {
1766
+ notify();
1767
+ } finally {
1768
+ landing = !1;
1769
+ }
1770
+ }
1771
+ function flush() {
1772
+ let ready = {};
1773
+ for (let [k, v] of pending)
1774
+ if (!isHeld(k))
1775
+ ready[k] = v, pending.delete(k);
1776
+ if (Object.keys(ready).length)
1777
+ place(ready, !1, !1);
1778
+ scheduleEcho();
1779
+ }
1780
+ function scheduleEcho() {
1781
+ if (clearTimeout(echoTimer), echoTimer = void 0, echo <= 0 || pending.size === 0)
1782
+ return;
1783
+ let soonest = 1 / 0, t = now();
1784
+ for (let k of pending.keys()) {
1785
+ if (grabbed.has(k))
1786
+ continue;
1787
+ soonest = Math.min(soonest, (changedAt.get(k) ?? -1 / 0) + echo - t);
1788
+ }
1789
+ if (soonest === 1 / 0)
1790
+ return;
1791
+ echoTimer = setTimeout(flush, Math.max(0, soonest) + 1);
1792
+ }
1793
+ let reshape = (next, values, reshapeOpts) => {
1794
+ if (!enabled())
1795
+ return;
1796
+ release();
1797
+ let before = schema, changed = Object.keys(before).length !== Object.keys(next).length || Object.keys(next).some((k) => !sameField(before[k], next[k])), land = Boolean(reshapeOpts?.land), fresh = !changed && Object.keys(next).some((k) => {
1798
+ let a = before[k], b = next[k];
1799
+ return Object.keys(b).some((p) => typeof b[p] === "function" && a[p] !== b[p]);
1800
+ });
1801
+ if (fresh)
1802
+ schema = Object.freeze({ ...next });
1803
+ if (changed) {
1804
+ let previous = load();
1805
+ schema = Object.freeze({ ...next }), locked = normalize(schema, {}), presets = buildPresets(), groupPresets = buildGroupPresets(), base = validBase(base);
1806
+ for (let k of [...locks])
1807
+ if (!valueField(k))
1808
+ locks.delete(k);
1809
+ for (let k of [...pending.keys()])
1810
+ if (!valueField(k))
1811
+ pending.delete(k);
1812
+ for (let k of [...mixed])
1813
+ if (!valueField(k))
1814
+ mixed.delete(k);
1815
+ let kept = {};
1816
+ for (let k of fieldKeys())
1817
+ kept[k] = Object.hasOwn(previous, k) ? previous[k] : locked[k];
1818
+ current = normalize(schema, kept);
1819
+ }
1820
+ if (land)
1821
+ settle();
1822
+ else if (changed)
1823
+ past.length = 0, future.length = 0;
1824
+ let remixed = replaceMixed(reshapeOpts?.mixed), target = incoming(values, !0), before2 = current;
1825
+ if (place(target, !1, land), current === before2 && (changed || fresh || land || remixed))
1826
+ announce(land);
1827
+ }, readouts = /* @__PURE__ */ new Map, readoutListeners = /* @__PURE__ */ new Set, readoutOf = (key) => {
1828
+ let field = Object.hasOwn(schema, key) ? schema[key] : void 0;
1829
+ if (!field || !isReadout(field))
1830
+ return;
1831
+ let shape = `${field.kind}\x00${"history" in field ? field.history : ""}\x00${field.kind === "graph" ? field.series.map((s) => s.id).join("\x00") : ""}\x00${field.kind === "log" ? field.max : ""}`, entry = readouts.get(key);
1832
+ if (!entry || entry.shape !== shape)
1833
+ entry = { shape, state: readoutState(field) }, readouts.set(key, entry);
1834
+ return { field, state: entry.state };
1835
+ }, readoutPort = {
1836
+ state: (key) => readoutOf(key)?.state,
1837
+ listen(cb) {
1838
+ return readoutListeners.add(cb), () => readoutListeners.delete(cb);
1839
+ }
1840
+ }, variants = createVariants(() => schema, id, storageKey(id, "variants"), variantStorage, get, (v, variant, coalesce) => write(v, { source: "variant", coalesce }, makeBase(Object.freeze({ kind: "variant", id: variant }), {})), notify, enabled), resettable = (keys) => (keys ?? fieldKeys()).filter((k) => valueField(k) && !blocked(k));
1841
+ function offBase(keys) {
1842
+ if (!hasBase())
1843
+ return !1;
1844
+ let cur = load(), b = baseValues();
1845
+ return keys.some((k) => !same(cur[k], b[k]));
1846
+ }
1847
+ function toBase(keys, meta) {
1848
+ let b = baseValues(), patch = Object.fromEntries(keys.map((k) => [k, b[k]]));
1849
+ write(patch, { source: "reset", base: !0, ...meta });
1850
+ }
1851
+ function toDefaults(keys, nextBase) {
1852
+ let patch = {};
1853
+ for (let k of keys)
1854
+ patch[k] = locked[k];
1855
+ write(patch, { source: "reset" }, nextBase);
1856
+ let resetKeys = options[RESET_KEYS];
1857
+ if (resetKeys)
1858
+ resetKeys(Object.keys(patch));
1859
+ else
1860
+ for (let key of Object.keys(patch))
1861
+ options.onReset?.({ key });
1862
+ }
1863
+ let controls = {
1864
+ id,
1865
+ get schema() {
1866
+ return schema;
1867
+ },
1868
+ get locked() {
1869
+ return locked;
1870
+ },
1871
+ get presets() {
1872
+ return presets;
1873
+ },
1874
+ actions,
1875
+ theme,
1876
+ density,
1877
+ renderer,
1878
+ keys: options.keys,
1879
+ marks: options.marks !== !1,
1880
+ chip: Object.freeze({
1881
+ ...options.chip,
1882
+ reveal: options.chip?.reveal ?? (devBuild() ? "hover" : "shortcut"),
1883
+ shortcut: options.chip?.shortcut ?? "Alt+Shift+D"
1884
+ }),
1885
+ groupPresetDisplay: options.groupPresetDisplay ?? "auto",
1886
+ groupLabel: (group) => options.groupLabels?.[group] ?? fieldLabels().group.get(group),
1887
+ sectionLabel: (section) => options.sectionLabels?.[section] ?? fieldLabels().section.get(section),
1888
+ groupDescription: (group) => options.groupDescriptions?.[group] ?? fieldLabels().groupText.get(group),
1889
+ sectionDescription: (section) => options.sectionDescriptions?.[section] ?? fieldLabels().sectionText.get(section),
1890
+ groupFolded: options.groupFolded,
1891
+ sectionFolded: options.sectionFolded,
1892
+ presetLabel: (presetId) => options.presetLabels?.[presetId] ?? (presetId === "default" ? options.defaultLabel : void 0) ?? presetId,
1893
+ get,
1894
+ set,
1895
+ sync,
1896
+ get enabled() {
1897
+ return enabled();
1898
+ },
1899
+ get storage() {
1900
+ return storage();
1901
+ },
1902
+ get error() {
1903
+ return error;
1904
+ },
1905
+ get canUndo() {
1906
+ return hostHistory ? hostHistory.canUndo() : past.length > 0;
1907
+ },
1908
+ get canRedo() {
1909
+ return hostHistory ? hostHistory.canRedo() : future.length > 0;
1910
+ },
1911
+ persist: persistMode,
1912
+ get comparing() {
1913
+ return comparing !== null;
1914
+ },
1915
+ get landing() {
1916
+ return landing;
1917
+ },
1918
+ [RESHAPE]: reshape,
1919
+ [READOUT]: readoutPort,
1920
+ watch(key, value) {
1921
+ let found = readoutOf(key);
1922
+ if (!found)
1923
+ return;
1924
+ pushReadout(found.field, found.state, value);
1925
+ for (let cb of readoutListeners)
1926
+ cb(key);
1927
+ },
1928
+ watched(key) {
1929
+ let state = readouts.get(key)?.state;
1930
+ return state && state.seq > 0 ? state.value : void 0;
1931
+ },
1932
+ hold(key, on = !0) {
1933
+ if (on) {
1934
+ if (grabbed.has(key))
1935
+ return;
1936
+ grabbed.add(key), options.onGesture?.({ key, phase: "start" });
1937
+ return;
1938
+ }
1939
+ if (!grabbed.delete(key))
1940
+ return;
1941
+ options.onGesture?.({ key, phase: "end" }), flush();
1942
+ },
1943
+ isHeld,
1944
+ action(key, item) {
1945
+ let field = schema[key];
1946
+ if (field?.kind === "toolbar") {
1947
+ let found = field.items.find((i) => (i.id ?? i.label) === item);
1948
+ if (!found)
1949
+ throw Error(`Unknown toolbar item: ${key} ${item}`);
1950
+ if (found.disabled)
1951
+ return;
1952
+ found.run?.(), options.onAction?.(found.id ?? found.label);
1953
+ return;
1954
+ }
1955
+ if (!field || field.kind !== "action")
1956
+ throw Error(`Unknown action: ${key}`);
1957
+ field.run?.(), options.onAction?.(key);
1958
+ },
1959
+ undo() {
1960
+ if (release(), hostHistory)
1961
+ return hostHistory.undo();
1962
+ let previous = past.pop();
1963
+ if (!previous)
1964
+ return;
1965
+ future.push(step(previous, "undo"));
1966
+ },
1967
+ redo() {
1968
+ if (release(), hostHistory)
1969
+ return hostHistory.redo();
1970
+ let next = future.pop();
1971
+ if (!next)
1972
+ return;
1973
+ past.push(step(next, "redo"));
1974
+ },
1975
+ getServerSnapshot: () => locked,
1976
+ subscribe(cb) {
1977
+ if (listeners.add(cb), hostHistory?.subscribe && !unsubscribeHistory)
1978
+ unsubscribeHistory = hostHistory.subscribe(notify);
1979
+ return () => {
1980
+ if (listeners.delete(cb), !listeners.size && unsubscribeHistory)
1981
+ unsubscribeHistory(), unsubscribeHistory = void 0;
1982
+ };
1983
+ },
1984
+ reset(keys) {
1985
+ if (!enabled())
1986
+ return;
1987
+ let open = resettable(keys);
1988
+ if (offBase(open))
1989
+ return toBase(open, {});
1990
+ let skipped = fieldKeys().some(blocked);
1991
+ if (!keys && !skipped) {
1992
+ write(locked, { source: "reset" }, NO_BASE), options.onReset?.({});
1993
+ return;
1994
+ }
1995
+ toDefaults(open, keys ? void 0 : NO_BASE);
1996
+ },
1997
+ resetKey(key) {
1998
+ if (!enabled())
1999
+ return;
2000
+ if (!valueField(key))
2001
+ throw Error(`Unknown control key: ${key}`);
2002
+ if (blocked(key))
2003
+ return;
2004
+ if (offBase([key]))
2005
+ return toBase([key], { key });
2006
+ set({ [key]: locked[key] }, { source: "reset", key }), options.onReset?.({ key });
2007
+ },
2008
+ resetGroup(group) {
2009
+ if (!enabled())
2010
+ return;
2011
+ let keys = Object.keys(schema).filter((k) => schema[k].group === group && hasValue(schema[k])), open = keys.filter((k) => !blocked(k)), b = activeBase(), index = b.groups[group];
2012
+ if (offBase(open)) {
2013
+ toBase(open, { group });
2014
+ let preset = index === void 0 ? void 0 : groupPresets[group]?.[index];
2015
+ if (preset && index !== void 0)
2016
+ options.onPreset?.({ group, index, name: preset.name });
2017
+ return;
2018
+ }
2019
+ let { [group]: _, ...others } = b.groups, forget = index === void 0 ? void 0 : makeBase(b.preset, others);
2020
+ if (open.length < keys.length)
2021
+ return toDefaults(open, forget);
2022
+ let patch = {};
2023
+ for (let k of keys)
2024
+ patch[k] = locked[k];
2025
+ write(patch, { source: "reset", group }, forget), options.onReset?.({ group });
2026
+ },
2027
+ isModified(key) {
2028
+ let cur = get(), b = baseValues();
2029
+ if (key !== void 0)
2030
+ return !same(cur[key], b[key]);
2031
+ return !equal(cur, b);
2032
+ },
2033
+ canReset(keys) {
2034
+ if (!enabled())
2035
+ return !1;
2036
+ let cur = get(), b = baseValues();
2037
+ return resettable(keys).some((k) => !same(cur[k], b[k]) || !same(cur[k], locked[k])) || !keys && hasBase();
2038
+ },
2039
+ modified() {
2040
+ let cur = get(), out = {};
2041
+ for (let k of fieldKeys())
2042
+ if (!same(cur[k], locked[k]))
2043
+ out[k] = cur[k];
2044
+ return Object.freeze(out);
2045
+ },
2046
+ randomize(keys) {
2047
+ if (!enabled())
2048
+ return;
2049
+ let chosen = pool(keys);
2050
+ if (chosen.length === 0)
2051
+ return;
2052
+ if (!hasBase()) {
2053
+ let sub = Object.fromEntries(chosen.map((k) => [k, schema[k]]));
2054
+ set(randomValues(sub, draw), { source: "randomize" });
2055
+ return;
2056
+ }
2057
+ let b = baseValues(), patch = {};
2058
+ for (let k of chosen) {
2059
+ let field = valueField(k);
2060
+ patch[k] = covered(k) ? around(field, b[k], draw) : pick(field, draw);
2061
+ }
2062
+ set(patch, { source: "randomize" });
2063
+ },
2064
+ mutate(keys) {
2065
+ if (!enabled())
2066
+ return;
2067
+ release();
2068
+ let chosen = pool(keys);
2069
+ if (chosen.length === 0)
2070
+ return;
2071
+ set(mutateValues(schema, load(), chosen, draw), { source: "mutate" });
2072
+ },
2073
+ lock(keys, on = !0) {
2074
+ if (!enabled())
2075
+ return;
2076
+ load();
2077
+ let changed = !1;
2078
+ for (let key of typeof keys === "string" ? [keys] : keys) {
2079
+ if (!valueField(key))
2080
+ throw Error(`Unknown control key: ${key}`);
2081
+ if (locks.has(key) === on)
2082
+ continue;
2083
+ if (on)
2084
+ locks.add(key);
2085
+ else
2086
+ locks.delete(key);
2087
+ changed = !0;
2088
+ }
2089
+ if (!changed)
2090
+ return;
2091
+ persist(), notify();
2092
+ },
2093
+ isLocked(key) {
2094
+ if (!enabled())
2095
+ return !1;
2096
+ return load(), locks.has(key);
2097
+ },
2098
+ shortcut(key, item) {
2099
+ let id = item === void 0 ? key : `${key}/${item}`, declared = declaredShortcut(id);
2100
+ if (!declared.found)
2101
+ return;
2102
+ if (enabled())
2103
+ load();
2104
+ return (enabled() ? bindings.get(id) : void 0) ?? declared.combo;
2105
+ },
2106
+ rebind(key, combo, item) {
2107
+ let id = item === void 0 ? key : `${key}/${item}`, declared = declaredShortcut(id);
2108
+ if (!declared.found)
2109
+ throw Error(`Not an action or toolbar button: ${id}`);
2110
+ if (!enabled())
2111
+ return;
2112
+ load();
2113
+ let next = combo === void 0 ? void 0 : normalizeCombo(combo);
2114
+ if (combo !== void 0 && combo.trim() && !next)
2115
+ throw Error(`Not a key combination: ${combo}`);
2116
+ let was = bindings.get(id);
2117
+ if (next === void 0 || next === (declared.combo ?? ""))
2118
+ bindings.delete(id);
2119
+ else
2120
+ bindings.set(id, next);
2121
+ if (bindings.get(id) === was)
2122
+ return;
2123
+ persist(), notify();
2124
+ },
2125
+ canRoll(key) {
2126
+ if (!enabled())
2127
+ return !1;
2128
+ return load(), rollable(key);
2129
+ },
2130
+ isDisabled: (key) => flag(key, "disabled"),
2131
+ isReadOnly: (key) => flag(key, "readOnly"),
2132
+ refresh() {
2133
+ refreshes++, notify();
2134
+ },
2135
+ get [REFRESHES]() {
2136
+ return refreshes;
2137
+ },
2138
+ setMixed(keys, on = !0) {
2139
+ let changed = !1;
2140
+ for (let key of typeof keys === "string" ? [keys] : keys) {
2141
+ if (!valueField(key) || mixed.has(key) === on)
2142
+ continue;
2143
+ if (on)
2144
+ mixed.add(key);
2145
+ else
2146
+ mixed.delete(key);
2147
+ changed = !0;
2148
+ }
2149
+ if (changed)
2150
+ notify();
2151
+ },
2152
+ isMixed: (key) => mixed.has(key),
2153
+ compare(hold) {
2154
+ if (!enabled())
2155
+ return;
2156
+ if (!hold)
2157
+ return release();
2158
+ let before = past.at(-1)?.values, now = load();
2159
+ if (comparing || !before || equal(before, now))
2160
+ return;
2161
+ comparing = before, options.onChange?.(changes(now, before), before, { source: "compare" }), notify();
2162
+ },
2163
+ preset(id, presetOptions) {
2164
+ if (!Object.hasOwn(presets, id))
2165
+ throw Error(`Unknown preset: ${id}`);
2166
+ write(presets[id], { source: "preset", coalesce: presetOptions?.coalesce }, id === "default" ? NO_BASE : makeBase(Object.freeze({ kind: "preset", id }), {}));
2167
+ },
2168
+ matchingPreset: () => Object.keys(presets).find((id) => equal(get(), presets[id])) ?? "custom",
2169
+ get groupPresets() {
2170
+ return groupPresets;
2171
+ },
2172
+ groupPreset(group, index, presetOptions) {
2173
+ let preset = groupPresets[group]?.[index];
2174
+ if (!preset)
2175
+ throw Error(`Unknown preset ${index + 1} for group ${group}`);
2176
+ if (!enabled())
2177
+ return;
2178
+ let b = activeBase();
2179
+ write(preset.values, { source: "preset", group, coalesce: presetOptions?.coalesce }, makeBase(b.preset, { ...b.groups, [group]: index })), options.onPreset?.({ group, index, name: preset.name });
2180
+ },
2181
+ activePreset() {
2182
+ if (!enabled())
2183
+ return null;
2184
+ return activeBase().preset;
2185
+ },
2186
+ activeGroupPreset(group) {
2187
+ if (!enabled())
2188
+ return -1;
2189
+ return activeBase().groups[group] ?? -1;
2190
+ },
2191
+ baseValues,
2192
+ matchingGroupPreset(group) {
2193
+ let cur = get();
2194
+ return (groupPresets[group] ?? []).findIndex((p) => Object.entries(p.values).every(([k, v]) => same(cur[k], v)));
2195
+ },
2196
+ code: (format, values) => formatCode(values ?? get(), format),
2197
+ variants,
2198
+ attach(element, chip) {
2199
+ let load = options.ship ? () => options.ship().then((m) => m.attach) : void 0;
2200
+ if (!load || !enabled())
2201
+ return () => {};
2202
+ let disposed = !1, detach;
2203
+ return load().then((attach) => {
2204
+ if (!disposed && enabled())
2205
+ detach = attach(controls, element, chip);
2206
+ }).catch((cause) => {
2207
+ error = String(cause), notify();
2208
+ }), () => {
2209
+ disposed = !0, detach?.();
2210
+ };
2211
+ },
2212
+ mount(container, mountOptions) {
2213
+ let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : void 0;
2214
+ if (!load || !enabled())
2215
+ return () => {};
2216
+ let disposed = !1, panel;
2217
+ return load().then((mountPanel) => {
2218
+ if (!disposed && enabled())
2219
+ panel = mountPanel(controls, container, mountOptions), mountOptions?.onReady?.(panel);
2220
+ }).catch((cause) => {
2221
+ error = String(cause), notify();
2222
+ }), () => {
2223
+ disposed = !0, panel?.dispose();
2224
+ };
2225
+ }
2226
+ };
2227
+ return Object.freeze(controls);
2228
+ }
2229
+ // src/core/remote.ts
2230
+ var readoutDescriptor = (d) => isReadout(d);
2231
+ function readoutField(d) {
2232
+ let { key: _key, kind, default: _d, bind: _b, ...rest } = d, opts = Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== void 0));
2233
+ if (kind === "watch")
2234
+ return watch(opts);
2235
+ if (kind === "sparkline")
2236
+ return sparkline(opts);
2237
+ if (kind === "graph")
2238
+ return graph(opts);
2239
+ if (kind === "meter")
2240
+ return meter(opts);
2241
+ if (kind === "table")
2242
+ return table(opts);
2243
+ return log(opts);
2244
+ }
2245
+ var optionId = (o) => typeof o === "string" ? o : String(o.id ?? o.value ?? o.label ?? "");
2246
+ function descriptorToField(d) {
2247
+ let meta = pickMeta(d);
2248
+ if (d.kind === "action")
2249
+ return action(d.label, {
2250
+ ...meta,
2251
+ ...d.global ? { global: !0 } : {},
2252
+ ...d.rebind ? { rebind: !0 } : {}
2253
+ });
2254
+ if (readoutDescriptor(d))
2255
+ return readoutField(d);
2256
+ if (d.kind === "toolbar")
2257
+ return toolbar(d.items.map(({ key, ...item }) => ({ ...item, id: key })), meta);
2258
+ if (d.bind)
2259
+ return flatSpring({}, { ...meta, id: d.key }).duration;
2260
+ if (d.kind === "knob" || d.kind === "number") {
2261
+ let def = typeof d.default === "number" && Number.isFinite(d.default) ? d.default : 0, min = d.min ?? Math.min(0, def * 2), max = d.max ?? Math.max(1, def * 2), step = d.step ?? (d.swiftType === "Int" ? 1 : (max - min) / 100 || 1);
2262
+ return knob(def, {
2263
+ min,
2264
+ max,
2265
+ step,
2266
+ ...d.unit !== void 0 ? { unit: d.unit } : {},
2267
+ ...d.decimals !== void 0 ? { decimals: d.decimals } : {},
2268
+ ...meta
2269
+ });
2270
+ }
2271
+ if (d.kind === "choice") {
2272
+ let given = d.options ?? d.choices ?? [String(d.default)];
2273
+ if (given.length === 0)
2274
+ throw Error(`Choice descriptor ${d.key} needs options.`);
2275
+ let list = given.map(optionId), labels = Object.fromEntries(given.flatMap((o) => typeof o === "string" || o.label === void 0 ? [] : [[optionId(o), o.label]])), def = list.includes(String(d.default)) ? String(d.default) : list[0];
2276
+ return choice(def, list, {
2277
+ ...meta,
2278
+ labels,
2279
+ ...d.display ? { display: d.display } : {}
2280
+ });
2281
+ }
2282
+ if (d.kind === "spring")
2283
+ return spring(d.default, meta);
2284
+ if (d.kind === "color")
2285
+ return color(parseColor(d.default) ? d.default : "#808080", meta);
2286
+ if (d.kind === "point")
2287
+ return point(d.default, { min: d.min ?? 0, max: d.max ?? 1, step: d.step, ...meta });
2288
+ if (d.kind === "easing")
2289
+ return easing(d.default, meta);
2290
+ let numbers = {
2291
+ ...d.unit !== void 0 ? { unit: d.unit } : {},
2292
+ ...d.decimals !== void 0 ? { decimals: d.decimals } : {},
2293
+ ...d.step !== void 0 ? { step: d.step } : {}
2294
+ };
2295
+ if (d.kind === "vec2" || d.kind === "vec3") {
2296
+ let v = d.default ?? {}, opts = {
2297
+ min: d.min ?? -1,
2298
+ max: d.max ?? 1,
2299
+ ...numbers,
2300
+ ...d.link ? { link: !0 } : {},
2301
+ ...d.linked ? { linked: !0 } : {},
2302
+ ...d.axes ? { axes: d.axes } : {},
2303
+ ...meta
2304
+ };
2305
+ return d.kind === "vec3" ? vec3({ x: v.x ?? 0, y: v.y ?? 0, z: v.z ?? 0 }, opts) : vec2({ x: v.x ?? 0, y: v.y ?? 0 }, opts);
2306
+ }
2307
+ if (d.kind === "range") {
2308
+ let v = Array.isArray(d.default) ? d.default : [d.min ?? 0, d.max ?? 1];
2309
+ return range([Number(v[0]), Number(v[1])], {
2310
+ min: d.min ?? 0,
2311
+ max: d.max ?? 1,
2312
+ ...numbers,
2313
+ ...meta
2314
+ });
2315
+ }
2316
+ if (d.kind === "angle")
2317
+ return angle(typeof d.default === "number" ? d.default : 0, {
2318
+ ...d.min !== void 0 ? { min: d.min } : {},
2319
+ ...d.max !== void 0 ? { max: d.max } : {},
2320
+ ...d.step !== void 0 ? { step: d.step } : {},
2321
+ ...d.wrap !== void 0 ? { wrap: d.wrap } : {},
2322
+ ...meta
2323
+ });
2324
+ if (d.kind === "seed")
2325
+ return seed(typeof d.default === "number" ? d.default : 0, {
2326
+ ...d.min !== void 0 ? { min: d.min } : {},
2327
+ ...d.max !== void 0 ? { max: d.max } : {},
2328
+ ...meta
2329
+ });
2330
+ if (d.kind === "keybind")
2331
+ return keybind(typeof d.default === "string" ? d.default : "", meta);
2332
+ if (d.kind === "gradient") {
2333
+ let fallback = [
2334
+ { at: 0, color: "#000000" },
2335
+ { at: 1, color: "#ffffff" }
2336
+ ], stops = Array.isArray(d.default) ? d.default : fallback, opts = {
2337
+ ...d.space ? { space: d.space } : {},
2338
+ ...d.presets ? { presets: d.presets } : {},
2339
+ ...meta
2340
+ };
2341
+ try {
2342
+ return gradient(stops, opts);
2343
+ } catch {
2344
+ return gradient(fallback, opts);
2345
+ }
2346
+ }
2347
+ if (d.kind === "curve") {
2348
+ let opts = {
2349
+ range: d.range ?? [d.min ?? 0, d.max ?? 1],
2350
+ ...d.interp ? { interp: d.interp } : {},
2351
+ ...d.shapes !== void 0 ? { shapes: d.shapes } : {},
2352
+ ...d.presets ? { presets: d.presets } : {},
2353
+ ...meta
2354
+ }, points = Array.isArray(d.default) ? d.default : [];
2355
+ return points.length >= 2 ? curve(points, opts) : curve("linear", opts);
2356
+ }
2357
+ return toggle(Boolean(d.default), meta);
2358
+ }
2359
+ function fromDescriptors(descriptors) {
2360
+ let byKey = new Map(descriptors.map((d) => [d.key, d])), bound = /* @__PURE__ */ new Set;
2361
+ for (let d of descriptors)
2362
+ if (d.bind) {
2363
+ if (d.bind.duration === d.bind.bounce)
2364
+ throw Error(`Spring ${d.key} binds one key twice.`);
2365
+ bound.add(d.bind.duration).add(d.bind.bounce);
2366
+ }
2367
+ let entries = [];
2368
+ for (let d of descriptors) {
2369
+ if (bound.has(d.key) && !d.bind)
2370
+ continue;
2371
+ if (!d.bind) {
2372
+ entries.push([d.key, descriptorToField(d)]);
2373
+ continue;
2374
+ }
2375
+ let half = (part) => {
2376
+ let own = byKey.get(d.bind[part]);
2377
+ if (own && !own.bind && (own.kind === "number" || own.kind === "knob"))
2378
+ return descriptorToField({
2379
+ ...own,
2380
+ group: d.group ?? own.group,
2381
+ groupLabel: d.groupLabel ?? own.groupLabel
2382
+ });
2383
+ return d.default?.[part] ?? (part === "duration" ? 0.5 : 0);
2384
+ }, pair = flatSpring({ duration: half("duration"), bounce: half("bounce") }, { id: d.key, ...pickMeta(d, ["shortcut"]) });
2385
+ entries.push([d.bind.duration, pair.duration], [d.bind.bounce, pair.bounce]);
2386
+ }
2387
+ return Object.freeze(Object.fromEntries(entries));
2388
+ }
2389
+ var decimal = (n) => String(Math.round(n * 1e4) / 1e4), swiftLiteral = (d, v) => {
2390
+ if (d.kind === "color") {
2391
+ let [r, g, b] = oklchToRgb(parseColor(v) ?? { l: 0, c: 0, h: 0 }).map(decimal);
2392
+ return `Color(red: ${r}, green: ${g}, blue: ${b})`;
2393
+ }
2394
+ if (d.kind === "point") {
2395
+ let p = v;
2396
+ return `CGPoint(x: ${decimal(p.x)}, y: ${decimal(p.y)})`;
2397
+ }
2398
+ if (d.kind === "easing") {
2399
+ let [x1, y1, x2, y2] = v.map(decimal);
2400
+ return `.bezier(startControlPoint: UnitPoint(x: ${x1}, y: ${y1}), endControlPoint: UnitPoint(x: ${x2}, y: ${y2}))`;
2401
+ }
2402
+ if (d.kind === "vec2" || d.kind === "vec3") {
2403
+ let p = v;
2404
+ return d.kind === "vec3" ? `SIMD3<Double>(${decimal(p.x)}, ${decimal(p.y)}, ${decimal(p.z)})` : `SIMD2<Double>(${decimal(p.x)}, ${decimal(p.y)})`;
2405
+ }
2406
+ if (d.kind === "range") {
2407
+ let [a, b] = v;
2408
+ return `${decimal(a)}...${decimal(b)}`;
2409
+ }
2410
+ if (d.kind === "keybind")
2411
+ return JSON.stringify(v);
2412
+ if (d.kind === "gradient")
2413
+ return `Gradient(stops: [${(Array.isArray(v) ? v : []).map((s) => {
2414
+ let [r, g, b] = oklchToRgb(parseColor(s.color) ?? { l: 0, c: 0, h: 0 }).map(decimal);
2415
+ return `.init(color: Color(red: ${r}, green: ${g}, blue: ${b}), location: ${decimal(s.at)})`;
2416
+ }).join(", ")}])`;
2417
+ if (d.kind === "curve")
2418
+ return `[${(Array.isArray(v) ? v : []).map((p) => `CGPoint(x: ${decimal(p.x)}, y: ${decimal(p.y)})`).join(", ")}]`;
2419
+ if (d.kind === "seed")
2420
+ return String(Math.round(v));
2421
+ if (typeof v === "boolean")
2422
+ return String(v);
2423
+ if (typeof v === "string")
2424
+ return `.${v}`;
2425
+ if (typeof v !== "number")
2426
+ return JSON.stringify(v);
2427
+ if (d.swiftType === "Int")
2428
+ return String(Math.round(v));
2429
+ return decimal(v);
2430
+ }, swiftTypes = {
2431
+ color: "Color",
2432
+ point: "CGPoint",
2433
+ easing: "UnitCurve",
2434
+ vec2: "SIMD2<Double>",
2435
+ vec3: "SIMD3<Double>",
2436
+ range: "ClosedRange<Double>",
2437
+ seed: "Int",
2438
+ keybind: "String",
2439
+ gradient: "Gradient",
2440
+ curve: "[CGPoint]"
2441
+ };
2442
+ function formatSwift(descriptors, values, group) {
2443
+ let byKey = new Map(descriptors.map((d) => [d.key, d])), done = /* @__PURE__ */ new Set, lines = [];
2444
+ for (let d of descriptors) {
2445
+ if (d.kind === "action" || d.kind === "toolbar" || d.bind)
2446
+ continue;
2447
+ if (readoutDescriptor(d))
2448
+ continue;
2449
+ if (!((d.key in values) && !same(values[d.key], d.default)) || done.has(d.key) || group && d.group !== group)
2450
+ continue;
2451
+ let base = d.key.replace(/\.(duration|bounce)$/, ""), duration = byKey.get(`${base}.duration`), bounce = byKey.get(`${base}.bounce`);
2452
+ if (base !== d.key && duration && bounce) {
2453
+ done.add(duration.key).add(bounce.key);
2454
+ let label = (duration.label ?? duration.key).replace(/ duration$/i, ""), dv = values[duration.key] ?? duration.default, bv = values[bounce.key] ?? bounce.default;
2455
+ lines.push(`TunableSpring(${JSON.stringify(base)}, ${JSON.stringify(label)}, duration: ${swiftLiteral(duration, dv)}, bounce: ${swiftLiteral(bounce, bv)}) // was duration: ${swiftLiteral(duration, duration.default)}, bounce: ${swiftLiteral(bounce, bounce.default)}`);
2456
+ continue;
2457
+ }
2458
+ if (done.add(d.key), d.kind === "spring") {
2459
+ let v = values[d.key], was = d.default;
2460
+ lines.push(`TunableSpring(${JSON.stringify(d.key)}, ${JSON.stringify(d.label ?? d.key)}, duration: ${decimal(v.duration)}, bounce: ${decimal(v.bounce)}) // was duration: ${decimal(was.duration)}, bounce: ${decimal(was.bounce)}`);
2461
+ continue;
2462
+ }
2463
+ let call = `Tunable<${d.swiftType ?? (d.kind === "bool" || d.kind === "toggle" ? "Bool" : (d.kind in swiftTypes) ? swiftTypes[d.kind] : "Double")}>(${JSON.stringify(d.key)}, ${JSON.stringify(d.label ?? d.key)}, default: ${swiftLiteral(d, values[d.key])}`;
2464
+ if (d.kind === "knob" || d.kind === "number") {
2465
+ if (d.min !== void 0 && d.max !== void 0)
2466
+ call += `, in: ${decimal(d.min)}...${decimal(d.max)}`;
2467
+ if (d.step !== void 0)
2468
+ call += `, step: ${decimal(d.step)}`;
2469
+ }
2470
+ let note = d.kind === "color" ? `${colorHex(values[d.key])} · ` : "";
2471
+ lines.push(`${call}) // ${note}was ${swiftLiteral(d, d.default)}`);
2472
+ }
2473
+ return lines.join(`
2474
+ `);
2475
+ }
2476
+ var ECHO_MS = 600;
2477
+ function extend(source, extra) {
2478
+ return Object.freeze(Object.defineProperties({}, {
2479
+ ...Object.getOwnPropertyDescriptors(source),
2480
+ ...Object.getOwnPropertyDescriptors(extra)
2481
+ }));
2482
+ }
2483
+ function createRemoteControls(id, descriptors, options = {}) {
2484
+ let frozenDescriptors = Object.freeze([...descriptors]), given = descriptors, nullDefaults = options.nullDefaults !== !1, send = (changes, meta) => {
2485
+ if (changes.length === 0)
2486
+ return;
2487
+ if (options.onSetBatch && (changes.length > 1 || !options.onSet))
2488
+ return options.onSetBatch(changes, meta);
2489
+ for (let { key, value } of changes)
2490
+ options.onSet?.(key, value, meta);
2491
+ }, onReset = (target) => {
2492
+ if (target.key && nullDefaults)
2493
+ send([{ key: target.key, value: null }], { source: "reset", key: target.key });
2494
+ options.onReset?.(target);
2495
+ }, resetKeys = (keys) => {
2496
+ if (!options.onSetBatch || !nullDefaults)
2497
+ return keys.forEach((key) => onReset({ key }));
2498
+ for (let key of keys)
2499
+ options.onReset?.({ key });
2500
+ send(keys.map((key) => ({ key, value: null })), { source: "reset" });
2501
+ }, controls = defineControls(id, fromDescriptors(frozenDescriptors), {
2502
+ enabled: options.enabled ?? !0,
2503
+ ship: options.ship,
2504
+ echo: options.echo ?? ECHO_MS,
2505
+ storage: options.storage ?? null,
2506
+ persist: options.persist,
2507
+ random: options.random,
2508
+ tunable: options.tunable,
2509
+ history: options.history,
2510
+ presets: options.presets,
2511
+ groupPresets: options.groupPresets,
2512
+ onPreset: options.onPreset,
2513
+ actions: options.actions,
2514
+ theme: options.theme,
2515
+ density: options.density,
2516
+ renderer: options.renderer,
2517
+ keys: options.keys,
2518
+ marks: options.marks,
2519
+ groupLabels: options.groupLabels,
2520
+ sectionLabels: options.sectionLabels,
2521
+ presetLabels: options.presetLabels,
2522
+ defaultLabel: options.defaultLabel,
2523
+ groupPresetDisplay: options.groupPresetDisplay,
2524
+ groupDescriptions: options.groupDescriptions,
2525
+ sectionDescriptions: options.sectionDescriptions,
2526
+ groupFolded: options.groupFolded,
2527
+ sectionFolded: options.sectionFolded,
2528
+ onChange(patch, _values, meta) {
2529
+ if (meta.source === "remote")
2530
+ return;
2531
+ if (meta.source === "reset" && !meta.base)
2532
+ return;
2533
+ if ((meta.source === "preset" || meta.base && meta.group !== void 0 && controls.activeGroupPreset(meta.group) >= 0) && meta.group !== void 0 && options.onPreset)
2534
+ return;
2535
+ let changes = [];
2536
+ for (let [key, val] of Object.entries(patch)) {
2537
+ if (val === void 0)
2538
+ continue;
2539
+ changes.push({ key, value: nullDefaults && same(val, controls.locked[key]) ? null : val });
2540
+ }
2541
+ send(changes, meta);
2542
+ },
2543
+ onReset,
2544
+ onGesture: options.onGesture,
2545
+ ...{ [RESET_KEYS]: resetKeys },
2546
+ onAction: (key) => options.onAction?.(key)
2547
+ });
2548
+ if (options.values)
2549
+ controls.sync(options.values, { replace: !0 });
2550
+ let reshape = controls[RESHAPE];
2551
+ return extend(controls, {
2552
+ get descriptors() {
2553
+ return frozenDescriptors;
2554
+ },
2555
+ update(next, values = {}, opts) {
2556
+ if (next === given || next === frozenDescriptors)
2557
+ return controls.sync(values, { replace: !0, land: opts?.land, mixed: opts?.mixed });
2558
+ given = next, frozenDescriptors = Object.freeze([...next]), reshape(fromDescriptors(frozenDescriptors), values, opts);
2559
+ },
2560
+ sync: (values, syncOptions) => controls.sync(values, syncOptions),
2561
+ swift: (group) => formatSwift(frozenDescriptors, controls.modified(), group)
2562
+ });
2563
+ }
2564
+ function fromSettings(groups, home = {}) {
2565
+ let descriptors = [], values = {};
2566
+ for (let g of groups)
2567
+ for (let item of g.items) {
2568
+ let meta = { key: item.id, label: item.label, ...g.title ? { group: g.title } : {} };
2569
+ if (item.kind === "action")
2570
+ descriptors.push({ ...meta, kind: "action" });
2571
+ else if (item.kind === "toggle") {
2572
+ values[item.id] = Boolean(item.value);
2573
+ let def = home[item.id];
2574
+ descriptors.push({
2575
+ ...meta,
2576
+ kind: "toggle",
2577
+ default: typeof def === "boolean" ? def : values[item.id]
2578
+ });
2579
+ } else if (item.choices.length) {
2580
+ let ids = item.choices.map((c) => c.id);
2581
+ values[item.id] = item.value != null && ids.includes(item.value) ? item.value : ids[0];
2582
+ let def = home[item.id];
2583
+ descriptors.push({
2584
+ ...meta,
2585
+ kind: "choice",
2586
+ default: typeof def === "string" && ids.includes(def) ? def : values[item.id],
2587
+ choices: item.choices
2588
+ });
2589
+ }
2590
+ }
2591
+ return { descriptors, values };
2592
+ }
2593
+ export {
2594
+ AROUND,
2595
+ CURVE_SHAPES,
2596
+ EASINGS,
2597
+ SPRING,
2598
+ action,
2599
+ actionShortcut,
2600
+ angle,
2601
+ bezierCss,
2602
+ bezierPoint,
2603
+ choice,
2604
+ color,
2605
+ colorHex,
2606
+ comboFromEvent,
2607
+ comboLabel,
2608
+ comboParts,
2609
+ createRemoteControls,
2610
+ curve,
2611
+ defineControls,
2612
+ descriptorToField,
2613
+ easing,
2614
+ flatSpring,
2615
+ formatCode,
2616
+ formatSwift,
2617
+ fromDescriptors,
2618
+ fromSettings,
2619
+ gradient,
2620
+ gradientCss,
2621
+ graph,
2622
+ hasValue,
2623
+ isReadout,
2624
+ keybind,
2625
+ knob,
2626
+ letterOf,
2627
+ log,
2628
+ matchesCombo,
2629
+ memoryStorage,
2630
+ meter,
2631
+ normalizeCombo,
2632
+ parseColor,
2633
+ point,
2634
+ range,
2635
+ sampleCurve,
2636
+ sampleGradient,
2637
+ seed,
2638
+ sparkline,
2639
+ spring,
2640
+ springCss,
2641
+ springMotion,
2642
+ table,
2643
+ toggle,
2644
+ toolbar,
2645
+ toolbarShortcut,
2646
+ urlFlag,
2647
+ vec2,
2648
+ vec3,
2649
+ watch,
2650
+ wrapAngle
2651
+ };