@kitlangton/knobs 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +174 -30
  2. package/dist/dev/{chunk-e13znr7c.js → chunk-4km87zfx.js} +3 -3
  3. package/dist/dev/{chunk-ktsn42p7.js → chunk-5cr85vv6.js} +7448 -5390
  4. package/dist/dev/{chunk-kdewv0mt.js → chunk-693jd1gj.js} +171 -70
  5. package/dist/dev/{chunk-qtt698hp.js → chunk-gezhc24g.js} +21 -7
  6. package/dist/dev/index.js +377 -106
  7. package/dist/index.js +392 -108
  8. package/dist/panel.js +9518 -6749
  9. package/dist/types/core/random.d.ts +61 -1
  10. package/dist/types/core/remote.d.ts +3 -1
  11. package/dist/types/core/schema.d.ts +30 -6
  12. package/dist/types/core/store.d.ts +68 -6
  13. package/dist/types/core/variants.d.ts +10 -2
  14. package/dist/types/index.d.ts +3 -2
  15. package/dist/types/panel/editors.d.ts +11 -0
  16. package/dist/types/panel/flash.d.ts +13 -0
  17. package/dist/types/panel/gpu/renderer.d.ts +78 -6
  18. package/dist/types/panel/haptics.d.ts +6 -2
  19. package/dist/types/panel/has.d.ts +0 -1
  20. package/dist/types/panel/index.d.ts +1 -0
  21. package/dist/types/panel/inspector.d.ts +32 -2
  22. package/dist/types/panel/optkeys.d.ts +7 -0
  23. package/dist/types/panel/presets.d.ts +17 -5
  24. package/dist/types/panel/ramp-morph.d.ts +117 -0
  25. package/dist/types/panel/ramps.d.ts +1 -1
  26. package/dist/types/panel/reel.d.ts +43 -0
  27. package/dist/types/panel/scope.d.ts +19 -0
  28. package/dist/types/panel/slider.d.ts +1 -1
  29. package/dist/types/panel/sound.d.ts +137 -0
  30. package/dist/types/panel/styles.d.ts +1 -1
  31. package/dist/types/panel/toolbar-grid.d.ts +6 -0
  32. package/dist/types/ui/activity-css.d.ts +1 -0
  33. package/dist/types/ui/activity-model.d.ts +122 -0
  34. package/dist/types/ui/activity.d.ts +67 -0
  35. package/dist/types/ui/follow.d.ts +17 -0
  36. package/dist/types/ui/index.d.ts +5 -1
  37. package/dist/types/ui/look.d.ts +9 -0
  38. package/dist/types/ui/picker-css.d.ts +1 -0
  39. package/dist/types/ui/picker-model.d.ts +53 -0
  40. package/dist/types/ui/picker.d.ts +67 -0
  41. package/dist/types/ui/react.d.ts +27 -0
  42. package/dist/types/ui/solid.d.ts +23 -0
  43. package/dist/types/ui/split-model.d.ts +54 -0
  44. package/dist/types/ui/split.d.ts +17 -0
  45. package/dist/ui-react.js +3239 -567
  46. package/dist/ui-solid.js +3123 -472
  47. package/dist/ui.js +2688 -100
  48. package/package.json +1 -1
