@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/index.js CHANGED
@@ -221,6 +221,17 @@ function comboParts(combo, apple = isApple()) {
221
221
  function comboLabel(combo, apple = isApple()) {
222
222
  return comboParts(combo, apple).join(apple ? "" : "+");
223
223
  }
224
+ function isTypingTarget(target) {
225
+ let el = target;
226
+ if (!el || typeof el.tagName !== "string")
227
+ return !1;
228
+ let tag = el.tagName;
229
+ if (tag === "SELECT" || el.isContentEditable)
230
+ return !0;
231
+ if (tag === "TEXTAREA")
232
+ return !el.readOnly;
233
+ return tag === "INPUT" && !el.readOnly && !["range", "checkbox", "radio", "button", "submit", "reset", "color"].includes(el.type);
234
+ }
224
235
 
225
236
  // src/core/ramp.ts
226
237
  var MAX_STOPS = 32, MAX_POINTS = 64, GRADIENT_SAMPLES = 64;
@@ -475,11 +486,9 @@ function withMeta(base, meta) {
475
486
  return checkShortcut(meta?.shortcut), Object.freeze({ ...base, ...pickMeta(meta) });
476
487
  }
477
488
  function knob(value, options) {
478
- let step = options.step ?? ((options.max - options.min) / 100 || 1);
479
- if (![value, options.min, options.max, step].every(Number.isFinite) || options.max < options.min || step <= 0)
489
+ if (!Number.isFinite(value))
480
490
  throw Error("A knob needs finite bounds and a positive step.");
481
- if (options.decimals !== void 0 && !(Number.isInteger(options.decimals) && options.decimals >= 0))
482
- throw Error("A knob's decimals must be a whole number.");
491
+ let step = bounds(options, "A knob");
483
492
  checkShortcut(options.shortcut);
484
493
  let field = { ...options, kind: "knob", value, step };
485
494
  return Object.freeze({ ...field, value: normalizeField(field, value) });
@@ -497,7 +506,15 @@ function choice(value, options, meta) {
497
506
  }, meta);
498
507
  }
499
508
  function action(label, meta) {
500
- return withMeta({ kind: "action", ...meta?.run ? { run: meta.run } : {} }, { ...meta, ...label !== void 0 ? { label } : {} });
509
+ let { shortcut, global, rebind, run, ...rest } = meta ?? {}, key = shortcutOf({ ...shortcut ? { shortcut } : {}, ...global ? { global } : {} }), combo = key ? normalizeCombo(key.key) : "";
510
+ if (key && !combo)
511
+ throw Error("An action's shortcut must name a key.");
512
+ return withMeta({
513
+ kind: "action",
514
+ ...run ? { run } : {},
515
+ ...key ? { shortcut: combo, scope: key.scope } : {},
516
+ ...rebind ? { rebind: !0 } : {}
517
+ }, { ...rest, ...label !== void 0 ? { label } : {} });
501
518
  }
