@kitlangton/knobs 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +303 -41
  2. package/dist/dev/{chunk-qtt698hp.js → chunk-7xgkerd2.js} +34 -11
  3. package/dist/dev/{chunk-ktsn42p7.js → chunk-jx1ktap3.js} +13762 -11794
  4. package/dist/dev/{chunk-kdewv0mt.js → chunk-pmkvb9ep.js} +1692 -207
  5. package/dist/dev/{chunk-e13znr7c.js → chunk-v8ssbcbk.js} +18 -7
  6. package/dist/dev/index.js +557 -130
  7. package/dist/index.js +589 -141
  8. package/dist/panel.js +21615 -17048
  9. package/dist/types/core/keys.d.ts +2 -0
  10. package/dist/types/core/random.d.ts +61 -1
  11. package/dist/types/core/remote.d.ts +5 -1
  12. package/dist/types/core/schema.d.ts +35 -6
  13. package/dist/types/core/store.d.ts +97 -8
  14. package/dist/types/core/unlock.d.ts +77 -0
  15. package/dist/types/core/variants.d.ts +10 -2
  16. package/dist/types/index.d.ts +4 -2
  17. package/dist/types/panel/attach.d.ts +2 -0
  18. package/dist/types/panel/crumbs.d.ts +19 -0
  19. package/dist/types/panel/editors.d.ts +20 -0
  20. package/dist/types/panel/feel.d.ts +103 -0
  21. package/dist/types/panel/flash.d.ts +13 -0
  22. package/dist/types/panel/give.d.ts +34 -1
  23. package/dist/types/panel/gpu/color.d.ts +4 -4
  24. package/dist/types/panel/gpu/renderer.d.ts +110 -6
  25. package/dist/types/panel/gpu/shader.wgsl.d.ts +1 -1
  26. package/dist/types/panel/haptics.d.ts +6 -2
  27. package/dist/types/panel/has.d.ts +0 -1
  28. package/dist/types/panel/index.d.ts +3 -1
  29. package/dist/types/panel/inspector.d.ts +33 -3
  30. package/dist/types/panel/lens.d.ts +113 -0
  31. package/dist/types/panel/motion.d.ts +2 -0
  32. package/dist/types/panel/optkeys.d.ts +7 -0
  33. package/dist/types/panel/page-hints.d.ts +2 -1
  34. package/dist/types/panel/prefs.d.ts +2 -2
  35. package/dist/types/panel/presence.d.ts +20 -0
  36. package/dist/types/panel/presets.d.ts +17 -5
  37. package/dist/types/panel/ramp-morph.d.ts +117 -0
  38. package/dist/types/panel/ramps.d.ts +1 -1
  39. package/dist/types/panel/reel.d.ts +43 -0
  40. package/dist/types/panel/scope.d.ts +48 -0
  41. package/dist/types/panel/slider.d.ts +22 -5
  42. package/dist/types/panel/sound.d.ts +137 -0
  43. package/dist/types/panel/styles.d.ts +1 -1
  44. package/dist/types/panel/tab-transition.d.ts +15 -0
  45. package/dist/types/panel/tabs.d.ts +1 -1
  46. package/dist/types/panel/toggle.d.ts +27 -0
  47. package/dist/types/panel/toolbar-grid.d.ts +6 -0
  48. package/dist/types/ui/activity-css.d.ts +1 -0
  49. package/dist/types/ui/activity-model.d.ts +122 -0
  50. package/dist/types/ui/activity.d.ts +67 -0
  51. package/dist/types/ui/follow.d.ts +17 -0
  52. package/dist/types/ui/index.d.ts +5 -1
  53. package/dist/types/ui/look.d.ts +9 -0
  54. package/dist/types/ui/picker-css.d.ts +1 -0
  55. package/dist/types/ui/picker-model.d.ts +53 -0
  56. package/dist/types/ui/picker.d.ts +67 -0
  57. package/dist/types/ui/react.d.ts +27 -0
  58. package/dist/types/ui/scrollbar.d.ts +12 -0
  59. package/dist/types/ui/solid.d.ts +23 -0
  60. package/dist/types/ui/split-model.d.ts +55 -0
  61. package/dist/types/ui/split.d.ts +22 -3
  62. package/dist/ui-react.js +3462 -690
  63. package/dist/ui-solid.js +3440 -689
  64. package/dist/ui.js +2963 -264
  65. package/package.json +1 -1
package/dist/dev/index.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  matchesCombo2,
16
16
  comboParts2,
17
17
  comboLabel2,
18
+ isTypingTarget,
18
19
  sampleGradient2,
19
20
  gradientCss2,
20
21
  sampleCurve2,