package/dist/dev/index.js CHANGED
@@ -46,6 +46,7 @@ import {
46
46
  gradient2,
47
47
  curve2,
48
48
  toolbarShortcut2,
49
+ actionShortcut2,
49
50
  toolbar2,
50
51
  watch2,
51
52
  sparkline2,
@@ -71,8 +72,141 @@ import {
71
72
  browserStorage,
72
73
  urlFlag2,
73
74
  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);
75
+ } from "./chunk-gezhc24g.js";
76
+ __chunks(import.meta.url,["jx253znt","5cr85vv6","4km87zfx","gezhc24g","693jd1gj"],[["./index.js",3],["./chunk-5cr85vv6.js",3,4],["./chunk-4km87zfx.js",3,4],["./chunk-gezhc24g.js"],["./chunk-693jd1gj.js",3]],0);
77
+ // src/core/random.ts
78
+ 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;
79
+ function pick(field, random) {
80
+ let r = () => Math.max(0, Math.min(1 - Number.EPSILON, random()));
81
+ switch (field.kind) {
82
+ case "knob":
83
+ return lattice(field.min, field.max, field.step, r());
84
+ case "choice":
85
+ return field.options[Math.floor(r() * field.options.length)];
86
+ case "toggle":
87
+ return r() >= 0.5;
88
+ case "spring":
89
+ return { duration: between(0.2, 0.9, r()), bounce: between(0, 0.45, r()) };
90
+ case "color": {
91
+ let l = between(0.5, 0.85, r()), h = between(0, 360, r());
92
+ return { l, c: between(0.25, 0.85, r()) * maxChroma(l, h), h };
93
+ }
94
+ case "point":
95
+ return {
96
+ x: lattice(field.min.x, field.max.x, field.step.x, r()),
97
+ y: lattice(field.min.y, field.max.y, field.step.y, r())
98
+ };
99
+ case "easing": {
100
+ let curves = Object.values(EASINGS2);
101
+ return curves[Math.floor(r() * curves.length)];
102
+ }
103
+ case "vec": {
104
+ let axis = () => lattice(field.min, field.max, field.step, r());
105
+ return field.axes === 3 ? { x: axis(), y: axis(), z: axis() } : { x: axis(), y: axis() };
106
+ }
107
+ case "range":
108
+ return [
109
+ lattice(field.min, field.max, field.step, r()),
110
+ lattice(field.min, field.max, field.step, r())
111
+ ];
112
+ case "angle": {
113
+ let top = field.wrap ? field.max - field.step : field.max;
114
+ return lattice(field.min, Math.max(field.min, top), field.step, r());
115
+ }
116
+ case "seed":
117
+ return lattice(field.min, field.max, 1, r());
118
+ case "keybind":
119
+ return field.value;
120
+ case "gradient":
121
+ return randomStops(field.value.length, r);
122
+ case "curve":
123
+ return randomPoints(field.range, r);
124
+ }
125
+ }
126
+ var AROUND = {
127
+ spread: 0.35,
128
+ switch: 0.3,
129
+ hue: 20,
130
+ lightness: 0.06,
131
+ chroma: 0.15
132
+ };
133
+ function near(v, min, max, step, r, reach = max - min) {
134
+ let span = max - min;
135
+ if (!(span > 0))
136
+ return v;
137
+ 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));
138
+ return Number((min + Math.max(0, Math.min(top, n)) * step).toPrecision(14));
139
+ }
140
+ var unit = (r) => r * 2 - 1;
141
+ function around(field, base, random) {
142
+ let r = () => Math.max(0, Math.min(1 - Number.EPSILON, random()));
143
+ switch (field.kind) {
144
+ case "knob":
145
+ return near(base, field.min, field.max, field.step, r());
146
+ case "choice": {
147
+ if (field.options.length < 2 || r() >= AROUND.switch)
148
+ return base;
149
+ let rest = field.options.filter((o) => o !== base);
150
+ return rest[Math.floor(r() * rest.length)];
151
+ }
152
+ case "toggle":
153
+ return r() < AROUND.switch ? !base : base;
154
+ case "spring": {
155
+ let s = base, d = SPRING2.duration, b = SPRING2.bounce;
156
+ return {
157
+ duration: near(s.duration, d.min, d.max, d.step, r(), 0.7),
158
+ bounce: near(s.bounce, b.min, b.max, b.step, r(), 0.45)
159
+ };
160
+ }
161
+ case "color": {
162
+ 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));
163
+ return { l, c: k * maxChroma(l, h), h };
164
+ }
165
+ case "point": {
166
+ let p = base;
167
+ return {
168
+ x: near(p.x, field.min.x, field.max.x, field.step.x, r()),
169
+ y: near(p.y, field.min.y, field.max.y, field.step.y, r())
170
+ };
171
+ }
172
+ case "easing":
173
+ return base.map((v, i) => {
174
+ let [lo, hi] = i % 2 === 0 ? [0, 1] : EASING_Y;
175
+ return near(v, lo, hi, 0.001, r());
176
+ });
177
+ case "vec": {
178
+ let v = base, axis = (n) => near(n, field.min, field.max, field.step, r());
179
+ 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) };
180
+ }
181
+ case "range": {
182
+ let [a, b] = base;
183
+ return [
184
+ near(a, field.min, field.max, field.step, r()),
185
+ near(b, field.min, field.max, field.step, r())
186
+ ];
187
+ }
188
+ case "angle": {
189
+ let v = base;
190
+ if (!field.wrap)
191
+ return near(v, field.min, field.max, field.step, r());
192
+ let span = field.max - field.min;
193
+ return v + unit(r()) * span * (AROUND.spread / 2);
194
+ }
195
+ case "gradient":
196
+ return mutateStops(base, r, [AROUND.hue / 2, AROUND.hue], [AROUND.lightness / 2, AROUND.lightness]);
197
+ case "curve":
198
+ return mutatePoints(base, field.range, r, [
199
+ AROUND.spread / 2,
200
+ AROUND.spread
201
+ ]);
202
+ case "seed":
203
+ case "keybind":
204
+ return pick(field, random);
205
+ }
206
+ }
207
+ function randomValues(schema, random = Math.random) {
208
+ return normalize(schema, Object.fromEntries(Object.entries(schema).flatMap(([key, field]) => hasValue2(field) ? [[key, pick(field, random)]] : [])));
209
+ }
76
210
  // src/core/code.ts