502
519
  function flatSpring(parts = {}, meta = {}) {
503
520
  let id = meta.id ?? meta.label ?? `spring-${++springIds}`, half = (part, input, fallback) => {
@@ -523,7 +540,7 @@ function flatSpring(parts = {}, meta = {}) {
523
540
  }
524
541
  var springIds = 0;
525
542
  function toggle(value, meta) {
526
- return withMeta({ kind: "toggle", value }, meta);
543
+ return withMeta({ kind: "toggle", value }, { ...meta, random: meta?.random ?? !1 });
527
544
  }
528
545
  function spring(value = {}, meta) {
529
546
  let field = { kind: "spring", value: { duration: 0.5, bounce: 0 } };
@@ -666,13 +683,19 @@ function curve(points, options = {}) {
666
683
  ...presets && Object.keys(presets).length ? { presets } : {}
667
684
  }, options);
668
685
  }
669
- function toolbarShortcut(item) {
670
- let s = item.shortcut;
686
+ function shortcutOf(o) {
687
+ let s = o.shortcut;
671
688
  if (!s)
672
689
  return;
673
690
  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;
691
+ return { key: s, scope: o.global ? "page" : "panel" };
692
+ return s.key ? { key: s.key, scope: s.scope ?? (o.global ? "page" : "panel") } : void 0;
693
+ }
694
+ function toolbarShortcut(item) {
695
+ return shortcutOf(item);
696
+ }
697
+ function actionShortcut(field) {
698
+ return field.shortcut ? { key: field.shortcut, scope: field.scope ?? "panel" } : void 0;
676
699
  }
677
700
  function toolbar(items, meta) {
678
701
  if (!items.length)
@@ -910,6 +933,139 @@ function springCss(value) {
910
933
  let { omega, zeta } = springMotion(value), { easing, ms } = springEasing(omega, zeta);
911
934
  return { easing, duration: ms };
912
935
  }
936
+ // src/core/random.ts
937
+ 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;
938
+ function pick(field, random) {
939
+ let r = () => Math.max(0, Math.min(1 - Number.EPSILON, random()));
940
+ switch (field.kind) {
941
+ case "knob":
942
+ return lattice(field.min, field.max, field.step, r());
943
+ case "choice":
944
+ return field.options[Math.floor(r() * field.options.length)];
945
+ case "toggle":
946
+ return r() >= 0.5;
947
+ case "spring":
948
+ return { duration: between2(0.2, 0.9, r()), bounce: between2(0, 0.45, r()) };
949
+ case "color": {
950
+ let l = between2(0.5, 0.85, r()), h = between2(0, 360, r());
951
+ return { l, c: between2(0.25, 0.85, r()) * maxChroma(l, h), h };
952
+ }
953
+ case "point":
954
+ return {
955
+ x: lattice(field.min.x, field.max.x, field.step.x, r()),
956
+ y: lattice(field.min.y, field.max.y, field.step.y, r())
957
+ };
958
+ case "easing": {
959
+ let curves = Object.values(EASINGS);
960
+ return curves[Math.floor(r() * curves.length)];
961
+ }
962
+ case "vec": {
963
+ let axis = () => lattice(field.min, field.max, field.step, r());
964
+ return field.axes === 3 ? { x: axis(), y: axis(), z: axis() } : { x: axis(), y: axis() };
965
+ }
966
+ case "range":
967
+ return [
968
+ lattice(field.min, field.max, field.step, r()),
969
+ lattice(field.min, field.max, field.step, r())
970
+ ];
971
+ case "angle": {
972
+ let top = field.wrap ? field.max - field.step : field.max;
973
+ return lattice(field.min, Math.max(field.min, top), field.step, r());
974
+ }
975
+ case "seed":
976
+ return lattice(field.min, field.max, 1, r());
977
+ case "keybind":
978
+ return field.value;
979
+ case "gradient":
980
+ return randomStops(field.value.length, r);
981
+ case "curve":
982
+ return randomPoints(field.range, r);
983
+ }
984
+ }
985
+ var AROUND = {
986
+ spread: 0.35,
987
+ switch: 0.3,
988
+ hue: 20,
989
+ lightness: 0.06,
990
+ chroma: 0.15
991
+ };
992
+ function near(v, min, max, step, r, reach = max - min) {
993
+ let span = max - min;
994
+ if (!(span > 0))
995
+ return v;
996
+ 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));
997
+ return Number((min + Math.max(0, Math.min(top, n)) * step).toPrecision(14));
998
+ }
999
+ var unit = (r) => r * 2 - 1;
1000
+ function around(field, base, random) {
1001
+ let r = () => Math.max(0, Math.min(1 - Number.EPSILON, random()));
1002
+ switch (field.kind) {
1003
+ case "knob":
1004
+ return near(base, field.min, field.max, field.step, r());
1005
+ case "choice": {
1006
+ if (field.options.length < 2 || r() >= AROUND.switch)
1007
+ return base;
1008
+ let rest = field.options.filter((o) => o !== base);
1009
+ return rest[Math.floor(r() * rest.length)];
1010
+ }
1011
+ case "toggle":
1012
+ return r() < AROUND.switch ? !base : base;
1013
+ case "spring": {
1014
+ let s = base, d = SPRING.duration, b = SPRING.bounce;
1015
+ return {
1016
+ duration: near(s.duration, d.min, d.max, d.step, r(), 0.7),
1017
+ bounce: near(s.bounce, b.min, b.max, b.step, r(), 0.45)
1018
+ };
1019
+ }
1020
+ case "color": {
1021
+ 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));
1022
+ return { l, c: k * maxChroma(l, h), h };
1023
+ }
1024
+ case "point": {
1025
+ let p = base;
1026
+ return {
1027
+ x: near(p.x, field.min.x, field.max.x, field.step.x, r()),
1028
+ y: near(p.y, field.min.y, field.max.y, field.step.y, r())
1029
+ };
1030
+ }
1031
+ case "easing":
1032
+ return base.map((v, i) => {
1033
+ let [lo, hi] = i % 2 === 0 ? [0, 1] : EASING_Y;
1034
+ return near(v, lo, hi, 0.001, r());
1035
+ });
1036
+ case "vec": {
1037
+ let v = base, axis = (n) => near(n, field.min, field.max, field.step, r());
1038
+ 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) };
1039
+ }
1040
+ case "range": {
1041
+ let [a, b] = base;
1042
+ return [
1043
+ near(a, field.min, field.max, field.step, r()),
1044
+ near(b, field.min, field.max, field.step, r())
1045
+ ];
1046
+ }
1047
+ case "angle": {
1048
+ let v = base;
1049
+ if (!field.wrap)
1050
+ return near(v, field.min, field.max, field.step, r());
1051
+ let span = field.max - field.min;
1052
+ return v + unit(r()) * span * (AROUND.spread / 2);
1053
+ }
1054
+ case "gradient":
1055
+ return mutateStops(base, r, [AROUND.hue / 2, AROUND.hue], [AROUND.lightness / 2, AROUND.lightness]);
1056
+ case "curve":
1057
+ return mutatePoints(base, field.range, r, [
1058
+ AROUND.spread / 2,
1059
+ AROUND.spread
1060
+ ]);
1061
+ case "seed":
1062
+ case "keybind":
1063
+ return pick(field, random);
1064
+ }
1065
+ }
1066
+ function randomValues(schema, random = Math.random) {
1067
+ return normalize(schema, Object.fromEntries(Object.entries(schema).flatMap(([key, field]) => hasValue(field) ? [[key, pick(field, random)]] : [])));
1068
+ }
913
1069
  // src/core/code.ts
914
1070
  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
1071
  function formatCode(values, format = "ts") {
@@ -933,12 +1089,12 @@ var MUTATE = {
933
1089
  chroma: 0.02,
934
1090
  lightness: [0.015, 0.045],
935
1091
  bezier: [0.03, 0.06]
936
- }, between2 = (min, max, r) => min + (max - min) * r, signed2 = (random) => random() < 0.5 ? -1 : 1;
1092
+ }, between3 = (min, max, r) => min + (max - min) * r, signed2 = (random) => random() < 0.5 ? -1 : 1;
937
1093
  function nudge(v, min, max, step, random, reach = max - min) {
938
1094
  let span = max - min;
939
1095
  if (!(span > 0))
940
1096
  return v;
941
- let sign = signed2(random), by = reach * between2(MUTATE.min, MUTATE.max, random()), next = v + sign * by;
1097
+ let sign = signed2(random), by = reach * between3(MUTATE.min, MUTATE.max, random()), next = v + sign * by;
942
1098
  if (next > max || next < min)
943
1099
  next = v - sign * by;
944
1100
  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 +1123,12 @@ function shift(field, value, random) {
967
1123
  };
968
1124
  }
969
1125
  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())));
1126
+ 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
1127
  return { l, c: nudge(rel0, 0, 1, 0.01, random) * maxChroma(l, h), h };
972
1128
  }
973
1129
  case "easing":
974
1130
  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());
1131
+ let [lo, hi] = i % 2 === 0 ? [0, 1] : EASING_Y, next = v + signed2(random) * between3(MUTATE.bezier[0], MUTATE.bezier[1], random());
976
1132
  return Math.min(hi, Math.max(lo, next));
977
1133
  });
