@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/index.js CHANGED
@@ -497,7 +497,15 @@ function choice(value, options, meta) {
497
497
  }, meta);
498
498
  }
499
499
  function action(label, meta) {
500
- return withMeta({ kind: "action", ...meta?.run ? { run: meta.run } : {} }, { ...meta, ...label !== void 0 ? { label } : {} });
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 } : {} });
501
509
  }
502
510
  function flatSpring(parts = {}, meta = {}) {
503
511
  let id = meta.id ?? meta.label ?? `spring-${++springIds}`, half = (part, input, fallback) => {
@@ -523,7 +531,7 @@ function flatSpring(parts = {}, meta = {}) {
523
531
  }
524
532
  var springIds = 0;
525
533
  function toggle(value, meta) {
526
- return withMeta({ kind: "toggle", value }, meta);
534
+ return withMeta({ kind: "toggle", value }, { ...meta, random: meta?.random ?? !1 });
527
535
  }
528
536
  function spring(value = {}, meta) {
529
537
  let field = { kind: "spring", value: { duration: 0.5, bounce: 0 } };
@@ -666,13 +674,19 @@ function curve(points, options = {}) {
666
674
  ...presets && Object.keys(presets).length ? { presets } : {}
667
675
  }, options);
668
676
  }
669
- function toolbarShortcut(item) {
670
- let s = item.shortcut;
677
+ function shortcutOf(o) {
678
+ let s = o.shortcut;
671
679
  if (!s)
672
680
  return;
673
681
  if (typeof s === "string")
674
- return { key: s, scope: item.global ? "page" : "panel" };
675
- return s.key ? { key: s.key, scope: s.scope ?? (item.global ? "page" : "panel") } : void 0;
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;
676
690
  }
677
691
  function toolbar(items, meta) {
678
692
  if (!items.length)
@@ -910,6 +924,139 @@ function springCss(value) {
910
924
  let { omega, zeta } = springMotion(value), { easing, ms } = springEasing(omega, zeta);
911
925
  return { easing, duration: ms };
912
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
+ }
913
1060
  // src/core/code.ts
914
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);
915
1062
  function formatCode(values, format = "ts") {
@@ -933,12 +1080,12 @@ var MUTATE = {
933
1080
  chroma: 0.02,
934
1081
  lightness: [0.015, 0.045],
935
1082
  bezier: [0.03, 0.06]
936
- }, between2 = (min, max, r) => min + (max - min) * r, signed2 = (random) => random() < 0.5 ? -1 : 1;
1083
+ }, between3 = (min, max, r) => min + (max - min) * r, signed2 = (random) => random() < 0.5 ? -1 : 1;
937
1084
  function nudge(v, min, max, step, random, reach = max - min) {
938
1085
  let span = max - min;
939
1086
  if (!(span > 0))
940
1087
  return v;
941
- let sign = signed2(random), by = reach * between2(MUTATE.min, MUTATE.max, random()), next = v + sign * by;
1088
+ let sign = signed2(random), by = reach * between3(MUTATE.min, MUTATE.max, random()), next = v + sign * by;
942
1089
  if (next > max || next < min)
943
1090
  next = v - sign * by;
944
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)));
@@ -967,12 +1114,12 @@ function shift(field, value, random) {
967
1114
  };
968
1115
  }
969
1116
  case "color": {
970
- 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) * between2(MUTATE.hue[0], MUTATE.hue[1], random()) + 360) % 360, l = Math.min(1, Math.max(0, c.l + signed2(random) * between2(MUTATE.lightness[0], MUTATE.lightness[1], random())));
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())));
971
1118
  return { l, c: nudge(rel0, 0, 1, 0.01, random) * maxChroma(l, h), h };
972
1119
  }
973
1120
  case "easing":
974
1121
  return value.map((v, i) => {
975
- let [lo, hi] = i % 2 === 0 ? [0, 1] : EASING_Y, next = v + signed2(random) * between2(MUTATE.bezier[0], MUTATE.bezier[1], random());
1122
+ let [lo, hi] = i % 2 === 0 ? [0, 1] : EASING_Y, next = v + signed2(random) * between3(MUTATE.bezier[0], MUTATE.bezier[1], random());
976
1123
  return Math.min(hi, Math.max(lo, next));
977
1124
  });