77
211
  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
212
  function formatCode(values, format = "ts") {
@@ -96,12 +230,12 @@ var MUTATE = {
96
230
  chroma: 0.02,
97
231
  lightness: [0.015, 0.045],
98
232
  bezier: [0.03, 0.06]
99
- }, between = (min, max, r) => min + (max - min) * r, signed = (random) => random() < 0.5 ? -1 : 1;
233
+ }, between2 = (min, max, r) => min + (max - min) * r, signed = (random) => random() < 0.5 ? -1 : 1;
100
234
  function nudge(v, min, max, step, random, reach = max - min) {
101
235
  let span = max - min;
102
236
  if (!(span > 0))
103
237
  return v;
104
- let sign = signed(random), by = reach * between(MUTATE.min, MUTATE.max, random()), next = v + sign * by;
238
+ let sign = signed(random), by = reach * between2(MUTATE.min, MUTATE.max, random()), next = v + sign * by;
105
239
  if (next > max || next < min)
106
240
  next = v - sign * by;
107
241
  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 +264,12 @@ function shift(field, value, random) {
130
264
  };
131
265
  }
132
266
  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())));
267
+ 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
268
  return { l, c: nudge(rel0, 0, 1, 0.01, random) * maxChroma(l, h), h };
135
269
  }
136
270
  case "easing":
137
271
  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());
272
+ let [lo, hi] = i % 2 === 0 ? [0, 1] : EASING_Y, next = v + signed(random) * between2(MUTATE.bezier[0], MUTATE.bezier[1], random());
139
273
  return Math.min(hi, Math.max(lo, next));
140
274
  });
141
275
  case "choice": {
@@ -161,7 +295,7 @@ function shift(field, value, random) {
161
295
  if (!field.wrap)
162
296
  return nudge(v, field.min, field.max, field.step, random);
163
297
  let span = field.max - field.min;
164
- return v + signed(random) * span * between(MUTATE.min, MUTATE.max, random());
298
+ return v + signed(random) * span * between2(MUTATE.min, MUTATE.max, random());
165
299
  }
166
300
  case "seed":
167
301
  return nudge(value, field.min, field.max, 1, random);
@@ -203,59 +337,6 @@ function mutateValues(schema, current, keys = valueKeys(schema), random = Math.r
203
337
  return out;
204
338
  }
205
339
 
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
- }
254
- }
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)]] : [])));
257
- }
258
-
259
340
  // src/core/variants.ts
260
341
  function createVariants(schema, id, key, storage, get, set, notify, enabled) {
261
342
  let ready = !1, list = Object.freeze([]);
@@ -312,12 +393,28 @@ function createVariants(schema, id, key, storage, get, set, notify, enabled) {
312
393
  });
313
394
  return persist([...list, variant]), variant;
314
395
  },