978
1134
  case "choice": {
@@ -998,7 +1154,7 @@ function shift(field, value, random) {
998
1154
  if (!field.wrap)
999
1155
  return nudge(v, field.min, field.max, field.step, random);
1000
1156
  let span = field.max - field.min;
1001
- return v + signed2(random) * span * between2(MUTATE.min, MUTATE.max, random());
1157
+ return v + signed2(random) * span * between3(MUTATE.min, MUTATE.max, random());
1002
1158
  }
1003
1159
  case "seed":
1004
1160
  return nudge(value, field.min, field.max, 1, random);
@@ -1040,59 +1196,6 @@ function mutateValues(schema, current, keys = valueKeys(schema), random = Math.r
1040
1196
  return out;
1041
1197
  }
1042
1198
 
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
1199
  // src/core/internal.ts
1097
1200
  var RESHAPE = Symbol.for("knobs.reshape"), RESET_KEYS = Symbol.for("knobs.resetKeys"), REFRESHES = Symbol.for("knobs.refreshes");
1098
1201
 
@@ -1234,6 +1337,143 @@ function urlFlag(name) {
1234
1337
  }
1235
1338
  var storageKey = (id, part) => `knobs:${encodeURIComponent(id)}:${part}:v1`;
1236
1339
 
1340
+ // src/core/unlock.ts
1341
+ var KONAMI = Object.freeze([
1342
+ "ArrowUp",
1343
+ "ArrowUp",
1344
+ "ArrowDown",
1345
+ "ArrowDown",
1346
+ "ArrowLeft",
1347
+ "ArrowRight",
1348
+ "ArrowLeft",
1349
+ "ArrowRight",
1350
+ "b",
1351
+ "a"
1352
+ ]), GATE = Symbol.for("knobs.unlock.v1"), gate = () => {
1353
+ let holder = globalThis;
1354
+ return holder[GATE] ??= { unlocked: !1, listeners: /* @__PURE__ */ new Set };
1355
+ }, isUnlocked = () => globalThis[GATE]?.unlocked ?? !1, unlockedPanel = () => isUnlocked() ? gate().panel : void 0;
1356
+ function onUnlock(listener) {
1357
+ let listeners = gate().listeners;
1358
+ return listeners.add(listener), () => void listeners.delete(listener);
1359
+ }
1360
+ var UNLOCK_KEY = "knobs:unlocked";
1361
+ function sequenceKeys(sequence) {
1362
+ return typeof sequence === "string" ? [...sequence] : [...sequence];
1363
+ }
1364
+ var fold = (key) => key.length === 1 ? key.toLowerCase() : key;
1365
+ function sequenceMatcher(sequence, gap = 1500) {
1366
+ let want = sequenceKeys(sequence).map(fold), typed = [], last = -1 / 0;
1367
+ return (key, at) => {
1368
+ if (!want.length)
1369
+ return !1;
1370
+ if (at - last > gap)
1371
+ typed = [];
1372
+ if (last = at, typed.push(fold(key)), typed.length > want.length)
1373
+ typed.shift();
1374
+ if (typed.length === want.length && typed.every((k, i) => k === want[i]))
1375
+ return typed = [], !0;
1376
+ return !1;
1377
+ };
1378
+ }
1379
+ function queryUnlock(search, name) {
1380
+ let params = new URLSearchParams(search);
1381
+ if (!params.has(name))
1382
+ return;
1383
+ return !["off", "0", "false", "lock", "no"].includes((params.get(name) ?? "").toLowerCase());
1384
+ }
1385
+ function resolveUnlock(input) {
1386
+ let unlocked = input.query ?? (input.persist && input.stored);
1387
+ return { unlocked, remember: input.persist && input.query !== void 0 ? unlocked : void 0 };
1388
+ }
1389
+ function inCorner(x, y, width, height, corner, size = 48) {
1390
+ let left = corner.endsWith("left") ? x <= size : x >= width - size, top = corner.startsWith("top") ? y <= size : y >= height - size;
1391
+ return left && top;
1392
+ }
1393
+ function tapCounter(count = 3, within = 500) {
1394
+ let taps = 0, last = -1 / 0;
1395
+ return (at) => {
1396
+ if (taps = at - last <= within ? taps + 1 : 1, last = at, taps < count)
1397
+ return !1;
1398
+ return taps = 0, !0;
1399
+ };
1400
+ }
1401
+ var readStored = () => {
1402
+ try {
1403
+ return globalThis.localStorage?.getItem(UNLOCK_KEY) === "1";
1404
+ } catch {
1405
+ return !1;
1406
+ }
1407
+ }, writeStored = (on) => {
1408
+ try {
1409
+ if (on)
1410
+ globalThis.localStorage?.setItem(UNLOCK_KEY, "1");
1411
+ else
1412
+ globalThis.localStorage?.removeItem(UNLOCK_KEY);
1413
+ } catch {}
1414
+ };
1415
+ function unlock(options) {
1416
+ let g = gate();
1417
+ g.panel = options.panel;
1418
+ 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) => {
1419
+ (g.module ? Promise.resolve(g.module) : g.panel?.())?.then((m) => {
1420
+ if (g.module = m, g.unlocked === (text === "knobs unlocked"))
1421
+ m.status?.(text);
1422
+ }).catch(() => {});
1423
+ }, set = (on, say) => {
1424
+ if (persist)
1425
+ writeStored(on);
1426
+ if (g.unlocked === on)
1427
+ return;
1428
+ g.unlocked = on;
1429
+ for (let listener of [...g.listeners])
1430
+ listener();
1431
+ if (say)
1432
+ announce(on ? "knobs unlocked" : "knobs locked");
1433
+ }, start = resolveUnlock({
1434
+ query: query && win ? queryUnlock(win.location.search, query) : void 0,
1435
+ stored: persist && readStored(),
1436
+ persist
1437
+ });
1438
+ if (start.remember !== void 0)
1439
+ writeStored(start.remember);
1440
+ set(start.unlocked || g.unlocked, !1);
1441
+ let offs = [];
1442
+ if (win && sequence !== !1 && sequenceKeys(sequence).length) {
1443
+ let typed = sequenceMatcher(sequence), onKey = (event) => {
1444
+ if (event.ctrlKey || event.metaKey || event.altKey || event.isComposing || event.repeat)
1445
+ return;
1446
+ if (event.key === "Shift" || event.key === "CapsLock" || isTypingTarget(event.target))
1447
+ return;
1448
+ if (typed(event.key, event.timeStamp || performance.now()))
1449
+ set(!g.unlocked, !0);
1450
+ };
1451
+ win.addEventListener("keydown", onKey, !0), offs.push(() => win.removeEventListener("keydown", onKey, !0));
1452
+ }
1453
+ if (win && corner) {
1454
+ let tapped = tapCounter(), onTap = (event) => {
1455
+ if (!event.isPrimary)
1456
+ return;
1457
+ if (!inCorner(event.clientX, event.clientY, win.innerWidth, win.innerHeight, corner))
1458
+ return void (tapped = tapCounter());
1459
+ if (tapped(event.timeStamp || performance.now()))
1460
+ set(!g.unlocked, !0);
1461
+ };
1462
+ win.addEventListener("pointerdown", onTap, { capture: !0, passive: !0 }), offs.push(() => win.removeEventListener("pointerdown", onTap, !0));
1463
+ }
1464
+ return {
1465
+ get unlocked() {
1466
+ return g.unlocked;
1467
+ },
1468
+ unlock: () => set(!0, !0),
1469
+ lock: () => set(!1, !0),
1470
+ dispose() {
1471
+ for (let off of offs.splice(0))
1472
+ off();
1473
+ }
1474
+ };
1475
+ }
1476
+
1237
1477
  // src/core/variants.ts