978
1125
  case "choice": {
@@ -998,7 +1145,7 @@ function shift(field, value, random) {
998
1145
  if (!field.wrap)
999
1146
  return nudge(v, field.min, field.max, field.step, random);
1000
1147
  let span = field.max - field.min;
1001
- return v + signed2(random) * span * between2(MUTATE.min, MUTATE.max, random());
1148
+ return v + signed2(random) * span * between3(MUTATE.min, MUTATE.max, random());
1002
1149
  }
1003
1150
  case "seed":
1004
1151
  return nudge(value, field.min, field.max, 1, random);
@@ -1040,59 +1187,6 @@ function mutateValues(schema, current, keys = valueKeys(schema), random = Math.r
1040
1187
  return out;
1041
1188
  }
1042
1189
 
1043
- // src/core/random.ts
1044
- var lattice = (min, max, step, r) => min + Math.floor(r * (Math.floor((max - min) / step + 0.000000001) + 1)) * step, between3 = (min, max, r) => min + (max - min) * r;
1045
- function pick(field, random) {
1046
- let r = () => Math.max(0, Math.min(1 - Number.EPSILON, random()));
1047
- switch (field.kind) {
1048
- case "knob":
1049
- return lattice(field.min, field.max, field.step, r());
1050
- case "choice":
1051
- return field.options[Math.floor(r() * field.options.length)];
1052
- case "toggle":
1053
- return r() >= 0.5;
1054
- case "spring":
1055
- return { duration: between3(0.2, 0.9, r()), bounce: between3(0, 0.45, r()) };
1056
- case "color": {
1057
- let l = between3(0.5, 0.85, r()), h = between3(0, 360, r());
1058
- return { l, c: between3(0.25, 0.85, r()) * maxChroma(l, h), h };
1059
- }
1060
- case "point":
1061
- return {
1062
- x: lattice(field.min.x, field.max.x, field.step.x, r()),
1063
- y: lattice(field.min.y, field.max.y, field.step.y, r())
1064
- };
1065
- case "easing": {
1066
- let curves = Object.values(EASINGS);
1067
- return curves[Math.floor(r() * curves.length)];
1068
- }
1069
- case "vec": {
1070
- let axis = () => lattice(field.min, field.max, field.step, r());
1071
- return field.axes === 3 ? { x: axis(), y: axis(), z: axis() } : { x: axis(), y: axis() };
1072
- }
1073
- case "range":
1074
- return [
1075
- lattice(field.min, field.max, field.step, r()),
1076
- lattice(field.min, field.max, field.step, r())
1077
- ];
1078
- case "angle": {
1079
- let top = field.wrap ? field.max - field.step : field.max;
1080
- return lattice(field.min, Math.max(field.min, top), field.step, r());
1081
- }
1082
- case "seed":
1083
- return lattice(field.min, field.max, 1, r());
1084
- case "keybind":
1085
- return field.value;
1086
- case "gradient":
1087
- return randomStops(field.value.length, r);
1088
- case "curve":
1089
- return randomPoints(field.range, r);
1090
- }
1091
- }
1092
- function randomValues(schema, random = Math.random) {
1093
- return normalize(schema, Object.fromEntries(Object.entries(schema).flatMap(([key, field]) => hasValue(field) ? [[key, pick(field, random)]] : [])));
1094
- }
1095
-
1096
1190
  // src/core/internal.ts
1097
1191
  var RESHAPE = Symbol.for("knobs.reshape"), RESET_KEYS = Symbol.for("knobs.resetKeys"), REFRESHES = Symbol.for("knobs.refreshes");
1098
1192
 
@@ -1290,12 +1384,28 @@ function createVariants(schema, id, key, storage, get, set, notify, enabled) {
1290
1384
  });
1291
1385
  return persist([...list, variant]), variant;
1292
1386
  },
1293
- load(id) {
1387
+ load(id, options) {
1294
1388
  read();
1295
1389
  let variant = list.find((v) => v.id === id);
1296
1390
  if (!variant)
1297
1391
  throw Error(`Unknown variant: ${id}`);
1298
- set(variant.values);
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);
1299
1409
  },