@@ -46,6 +47,7 @@ import {
46
47
  gradient2,
47
48
  curve2,
48
49
  toolbarShortcut2,
50
+ actionShortcut2,
49
51
  toolbar2,
50
52
  watch2,
51
53
  sparkline2,
@@ -71,8 +73,141 @@ import {
71
73
  browserStorage,
72
74
  urlFlag2,
73
75
  storageKey
74
- } from "./chunk-qtt698hp.js";
75
- __chunks(import.meta.url,["yfpdrfyw","ktsn42p7","e13znr7c","qtt698hp","kdewv0mt"],[["./index.js",3],["./chunk-ktsn42p7.js",3,4],["./chunk-e13znr7c.js",3,4],["./chunk-qtt698hp.js"],["./chunk-kdewv0mt.js",3]],0);
76
+ } from "./chunk-7xgkerd2.js";
77
+ __chunks(import.meta.url,["8qt6yygv","jx1ktap3","v8ssbcbk","7xgkerd2","pmkvb9ep"],[["./index.js",3],["./chunk-jx1ktap3.js",3,4],["./chunk-v8ssbcbk.js",3,4],["./chunk-7xgkerd2.js"],["./chunk-pmkvb9ep.js",3]],0);
78
+ // src/core/random.ts
79
+ var lattice = (min, max, step, r) => min + Math.floor(r * (Math.floor((max - min) / step + 0.000000001) + 1)) * step, between = (min, max, r) => min + (max - min) * r;
80
+ function pick(field, random) {
81
+ let r = () => Math.max(0, Math.min(1 - Number.EPSILON, random()));
82
+ switch (field.kind) {
83
+ case "knob":
84
+ return lattice(field.min, field.max, field.step, r());
85
+ case "choice":
86
+ return field.options[Math.floor(r() * field.options.length)];
87
+ case "toggle":
88
+ return r() >= 0.5;
89
+ case "spring":
90
+ return { duration: between(0.2, 0.9, r()), bounce: between(0, 0.45, r()) };
91
+ case "color": {
92
+ let l = between(0.5, 0.85, r()), h = between(0, 360, r());
93
+ return { l, c: between(0.25, 0.85, r()) * maxChroma(l, h), h };
94
+ }
95
+ case "point":
96
+ return {
97
+ x: lattice(field.min.x, field.max.x, field.step.x, r()),
98
+ y: lattice(field.min.y, field.max.y, field.step.y, r())
99
+ };
100
+ case "easing": {
101
+ let curves = Object.values(EASINGS2);
102
+ return curves[Math.floor(r() * curves.length)];
103
+ }
104
+ case "vec": {
105
+ let axis = () => lattice(field.min, field.max, field.step, r());
106
+ return field.axes === 3 ? { x: axis(), y: axis(), z: axis() } : { x: axis(), y: axis() };
107
+ }
108
+ case "range":
109
+ return [
110
+ lattice(field.min, field.max, field.step, r()),
111
+ lattice(field.min, field.max, field.step, r())
112
+ ];
113
+ case "angle": {
114
+ let top = field.wrap ? field.max - field.step : field.max;
115
+ return lattice(field.min, Math.max(field.min, top), field.step, r());
116
+ }
117
+ case "seed":
118
+ return lattice(field.min, field.max, 1, r());
119
+ case "keybind":
120
+ return field.value;
121
+ case "gradient":
122
+ return randomStops(field.value.length, r);
123
+ case "curve":
124
+ return randomPoints(field.range, r);
125
+ }
126
+ }
127
+ var AROUND = {
128
+ spread: 0.35,
129
+ switch: 0.3,
130
+ hue: 20,
131
+ lightness: 0.06,
132
+ chroma: 0.15
133
+ };
134
+ function near(v, min, max, step, r, reach = max - min) {
135
+ let span = max - min;
136
+ if (!(span > 0))
137
+ return v;
138
+ 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));
139
+ return Number((min + Math.max(0, Math.min(top, n)) * step).toPrecision(14));
140
+ }
141
+ var unit = (r) => r * 2 - 1;
142
+ function around(field, base, random) {
143
+ let r = () => Math.max(0, Math.min(1 - Number.EPSILON, random()));
144
+ switch (field.kind) {
145
+ case "knob":
146
+ return near(base, field.min, field.max, field.step, r());
147
+ case "choice": {
148
+ if (field.options.length < 2 || r() >= AROUND.switch)
149
+ return base;
150
+ let rest = field.options.filter((o) => o !== base);
151
+ return rest[Math.floor(r() * rest.length)];
152
+ }
153
+ case "toggle":
154
+ return r() < AROUND.switch ? !base : base;
155
+ case "spring": {
156
+ let s = base, d = SPRING2.duration, b = SPRING2.bounce;
157
+ return {
158
+ duration: near(s.duration, d.min, d.max, d.step, r(), 0.7),
159
+ bounce: near(s.bounce, b.min, b.max, b.step, r(), 0.45)
160
+ };
161
+ }
162
+ case "color": {
163
+ let c = parseColor2(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));
164
+ return { l, c: k * maxChroma(l, h), h };
165
+ }
166
+ case "point": {
167
+ let p = base;
168
+ return {
169
+ x: near(p.x, field.min.x, field.max.x, field.step.x, r()),
170
+ y: near(p.y, field.min.y, field.max.y, field.step.y, r())
171
+ };
172
+ }
173
+ case "easing":
174
+ return base.map((v, i) => {
175
+ let [lo, hi] = i % 2 === 0 ? [0, 1] : EASING_Y;
176
+ return near(v, lo, hi, 0.001, r());
177
+ });
178
+ case "vec": {
179
+ let v = base, axis = (n) => near(n, field.min, field.max, field.step, r());
180
+ 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) };
181
+ }
182
+ case "range": {
183
+ let [a, b] = base;
184
+ return [
185
+ near(a, field.min, field.max, field.step, r()),
186
+ near(b, field.min, field.max, field.step, r())
187
+ ];
188
+ }
189
+ case "angle": {
190
+ let v = base;
191
+ if (!field.wrap)
192
+ return near(v, field.min, field.max, field.step, r());
193
+ let span = field.max - field.min;
194
+ return v + unit(r()) * span * (AROUND.spread / 2);
195
+ }
196
+ case "gradient":
197
+ return mutateStops(base, r, [AROUND.hue / 2, AROUND.hue], [AROUND.lightness / 2, AROUND.lightness]);
198
+ case "curve":
199
+ return mutatePoints(base, field.range, r, [
200
+ AROUND.spread / 2,
201
+ AROUND.spread
202
+ ]);
203
+ case "seed":
204
+ case "keybind":
205
+ return pick(field, random);
206
+ }
207
+ }
208
+ function randomValues(schema, random = Math.random) {
209
+ return normalize(schema, Object.fromEntries(Object.entries(schema).flatMap(([key, field]) => hasValue2(field) ? [[key, pick(field, random)]] : [])));
210
+ }
76
211
  // src/core/code.ts
77
212
  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);
78
213
  function formatCode(values, format = "ts") {
@@ -96,12 +231,12 @@ var MUTATE = {
96
231
  chroma: 0.02,
97
232
  lightness: [0.015, 0.045],
98
233
  bezier: [0.03, 0.06]
99
- }, between = (min, max, r) => min + (max - min) * r, signed = (random) => random() < 0.5 ? -1 : 1;
234
+ }, between2 = (min, max, r) => min + (max - min) * r, signed = (random) => random() < 0.5 ? -1 : 1;
100
235
  function nudge(v, min, max, step, random, reach = max - min) {
101
236
  let span = max - min;
102
237
  if (!(span > 0))
103
238
  return v;
104
- let sign = signed(random), by = reach * between(MUTATE.min, MUTATE.max, random()), next = v + sign * by;
239
+ let sign = signed(random), by = reach * between2(MUTATE.min, MUTATE.max, random()), next = v + sign * by;
105
240
  if (next > max || next < min)
106
241
  next = v - sign * by;
107
242
  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)));
@@ -130,12 +265,12 @@ function shift(field, value, random) {
130
265
  };
131
266
  }
132
267
  case "color": {
133
- let c = parseColor2(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 + signed(random) * between(MUTATE.hue[0], MUTATE.hue[1], random()) + 360) % 360, l = Math.min(1, Math.max(0, c.l + signed(random) * between(MUTATE.lightness[0], MUTATE.lightness[1], random())));
268
+ let c = parseColor2(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 + signed(random) * between2(MUTATE.hue[0], MUTATE.hue[1], random()) + 360) % 360, l = Math.min(1, Math.max(0, c.l + signed(random) * between2(MUTATE.lightness[0], MUTATE.lightness[1], random())));
134
269
  return { l, c: nudge(rel0, 0, 1, 0.01, random) * maxChroma(l, h), h };
135
270
  }