1238
1478
  function createVariants(schema, id, key, storage, get, set, notify, enabled) {
1239
1479
  let ready = !1, list = Object.freeze([]);
@@ -1290,12 +1530,28 @@ function createVariants(schema, id, key, storage, get, set, notify, enabled) {
1290
1530
  });
1291
1531
  return persist([...list, variant]), variant;
1292
1532
  },
1293
- load(id) {
1533
+ load(id, options) {
1294
1534
  read();
1295
1535
  let variant = list.find((v) => v.id === id);
1296
1536
  if (!variant)
1297
1537
  throw Error(`Unknown variant: ${id}`);
1298
- set(variant.values);
1538
+ set(variant.values, id, Boolean(options?.coalesce));
1539
+ },
1540
+ rename(id, name) {
1541
+ if (read(), name.length > 100)
1542
+ throw Error("Variant names must be at most 100 characters.");
1543
+ let at = list.findIndex((v) => v.id === id);
1544
+ if (at < 0)
1545
+ throw Error(`Unknown variant: ${id}`);
1546
+ let was = list[at], trimmed = name.trim();
1547
+ if ((was.name ?? "") === trimmed)
1548
+ return;
1549
+ let next = [...list];
1550
+ next[at] = Object.freeze({
1551
+ id: was.id,
1552
+ ...trimmed ? { name: trimmed } : {},
1553
+ values: was.values
1554
+ }), persist(next);
1299
1555
  },
1300
1556
  remove(id) {
1301
1557
  read(), persist(list.filter((v) => v.id !== id));
@@ -1332,10 +1588,10 @@ function sameField(a, b) {
1332
1588
  function defineControls(id, input, options = {}) {
1333
1589
  if (!id.trim())
1334
1590
  throw Error("Controls need a stable, nonempty id.");
1335
- 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 = () => {
1591
+ 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) || isUnlocked()) && (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 = () => {
1336
1592
  for (let cb of [...listeners])
1337
1593
  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([
1594
+ }, 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
1595
  ["default", locked],
1340
1596
  ...Object.entries(options.presets ?? {}).map(([id, patch]) => {
1341
1597
  if (id === "default" || id === "custom")
@@ -1375,6 +1631,9 @@ function defineControls(id, input, options = {}) {
1375
1631
  if (data?.version === 1) {
1376
1632
  if (current = normalize(schema, data.values), Array.isArray(data.locks))
1377
1633
  locks = new Set(data.locks.filter((k) => typeof k === "string" && Object.hasOwn(schema, k)));
1634
+ if (data.bindings && typeof data.bindings === "object")
1635
+ bindings = new Map(Object.entries(data.bindings).flatMap(([id, combo]) => typeof combo === "string" && bindable(id) ? [[id, normalizeCombo(combo)]] : []));
1636
+ base = readBase(data.base);
1378
1637
  }
1379
1638
  }
1380
1639
  } catch (cause) {
@@ -1393,7 +1652,9 @@ function defineControls(id, input, options = {}) {
1393
1652
  valueStorage()?.setItem(stateKey, JSON.stringify({
1394
1653
  version: 1,
1395
1654
  values: current,
1396
- ...locks.size ? { locks: [...locks] } : {}
1655
+ ...locks.size ? { locks: [...locks] } : {},
1656
+ ...bindings.size ? { bindings: Object.fromEntries(bindings) } : {},
1657
+ ...base !== NO_BASE ? { base } : {}
1397
1658
  })), error = null;
1398
1659
  } catch (cause) {
1399
1660
  error = String(cause);
@@ -1402,6 +1663,74 @@ function defineControls(id, input, options = {}) {
1402
1663
  function commit(next) {
1403
1664
  current = next, persist(), notify();
1404
1665
  }
1666
+ 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);
1667
+ function validBase(b) {
1668
+ 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);
1669
+ return sameBase(next, b) ? b : next;
1670
+ }
1671
+ function readBase(raw) {
1672
+ if (!raw || typeof raw !== "object")
1673
+ return NO_BASE;
1674
+ 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]))) : {};
1675
+ return validBase(makeBase(ref, byGroup));
1676
+ }
1677
+ function activeBase() {
1678
+ load();
1679
+ let ref = base.preset;
1680
+ if (ref?.kind === "variant" && !variants.list().some((v) => v.id === ref.id))
1681
+ base = makeBase(null, base.groups);
1682
+ return base;
1683
+ }
1684
+ let hasBase = () => activeBase() !== NO_BASE, baseCache;
1685
+ function baseValues() {
1686
+ if (!enabled())
1687
+ return locked;
1688
+ let b = activeBase();
1689
+ if (b === NO_BASE)
1690
+ return locked;
1691
+ let list = b.preset?.kind === "variant" ? variants.list() : null, c = baseCache;
1692
+ if (c && c.base === b && c.schema === schema && c.presets === presets && c.groupPresets === groupPresets && c.variants === list)
1693
+ return c.values;
1694
+ let out = locked, ref = b.preset;
1695
+ if (ref?.kind === "preset" && Object.hasOwn(presets, ref.id))
1696
+ out = presets[ref.id];
1697
+ else if (ref?.kind === "variant") {
1698
+ let found = list?.find((v) => v.id === ref.id);
1699
+ if (found)
1700
+ out = normalize(schema, found.values);
1701
+ }
1702
+ let groups = Object.entries(b.groups);
1703
+ if (groups.length) {
1704
+ out = { ...out };
1705
+ for (let [g, i] of groups)
1706
+ Object.assign(out, groupPresets[g]?.[i]?.values);
1707
+ }
1708
+ let values = Object.freeze(out);
1709
+ return baseCache = { base: b, schema, presets, groupPresets, variants: list, values }, values;
1710
+ }
1711
+ let covered = (key) => {
1712
+ let b = activeBase();
1713
+ if (b.preset)
1714
+ return !0;
1715
+ let group = schema[key]?.group;
1716
+ return group !== void 0 && Object.hasOwn(b.groups, group);
1717
+ };
1718
+ function declaredShortcut(id) {
1719
+ let slash = id.indexOf("/"), key = slash < 0 ? id : id.slice(0, slash), field = Object.hasOwn(schema, key) ? schema[key] : void 0;
1720
+ if (field?.kind === "action" && slash < 0)
1721
+ return { found: !0, ...field.shortcut ? { combo: field.shortcut } : {} };
1722
+ if (field?.kind === "toolbar" && slash >= 0) {
1723
+ let item = field.items.find((i) => (i.id ?? i.label) === id.slice(slash + 1));
1724
+ if (!item)
1725
+ return { found: !1 };
1726
+ let key = toolbarShortcut(item)?.key;
1727
+ return { found: !0, ...key ? { combo: normalizeCombo(key) } : {} };
1728
+ }
1729
+ return { found: !1 };
1730
+ }
1731
+ function bindable(id) {
1732
+ return declaredShortcut(id).found;
1733
+ }
1405
1734
  let fieldKeys = () => valueKeys(schema), valueField = (k) => {
1406
1735
  let field = Object.hasOwn(schema, k) ? schema[k] : void 0;
1407
1736
  return field && hasValue(field) ? field : void 0;
@@ -1464,10 +1793,13 @@ function defineControls(id, input, options = {}) {
1464
1793
  pending.delete(k);
1465
1794
  };
1466
1795
  function set(patch, setOpts) {
1796
+ write(patch, setOpts);
1797
+ }
1798
+ function write(patch, setOpts, nextBase) {
1467
1799
  if (!enabled())
1468
1800
  return;
1469
1801
  release();
1470
- let previous = load(), forced = mixed.size ? Object.keys(patch).filter((k) => mixed.has(k) && valueField(k)) : [];
1802
+ 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
1803
  for (let k of forced)
1472
1804
  mixed.delete(k);
1473
1805
  let applied = applyPatch(previous, previous, patch) ?? {
@@ -1477,9 +1809,10 @@ function defineControls(id, input, options = {}) {
1477
1809
  for (let k of forced)
1478
1810
  if (!(k in diff))
1479
1811
  diff[k] = next[k];
1480
- if (Object.keys(diff).length === 0)
1812
+ let changed = Object.keys(diff).length > 0;
1813
+ if (!changed && !moves)
1481
1814
  return;
1482
- if (next === previous) {
1815
+ if (next === previous && !moves) {
1483
1816
  notify(), options.onChange?.(diff, next, {
1484
1817
  source: setOpts?.source ?? "local",
1485
1818
  ...setOpts?.key ? { key: setOpts.key } : {}
@@ -1487,16 +1820,27 @@ function defineControls(id, input, options = {}) {
1487
1820
  return;
1488
1821
  }
1489
1822
  if (!setOpts?.coalesce || past.length === 0) {
1490
- if (past.push(previous), past.length > HISTORY)
1823
+ if (past.push({ values: previous, base: was }), past.length > HISTORY)
1491
1824
  past.shift();
1492
1825
  }
1493
- future.length = 0, markChanged(diff), commit(next), options.onChange?.(diff, next, {
1826
+ if (future.length = 0, moves)
1827
+ base = nextBase;
1828
+ if (markChanged(diff), commit(next), !changed)
1829
+ return;
1830
+ options.onChange?.(diff, next, {
1494
1831
  source: setOpts?.source ?? "local",
1495
1832
  ...setOpts?.coalesce ? { coalesce: !0 } : {},
1496
1833
  ...setOpts?.key ? { key: setOpts.key } : {},
1497
- ...setOpts?.group ? { group: setOpts.group } : {}
1834
+ ...setOpts?.group ? { group: setOpts.group } : {},
1835
+ ...setOpts?.base ? { base: !0 } : {}
1498
1836
  });
1499
1837
  }
1838
+ function step(to, source) {
1839
+ let before = load(), left = { values: before, base: activeBase() }, diff = changes(before, to.values);
1840
+ if (base = validBase(to.base), markChanged(diff), commit(to.values), Object.keys(diff).length)
1841
+ options.onChange?.(diff, to.values, { source });
1842
+ return left;
1843
+ }
1500
1844
  function place(values, replace, land) {
1501
1845
  let previous = load(), applied = applyPatch(replace ? locked : previous, previous, values);
1502
1846
  if (!applied)
@@ -1604,7 +1948,7 @@ function defineControls(id, input, options = {}) {
1604
1948
  schema = Object.freeze({ ...next });
1605
1949
  if (changed) {
1606
1950
  let previous = load();
1607
- schema = Object.freeze({ ...next }), locked = normalize(schema, {}), presets = buildPresets(), groupPresets = buildGroupPresets();
1951
+ schema = Object.freeze({ ...next }), locked = normalize(schema, {}), presets = buildPresets(), groupPresets = buildGroupPresets(), base = validBase(base);
1608
1952
  for (let k of [...locks])
1609
1953
  if (!valueField(k))
1610
1954
  locks.delete(k);
@@ -1639,7 +1983,60 @@ function defineControls(id, input, options = {}) {
1639
1983
  listen(cb) {
1640
1984
  return readoutListeners.add(cb), () => readoutListeners.delete(cb);
1641
1985
  }
1642
- }, controls = {
1986
+ }, 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));
1987
+ function offBase(keys) {
1988
+ if (!hasBase())
1989
+ return !1;
1990
+ let cur = load(), b = baseValues();
1991
+ return keys.some((k) => !same(cur[k], b[k]));
1992
+ }
1993
+ function toBase(keys, meta) {
1994
+ let b = baseValues(), patch = Object.fromEntries(keys.map((k) => [k, b[k]]));
1995
+ write(patch, { source: "reset", base: !0, ...meta });
1996
+ }
1997
+ function toDefaults(keys, nextBase) {
1998
+ let patch = {};
1999
+ for (let k of keys)
2000
+ patch[k] = locked[k];
2001
+ write(patch, { source: "reset" }, nextBase);
2002
+ let resetKeys = options[RESET_KEYS];
2003
+ if (resetKeys)
2004
+ resetKeys(Object.keys(patch));
2005
+ else
2006
+ for (let key of Object.keys(patch))
2007
+ options.onReset?.({ key });
2008
+ }
2009
+ function live(load, make, unmake) {
2010
+ let disposed = !1, loading = !1, handle, sync = () => {
2011
+ if (disposed)
2012
+ return;
2013
+ if (!enabled()) {
2014
+ if (handle !== void 0)
2015
+ unmake(handle);
2016
+ handle = void 0;
2017
+ return;
2018
+ }
2019
+ if (handle !== void 0 || loading)
2020
+ return;
2021
+ let pending = load();
2022
+ if (!pending)
2023
+ return;
2024
+ loading = !0, pending.then((module) => {
2025
+ if (loading = !1, !disposed && enabled() && handle === void 0)
2026
+ handle = make(module);
2027
+ }).catch((cause) => {
2028
+ loading = !1, error = String(cause), notify();
2029
+ });
2030
+ };
2031
+ sync();
2032
+ let off = onUnlock(sync);
2033
+ return () => {
2034
+ if (disposed = !0, off(), handle !== void 0)
2035
+ unmake(handle);
2036
+ handle = void 0;
2037
+ };
2038
+ }
2039
+ let controls = {
1643
2040
  id,
1644
2041
  get schema() {
1645
2042
  return schema;
@@ -1658,9 +2055,10 @@ function defineControls(id, input, options = {}) {
1658
2055
  marks: options.marks !== !1,
1659
2056
  chip: Object.freeze({
1660
2057
  ...options.chip,
1661
- reveal: options.chip?.reveal ?? (devBuild() ? "hover" : "shortcut"),
2058
+ reveal: options.chip?.reveal ?? (devBuild() || !options.ship ? "hover" : "shortcut"),
1662
2059
  shortcut: options.chip?.shortcut ?? "Alt+Shift+D"
1663
2060
  }),
2061
+ toggleKey: options.toggleKey ?? (options.ship ? !1 : "`"),
1664
2062
  groupPresetDisplay: options.groupPresetDisplay ?? "auto",
1665
2063
  groupLabel: (group) => options.groupLabels?.[group] ?? fieldLabels().group.get(group),
1666
2064
  sectionLabel: (section) => options.sectionLabels?.[section] ?? fieldLabels().section.get(section),
@@ -1741,8 +2139,7 @@ function defineControls(id, input, options = {}) {
1741
2139
  let previous = past.pop();
1742
2140
  if (!previous)
1743
2141
  return;
1744
- let before = load(), diff = changes(before, previous);
1745
- future.push(before), markChanged(diff), commit(previous), options.onChange?.(diff, previous, { source: "undo" });
2142
+ future.push(step(previous, "undo"));
1746
2143
  },
1747
2144
  redo() {
1748
2145
  if (release(), hostHistory)
@@ -1750,37 +2147,30 @@ function defineControls(id, input, options = {}) {
1750
2147
  let next = future.pop();
1751
2148
  if (!next)
1752
2149
  return;
1753
- let before = load(), diff = changes(before, next);
1754
- past.push(before), markChanged(diff), commit(next), options.onChange?.(diff, next, { source: "redo" });
2150
+ past.push(step(next, "redo"));
1755
2151
  },
1756
2152
  getServerSnapshot: () => locked,
1757
2153
  subscribe(cb) {
1758
2154
  if (listeners.add(cb), hostHistory?.subscribe && !unsubscribeHistory)
1759
2155
  unsubscribeHistory = hostHistory.subscribe(notify);
1760
- return () => {
1761
- if (listeners.delete(cb), !listeners.size && unsubscribeHistory)
1762
- unsubscribeHistory(), unsubscribeHistory = void 0;
2156
+ return unsubscribeUnlock ??= onUnlock(notify), () => {
2157
+ if (listeners.delete(cb), listeners.size)
2158
+ return;
2159
+ unsubscribeHistory?.(), unsubscribeHistory = void 0, unsubscribeUnlock?.(), unsubscribeUnlock = void 0;
1763
2160
  };
1764
2161
  },
1765
2162
  reset(keys) {
1766
2163
  if (!enabled())
1767
2164
  return;
2165
+ let open = resettable(keys);
2166
+ if (offBase(open))
2167
+ return toBase(open, {});
1768
2168
  let skipped = fieldKeys().some(blocked);
1769
2169
  if (!keys && !skipped) {
1770
- set(locked, { source: "reset" }), options.onReset?.({});
2170
+ write(locked, { source: "reset" }, NO_BASE), options.onReset?.({});
1771
2171
  return;
1772
2172
  }
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 });
2173
+ toDefaults(open, keys ? void 0 : NO_BASE);
1784
2174
  },
1785
2175
  resetKey(key) {
1786
2176
  if (!enabled())
@@ -1789,24 +2179,40 @@ function defineControls(id, input, options = {}) {
1789
2179
  throw Error(`Unknown control key: ${key}`);
1790
2180
  if (blocked(key))
1791
2181
  return;
2182
+ if (offBase([key]))
2183
+ return toBase([key], { key });
1792
2184
  set({ [key]: locked[key] }, { source: "reset", key }), options.onReset?.({ key });
1793
2185
  },
1794
2186
  resetGroup(group) {
1795
2187
  if (!enabled())
1796
2188
  return;
1797
- let keys = Object.keys(schema).filter((k) => schema[k].group === group && hasValue(schema[k])), open = keys.filter((k) => !blocked(k));
2189
+ 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];
2190
+ if (offBase(open)) {
2191
+ toBase(open, { group });
2192
+ let preset = index === void 0 ? void 0 : groupPresets[group]?.[index];
2193
+ if (preset && index !== void 0)
2194
+ options.onPreset?.({ group, index, name: preset.name });
2195
+ return;
2196
+ }
2197
+ let { [group]: _, ...others } = b.groups, forget = index === void 0 ? void 0 : makeBase(b.preset, others);
1798
2198
  if (open.length < keys.length)
1799
- return controls.reset(open);
2199
+ return toDefaults(open, forget);
1800
2200
  let patch = {};
1801
2201
  for (let k of keys)
1802
2202
  patch[k] = locked[k];
1803
- set(patch, { source: "reset", group }), options.onReset?.({ group });
2203
+ write(patch, { source: "reset", group }, forget), options.onReset?.({ group });
1804
2204
  },
1805
2205
  isModified(key) {
1806
- let cur = get();
2206
+ let cur = get(), b = baseValues();
1807
2207
  if (key !== void 0)
1808
- return !same(cur[key], locked[key]);
1809
- return !equal(cur, locked);
2208
+ return !same(cur[key], b[key]);
2209
+ return !equal(cur, b);
2210
+ },
2211
+ canReset(keys) {
2212
+ if (!enabled())
2213
+ return !1;
2214
+ let cur = get(), b = baseValues();
2215
+ return resettable(keys).some((k) => !same(cur[k], b[k]) || !same(cur[k], locked[k])) || !keys && hasBase();
1810
2216
  },
1811
2217
  modified() {
1812
2218
  let cur = get(), out = {};
@@ -1821,8 +2227,17 @@ function defineControls(id, input, options = {}) {
1821
2227
  let chosen = pool(keys);
1822
2228
  if (chosen.length === 0)
1823
2229
  return;
1824
- let sub = Object.fromEntries(chosen.map((k) => [k, schema[k]]));
1825
- set(randomValues(sub, draw), { source: "randomize" });
2230
+ if (!hasBase()) {
2231
+ let sub = Object.fromEntries(chosen.map((k) => [k, schema[k]]));
2232
+ set(randomValues(sub, draw), { source: "randomize" });
2233
+ return;
2234
+ }
2235
+ let b = baseValues(), patch = {};
2236
+ for (let k of chosen) {
2237
+ let field = valueField(k);
2238
+ patch[k] = covered(k) ? around(field, b[k], draw) : pick(field, draw);
2239
+ }
2240
+ set(patch, { source: "randomize" });
1826
2241
  },
1827
2242
  mutate(keys) {
1828
2243
  if (!enabled())
@@ -1858,6 +2273,33 @@ function defineControls(id, input, options = {}) {
1858
2273
  return !1;
1859
2274
  return load(), locks.has(key);
1860
2275
  },
2276
+ shortcut(key, item) {
2277
+ let id = item === void 0 ? key : `${key}/${item}`, declared = declaredShortcut(id);
2278
+ if (!declared.found)
2279
+ return;
2280
+ if (enabled())
2281
+ load();
2282
+ return (enabled() ? bindings.get(id) : void 0) ?? declared.combo;
2283
+ },
2284
+ rebind(key, combo, item) {
2285
+ let id = item === void 0 ? key : `${key}/${item}`, declared = declaredShortcut(id);
2286
+ if (!declared.found)
2287
+ throw Error(`Not an action or toolbar button: ${id}`);
2288
+ if (!enabled())
2289
+ return;
2290
+ load();
2291
+ let next = combo === void 0 ? void 0 : normalizeCombo(combo);
2292
+ if (combo !== void 0 && combo.trim() && !next)
2293
+ throw Error(`Not a key combination: ${combo}`);
2294
+ let was = bindings.get(id);
2295
+ if (next === void 0 || next === (declared.combo ?? ""))
2296
+ bindings.delete(id);
2297
+ else
2298
+ bindings.set(id, next);
2299
+ if (bindings.get(id) === was)
2300
+ return;
2301
+ persist(), notify();
2302
+ },
1861
2303
  canRoll(key) {
1862
2304
  if (!enabled())
1863
2305
  return !1;
@@ -1891,15 +2333,15 @@ function defineControls(id, input, options = {}) {
1891
2333
  return;
1892
2334
  if (!hold)
1893
2335
  return release();
1894
- let before = past.at(-1), now = load();
2336
+ let before = past.at(-1)?.values, now = load();
1895
2337
  if (comparing || !before || equal(before, now))
1896
2338
  return;
1897
2339
  comparing = before, options.onChange?.(changes(now, before), before, { source: "compare" }), notify();
1898
2340
  },
1899
- preset(id) {
2341
+ preset(id, presetOptions) {
1900
2342
  if (!Object.hasOwn(presets, id))
1901
2343
  throw Error(`Unknown preset: ${id}`);
1902
- set(presets[id], { source: "preset" });
2344
+ write(presets[id], { source: "preset", coalesce: presetOptions?.coalesce }, id === "default" ? NO_BASE : makeBase(Object.freeze({ kind: "preset", id }), {}));
1903
2345
  },
1904
2346
  matchingPreset: () => Object.keys(presets).find((id) => equal(get(), presets[id])) ?? "custom",
1905
2347
  get groupPresets() {
@@ -1911,41 +2353,36 @@ function defineControls(id, input, options = {}) {
1911
2353
  throw Error(`Unknown preset ${index + 1} for group ${group}`);
1912
2354
  if (!enabled())
1913
2355
  return;
1914
- set(preset.values, { source: "preset", group, coalesce: presetOptions?.coalesce }), options.onPreset?.({ group, index, name: preset.name });
2356
+ let b = activeBase();
2357
+ write(preset.values, { source: "preset", group, coalesce: presetOptions?.coalesce }, makeBase(b.preset, { ...b.groups, [group]: index })), options.onPreset?.({ group, index, name: preset.name });
2358
+ },
2359
+ activePreset() {
2360
+ if (!enabled())
2361
+ return null;
2362
+ return activeBase().preset;
2363
+ },
2364
+ activeGroupPreset(group) {
2365
+ if (!enabled())
2366
+ return -1;
2367
+ return activeBase().groups[group] ?? -1;
1915
2368
  },
2369
+ baseValues,
1916
2370
  matchingGroupPreset(group) {
1917
2371
  let cur = get();
1918
2372
  return (groupPresets[group] ?? []).findIndex((p) => Object.entries(p.values).every(([k, v]) => same(cur[k], v)));
1919
2373
  },
1920
2374
  code: (format, values) => formatCode(values ?? get(), format),
1921
- variants: createVariants(() => schema, id, storageKey(id, "variants"), variantStorage, get, (v) => set(v, { source: "variant" }), notify, enabled),
2375
+ variants,
1922
2376
  attach(element, chip) {
1923
- let load = options.ship ? () => options.ship().then((m) => m.attach) : void 0;
1924
- if (!load || !enabled())
1925
- return () => {};
1926
- let disposed = !1, detach;
1927
- return load().then((attach) => {
1928
- if (!disposed && enabled())
1929
- detach = attach(controls, element, chip);
1930
- }).catch((cause) => {
1931
- error = String(cause), notify();
1932
- }), () => {
1933
- disposed = !0, detach?.();
1934
- };
2377
+ let load = options.ship ? () => options.ship().then((m) => m.attach) : () => unlockedPanel()?.().then((m) => m.attach);
2378
+ return live(load, (attach) => attach(controls, element, chip), (detach) => detach());
1935
2379
  },
1936
2380
  mount(container, mountOptions) {
1937
- let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : void 0;
1938
- if (!load || !enabled())
1939
- return () => {};
1940
- let disposed = !1, panel;
1941
- return load().then((mountPanel) => {
1942
- if (!disposed && enabled())
1943
- panel = mountPanel(controls, container, mountOptions), mountOptions?.onReady?.(panel);
1944
- }).catch((cause) => {
1945
- error = String(cause), notify();
1946
- }), () => {
1947
- disposed = !0, panel?.dispose();
1948
- };
2381
+ let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => unlockedPanel()?.().then((m) => m.mountPanel);
2382
+ return live(load, (mountPanel) => {
2383
+ let panel = mountPanel(controls, container, mountOptions);
2384
+ return mountOptions?.onReady?.(panel), panel;
2385
+ }, (panel) => panel.dispose());
1949
2386
  }
1950
2387
  };
1951
2388
  return Object.freeze(controls);
@@ -1970,7 +2407,11 @@ var optionId = (o) => typeof o === "string" ? o : String(o.id ?? o.value ?? o.la
1970
2407
  function descriptorToField(d) {
1971
2408
  let meta = pickMeta(d);
1972
2409
  if (d.kind === "action")
1973
- return action(d.label, meta);
2410
+ return action(d.label, {
2411
+ ...meta,
2412
+ ...d.global ? { global: !0 } : {},
2413
+ ...d.rebind ? { rebind: !0 } : {}
2414
+ });
1974
2415
  if (readoutDescriptor(d))
1975
2416
  return readoutField(d);
1976
2417
  if (d.kind === "toolbar")
@@ -2221,6 +2662,7 @@ function createRemoteControls(id, descriptors, options = {}) {
2221
2662
  }, controls = defineControls(id, fromDescriptors(frozenDescriptors), {
2222
2663
  enabled: options.enabled ?? !0,
2223
2664
  ship: options.ship,
2665
+ toggleKey: options.toggleKey,
2224
2666
  echo: options.echo ?? ECHO_MS,
2225
2667
  storage: options.storage ?? null,
2226
2668
  persist: options.persist,
@@ -2246,9 +2688,11 @@ function createRemoteControls(id, descriptors, options = {}) {
2246
2688
  groupFolded: options.groupFolded,
2247
2689
  sectionFolded: options.sectionFolded,
2248
2690
  onChange(patch, _values, meta) {
2249
- if (meta.source === "remote" || meta.source === "reset")
2691
+ if (meta.source === "remote")
2692
+ return;
2693
+ if (meta.source === "reset" && !meta.base)
2250
2694
  return;
2251
- if (meta.source === "preset" && meta.group !== void 0 && options.onPreset)
2695
+ if ((meta.source === "preset" || meta.base && meta.group !== void 0 && controls.activeGroupPreset(meta.group) >= 0) && meta.group !== void 0 && options.onPreset)
2252
2696
  return;
2253
2697
  let changes = [];
2254
2698
  for (let [key, val] of Object.entries(patch)) {
@@ -2309,10 +2753,13 @@ function fromSettings(groups, home = {}) {
2309
2753
  return { descriptors, values };
2310
2754
  }
2311
2755
  export {
2756
+ AROUND,
2312
2757
  CURVE_SHAPES,
2313
2758
  EASINGS,
2759
+ KONAMI,
2314
2760
  SPRING,
2315
2761
  action,
2762
+ actionShortcut,
2316
2763
  angle,
2317
2764
  bezierCss,
2318
2765
  bezierPoint,
@@ -2359,6 +2806,7 @@ export {
2359
2806
  toggle,
2360
2807
  toolbar,
2361
2808
  toolbarShortcut,
2809
+ unlock,
2362
2810
  urlFlag,
2363
2811
  vec2,
2364
2812
  vec3,