1300
1410
  remove(id) {
1301
1411
  read(), persist(list.filter((v) => v.id !== id));
@@ -1335,7 +1445,7 @@ function defineControls(id, input, options = {}) {
1335
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 = () => {
1336
1446
  for (let cb of [...listeners])
1337
1447
  cb();
1338
- }, 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([
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([
1339
1449
  ["default", locked],
1340
1450
  ...Object.entries(options.presets ?? {}).map(([id, patch]) => {
1341
1451
  if (id === "default" || id === "custom")
@@ -1375,6 +1485,9 @@ function defineControls(id, input, options = {}) {
1375
1485
  if (data?.version === 1) {
1376
1486
  if (current = normalize(schema, data.values), Array.isArray(data.locks))
1377
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);
1378
1491
  }
1379
1492
  }
1380
1493
  } catch (cause) {
@@ -1393,7 +1506,9 @@ function defineControls(id, input, options = {}) {
1393
1506
  valueStorage()?.setItem(stateKey, JSON.stringify({
1394
1507
  version: 1,
1395
1508
  values: current,
1396
- ...locks.size ? { locks: [...locks] } : {}
1509
+ ...locks.size ? { locks: [...locks] } : {},
1510
+ ...bindings.size ? { bindings: Object.fromEntries(bindings) } : {},
1511
+ ...base !== NO_BASE ? { base } : {}
1397
1512
  })), error = null;
1398
1513
  } catch (cause) {
1399
1514
  error = String(cause);
@@ -1402,6 +1517,74 @@ function defineControls(id, input, options = {}) {
1402
1517
  function commit(next) {
1403
1518
  current = next, persist(), notify();
1404
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
+ }
1405
1588
  let fieldKeys = () => valueKeys(schema), valueField = (k) => {
1406
1589
  let field = Object.hasOwn(schema, k) ? schema[k] : void 0;
1407
1590
  return field && hasValue(field) ? field : void 0;
@@ -1464,10 +1647,13 @@ function defineControls(id, input, options = {}) {
1464
1647
  pending.delete(k);
1465
1648
  };
1466
1649
  function set(patch, setOpts) {
1650
+ write(patch, setOpts);
1651
+ }
1652
+ function write(patch, setOpts, nextBase) {
1467
1653
  if (!enabled())
1468
1654
  return;
1469
1655
  release();
1470
- let previous = load(), forced = mixed.size ? Object.keys(patch).filter((k) => mixed.has(k) && valueField(k)) : [];
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)) : [];
1471
1657
  for (let k of forced)
1472
1658
  mixed.delete(k);
1473
1659
  let applied = applyPatch(previous, previous, patch) ?? {
@@ -1477,9 +1663,10 @@ function defineControls(id, input, options = {}) {
1477
1663
  for (let k of forced)
1478
1664
  if (!(k in diff))
1479
1665
  diff[k] = next[k];
1480
- if (Object.keys(diff).length === 0)
1666
+ let changed = Object.keys(diff).length > 0;
1667
+ if (!changed && !moves)
1481
1668
  return;
1482
- if (next === previous) {
1669
+ if (next === previous && !moves) {
1483
1670
  notify(), options.onChange?.(diff, next, {
1484
1671
  source: setOpts?.source ?? "local",
1485
1672
  ...setOpts?.key ? { key: setOpts.key } : {}
@@ -1487,16 +1674,27 @@ function defineControls(id, input, options = {}) {
1487
1674
  return;
1488
1675
  }
1489
1676
  if (!setOpts?.coalesce || past.length === 0) {
1490
- if (past.push(previous), past.length > HISTORY)
1677
+ if (past.push({ values: previous, base: was }), past.length > HISTORY)
1491
1678
  past.shift();
1492
1679
  }
1493
- future.length = 0, markChanged(diff), commit(next), options.onChange?.(diff, next, {
1680
+ if (future.length = 0, moves)
1681
+ base = nextBase;
1682
+ if (markChanged(diff), commit(next), !changed)
1683
+ return;
1684
+ options.onChange?.(diff, next, {
1494
1685
  source: setOpts?.source ?? "local",
1495
1686
  ...setOpts?.coalesce ? { coalesce: !0 } : {},
1496
1687
  ...setOpts?.key ? { key: setOpts.key } : {},
1497
- ...setOpts?.group ? { group: setOpts.group } : {}
1688
+ ...setOpts?.group ? { group: setOpts.group } : {},
1689
+ ...setOpts?.base ? { base: !0 } : {}
1498
1690
  });
1499
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
+ }
1500
1698
  function place(values, replace, land) {
1501
1699
  let previous = load(), applied = applyPatch(replace ? locked : previous, previous, values);
1502
1700
  if (!applied)
@@ -1604,7 +1802,7 @@ function defineControls(id, input, options = {}) {
1604
1802
  schema = Object.freeze({ ...next });
1605
1803
  if (changed) {
1606
1804
  let previous = load();
1607
- schema = Object.freeze({ ...next }), locked = normalize(schema, {}), presets = buildPresets(), groupPresets = buildGroupPresets();
1805
+ schema = Object.freeze({ ...next }), locked = normalize(schema, {}), presets = buildPresets(), groupPresets = buildGroupPresets(), base = validBase(base);
1608
1806
  for (let k of [...locks])
1609
1807
  if (!valueField(k))
1610
1808
  locks.delete(k);
@@ -1639,7 +1837,30 @@ function defineControls(id, input, options = {}) {
1639
1837
  listen(cb) {
1640
1838
  return readoutListeners.add(cb), () => readoutListeners.delete(cb);
1641
1839
  }
1642
- }, controls = {
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 = {
1643
1864
  id,
1644
1865
  get schema() {
1645
1866
  return schema;
@@ -1741,8 +1962,7 @@ function defineControls(id, input, options = {}) {
1741
1962
  let previous = past.pop();
1742
1963
  if (!previous)
1743
1964
  return;
1744
- let before = load(), diff = changes(before, previous);
1745
- future.push(before), markChanged(diff), commit(previous), options.onChange?.(diff, previous, { source: "undo" });
1965
+ future.push(step(previous, "undo"));
1746
1966
  },
1747
1967
  redo() {
1748
1968
  if (release(), hostHistory)
@@ -1750,8 +1970,7 @@ function defineControls(id, input, options = {}) {
1750
1970
  let next = future.pop();
1751
1971
  if (!next)
1752
1972
  return;
1753
- let before = load(), diff = changes(before, next);
1754
- past.push(before), markChanged(diff), commit(next), options.onChange?.(diff, next, { source: "redo" });
1973
+ past.push(step(next, "redo"));
1755
1974
  },
1756
1975
  getServerSnapshot: () => locked,
1757
1976
  subscribe(cb) {
@@ -1765,22 +1984,15 @@ function defineControls(id, input, options = {}) {
1765
1984
  reset(keys) {
1766
1985
  if (!enabled())
1767
1986
  return;
1987
+ let open = resettable(keys);
1988
+ if (offBase(open))
1989
+ return toBase(open, {});
1768
1990
  let skipped = fieldKeys().some(blocked);
1769
1991
  if (!keys && !skipped) {
1770
- set(locked, { source: "reset" }), options.onReset?.({});
1992
+ write(locked, { source: "reset" }, NO_BASE), options.onReset?.({});
1771
1993
  return;
1772
1994
  }
1773
- let patch = {};
1774
- for (let k of keys ?? fieldKeys())
1775
- if (valueField(k) && !blocked(k))
1776
- patch[k] = locked[k];
1777
- set(patch, { source: "reset" });
1778
- let resetKeys = options[RESET_KEYS];
1779
- if (resetKeys)
1780
- resetKeys(Object.keys(patch));
1781
- else
1782
- for (let key of Object.keys(patch))
1783
- options.onReset?.({ key });
1995
+ toDefaults(open, keys ? void 0 : NO_BASE);
1784
1996
  },
1785
1997
  resetKey(key) {
1786
1998
  if (!enabled())
@@ -1789,24 +2001,40 @@ function defineControls(id, input, options = {}) {
1789
2001
  throw Error(`Unknown control key: ${key}`);
1790
2002
  if (blocked(key))
1791
2003
  return;
2004
+ if (offBase([key]))
2005
+ return toBase([key], { key });
1792
2006
  set({ [key]: locked[key] }, { source: "reset", key }), options.onReset?.({ key });
1793
2007
  },
1794
2008
  resetGroup(group) {
1795
2009
  if (!enabled())
1796
2010
  return;
1797
- let keys = Object.keys(schema).filter((k) => schema[k].group === group && hasValue(schema[k])), open = keys.filter((k) => !blocked(k));
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);
1798
2020
  if (open.length < keys.length)
1799
- return controls.reset(open);
2021
+ return toDefaults(open, forget);
1800
2022
  let patch = {};
1801
2023
  for (let k of keys)
1802
2024
  patch[k] = locked[k];
1803
- set(patch, { source: "reset", group }), options.onReset?.({ group });
2025
+ write(patch, { source: "reset", group }, forget), options.onReset?.({ group });
1804
2026
  },
1805
2027
  isModified(key) {
1806
- let cur = get();
2028
+ let cur = get(), b = baseValues();
1807
2029
  if (key !== void 0)
1808
- return !same(cur[key], locked[key]);
1809
- return !equal(cur, locked);
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();
1810
2038
  },
1811
2039
  modified() {
1812
2040
  let cur = get(), out = {};
@@ -1821,8 +2049,17 @@ function defineControls(id, input, options = {}) {
1821
2049
  let chosen = pool(keys);
1822
2050
  if (chosen.length === 0)
1823
2051
  return;
1824
- let sub = Object.fromEntries(chosen.map((k) => [k, schema[k]]));
1825
- set(randomValues(sub, draw), { source: "randomize" });
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" });
1826
2063
  },
1827
2064
  mutate(keys) {
1828
2065
  if (!enabled())
@@ -1858,6 +2095,33 @@ function defineControls(id, input, options = {}) {
1858
2095
  return !1;
1859
2096
  return load(), locks.has(key);
1860
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
+ },
1861
2125
  canRoll(key) {
1862
2126
  if (!enabled())
1863
2127
  return !1;
@@ -1891,15 +2155,15 @@ function defineControls(id, input, options = {}) {
1891
2155
  return;
1892
2156
  if (!hold)
1893
2157
  return release();
1894
- let before = past.at(-1), now = load();
2158
+ let before = past.at(-1)?.values, now = load();
1895
2159
  if (comparing || !before || equal(before, now))
1896
2160
  return;
1897
2161
  comparing = before, options.onChange?.(changes(now, before), before, { source: "compare" }), notify();
1898
2162
  },
1899
- preset(id) {
2163
+ preset(id, presetOptions) {
1900
2164
  if (!Object.hasOwn(presets, id))
1901
2165
  throw Error(`Unknown preset: ${id}`);
1902
- set(presets[id], { source: "preset" });
2166
+ write(presets[id], { source: "preset", coalesce: presetOptions?.coalesce }, id === "default" ? NO_BASE : makeBase(Object.freeze({ kind: "preset", id }), {}));
1903
2167
  },
1904
2168
  matchingPreset: () => Object.keys(presets).find((id) => equal(get(), presets[id])) ?? "custom",
1905
2169
  get groupPresets() {
@@ -1911,14 +2175,26 @@ function defineControls(id, input, options = {}) {
1911
2175
  throw Error(`Unknown preset ${index + 1} for group ${group}`);
1912
2176
  if (!enabled())
1913
2177
  return;
1914
- set(preset.values, { source: "preset", group, coalesce: presetOptions?.coalesce }), options.onPreset?.({ group, index, name: preset.name });
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;
1915
2190
  },
2191
+ baseValues,
1916
2192
  matchingGroupPreset(group) {
1917
2193
  let cur = get();
1918
2194
  return (groupPresets[group] ?? []).findIndex((p) => Object.entries(p.values).every(([k, v]) => same(cur[k], v)));
1919
2195
  },
1920
2196
  code: (format, values) => formatCode(values ?? get(), format),
1921
- variants: createVariants(() => schema, id, storageKey(id, "variants"), variantStorage, get, (v) => set(v, { source: "variant" }), notify, enabled),
2197
+ variants,
1922
2198
  attach(element, chip) {
1923
2199
  let load = options.ship ? () => options.ship().then((m) => m.attach) : void 0;
1924
2200
  if (!load || !enabled())
@@ -1970,7 +2246,11 @@ var optionId = (o) => typeof o === "string" ? o : String(o.id ?? o.value ?? o.la
1970
2246
  function descriptorToField(d) {
1971
2247
  let meta = pickMeta(d);
1972
2248
  if (d.kind === "action")
1973
- return action(d.label, meta);
2249
+ return action(d.label, {
2250
+ ...meta,
2251
+ ...d.global ? { global: !0 } : {},
2252
+ ...d.rebind ? { rebind: !0 } : {}
2253
+ });
1974
2254
  if (readoutDescriptor(d))
1975
2255
  return readoutField(d);
1976
2256
  if (d.kind === "toolbar")
@@ -2246,9 +2526,11 @@ function createRemoteControls(id, descriptors, options = {}) {
2246
2526
  groupFolded: options.groupFolded,
2247
2527
  sectionFolded: options.sectionFolded,
2248
2528
  onChange(patch, _values, meta) {
2249
- if (meta.source === "remote" || meta.source === "reset")
2529
+ if (meta.source === "remote")
2530
+ return;
2531
+ if (meta.source === "reset" && !meta.base)
2250
2532
  return;
2251
- if (meta.source === "preset" && meta.group !== void 0 && options.onPreset)
2533
+ if ((meta.source === "preset" || meta.base && meta.group !== void 0 && controls.activeGroupPreset(meta.group) >= 0) && meta.group !== void 0 && options.onPreset)
2252
2534
  return;
2253
2535
  let changes = [];
2254
2536
  for (let [key, val] of Object.entries(patch)) {
@@ -2309,10 +2591,12 @@ function fromSettings(groups, home = {}) {
2309
2591
  return { descriptors, values };
2310
2592
  }
2311
2593
  export {
2594
+ AROUND,
2312
2595
  CURVE_SHAPES,
2313
2596
  EASINGS,
2314
2597
  SPRING,
2315
2598
  action,
2599
+ actionShortcut,
2316
2600
  angle,
2317
2601
  bezierCss,
2318
2602
  bezierPoint,