315
- load(id) {
396
+ load(id, options) {
316
397
  read();
317
398
  let variant = list.find((v) => v.id === id);
318
399
  if (!variant)
319
400
  throw Error(`Unknown variant: ${id}`);
320
- set(variant.values);
401
+ set(variant.values, id, Boolean(options?.coalesce));
402
+ },
403
+ rename(id, name) {
404
+ if (read(), name.length > 100)
405
+ throw Error("Variant names must be at most 100 characters.");
406
+ let at = list.findIndex((v) => v.id === id);
407
+ if (at < 0)
408
+ throw Error(`Unknown variant: ${id}`);
409
+ let was = list[at], trimmed = name.trim();
410
+ if ((was.name ?? "") === trimmed)
411
+ return;
412
+ let next = [...list];
413
+ next[at] = Object.freeze({
414
+ id: was.id,
415
+ ...trimmed ? { name: trimmed } : {},
416
+ values: was.values
417
+ }), persist(next);
321
418
  },
322
419
  remove(id) {
323
420
  read(), persist(list.filter((v) => v.id !== id));
@@ -357,7 +454,7 @@ function defineControls(id, input, options = {}) {
357
454
  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 = () => {
358
455
  for (let cb of [...listeners])
359
456
  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([
457
+ }, 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
458
  ["default", locked],
362
459
  ...Object.entries(options.presets ?? {}).map(([id, patch]) => {
363
460
  if (id === "default" || id === "custom")
@@ -397,6 +494,9 @@ function defineControls(id, input, options = {}) {
397
494
  if (data?.version === 1) {
398
495
  if (current = normalize(schema, data.values), Array.isArray(data.locks))
399
496
  locks = new Set(data.locks.filter((k) => typeof k === "string" && Object.hasOwn(schema, k)));
497
+ if (data.bindings && typeof data.bindings === "object")
498
+ bindings = new Map(Object.entries(data.bindings).flatMap(([id, combo]) => typeof combo === "string" && bindable(id) ? [[id, normalizeCombo2(combo)]] : []));
499
+ base = readBase(data.base);
400
500
  }
401
501
  }
402
502
  } catch (cause) {
@@ -415,7 +515,9 @@ function defineControls(id, input, options = {}) {
415
515
  valueStorage()?.setItem(stateKey, JSON.stringify({
416
516
  version: 1,
417
517
  values: current,
418
- ...locks.size ? { locks: [...locks] } : {}
518
+ ...locks.size ? { locks: [...locks] } : {},
519
+ ...bindings.size ? { bindings: Object.fromEntries(bindings) } : {},
520
+ ...base !== NO_BASE ? { base } : {}
419
521
  })), error = null;
420
522
  } catch (cause) {
421
523
  error = String(cause);
@@ -424,6 +526,74 @@ function defineControls(id, input, options = {}) {
424
526
  function commit(next) {
425
527
  current = next, persist(), notify();
426
528
  }
529
+ 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);
530
+ function validBase(b) {
531
+ 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);
532
+ return sameBase(next, b) ? b : next;
533
+ }
534
+ function readBase(raw) {
535
+ if (!raw || typeof raw !== "object")
536
+ return NO_BASE;
537
+ 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]))) : {};
538
+ return validBase(makeBase(ref, byGroup));
539
+ }
540
+ function activeBase() {
541
+ load();
542
+ let ref = base.preset;
543
+ if (ref?.kind === "variant" && !variants.list().some((v) => v.id === ref.id))
544
+ base = makeBase(null, base.groups);
545
+ return base;
546
+ }
547
+ let hasBase = () => activeBase() !== NO_BASE, baseCache;
548
+ function baseValues() {
549
+ if (!enabled())
550
+ return locked;
551
+ let b = activeBase();
552
+ if (b === NO_BASE)
553
+ return locked;
554
+ let list = b.preset?.kind === "variant" ? variants.list() : null, c = baseCache;
555
+ if (c && c.base === b && c.schema === schema && c.presets === presets && c.groupPresets === groupPresets && c.variants === list)
556
+ return c.values;
557
+ let out = locked, ref = b.preset;
558
+ if (ref?.kind === "preset" && Object.hasOwn(presets, ref.id))
559
+ out = presets[ref.id];
560
+ else if (ref?.kind === "variant") {
561
+ let found = list?.find((v) => v.id === ref.id);
562
+ if (found)
563
+ out = normalize(schema, found.values);
564
+ }
565
+ let groups = Object.entries(b.groups);
566
+ if (groups.length) {
567
+ out = { ...out };
568
+ for (let [g, i] of groups)
569
+ Object.assign(out, groupPresets[g]?.[i]?.values);
570
+ }
571
+ let values = Object.freeze(out);
572
+ return baseCache = { base: b, schema, presets, groupPresets, variants: list, values }, values;
573
+ }
574
+ let covered = (key) => {
575
+ let b = activeBase();
576
+ if (b.preset)
577
+ return !0;
578
+ let group = schema[key]?.group;
579
+ return group !== void 0 && Object.hasOwn(b.groups, group);
580
+ };
581
+ function declaredShortcut(id) {
582
+ let slash = id.indexOf("/"), key = slash < 0 ? id : id.slice(0, slash), field = Object.hasOwn(schema, key) ? schema[key] : void 0;
583
+ if (field?.kind === "action" && slash < 0)
584
+ return { found: !0, ...field.shortcut ? { combo: field.shortcut } : {} };
585
+ if (field?.kind === "toolbar" && slash >= 0) {
586
+ let item = field.items.find((i) => (i.id ?? i.label) === id.slice(slash + 1));
587
+ if (!item)
588
+ return { found: !1 };
589
+ let key = toolbarShortcut2(item)?.key;
590
+ return { found: !0, ...key ? { combo: normalizeCombo2(key) } : {} };
591
+ }
592
+ return { found: !1 };
593
+ }
594
+ function bindable(id) {
595
+ return declaredShortcut(id).found;
596
+ }
427
597
  let fieldKeys = () => valueKeys(schema), valueField = (k) => {
428
598
  let field = Object.hasOwn(schema, k) ? schema[k] : void 0;
429
599
  return field && hasValue2(field) ? field : void 0;
@@ -486,10 +656,13 @@ function defineControls(id, input, options = {}) {
486
656
  pending.delete(k);
487
657
  };
488
658
  function set(patch, setOpts) {
659
+ write(patch, setOpts);
660
+ }
661
+ function write(patch, setOpts, nextBase) {
489
662
  if (!enabled())
490
663
  return;
491
664
  release();
492
- let previous = load(), forced = mixed.size ? Object.keys(patch).filter((k) => mixed.has(k) && valueField(k)) : [];
665
+ 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
666
  for (let k of forced)
494
667
  mixed.delete(k);
495
668
  let applied = applyPatch(previous, previous, patch) ?? {
@@ -499,9 +672,10 @@ function defineControls(id, input, options = {}) {
499
672
  for (let k of forced)
500
673
  if (!(k in diff))
501
674
  diff[k] = next[k];
502
- if (Object.keys(diff).length === 0)
675
+ let changed = Object.keys(diff).length > 0;
676
+ if (!changed && !moves)
503
677
  return;
504
- if (next === previous) {
678
+ if (next === previous && !moves) {
505
679
  notify(), options.onChange?.(diff, next, {
506
680
  source: setOpts?.source ?? "local",
507
681
  ...setOpts?.key ? { key: setOpts.key } : {}
@@ -509,16 +683,27 @@ function defineControls(id, input, options = {}) {
509
683
  return;
510
684
  }
511
685
  if (!setOpts?.coalesce || past.length === 0) {
512
- if (past.push(previous), past.length > HISTORY)
686
+ if (past.push({ values: previous, base: was }), past.length > HISTORY)
513
687
  past.shift();
514
688
  }
515
- future.length = 0, markChanged(diff), commit(next), options.onChange?.(diff, next, {
689
+ if (future.length = 0, moves)
690
+ base = nextBase;
691
+ if (markChanged(diff), commit(next), !changed)
692
+ return;
693
+ options.onChange?.(diff, next, {
516
694
  source: setOpts?.source ?? "local",
517
695
  ...setOpts?.coalesce ? { coalesce: !0 } : {},
518
696
  ...setOpts?.key ? { key: setOpts.key } : {},
519
- ...setOpts?.group ? { group: setOpts.group } : {}
697
+ ...setOpts?.group ? { group: setOpts.group } : {},
698
+ ...setOpts?.base ? { base: !0 } : {}
520
699
  });
521
700
  }
701
+ function step(to, source) {
702
+ let before = load(), left = { values: before, base: activeBase() }, diff = changes(before, to.values);
703
+ if (base = validBase(to.base), markChanged(diff), commit(to.values), Object.keys(diff).length)
704
+ options.onChange?.(diff, to.values, { source });
705
+ return left;
706
+ }
522
707
  function place(values, replace, land) {
523
708
  let previous = load(), applied = applyPatch(replace ? locked : previous, previous, values);
524
709
  if (!applied)
@@ -626,7 +811,7 @@ function defineControls(id, input, options = {}) {
626
811
  schema = Object.freeze({ ...next });
627
812
  if (changed) {
628
813
  let previous = load();
629
- schema = Object.freeze({ ...next }), locked = normalize(schema, {}), presets = buildPresets(), groupPresets = buildGroupPresets();
814
+ schema = Object.freeze({ ...next }), locked = normalize(schema, {}), presets = buildPresets(), groupPresets = buildGroupPresets(), base = validBase(base);
630
815
  for (let k of [...locks])
631
816
  if (!valueField(k))
632
817
  locks.delete(k);
@@ -661,7 +846,30 @@ function defineControls(id, input, options = {}) {
661
846
  listen(cb) {
662
847
  return readoutListeners.add(cb), () => readoutListeners.delete(cb);
663
848
  }
664
- }, controls = {
849
+ }, 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));
850
+ function offBase(keys) {
851
+ if (!hasBase())
852
+ return !1;
853
+ let cur = load(), b = baseValues();
854
+ return keys.some((k) => !same(cur[k], b[k]));
855
+ }
856
+ function toBase(keys, meta) {
857
+ let b = baseValues(), patch = Object.fromEntries(keys.map((k) => [k, b[k]]));
858
+ write(patch, { source: "reset", base: !0, ...meta });
859
+ }
860
+ function toDefaults(keys, nextBase) {
861
+ let patch = {};
862
+ for (let k of keys)
863
+ patch[k] = locked[k];
864
+ write(patch, { source: "reset" }, nextBase);
865
+ let resetKeys = options[RESET_KEYS];
866
+ if (resetKeys)
867
+ resetKeys(Object.keys(patch));
868
+ else
869
+ for (let key of Object.keys(patch))
870
+ options.onReset?.({ key });
871
+ }
872
+ let controls = {
665
873
  id,
666
874
  get schema() {
667
875
  return schema;
@@ -763,8 +971,7 @@ function defineControls(id, input, options = {}) {
763
971
  let previous = past.pop();
764
972
  if (!previous)
765
973
  return;
766
- let before = load(), diff = changes(before, previous);
767
- future.push(before), markChanged(diff), commit(previous), options.onChange?.(diff, previous, { source: "undo" });
974
+ future.push(step(previous, "undo"));
768
975
  },
769
976
  redo() {
770
977
  if (release(), hostHistory)
@@ -772,8 +979,7 @@ function defineControls(id, input, options = {}) {
772
979
  let next = future.pop();
773
980
  if (!next)
774
981
  return;
775
- let before = load(), diff = changes(before, next);
776
- past.push(before), markChanged(diff), commit(next), options.onChange?.(diff, next, { source: "redo" });
982
+ past.push(step(next, "redo"));
777
983
  },
778
984
  getServerSnapshot: () => locked,
779
985
  subscribe(cb) {
@@ -787,22 +993,15 @@ function defineControls(id, input, options = {}) {
787
993
  reset(keys) {
788
994
  if (!enabled())
789
995
  return;
996
+ let open = resettable(keys);
997
+ if (offBase(open))
998
+ return toBase(open, {});
790
999
  let skipped = fieldKeys().some(blocked);
791
1000
  if (!keys && !skipped) {
792
- set(locked, { source: "reset" }), options.onReset?.({});
1001
+ write(locked, { source: "reset" }, NO_BASE), options.onReset?.({});
793
1002
  return;
794
1003
  }
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 });
1004
+ toDefaults(open, keys ? void 0 : NO_BASE);
806
1005
  },
807
1006
  resetKey(key) {
808
1007
  if (!enabled())
@@ -811,24 +1010,40 @@ function defineControls(id, input, options = {}) {
811
1010
  throw Error(`Unknown control key: ${key}`);
812
1011
  if (blocked(key))
813
1012
  return;
1013
+ if (offBase([key]))
1014
+ return toBase([key], { key });
814
1015
  set({ [key]: locked[key] }, { source: "reset", key }), options.onReset?.({ key });
815
1016
  },
816
1017
  resetGroup(group) {
817
1018
  if (!enabled())
818
1019
  return;
819
- let keys = Object.keys(schema).filter((k) => schema[k].group === group && hasValue2(schema[k])), open = keys.filter((k) => !blocked(k));
1020
+ 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];
1021
+ if (offBase(open)) {
1022
+ toBase(open, { group });
1023
+ let preset = index === void 0 ? void 0 : groupPresets[group]?.[index];
1024
+ if (preset && index !== void 0)
1025
+ options.onPreset?.({ group, index, name: preset.name });
1026
+ return;
1027
+ }
1028
+ let { [group]: _, ...others } = b.groups, forget = index === void 0 ? void 0 : makeBase(b.preset, others);
820
1029
  if (open.length < keys.length)
821
- return controls.reset(open);
1030
+ return toDefaults(open, forget);
822
1031
  let patch = {};
823
1032
  for (let k of keys)
824
1033
  patch[k] = locked[k];
825
- set(patch, { source: "reset", group }), options.onReset?.({ group });
1034
+ write(patch, { source: "reset", group }, forget), options.onReset?.({ group });
826
1035
  },
827
1036
  isModified(key) {
828
- let cur = get();
1037
+ let cur = get(), b = baseValues();
829
1038
  if (key !== void 0)
830
- return !same(cur[key], locked[key]);
831
- return !equal(cur, locked);
1039
+ return !same(cur[key], b[key]);
1040
+ return !equal(cur, b);
1041
+ },
1042
+ canReset(keys) {
1043
+ if (!enabled())
1044
+ return !1;
1045
+ let cur = get(), b = baseValues();
1046
+ return resettable(keys).some((k) => !same(cur[k], b[k]) || !same(cur[k], locked[k])) || !keys && hasBase();
832
1047
  },
833
1048
  modified() {
834
1049
  let cur = get(), out = {};
@@ -843,8 +1058,17 @@ function defineControls(id, input, options = {}) {
843
1058
  let chosen = pool(keys);
844
1059
  if (chosen.length === 0)
845
1060
  return;
846
- let sub = Object.fromEntries(chosen.map((k) => [k, schema[k]]));
847
- set(randomValues(sub, draw), { source: "randomize" });
1061
+ if (!hasBase()) {
1062
+ let sub = Object.fromEntries(chosen.map((k) => [k, schema[k]]));
1063
+ set(randomValues(sub, draw), { source: "randomize" });
1064
+ return;
1065
+ }
1066
+ let b = baseValues(), patch = {};
1067
+ for (let k of chosen) {
1068
+ let field = valueField(k);
1069
+ patch[k] = covered(k) ? around(field, b[k], draw) : pick(field, draw);
1070
+ }
1071
+ set(patch, { source: "randomize" });
848
1072
  },
849
1073
  mutate(keys) {
850
1074
  if (!enabled())
@@ -880,6 +1104,33 @@ function defineControls(id, input, options = {}) {
880
1104
  return !1;
881
1105
  return load(), locks.has(key);
882
1106
  },
1107
+ shortcut(key, item) {
1108
+ let id = item === void 0 ? key : `${key}/${item}`, declared = declaredShortcut(id);
1109
+ if (!declared.found)
1110
+ return;
1111
+ if (enabled())
1112
+ load();
1113
+ return (enabled() ? bindings.get(id) : void 0) ?? declared.combo;
1114
+ },
1115
+ rebind(key, combo, item) {
1116
+ let id = item === void 0 ? key : `${key}/${item}`, declared = declaredShortcut(id);
1117
+ if (!declared.found)
1118
+ throw Error(`Not an action or toolbar button: ${id}`);
1119
+ if (!enabled())
1120
+ return;
1121
+ load();
1122
+ let next = combo === void 0 ? void 0 : normalizeCombo2(combo);
1123
+ if (combo !== void 0 && combo.trim() && !next)
1124
+ throw Error(`Not a key combination: ${combo}`);
1125
+ let was = bindings.get(id);
1126
+ if (next === void 0 || next === (declared.combo ?? ""))
1127
+ bindings.delete(id);
1128
+ else
1129
+ bindings.set(id, next);
1130
+ if (bindings.get(id) === was)
1131
+ return;
1132
+ persist(), notify();
1133
+ },
883
1134
  canRoll(key) {
884
1135
  if (!enabled())
885
1136
  return !1;
@@ -913,15 +1164,15 @@ function defineControls(id, input, options = {}) {
913
1164
  return;
914
1165
  if (!hold)
915
1166
  return release();
916
- let before = past.at(-1), now = load();
1167
+ let before = past.at(-1)?.values, now = load();
917
1168
  if (comparing || !before || equal(before, now))
918
1169
  return;
919
1170
  comparing = before, options.onChange?.(changes(now, before), before, { source: "compare" }), notify();
920
1171
  },
921
- preset(id) {
1172
+ preset(id, presetOptions) {
922
1173
  if (!Object.hasOwn(presets, id))
923
1174
  throw Error(`Unknown preset: ${id}`);
924
- set(presets[id], { source: "preset" });
1175
+ write(presets[id], { source: "preset", coalesce: presetOptions?.coalesce }, id === "default" ? NO_BASE : makeBase(Object.freeze({ kind: "preset", id }), {}));
925
1176
  },
926
1177
  matchingPreset: () => Object.keys(presets).find((id) => equal(get(), presets[id])) ?? "custom",
927
1178
  get groupPresets() {
@@ -933,16 +1184,28 @@ function defineControls(id, input, options = {}) {
933
1184
  throw Error(`Unknown preset ${index + 1} for group ${group}`);
934
1185
  if (!enabled())
935
1186
  return;
936
- set(preset.values, { source: "preset", group, coalesce: presetOptions?.coalesce }), options.onPreset?.({ group, index, name: preset.name });
1187
+ let b = activeBase();
1188
+ write(preset.values, { source: "preset", group, coalesce: presetOptions?.coalesce }, makeBase(b.preset, { ...b.groups, [group]: index })), options.onPreset?.({ group, index, name: preset.name });
1189
+ },
1190
+ activePreset() {
1191
+ if (!enabled())
1192
+ return null;
1193
+ return activeBase().preset;
937
1194
  },
1195
+ activeGroupPreset(group) {
1196
+ if (!enabled())
1197
+ return -1;
1198
+ return activeBase().groups[group] ?? -1;
1199
+ },
1200
+ baseValues,
938
1201
  matchingGroupPreset(group) {
939
1202
  let cur = get();
940
1203
  return (groupPresets[group] ?? []).findIndex((p) => Object.entries(p.values).every(([k, v]) => same(cur[k], v)));
941
1204
  },
942
1205
  code: (format, values) => formatCode(values ?? get(), format),
943
- variants: createVariants(() => schema, id, storageKey(id, "variants"), variantStorage, get, (v) => set(v, { source: "variant" }), notify, enabled),
1206
+ variants,
944
1207
  attach(element, chip) {
945
- let load = options.ship ? () => options.ship().then((m) => m.attach) : () => (__preload("e13znr7c"), import("./chunk-e13znr7c.js")).then((m) => m.attach);
1208
+ let load = options.ship ? () => options.ship().then((m) => m.attach) : () => (__preload("4km87zfx"), import("./chunk-4km87zfx.js")).then((m) => m.attach);
946
1209
  if (!load || !enabled())
947
1210
  return () => {};
948
1211
  let disposed = !1, detach;
@@ -956,7 +1219,7 @@ function defineControls(id, input, options = {}) {
956
1219
  };
957
1220
  },
958
1221
  mount(container, mountOptions) {
959
- let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => (__preload("ktsn42p7"), import("./chunk-ktsn42p7.js")).then((m) => m.mountPanel);
1222
+ let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => (__preload("5cr85vv6"), import("./chunk-5cr85vv6.js")).then((m) => m.mountPanel);
960
1223
  if (!load || !enabled())
961
1224
  return () => {};
962
1225
  let disposed = !1, panel;
@@ -992,7 +1255,11 @@ var optionId = (o) => typeof o === "string" ? o : String(o.id ?? o.value ?? o.la
992
1255
  function descriptorToField(d) {
993
1256
  let meta = pickMeta(d);
994
1257
  if (d.kind === "action")
995
- return action2(d.label, meta);
1258
+ return action2(d.label, {
1259
+ ...meta,
1260
+ ...d.global ? { global: !0 } : {},
1261
+ ...d.rebind ? { rebind: !0 } : {}
1262
+ });
996
1263
  if (readoutDescriptor(d))
997
1264
  return readoutField(d);
998
1265
  if (d.kind === "toolbar")
@@ -1268,9 +1535,11 @@ function createRemoteControls(id, descriptors, options = {}) {
1268
1535
  groupFolded: options.groupFolded,
1269
1536
  sectionFolded: options.sectionFolded,
1270
1537
  onChange(patch, _values, meta) {
1271
- if (meta.source === "remote" || meta.source === "reset")
1538
+ if (meta.source === "remote")
1539
+ return;
1540
+ if (meta.source === "reset" && !meta.base)
1272
1541
  return;
1273
- if (meta.source === "preset" && meta.group !== void 0 && options.onPreset)
1542
+ if ((meta.source === "preset" || meta.base && meta.group !== void 0 && controls.activeGroupPreset(meta.group) >= 0) && meta.group !== void 0 && options.onPreset)
1274
1543
  return;
1275
1544
  let changes = [];
1276
1545
  for (let [key, val] of Object.entries(patch)) {
@@ -1331,10 +1600,12 @@ function fromSettings(groups, home = {}) {
1331
1600
  return { descriptors, values };
1332
1601
  }
1333
1602
  export {
1603
+ AROUND,
1334
1604
  CURVE_SHAPES2 as CURVE_SHAPES,
1335
1605
  EASINGS2 as EASINGS,
1336
1606
  SPRING2 as SPRING,
1337
1607
  action2 as action,
1608
+ actionShortcut2 as actionShortcut,
1338
1609
  angle2 as angle,
1339
1610
  bezierCss2 as bezierCss,
1340
1611
  bezierPoint2 as bezierPoint,