136
271
  case "easing":
137
272
  return value.map((v, i) => {
138
- let [lo, hi] = i % 2 === 0 ? [0, 1] : EASING_Y, next = v + signed(random) * between(MUTATE.bezier[0], MUTATE.bezier[1], random());
273
+ let [lo, hi] = i % 2 === 0 ? [0, 1] : EASING_Y, next = v + signed(random) * between2(MUTATE.bezier[0], MUTATE.bezier[1], random());
139
274
  return Math.min(hi, Math.max(lo, next));
140
275
  });
141
276
  case "choice": {
@@ -161,7 +296,7 @@ function shift(field, value, random) {
161
296
  if (!field.wrap)
162
297
  return nudge(v, field.min, field.max, field.step, random);
163
298
  let span = field.max - field.min;
164
- return v + signed(random) * span * between(MUTATE.min, MUTATE.max, random());
299
+ return v + signed(random) * span * between2(MUTATE.min, MUTATE.max, random());
165
300
  }
166
301
  case "seed":
167
302
  return nudge(value, field.min, field.max, 1, random);
@@ -203,57 +338,141 @@ function mutateValues(schema, current, keys = valueKeys(schema), random = Math.r
203
338
  return out;
204
339
  }
205
340
 
206
- // src/core/random.ts
207
- 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;
208
- function pick(field, random) {
209
- let r = () => Math.max(0, Math.min(1 - Number.EPSILON, random()));
210
- switch (field.kind) {
211
- case "knob":
212
- return lattice(field.min, field.max, field.step, r());
213
- case "choice":
214
- return field.options[Math.floor(r() * field.options.length)];
215
- case "toggle":
216
- return r() >= 0.5;
217
- case "spring":
218
- return { duration: between2(0.2, 0.9, r()), bounce: between2(0, 0.45, r()) };
219
- case "color": {
220
- let l = between2(0.5, 0.85, r()), h = between2(0, 360, r());
221
- return { l, c: between2(0.25, 0.85, r()) * maxChroma(l, h), h };
222
- }
223
- case "point":
224
- return {
225
- x: lattice(field.min.x, field.max.x, field.step.x, r()),
226
- y: lattice(field.min.y, field.max.y, field.step.y, r())
227
- };
228
- case "easing": {
229
- let curves = Object.values(EASINGS2);
230
- return curves[Math.floor(r() * curves.length)];
231
- }
232
- case "vec": {
233
- let axis = () => lattice(field.min, field.max, field.step, r());
234
- return field.axes === 3 ? { x: axis(), y: axis(), z: axis() } : { x: axis(), y: axis() };
235
- }
236
- case "range":
237
- return [
238
- lattice(field.min, field.max, field.step, r()),
239
- lattice(field.min, field.max, field.step, r())
240
- ];
241
- case "angle": {
242
- let top = field.wrap ? field.max - field.step : field.max;
243
- return lattice(field.min, Math.max(field.min, top), field.step, r());
244
- }
245
- case "seed":
246
- return lattice(field.min, field.max, 1, r());
247
- case "keybind":
248
- return field.value;
249
- case "gradient":
250
- return randomStops(field.value.length, r);
251
- case "curve":
252
- return randomPoints(field.range, r);
253
- }
341
+ // src/core/unlock.ts
342
+ var KONAMI = Object.freeze([
343
+ "ArrowUp",
344
+ "ArrowUp",
345
+ "ArrowDown",
346
+ "ArrowDown",
347
+ "ArrowLeft",
348
+ "ArrowRight",
349
+ "ArrowLeft",
350
+ "ArrowRight",
351
+ "b",
352
+ "a"
353
+ ]), GATE = Symbol.for("knobs.unlock.v1"), gate = () => {
354
+ let holder = globalThis;
355
+ return holder[GATE] ??= { unlocked: !1, listeners: /* @__PURE__ */ new Set };
356
+ }, isUnlocked = () => globalThis[GATE]?.unlocked ?? !1;
357
+ function onUnlock(listener) {
358
+ let listeners = gate().listeners;
359
+ return listeners.add(listener), () => void listeners.delete(listener);
254
360
  }
255
- function randomValues(schema, random = Math.random) {
256
- return normalize(schema, Object.fromEntries(Object.entries(schema).flatMap(([key, field]) => hasValue2(field) ? [[key, pick(field, random)]] : [])));
361
+ var UNLOCK_KEY = "knobs:unlocked";
362
+ function sequenceKeys(sequence) {
363
+ return typeof sequence === "string" ? [...sequence] : [...sequence];
364
+ }
365
+ var fold = (key) => key.length === 1 ? key.toLowerCase() : key;
366
+ function sequenceMatcher(sequence, gap = 1500) {
367
+ let want = sequenceKeys(sequence).map(fold), typed = [], last = -1 / 0;
368
+ return (key, at) => {
369
+ if (!want.length)
370
+ return !1;
371
+ if (at - last > gap)
372
+ typed = [];
373
+ if (last = at, typed.push(fold(key)), typed.length > want.length)
374
+ typed.shift();
375
+ if (typed.length === want.length && typed.every((k, i) => k === want[i]))
376
+ return typed = [], !0;
377
+ return !1;
378
+ };
379
+ }
380
+ function queryUnlock(search, name) {
381
+ let params = new URLSearchParams(search);
382
+ if (!params.has(name))
383
+ return;
384
+ return !["off", "0", "false", "lock", "no"].includes((params.get(name) ?? "").toLowerCase());
385
+ }
386
+ function resolveUnlock(input) {
387
+ let unlocked = input.query ?? (input.persist && input.stored);
388
+ return { unlocked, remember: input.persist && input.query !== void 0 ? unlocked : void 0 };
389
+ }
390
+ function inCorner(x, y, width, height, corner, size = 48) {
391
+ let left = corner.endsWith("left") ? x <= size : x >= width - size, top = corner.startsWith("top") ? y <= size : y >= height - size;
392
+ return left && top;
393
+ }
394
+ function tapCounter(count = 3, within = 500) {
395
+ let taps = 0, last = -1 / 0;
396
+ return (at) => {
397
+ if (taps = at - last <= within ? taps + 1 : 1, last = at, taps < count)
398
+ return !1;
399
+ return taps = 0, !0;
400
+ };
401
+ }
402
+ var readStored = () => {
403
+ try {
404
+ return globalThis.localStorage?.getItem(UNLOCK_KEY) === "1";
405
+ } catch {
406
+ return !1;
407
+ }
408
+ }, writeStored = (on) => {
409
+ try {
410
+ if (on)
411
+ globalThis.localStorage?.setItem(UNLOCK_KEY, "1");
412
+ else
413
+ globalThis.localStorage?.removeItem(UNLOCK_KEY);
414
+ } catch {}
415
+ };
416
+ function unlock(options) {
417
+ let g = gate();
418
+ g.panel = options.panel;
419
+ let persist = options.persist !== !1, query = options.query === void 0 ? "knobs" : options.query, sequence = options.sequence === void 0 ? "knobs" : options.sequence, corner = options.corner === void 0 ? "top-right" : options.corner, win = typeof window > "u" ? void 0 : window, announce = (text) => {
420
+ (g.module ? Promise.resolve(g.module) : g.panel?.())?.then((m) => {
421
+ if (g.module = m, g.unlocked === (text === "knobs unlocked"))
422
+ m.status?.(text);
423
+ }).catch(() => {});
424
+ }, set = (on, say) => {
425
+ if (persist)
426
+ writeStored(on);
427
+ if (g.unlocked === on)
428
+ return;
429
+ g.unlocked = on;
430
+ for (let listener of [...g.listeners])
431
+ listener();
432
+ if (say)
433
+ announce(on ? "knobs unlocked" : "knobs locked");
434
+ }, start = resolveUnlock({
435
+ query: query && win ? queryUnlock(win.location.search, query) : void 0,
436
+ stored: persist && readStored(),
437
+ persist
438
+ });
439
+ if (start.remember !== void 0)
440
+ writeStored(start.remember);
441
+ set(start.unlocked || g.unlocked, !1);
442
+ let offs = [];
443
+ if (win && sequence !== !1 && sequenceKeys(sequence).length) {
444
+ let typed = sequenceMatcher(sequence), onKey = (event) => {
445
+ if (event.ctrlKey || event.metaKey || event.altKey || event.isComposing || event.repeat)
446
+ return;
447
+ if (event.key === "Shift" || event.key === "CapsLock" || isTypingTarget(event.target))
448
+ return;
449
+ if (typed(event.key, event.timeStamp || performance.now()))
450
+ set(!g.unlocked, !0);
451
+ };
452
+ win.addEventListener("keydown", onKey, !0), offs.push(() => win.removeEventListener("keydown", onKey, !0));
453
+ }
454
+ if (win && corner) {
455
+ let tapped = tapCounter(), onTap = (event) => {
456
+ if (!event.isPrimary)
457
+ return;
458
+ if (!inCorner(event.clientX, event.clientY, win.innerWidth, win.innerHeight, corner))
459
+ return void (tapped = tapCounter());
460
+ if (tapped(event.timeStamp || performance.now()))
461
+ set(!g.unlocked, !0);
462
+ };
463
+ win.addEventListener("pointerdown", onTap, { capture: !0, passive: !0 }), offs.push(() => win.removeEventListener("pointerdown", onTap, !0));
464
+ }
465
+ return {
466
+ get unlocked() {
467
+ return g.unlocked;
468
+ },
469
+ unlock: () => set(!0, !0),
470
+ lock: () => set(!1, !0),
471
+ dispose() {
472
+ for (let off of offs.splice(0))
473
+ off();
474
+ }
475
+ };
257
476
  }
258
477
 
259
478
  // src/core/variants.ts
@@ -312,12 +531,28 @@ function createVariants(schema, id, key, storage, get, set, notify, enabled) {
312
531
  });
313
532
  return persist([...list, variant]), variant;
314
533
  },
315
- load(id) {
534
+ load(id, options) {
316
535
  read();
317
536
  let variant = list.find((v) => v.id === id);
318
537
  if (!variant)
319
538
  throw Error(`Unknown variant: ${id}`);
320
- set(variant.values);
539
+ set(variant.values, id, Boolean(options?.coalesce));
540
+ },
541
+ rename(id, name) {
542
+ if (read(), name.length > 100)
543
+ throw Error("Variant names must be at most 100 characters.");
544
+ let at = list.findIndex((v) => v.id === id);
545
+ if (at < 0)
546
+ throw Error(`Unknown variant: ${id}`);
547
+ let was = list[at], trimmed = name.trim();
548
+ if ((was.name ?? "") === trimmed)
549
+ return;
550
+ let next = [...list];
551
+ next[at] = Object.freeze({
552
+ id: was.id,
553
+ ...trimmed ? { name: trimmed } : {},
554
+ values: was.values
555
+ }), persist(next);
321
556
  },
322
557
  remove(id) {
323
558
  read(), persist(list.filter((v) => v.id !== id));
@@ -354,10 +589,10 @@ function sameField(a, b) {
354
589
  function defineControls(id, input, options = {}) {
355
590
  if (!id.trim())
356
591
  throw Error("Controls need a stable, nonempty id.");
357
- 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 = () => 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 = () => {
592
+ 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 = () => typeof options.enabled === "function" ? options.enabled() : options.enabled ?? (Boolean(options.ship) || isUnlocked() || 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, unsubscribeUnlock, listeners = /* @__PURE__ */ new Set, refreshes = 0, notify = () => {
358
593
  for (let cb of [...listeners])
359
594
  cb();
360
- }, mixed = /* @__PURE__ */ new Set, current = locked, read = !1, error = null, locks = /* @__PURE__ */ new Set, 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, past = [], future = [], HISTORY = 100, stateKey = storageKey(id, "state"), buildPresets = () => Object.freeze(Object.fromEntries([
595
+ }, 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([
361
596
  ["default", locked],
362
597
  ...Object.entries(options.presets ?? {}).map(([id, patch]) => {
363
598
  if (id === "default" || id === "custom")
@@ -397,6 +632,9 @@ function defineControls(id, input, options = {}) {
397
632
  if (data?.version === 1) {
398
633
  if (current = normalize(schema, data.values), Array.isArray(data.locks))
399
634
  locks = new Set(data.locks.filter((k) => typeof k === "string" && Object.hasOwn(schema, k)));
635
+ if (data.bindings && typeof data.bindings === "object")
636
+ bindings = new Map(Object.entries(data.bindings).flatMap(([id, combo]) => typeof combo === "string" && bindable(id) ? [[id, normalizeCombo2(combo)]] : []));
637
+ base = readBase(data.base);
400
638
  }
401
639
  }
402
640
  } catch (cause) {
@@ -415,7 +653,9 @@ function defineControls(id, input, options = {}) {
415
653
  valueStorage()?.setItem(stateKey, JSON.stringify({
416
654
  version: 1,
417
655
  values: current,
418
- ...locks.size ? { locks: [...locks] } : {}
656
+ ...locks.size ? { locks: [...locks] } : {},
657
+ ...bindings.size ? { bindings: Object.fromEntries(bindings) } : {},
658
+ ...base !== NO_BASE ? { base } : {}
419
659
  })), error = null;
420
660
  } catch (cause) {
421
661
  error = String(cause);
@@ -424,6 +664,74 @@ function defineControls(id, input, options = {}) {
424
664
  function commit(next) {
425
665
  current = next, persist(), notify();
426
666
  }
667
+ 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);
668
+ function validBase(b) {
669
+ 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);
670
+ return sameBase(next, b) ? b : next;
671
+ }
672
+ function readBase(raw) {
673
+ if (!raw || typeof raw !== "object")
674
+ return NO_BASE;
675
+ 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]))) : {};
676
+ return validBase(makeBase(ref, byGroup));
677
+ }
678
+ function activeBase() {
679
+ load();
680
+ let ref = base.preset;
681
+ if (ref?.kind === "variant" && !variants.list().some((v) => v.id === ref.id))
682
+ base = makeBase(null, base.groups);
683
+ return base;
684
+ }
685
+ let hasBase = () => activeBase() !== NO_BASE, baseCache;
686
+ function baseValues() {
687
+ if (!enabled())
688
+ return locked;
689
+ let b = activeBase();
690
+ if (b === NO_BASE)
691
+ return locked;
692
+ let list = b.preset?.kind === "variant" ? variants.list() : null, c = baseCache;
693
+ if (c && c.base === b && c.schema === schema && c.presets === presets && c.groupPresets === groupPresets && c.variants === list)
694
+ return c.values;
695
+ let out = locked, ref = b.preset;
696
+ if (ref?.kind === "preset" && Object.hasOwn(presets, ref.id))
697
+ out = presets[ref.id];
698
+ else if (ref?.kind === "variant") {
699
+ let found = list?.find((v) => v.id === ref.id);
700
+ if (found)
701
+ out = normalize(schema, found.values);
702
+ }
703
+ let groups = Object.entries(b.groups);
704
+ if (groups.length) {
705
+ out = { ...out };
706
+ for (let [g, i] of groups)
707
+ Object.assign(out, groupPresets[g]?.[i]?.values);
708
+ }
709
+ let values = Object.freeze(out);
710
+ return baseCache = { base: b, schema, presets, groupPresets, variants: list, values }, values;
711
+ }
712
+ let covered = (key) => {
713
+ let b = activeBase();
714
+ if (b.preset)
715
+ return !0;
716
+ let group = schema[key]?.group;
717
+ return group !== void 0 && Object.hasOwn(b.groups, group);
718
+ };
719
+ function declaredShortcut(id) {
720
+ let slash = id.indexOf("/"), key = slash < 0 ? id : id.slice(0, slash), field = Object.hasOwn(schema, key) ? schema[key] : void 0;
721
+ if (field?.kind === "action" && slash < 0)
722
+ return { found: !0, ...field.shortcut ? { combo: field.shortcut } : {} };
723
+ if (field?.kind === "toolbar" && slash >= 0) {
724
+ let item = field.items.find((i) => (i.id ?? i.label) === id.slice(slash + 1));
725
+ if (!item)
726
+ return { found: !1 };
727
+ let key = toolbarShortcut2(item)?.key;
728
+ return { found: !0, ...key ? { combo: normalizeCombo2(key) } : {} };
729
+ }
730
+ return { found: !1 };
731
+ }
732
+ function bindable(id) {
733
+ return declaredShortcut(id).found;
734
+ }
427
735
  let fieldKeys = () => valueKeys(schema), valueField = (k) => {
428
736
  let field = Object.hasOwn(schema, k) ? schema[k] : void 0;
429
737
  return field && hasValue2(field) ? field : void 0;
@@ -486,10 +794,13 @@ function defineControls(id, input, options = {}) {
486
794
  pending.delete(k);
487
795
  };
488
796
  function set(patch, setOpts) {
797
+ write(patch, setOpts);
798
+ }
799
+ function write(patch, setOpts, nextBase) {
489
800
  if (!enabled())
490
801
  return;
491
802
  release();
492
- let previous = load(), forced = mixed.size ? Object.keys(patch).filter((k) => mixed.has(k) && valueField(k)) : [];
803
+ 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)) : [];
493
804
  for (let k of forced)
494
805
  mixed.delete(k);
495
806
  let applied = applyPatch(previous, previous, patch) ?? {
@@ -499,9 +810,10 @@ function defineControls(id, input, options = {}) {
499
810
  for (let k of forced)
500
811
  if (!(k in diff))
501
812
  diff[k] = next[k];
502
- if (Object.keys(diff).length === 0)
813
+ let changed = Object.keys(diff).length > 0;
814
+ if (!changed && !moves)
503
815
  return;
504
- if (next === previous) {
816
+ if (next === previous && !moves) {
505
817
  notify(), options.onChange?.(diff, next, {
506
818
  source: setOpts?.source ?? "local",
507
819
  ...setOpts?.key ? { key: setOpts.key } : {}
@@ -509,16 +821,27 @@ function defineControls(id, input, options = {}) {
509
821
  return;
510
822
  }
511
823
  if (!setOpts?.coalesce || past.length === 0) {
512
- if (past.push(previous), past.length > HISTORY)
824
+ if (past.push({ values: previous, base: was }), past.length > HISTORY)
513
825
  past.shift();
514
826
  }
515
- future.length = 0, markChanged(diff), commit(next), options.onChange?.(diff, next, {
827
+ if (future.length = 0, moves)
828
+ base = nextBase;
829
+ if (markChanged(diff), commit(next), !changed)
830
+ return;
831
+ options.onChange?.(diff, next, {
516
832
  source: setOpts?.source ?? "local",
517
833
  ...setOpts?.coalesce ? { coalesce: !0 } : {},
518
834
  ...setOpts?.key ? { key: setOpts.key } : {},
519
- ...setOpts?.group ? { group: setOpts.group } : {}
835
+ ...setOpts?.group ? { group: setOpts.group } : {},
836
+ ...setOpts?.base ? { base: !0 } : {}
520
837
  });
521
838
  }
839
+ function step(to, source) {
840
+ let before = load(), left = { values: before, base: activeBase() }, diff = changes(before, to.values);
841
+ if (base = validBase(to.base), markChanged(diff), commit(to.values), Object.keys(diff).length)
842
+ options.onChange?.(diff, to.values, { source });
843
+ return left;
844
+ }
522
845
  function place(values, replace, land) {
523
846
  let previous = load(), applied = applyPatch(replace ? locked : previous, previous, values);
524
847
  if (!applied)
@@ -626,7 +949,7 @@ function defineControls(id, input, options = {}) {
626
949
  schema = Object.freeze({ ...next });
627
950
  if (changed) {
628
951
  let previous = load();
629
- schema = Object.freeze({ ...next }), locked = normalize(schema, {}), presets = buildPresets(), groupPresets = buildGroupPresets();
952
+ schema = Object.freeze({ ...next }), locked = normalize(schema, {}), presets = buildPresets(), groupPresets = buildGroupPresets(), base = validBase(base);
630
953
  for (let k of [...locks])
631
954
  if (!valueField(k))
632
955
  locks.delete(k);
@@ -661,7 +984,60 @@ function defineControls(id, input, options = {}) {
661
984
  listen(cb) {
662
985
  return readoutListeners.add(cb), () => readoutListeners.delete(cb);
663
986
  }
664
- }, controls = {
987
+ }, 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));
988
+ function offBase(keys) {
989
+ if (!hasBase())
990
+ return !1;
991
+ let cur = load(), b = baseValues();
992
+ return keys.some((k) => !same(cur[k], b[k]));
993
+ }
994
+ function toBase(keys, meta) {
995
+ let b = baseValues(), patch = Object.fromEntries(keys.map((k) => [k, b[k]]));
996
+ write(patch, { source: "reset", base: !0, ...meta });
997
+ }
998
+ function toDefaults(keys, nextBase) {
999
+ let patch = {};
1000
+ for (let k of keys)
1001
+ patch[k] = locked[k];
1002
+ write(patch, { source: "reset" }, nextBase);
1003
+ let resetKeys = options[RESET_KEYS];
1004
+ if (resetKeys)
1005
+ resetKeys(Object.keys(patch));
1006
+ else
1007
+ for (let key of Object.keys(patch))
1008
+ options.onReset?.({ key });
1009
+ }
1010
+ function live(load, make, unmake) {
1011
+ let disposed = !1, loading = !1, handle, sync = () => {
1012
+ if (disposed)
1013
+ return;
1014
+ if (!enabled()) {
1015
+ if (handle !== void 0)
1016
+ unmake(handle);
1017
+ handle = void 0;
1018
+ return;
1019
+ }
1020
+ if (handle !== void 0 || loading)
1021
+ return;
1022
+ let pending = load();
1023
+ if (!pending)
1024
+ return;
1025
+ loading = !0, pending.then((module) => {
1026
+ if (loading = !1, !disposed && enabled() && handle === void 0)
1027
+ handle = make(module);
1028
+ }).catch((cause) => {
1029
+ loading = !1, error = String(cause), notify();
1030
+ });
1031
+ };
1032
+ sync();
1033
+ let off = onUnlock(sync);
1034
+ return () => {
1035
+ if (disposed = !0, off(), handle !== void 0)
1036
+ unmake(handle);
1037
+ handle = void 0;
1038
+ };
1039
+ }
1040
+ let controls = {
665
1041
  id,
666
1042
  get schema() {
667
1043
  return schema;
@@ -680,9 +1056,10 @@ function defineControls(id, input, options = {}) {
680
1056
  marks: options.marks !== !1,
681
1057
  chip: Object.freeze({
682
1058
  ...options.chip,
683
- reveal: options.chip?.reveal ?? (devBuild() ? "hover" : "shortcut"),
1059
+ reveal: options.chip?.reveal ?? (devBuild() || !options.ship ? "hover" : "shortcut"),
684
1060
  shortcut: options.chip?.shortcut ?? "Alt+Shift+D"
685
1061
  }),
1062
+ toggleKey: options.toggleKey ?? (options.ship ? !1 : "`"),
686
1063
  groupPresetDisplay: options.groupPresetDisplay ?? "auto",
687
1064
  groupLabel: (group) => options.groupLabels?.[group] ?? fieldLabels().group.get(group),
688
1065
  sectionLabel: (section) => options.sectionLabels?.[section] ?? fieldLabels().section.get(section),
@@ -763,8 +1140,7 @@ function defineControls(id, input, options = {}) {
763
1140
  let previous = past.pop();
764
1141
  if (!previous)
765
1142
  return;
766
- let before = load(), diff = changes(before, previous);
767
- future.push(before), markChanged(diff), commit(previous), options.onChange?.(diff, previous, { source: "undo" });
1143
+ future.push(step(previous, "undo"));
768
1144
  },
769
1145
  redo() {
770
1146
  if (release(), hostHistory)
@@ -772,37 +1148,30 @@ function defineControls(id, input, options = {}) {
772
1148
  let next = future.pop();
773
1149
  if (!next)
774
1150
  return;
775
- let before = load(), diff = changes(before, next);
776
- past.push(before), markChanged(diff), commit(next), options.onChange?.(diff, next, { source: "redo" });
1151
+ past.push(step(next, "redo"));
777
1152
  },
778
1153
  getServerSnapshot: () => locked,
779
1154
  subscribe(cb) {
780
1155
  if (listeners.add(cb), hostHistory?.subscribe && !unsubscribeHistory)
781
1156
  unsubscribeHistory = hostHistory.subscribe(notify);
782
- return () => {
783
- if (listeners.delete(cb), !listeners.size && unsubscribeHistory)
784
- unsubscribeHistory(), unsubscribeHistory = void 0;
1157
+ return unsubscribeUnlock ??= onUnlock(notify), () => {
1158
+ if (listeners.delete(cb), listeners.size)
1159
+ return;
1160
+ unsubscribeHistory?.(), unsubscribeHistory = void 0, unsubscribeUnlock?.(), unsubscribeUnlock = void 0;
785
1161
  };
786
1162
  },
787
1163
  reset(keys) {
788
1164
  if (!enabled())
789
1165
  return;
1166
+ let open = resettable(keys);
1167
+ if (offBase(open))
1168
+ return toBase(open, {});
790
1169
  let skipped = fieldKeys().some(blocked);
791
1170
  if (!keys && !skipped) {
792
- set(locked, { source: "reset" }), options.onReset?.({});
1171
+ write(locked, { source: "reset" }, NO_BASE), options.onReset?.({});
793
1172
  return;
794
1173
  }
795
- let patch = {};
796
- for (let k of keys ?? fieldKeys())
797
- if (valueField(k) && !blocked(k))
798
- patch[k] = locked[k];
799
- set(patch, { source: "reset" });
800
- let resetKeys = options[RESET_KEYS];
801
- if (resetKeys)
802
- resetKeys(Object.keys(patch));
803
- else
804
- for (let key of Object.keys(patch))
805
- options.onReset?.({ key });
1174
+ toDefaults(open, keys ? void 0 : NO_BASE);
806
1175
  },
807
1176
  resetKey(key) {
808
1177
  if (!enabled())
@@ -811,24 +1180,40 @@ function defineControls(id, input, options = {}) {
811
1180
  throw Error(`Unknown control key: ${key}`);
812
1181
  if (blocked(key))
813
1182
  return;
1183
+ if (offBase([key]))
1184
+ return toBase([key], { key });
814
1185
  set({ [key]: locked[key] }, { source: "reset", key }), options.onReset?.({ key });
815
1186
  },
816
1187
  resetGroup(group) {
817
1188
  if (!enabled())
818
1189
  return;
819
- let keys = Object.keys(schema).filter((k) => schema[k].group === group && hasValue2(schema[k])), open = keys.filter((k) => !blocked(k));
1190
+ let keys = Object.keys(schema).filter((k) => schema[k].group === group && hasValue2(schema[k])), open = keys.filter((k) => !blocked(k)), b = activeBase(), index = b.groups[group];
1191
+ if (offBase(open)) {
1192
+ toBase(open, { group });
1193
+ let preset = index === void 0 ? void 0 : groupPresets[group]?.[index];
1194
+ if (preset && index !== void 0)
1195
+ options.onPreset?.({ group, index, name: preset.name });
1196
+ return;
1197
+ }
1198
+ let { [group]: _, ...others } = b.groups, forget = index === void 0 ? void 0 : makeBase(b.preset, others);
820
1199
  if (open.length < keys.length)
821
- return controls.reset(open);
1200
+ return toDefaults(open, forget);
822
1201
  let patch = {};
823
1202
  for (let k of keys)
824
1203
  patch[k] = locked[k];
825
- set(patch, { source: "reset", group }), options.onReset?.({ group });
1204
+ write(patch, { source: "reset", group }, forget), options.onReset?.({ group });
826
1205
  },
827
1206
  isModified(key) {
828
- let cur = get();
1207
+ let cur = get(), b = baseValues();
829
1208
  if (key !== void 0)
830
- return !same(cur[key], locked[key]);
831
- return !equal(cur, locked);
1209
+ return !same(cur[key], b[key]);
1210
+ return !equal(cur, b);
1211
+ },
1212
+ canReset(keys) {
1213
+ if (!enabled())
1214
+ return !1;
1215
+ let cur = get(), b = baseValues();
1216
+ return resettable(keys).some((k) => !same(cur[k], b[k]) || !same(cur[k], locked[k])) || !keys && hasBase();
832
1217
  },
833
1218
  modified() {
834
1219
  let cur = get(), out = {};
@@ -843,8 +1228,17 @@ function defineControls(id, input, options = {}) {
843
1228
  let chosen = pool(keys);
844
1229
  if (chosen.length === 0)
845
1230
  return;
846
- let sub = Object.fromEntries(chosen.map((k) => [k, schema[k]]));
847
- set(randomValues(sub, draw), { source: "randomize" });
1231
+ if (!hasBase()) {
1232
+ let sub = Object.fromEntries(chosen.map((k) => [k, schema[k]]));
1233
+ set(randomValues(sub, draw), { source: "randomize" });
1234
+ return;
1235
+ }
1236
+ let b = baseValues(), patch = {};
1237
+ for (let k of chosen) {
1238
+ let field = valueField(k);
1239
+ patch[k] = covered(k) ? around(field, b[k], draw) : pick(field, draw);
1240
+ }
1241
+ set(patch, { source: "randomize" });
848
1242
  },
849
1243
  mutate(keys) {
850
1244
  if (!enabled())
@@ -880,6 +1274,33 @@ function defineControls(id, input, options = {}) {
880
1274
  return !1;
881
1275
  return load(), locks.has(key);
882
1276
  },
1277
+ shortcut(key, item) {
1278
+ let id = item === void 0 ? key : `${key}/${item}`, declared = declaredShortcut(id);
1279
+ if (!declared.found)
1280
+ return;
1281
+ if (enabled())
1282
+ load();
1283
+ return (enabled() ? bindings.get(id) : void 0) ?? declared.combo;
1284
+ },
1285
+ rebind(key, combo, item) {
1286
+ let id = item === void 0 ? key : `${key}/${item}`, declared = declaredShortcut(id);
1287
+ if (!declared.found)
1288
+ throw Error(`Not an action or toolbar button: ${id}`);
1289
+ if (!enabled())
1290
+ return;
1291
+ load();
1292
+ let next = combo === void 0 ? void 0 : normalizeCombo2(combo);
1293
+ if (combo !== void 0 && combo.trim() && !next)
1294
+ throw Error(`Not a key combination: ${combo}`);
1295
+ let was = bindings.get(id);
1296
+ if (next === void 0 || next === (declared.combo ?? ""))
1297
+ bindings.delete(id);
1298
+ else
1299
+ bindings.set(id, next);
1300
+ if (bindings.get(id) === was)
1301
+ return;
1302
+ persist(), notify();
1303
+ },
883
1304
  canRoll(key) {
884
1305
  if (!enabled())
885
1306
  return !1;
@@ -913,15 +1334,15 @@ function defineControls(id, input, options = {}) {
913
1334
  return;
914
1335
  if (!hold)
915
1336
  return release();
916
- let before = past.at(-1), now = load();
1337
+ let before = past.at(-1)?.values, now = load();
917
1338
  if (comparing || !before || equal(before, now))
918
1339
  return;
919
1340
  comparing = before, options.onChange?.(changes(now, before), before, { source: "compare" }), notify();
920
1341
  },
921
- preset(id) {
1342
+ preset(id, presetOptions) {
922
1343
  if (!Object.hasOwn(presets, id))
923
1344
  throw Error(`Unknown preset: ${id}`);
924
- set(presets[id], { source: "preset" });
1345
+ write(presets[id], { source: "preset", coalesce: presetOptions?.coalesce }, id === "default" ? NO_BASE : makeBase(Object.freeze({ kind: "preset", id }), {}));
925
1346
  },
926
1347
  matchingPreset: () => Object.keys(presets).find((id) => equal(get(), presets[id])) ?? "custom",
927
1348
  get groupPresets() {
@@ -933,41 +1354,36 @@ function defineControls(id, input, options = {}) {
933
1354
  throw Error(`Unknown preset ${index + 1} for group ${group}`);
934
1355
  if (!enabled())
935
1356
  return;
936
- set(preset.values, { source: "preset", group, coalesce: presetOptions?.coalesce }), options.onPreset?.({ group, index, name: preset.name });
1357
+ let b = activeBase();
1358
+ write(preset.values, { source: "preset", group, coalesce: presetOptions?.coalesce }, makeBase(b.preset, { ...b.groups, [group]: index })), options.onPreset?.({ group, index, name: preset.name });
1359
+ },
1360
+ activePreset() {
1361
+ if (!enabled())
1362
+ return null;
1363
+ return activeBase().preset;
1364
+ },
1365
+ activeGroupPreset(group) {
1366
+ if (!enabled())
1367
+ return -1;
1368
+ return activeBase().groups[group] ?? -1;
937
1369
  },
1370
+ baseValues,
938
1371
  matchingGroupPreset(group) {
939
1372
  let cur = get();
940
1373
  return (groupPresets[group] ?? []).findIndex((p) => Object.entries(p.values).every(([k, v]) => same(cur[k], v)));
941
1374
  },
942
1375
  code: (format, values) => formatCode(values ?? get(), format),
943
- variants: createVariants(() => schema, id, storageKey(id, "variants"), variantStorage, get, (v) => set(v, { source: "variant" }), notify, enabled),
1376
+ variants,
944
1377
  attach(element, chip) {
945
- let load = options.ship ? () => options.ship().then((m) => m.attach) : () => (__preload("e13znr7c"), import("./chunk-e13znr7c.js")).then((m) => m.attach);
946
- if (!load || !enabled())
947
- return () => {};
948
- let disposed = !1, detach;
949
- return load().then((attach) => {
950
- if (!disposed && enabled())
951
- detach = attach(controls, element, chip);
952
- }).catch((cause) => {
953
- error = String(cause), notify();
954
- }), () => {
955
- disposed = !0, detach?.();
956
- };
1378
+ let load = options.ship ? () => options.ship().then((m) => m.attach) : () => (__preload("v8ssbcbk"), import("./chunk-v8ssbcbk.js")).then((m) => m.attach);
1379
+ return live(load, (attach) => attach(controls, element, chip), (detach) => detach());
957
1380
  },
958
1381
  mount(container, mountOptions) {
959
- let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => (__preload("ktsn42p7"), import("./chunk-ktsn42p7.js")).then((m) => m.mountPanel);
960
- if (!load || !enabled())
961
- return () => {};
962
- let disposed = !1, panel;
963
- return load().then((mountPanel) => {
964
- if (!disposed && enabled())
965
- panel = mountPanel(controls, container, mountOptions), mountOptions?.onReady?.(panel);
966
- }).catch((cause) => {
967
- error = String(cause), notify();
968
- }), () => {
969
- disposed = !0, panel?.dispose();
970
- };
1382
+ let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => (__preload("jx1ktap3"), import("./chunk-jx1ktap3.js")).then((m) => m.mountPanel);
1383
+ return live(load, (mountPanel) => {
1384
+ let panel = mountPanel(controls, container, mountOptions);
1385
+ return mountOptions?.onReady?.(panel), panel;
1386
+ }, (panel) => panel.dispose());
971
1387
  }
972
1388
  };
973
1389
  return Object.freeze(controls);
@@ -992,7 +1408,11 @@ var optionId = (o) => typeof o === "string" ? o : String(o.id ?? o.value ?? o.la
992
1408
  function descriptorToField(d) {
993
1409
  let meta = pickMeta(d);
994
1410
  if (d.kind === "action")
995
- return action2(d.label, meta);
1411
+ return action2(d.label, {
1412
+ ...meta,
1413
+ ...d.global ? { global: !0 } : {},
1414
+ ...d.rebind ? { rebind: !0 } : {}
1415
+ });
996
1416
  if (readoutDescriptor(d))
997
1417
  return readoutField(d);
998
1418
  if (d.kind === "toolbar")
@@ -1243,6 +1663,7 @@ function createRemoteControls(id, descriptors, options = {}) {
1243
1663
  }, controls = defineControls(id, fromDescriptors(frozenDescriptors), {
1244
1664
  enabled: options.enabled ?? !0,
1245
1665
  ship: options.ship,
1666
+ toggleKey: options.toggleKey,
1246
1667
  echo: options.echo ?? ECHO_MS,
1247
1668
  storage: options.storage ?? null,
1248
1669
  persist: options.persist,
@@ -1268,9 +1689,11 @@ function createRemoteControls(id, descriptors, options = {}) {
1268
1689
  groupFolded: options.groupFolded,
1269
1690
  sectionFolded: options.sectionFolded,
1270
1691
  onChange(patch, _values, meta) {
1271
- if (meta.source === "remote" || meta.source === "reset")
1692
+ if (meta.source === "remote")
1693
+ return;
1694
+ if (meta.source === "reset" && !meta.base)
1272
1695
  return;
1273
- if (meta.source === "preset" && meta.group !== void 0 && options.onPreset)
1696
+ if ((meta.source === "preset" || meta.base && meta.group !== void 0 && controls.activeGroupPreset(meta.group) >= 0) && meta.group !== void 0 && options.onPreset)
1274
1697
  return;
1275
1698
  let changes = [];
1276
1699
  for (let [key, val] of Object.entries(patch)) {
@@ -1331,10 +1754,13 @@ function fromSettings(groups, home = {}) {
1331
1754
  return { descriptors, values };
1332
1755
  }
1333
1756
  export {
1757
+ AROUND,
1334
1758
  CURVE_SHAPES2 as CURVE_SHAPES,
1335
1759
  EASINGS2 as EASINGS,
1760
+ KONAMI,
1336
1761
  SPRING2 as SPRING,
1337
1762
  action2 as action,
1763
+ actionShortcut2 as actionShortcut,
1338
1764
  angle2 as angle,
1339
1765
  bezierCss2 as bezierCss,
1340
1766
  bezierPoint2 as bezierPoint,
@@ -1381,6 +1807,7 @@ export {
1381
1807
  toggle2 as toggle,
1382
1808
  toolbar2 as toolbar,
1383
1809
  toolbarShortcut2 as toolbarShortcut,
1810
+ unlock,
1384
1811
  urlFlag2 as urlFlag,
1385
1812
  vec22 as vec2,
1386
1813
  vec32 as vec3,