@kitlangton/knobs 0.2.0 → 0.4.0-next.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 (62) hide show
  1. package/README.md +197 -19
  2. package/dist/dev/{chunk-gezhc24g.js → chunk-7xgkerd2.js} +14 -5
  3. package/dist/dev/{chunk-693jd1gj.js → chunk-pmkvb9ep.js} +1544 -160
  4. package/dist/dev/{chunk-5cr85vv6.js → chunk-stbcgz1y.js} +7924 -7931
  5. package/dist/dev/{chunk-4km87zfx.js → chunk-t68smqkc.js} +18 -7
  6. package/dist/dev/index.js +187 -31
  7. package/dist/index.js +197 -33
  8. package/dist/panel.js +20342 -18461
  9. package/dist/types/core/keys.d.ts +2 -0
  10. package/dist/types/core/remote.d.ts +2 -0
  11. package/dist/types/core/schema.d.ts +5 -0
  12. package/dist/types/core/store.d.ts +29 -2
  13. package/dist/types/core/unlock.d.ts +77 -0
  14. package/dist/types/index.d.ts +1 -0
  15. package/dist/types/panel/attach.d.ts +2 -0
  16. package/dist/types/panel/crumbs.d.ts +19 -0
  17. package/dist/types/panel/editors.d.ts +9 -0
  18. package/dist/types/panel/feel.d.ts +103 -0
  19. package/dist/types/panel/give.d.ts +34 -1
  20. package/dist/types/panel/gpu/color.d.ts +4 -4
  21. package/dist/types/panel/gpu/renderer.d.ts +32 -0
  22. package/dist/types/panel/gpu/shader.wgsl.d.ts +1 -1
  23. package/dist/types/panel/index.d.ts +2 -1
  24. package/dist/types/panel/inspector.d.ts +1 -1
  25. package/dist/types/panel/lens.d.ts +113 -0
  26. package/dist/types/panel/motion.d.ts +2 -0
  27. package/dist/types/panel/page-hints.d.ts +2 -1
  28. package/dist/types/panel/prefs.d.ts +2 -2
  29. package/dist/types/panel/presence.d.ts +20 -0
  30. package/dist/types/panel/scope.d.ts +29 -0
  31. package/dist/types/panel/slider.d.ts +21 -4
  32. package/dist/types/panel/tab-transition.d.ts +15 -0
  33. package/dist/types/panel/tabs.d.ts +1 -1
  34. package/dist/types/panel/toggle.d.ts +27 -0
  35. package/dist/types/ui/compare-css.d.ts +1 -0
  36. package/dist/types/ui/compare-frames.d.ts +41 -0
  37. package/dist/types/ui/compare-gpu.d.ts +58 -0
  38. package/dist/types/ui/compare-model.d.ts +169 -0
  39. package/dist/types/ui/compare-mp4.d.ts +29 -0
  40. package/dist/types/ui/compare-strip.d.ts +28 -0
  41. package/dist/types/ui/compare.d.ts +103 -0
  42. package/dist/types/ui/diff-css.d.ts +1 -0
  43. package/dist/types/ui/diff-model.d.ts +128 -0
  44. package/dist/types/ui/diff-scroll.d.ts +29 -0
  45. package/dist/types/ui/diff.d.ts +158 -0
  46. package/dist/types/ui/index.d.ts +9 -1
  47. package/dist/types/ui/look.d.ts +1 -1
  48. package/dist/types/ui/react.d.ts +66 -0
  49. package/dist/types/ui/scrollbar.d.ts +12 -0
  50. package/dist/types/ui/solid.d.ts +52 -0
  51. package/dist/types/ui/split-model.d.ts +4 -3
  52. package/dist/types/ui/split.d.ts +8 -6
  53. package/dist/types/ui/timeline.d.ts +35 -2
  54. package/dist/types/ui/tree.d.ts +7 -2
  55. package/dist/types/ui/walkthrough-css.d.ts +1 -0
  56. package/dist/types/ui/walkthrough-media.d.ts +29 -0
  57. package/dist/types/ui/walkthrough-model.d.ts +447 -0
  58. package/dist/types/ui/walkthrough.d.ts +63 -0
  59. package/dist/ui-react.js +16486 -8140
  60. package/dist/ui-solid.js +16030 -7775
  61. package/dist/ui.js +17510 -9506
  62. package/package.json +6 -1
@@ -1,12 +1,13 @@
1
1
  import {
2
2
  __preload
3
- } from "./chunk-gezhc24g.js";
3
+ } from "./chunk-7xgkerd2.js";
4
4
  import {
5
5
  iconSvg,
6
6
  readSession,
7
7
  writeSession,
8
- injectStyles
9
- } from "./chunk-693jd1gj.js";
8
+ injectStyles,
9
+ toggleLayer
10
+ } from "./chunk-pmkvb9ep.js";
10
11
 
11
12
  // src/panel/attach.ts
12
13
  function attach(controls, element, options = {}) {
@@ -32,11 +33,11 @@ function attach(controls, element, options = {}) {
32
33
  let addedTabIndex = !element.hasAttribute("tabindex") && !element.matches("a[href],button,input,select,textarea");
33
34
  if (addedTabIndex)
34
35
  element.tabIndex = 0;
35
- let hovered = element.matches(":hover"), disposed = !1, loading = !1, panel, leaving, revealed = () => reveal !== "shortcut" || knobsMode(win), position = () => {
36
+ let hovered = element.matches(":hover"), disposed = !1, loading = !1, panel, leaving, toggledOn = !0, revealed = () => reveal !== "shortcut" || knobsMode(win), position = () => {
36
37
  let box = element.getBoundingClientRect();
37
38
  chip.style.left = `${Math.max(4, Math.min(win.innerWidth - chip.offsetWidth - 4, box.right - chip.offsetWidth - 6))}px`, chip.style.top = `${Math.max(4, box.top + 6)}px`;
38
39
  }, update = () => {
39
- let shown = controls.enabled && revealed() && !panel && (reveal === "always" || hovered || element.contains(doc.activeElement) || doc.activeElement === chip);
40
+ let shown = controls.enabled && toggledOn && revealed() && !panel && (reveal === "always" || hovered || element.contains(doc.activeElement) || doc.activeElement === chip);
40
41
  if (shown)
41
42
  position();
42
43
  if (setShown(shown), !controls.enabled)
@@ -82,7 +83,7 @@ function attach(controls, element, options = {}) {
82
83
  return;
83
84
  loading = !0;
84
85
  try {
85
- let { createPanel } = await (__preload("5cr85vv6"), import("./chunk-5cr85vv6.js"));
86
+ let { createPanel } = await (__preload("stbcgz1y"), import("./chunk-stbcgz1y.js"));
86
87
  if (disposed || !controls.enabled || panel)
87
88
  return;
88
89
  if (panel = createPanel(controls, element, close, { restored, chip }), remember)
@@ -105,9 +106,19 @@ function attach(controls, element, options = {}) {
105
106
  update();
106
107
  }, offShortcut = reveal === "shortcut" ? listenForShortcut(win, shortcut, controls.theme) : void 0;
107
108
  win.addEventListener(MODE_EVENT, onMode);
109
+ let toggled = controls.toggleKey ? toggleLayer(doc).register({
110
+ id: controls.id,
111
+ key: controls.toggleKey,
112
+ storage: () => controls.storage,
113
+ root: chip,
114
+ show: (shown) => {
115
+ toggledOn = shown, update();
116
+ }
117
+ }) : void 0;
118
+ toggledOn = toggled?.shown ?? !0;
108
119
  let unsubscribe = controls.subscribe(update);
109
120
  return update(), () => {
110
- disposed = !0, unsubscribe(), panel?.dispose(), leaving?.dispose(), observer.disconnect(), offShortcut?.(), win.removeEventListener(MODE_EVENT, onMode), win.removeEventListener("scroll", position, !0), win.removeEventListener("resize", position);
121
+ disposed = !0, toggled?.dispose(), unsubscribe(), panel?.dispose(), leaving?.dispose(), observer.disconnect(), offShortcut?.(), win.removeEventListener(MODE_EVENT, onMode), win.removeEventListener("scroll", position, !0), win.removeEventListener("resize", position);
111
122
  for (let node of [element, chip])
112
123
  node.removeEventListener("pointerenter", enter), node.removeEventListener("pointerleave", leave), node.removeEventListener("focusin", update), node.removeEventListener("focusout", focusOut);
113
124
  if (chip.remove(), addedTabIndex && element.getAttribute("tabindex") === "0")
package/dist/dev/index.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  matchesCombo2,
16
16
  comboParts2,
17
17
  comboLabel2,
18
+ isTypingTarget,
18
19
  sampleGradient2,
19
20
  gradientCss2,
20
21
  sampleCurve2,
@@ -72,8 +73,8 @@ import {
72
73
  browserStorage,
73
74
  urlFlag2,
74
75
  storageKey
75
- } from "./chunk-gezhc24g.js";
76
- __chunks(import.meta.url,["jx253znt","5cr85vv6","4km87zfx","gezhc24g","693jd1gj"],[["./index.js",3],["./chunk-5cr85vv6.js",3,4],["./chunk-4km87zfx.js",3,4],["./chunk-gezhc24g.js"],["./chunk-693jd1gj.js",3]],0);
76
+ } from "./chunk-7xgkerd2.js";
77
+ __chunks(import.meta.url,["freshcfm","stbcgz1y","t68smqkc","7xgkerd2","pmkvb9ep"],[["./index.js",3],["./chunk-stbcgz1y.js",3,4],["./chunk-t68smqkc.js",3,4],["./chunk-7xgkerd2.js"],["./chunk-pmkvb9ep.js",3]],0);
77
78
  // src/core/random.ts
78
79
  var lattice = (min, max, step, r) => min + Math.floor(r * (Math.floor((max - min) / step + 0.000000001) + 1)) * step, between = (min, max, r) => min + (max - min) * r;
79
80
  function pick(field, random) {
@@ -337,6 +338,143 @@ function mutateValues(schema, current, keys = valueKeys(schema), random = Math.r
337
338
  return out;
338
339
  }
339
340
 
341
+ // src/core/unlock.ts
342
+ var KONAMI = Object.freeze([
343
+ "ArrowUp",
344
+ "ArrowUp",
345
+ "ArrowDown",
346
+ "ArrowDown",
347
+ "ArrowLeft",
348
+ "ArrowRight",
349
+ "ArrowLeft",
350
+ "ArrowRight",
351
+ "b",
352
+ "a"
353
+ ]), GATE = Symbol.for("knobs.unlock.v1"), gate = () => {
354
+ let holder = globalThis;
355
+ return holder[GATE] ??= { unlocked: !1, listeners: /* @__PURE__ */ new Set };
356
+ }, isUnlocked = () => globalThis[GATE]?.unlocked ?? !1;
357
+ function onUnlock(listener) {
358
+ let listeners = gate().listeners;
359
+ return listeners.add(listener), () => void listeners.delete(listener);
360
+ }
361
+ var UNLOCK_KEY = "knobs:unlocked";
362
+ function sequenceKeys(sequence) {
363
+ return typeof sequence === "string" ? [...sequence] : [...sequence];
364
+ }
365
+ var fold = (key) => key.length === 1 ? key.toLowerCase() : key;
366
+ function sequenceMatcher(sequence, gap = 1500) {
367
+ let want = sequenceKeys(sequence).map(fold), typed = [], last = -1 / 0;
368
+ return (key, at) => {
369
+ if (!want.length)
370
+ return !1;
371
+ if (at - last > gap)
372
+ typed = [];
373
+ if (last = at, typed.push(fold(key)), typed.length > want.length)
374
+ typed.shift();
375
+ if (typed.length === want.length && typed.every((k, i) => k === want[i]))
376
+ return typed = [], !0;
377
+ return !1;
378
+ };
379
+ }
380
+ function queryUnlock(search, name) {
381
+ let params = new URLSearchParams(search);
382
+ if (!params.has(name))
383
+ return;
384
+ return !["off", "0", "false", "lock", "no"].includes((params.get(name) ?? "").toLowerCase());
385
+ }
386
+ function resolveUnlock(input) {
387
+ let unlocked = input.query ?? (input.persist && input.stored);
388
+ return { unlocked, remember: input.persist && input.query !== void 0 ? unlocked : void 0 };
389
+ }
390
+ function inCorner(x, y, width, height, corner, size = 48) {
391
+ let left = corner.endsWith("left") ? x <= size : x >= width - size, top = corner.startsWith("top") ? y <= size : y >= height - size;
392
+ return left && top;
393
+ }
394
+ function tapCounter(count = 3, within = 500) {
395
+ let taps = 0, last = -1 / 0;
396
+ return (at) => {
397
+ if (taps = at - last <= within ? taps + 1 : 1, last = at, taps < count)
398
+ return !1;
399
+ return taps = 0, !0;
400
+ };
401
+ }
402
+ var readStored = () => {
403
+ try {
404
+ return globalThis.localStorage?.getItem(UNLOCK_KEY) === "1";
405
+ } catch {
406
+ return !1;
407
+ }
408
+ }, writeStored = (on) => {
409
+ try {
410
+ if (on)
411
+ globalThis.localStorage?.setItem(UNLOCK_KEY, "1");
412
+ else
413
+ globalThis.localStorage?.removeItem(UNLOCK_KEY);
414
+ } catch {}
415
+ };
416
+ function unlock(options) {
417
+ let g = gate();
418
+ g.panel = options.panel;
419
+ let persist = options.persist !== !1, query = options.query === void 0 ? "knobs" : options.query, sequence = options.sequence === void 0 ? "knobs" : options.sequence, corner = options.corner === void 0 ? "top-right" : options.corner, win = typeof window > "u" ? void 0 : window, announce = (text) => {
420
+ (g.module ? Promise.resolve(g.module) : g.panel?.())?.then((m) => {
421
+ if (g.module = m, g.unlocked === (text === "knobs unlocked"))
422
+ m.status?.(text);
423
+ }).catch(() => {});
424
+ }, set = (on, say) => {
425
+ if (persist)
426
+ writeStored(on);
427
+ if (g.unlocked === on)
428
+ return;
429
+ g.unlocked = on;
430
+ for (let listener of [...g.listeners])
431
+ listener();
432
+ if (say)
433
+ announce(on ? "knobs unlocked" : "knobs locked");
434
+ }, start = resolveUnlock({
435
+ query: query && win ? queryUnlock(win.location.search, query) : void 0,
436
+ stored: persist && readStored(),
437
+ persist
438
+ });
439
+ if (start.remember !== void 0)
440
+ writeStored(start.remember);
441
+ set(start.unlocked || g.unlocked, !1);
442
+ let offs = [];
443
+ if (win && sequence !== !1 && sequenceKeys(sequence).length) {
444
+ let typed = sequenceMatcher(sequence), onKey = (event) => {
445
+ if (event.ctrlKey || event.metaKey || event.altKey || event.isComposing || event.repeat)
446
+ return;
447
+ if (event.key === "Shift" || event.key === "CapsLock" || isTypingTarget(event.target))
448
+ return;
449
+ if (typed(event.key, event.timeStamp || performance.now()))
450
+ set(!g.unlocked, !0);
451
+ };
452
+ win.addEventListener("keydown", onKey, !0), offs.push(() => win.removeEventListener("keydown", onKey, !0));
453
+ }
454
+ if (win && corner) {
455
+ let tapped = tapCounter(), onTap = (event) => {
456
+ if (!event.isPrimary)
457
+ return;
458
+ if (!inCorner(event.clientX, event.clientY, win.innerWidth, win.innerHeight, corner))
459
+ return void (tapped = tapCounter());
460
+ if (tapped(event.timeStamp || performance.now()))
461
+ set(!g.unlocked, !0);
462
+ };
463
+ win.addEventListener("pointerdown", onTap, { capture: !0, passive: !0 }), offs.push(() => win.removeEventListener("pointerdown", onTap, !0));
464
+ }
465
+ return {
466
+ get unlocked() {
467
+ return g.unlocked;
468
+ },
469
+ unlock: () => set(!0, !0),
470
+ lock: () => set(!1, !0),
471
+ dispose() {
472
+ for (let off of offs.splice(0))
473
+ off();
474
+ }
475
+ };
476
+ }
477
+
340
478
  // src/core/variants.ts
341
479
  function createVariants(schema, id, key, storage, get, set, notify, enabled) {
342
480
  let ready = !1, list = Object.freeze([]);
@@ -451,7 +589,7 @@ function sameField(a, b) {
451
589
  function defineControls(id, input, options = {}) {
452
590
  if (!id.trim())
453
591
  throw Error("Controls need a stable, nonempty id.");
454
- let schema = Object.freeze({ ...input }), locked = normalize(schema, {}), actions = Object.freeze([...options.actions ?? []]), theme = options.theme ?? "dark", density = options.density ?? "default", renderer = options.renderer ?? "auto", enabled = () => typeof options.enabled === "function" ? options.enabled() : options.enabled ?? (Boolean(options.ship) || typeof location < "u" && ["localhost", "127.0.0.1", "[::1]"].includes(location.hostname)), storage = () => options.storage === void 0 ? browserStorage() : options.storage, persistMode = options.persist ?? "all", valueStorage = () => persistMode === "all" ? storage() : null, variantStorage = () => persistMode === "none" ? null : storage(), draw = options.random ?? Math.random, hostHistory = options.history, unsubscribeHistory, listeners = /* @__PURE__ */ new Set, refreshes = 0, notify = () => {
592
+ let schema = Object.freeze({ ...input }), locked = normalize(schema, {}), actions = Object.freeze([...options.actions ?? []]), theme = options.theme ?? "dark", density = options.density ?? "default", renderer = options.renderer ?? "auto", enabled = () => typeof options.enabled === "function" ? options.enabled() : options.enabled ?? (Boolean(options.ship) || isUnlocked() || typeof location < "u" && ["localhost", "127.0.0.1", "[::1]"].includes(location.hostname)), storage = () => options.storage === void 0 ? browserStorage() : options.storage, persistMode = options.persist ?? "all", valueStorage = () => persistMode === "all" ? storage() : null, variantStorage = () => persistMode === "none" ? null : storage(), draw = options.random ?? Math.random, hostHistory = options.history, unsubscribeHistory, unsubscribeUnlock, listeners = /* @__PURE__ */ new Set, refreshes = 0, notify = () => {
455
593
  for (let cb of [...listeners])
456
594
  cb();
457
595
  }, mixed = /* @__PURE__ */ new Set, current = locked, read = !1, error = null, locks = /* @__PURE__ */ new Set, bindings = /* @__PURE__ */ new Map, comparing = null, landing = !1, echo = Math.max(0, options.echo ?? 0), grabbed = /* @__PURE__ */ new Set, changedAt = /* @__PURE__ */ new Map, sent = /* @__PURE__ */ new Map, pending = /* @__PURE__ */ new Map, echoTimer, now = () => typeof performance < "u" ? performance.now() : Date.now(), isHeld = (key) => grabbed.has(key) || echo > 0 && now() - (changedAt.get(key) ?? -1 / 0) < echo, NO_BASE = Object.freeze({ preset: null, groups: Object.freeze({}) }), base = NO_BASE, past = [], future = [], HISTORY = 100, stateKey = storageKey(id, "state"), buildPresets = () => Object.freeze(Object.fromEntries([
@@ -869,6 +1007,36 @@ function defineControls(id, input, options = {}) {
869
1007
  for (let key of Object.keys(patch))
870
1008
  options.onReset?.({ key });
871
1009
  }
1010
+ function live(load, make, unmake) {
1011
+ let disposed = !1, loading = !1, handle, sync = () => {
1012
+ if (disposed)
1013
+ return;
1014
+ if (!enabled()) {
1015
+ if (handle !== void 0)
1016
+ unmake(handle);
1017
+ handle = void 0;
1018
+ return;
1019
+ }
1020
+ if (handle !== void 0 || loading)
1021
+ return;
1022
+ let pending = load();
1023
+ if (!pending)
1024
+ return;
1025
+ loading = !0, pending.then((module) => {
1026
+ if (loading = !1, !disposed && enabled() && handle === void 0)
1027
+ handle = make(module);
1028
+ }).catch((cause) => {
1029
+ loading = !1, error = String(cause), notify();
1030
+ });
1031
+ };
1032
+ sync();
1033
+ let off = onUnlock(sync);
1034
+ return () => {
1035
+ if (disposed = !0, off(), handle !== void 0)
1036
+ unmake(handle);
1037
+ handle = void 0;
1038
+ };
1039
+ }
872
1040
  let controls = {
873
1041
  id,
874
1042
  get schema() {
@@ -888,9 +1056,10 @@ function defineControls(id, input, options = {}) {
888
1056
  marks: options.marks !== !1,
889
1057
  chip: Object.freeze({
890
1058
  ...options.chip,
891
- reveal: options.chip?.reveal ?? (devBuild() ? "hover" : "shortcut"),
1059
+ reveal: options.chip?.reveal ?? (devBuild() || !options.ship ? "hover" : "shortcut"),
892
1060
  shortcut: options.chip?.shortcut ?? "Alt+Shift+D"
893
1061
  }),
1062
+ toggleKey: options.toggleKey ?? (options.ship ? !1 : "`"),
894
1063
  groupPresetDisplay: options.groupPresetDisplay ?? "auto",
895
1064
  groupLabel: (group) => options.groupLabels?.[group] ?? fieldLabels().group.get(group),
896
1065
  sectionLabel: (section) => options.sectionLabels?.[section] ?? fieldLabels().section.get(section),
@@ -985,9 +1154,10 @@ function defineControls(id, input, options = {}) {
985
1154
  subscribe(cb) {
986
1155
  if (listeners.add(cb), hostHistory?.subscribe && !unsubscribeHistory)
987
1156
  unsubscribeHistory = hostHistory.subscribe(notify);
988
- return () => {
989
- if (listeners.delete(cb), !listeners.size && unsubscribeHistory)
990
- unsubscribeHistory(), unsubscribeHistory = void 0;
1157
+ return unsubscribeUnlock ??= onUnlock(notify), () => {
1158
+ if (listeners.delete(cb), listeners.size)
1159
+ return;
1160
+ unsubscribeHistory?.(), unsubscribeHistory = void 0, unsubscribeUnlock?.(), unsubscribeUnlock = void 0;
991
1161
  };
992
1162
  },
993
1163
  reset(keys) {
@@ -1205,32 +1375,15 @@ function defineControls(id, input, options = {}) {
1205
1375
  code: (format, values) => formatCode(values ?? get(), format),
1206
1376
  variants,
1207
1377
  attach(element, chip) {
1208
- let load = options.ship ? () => options.ship().then((m) => m.attach) : () => (__preload("4km87zfx"), import("./chunk-4km87zfx.js")).then((m) => m.attach);
1209
- if (!load || !enabled())
1210
- return () => {};
1211
- let disposed = !1, detach;
1212
- return load().then((attach) => {
1213
- if (!disposed && enabled())
1214
- detach = attach(controls, element, chip);
1215
- }).catch((cause) => {
1216
- error = String(cause), notify();
1217
- }), () => {
1218
- disposed = !0, detach?.();
1219
- };
1378
+ let load = options.ship ? () => options.ship().then((m) => m.attach) : () => (__preload("t68smqkc"), import("./chunk-t68smqkc.js")).then((m) => m.attach);
1379
+ return live(load, (attach) => attach(controls, element, chip), (detach) => detach());
1220
1380
  },
1221
1381
  mount(container, mountOptions) {
1222
- let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => (__preload("5cr85vv6"), import("./chunk-5cr85vv6.js")).then((m) => m.mountPanel);
1223
- if (!load || !enabled())
1224
- return () => {};
1225
- let disposed = !1, panel;
1226
- return load().then((mountPanel) => {
1227
- if (!disposed && enabled())
1228
- panel = mountPanel(controls, container, mountOptions), mountOptions?.onReady?.(panel);
1229
- }).catch((cause) => {
1230
- error = String(cause), notify();
1231
- }), () => {
1232
- disposed = !0, panel?.dispose();
1233
- };
1382
+ let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => (__preload("stbcgz1y"), import("./chunk-stbcgz1y.js")).then((m) => m.mountPanel);
1383
+ return live(load, (mountPanel) => {
1384
+ let panel = mountPanel(controls, container, mountOptions);
1385
+ return mountOptions?.onReady?.(panel), panel;
1386
+ }, (panel) => panel.dispose());
1234
1387
  }
1235
1388
  };
1236
1389
  return Object.freeze(controls);
@@ -1510,6 +1663,7 @@ function createRemoteControls(id, descriptors, options = {}) {
1510
1663
  }, controls = defineControls(id, fromDescriptors(frozenDescriptors), {
1511
1664
  enabled: options.enabled ?? !0,
1512
1665
  ship: options.ship,
1666
+ toggleKey: options.toggleKey,
1513
1667
  echo: options.echo ?? ECHO_MS,
1514
1668
  storage: options.storage ?? null,
1515
1669
  persist: options.persist,
@@ -1603,6 +1757,7 @@ export {
1603
1757
  AROUND,
1604
1758
  CURVE_SHAPES2 as CURVE_SHAPES,
1605
1759
  EASINGS2 as EASINGS,
1760
+ KONAMI,
1606
1761
  SPRING2 as SPRING,
1607
1762
  action2 as action,
1608
1763
  actionShortcut2 as actionShortcut,
@@ -1652,6 +1807,7 @@ export {
1652
1807
  toggle2 as toggle,
1653
1808
  toolbar2 as toolbar,
1654
1809
  toolbarShortcut2 as toolbarShortcut,
1810
+ unlock,
1655
1811
  urlFlag2 as urlFlag,
1656
1812
  vec22 as vec2,
1657
1813
  vec32 as vec3,
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) });
@@ -1328,6 +1337,143 @@ function urlFlag(name) {
1328
1337
  }
1329
1338
  var storageKey = (id, part) => `knobs:${encodeURIComponent(id)}:${part}:v1`;
1330
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
+
1331
1477
  // src/core/variants.ts
1332
1478
  function createVariants(schema, id, key, storage, get, set, notify, enabled) {
1333
1479
  let ready = !1, list = Object.freeze([]);
@@ -1442,7 +1588,7 @@ function sameField(a, b) {
1442
1588
  function defineControls(id, input, options = {}) {
1443
1589
  if (!id.trim())
1444
1590
  throw Error("Controls need a stable, nonempty id.");
1445
- let schema = Object.freeze({ ...input }), locked = normalize(schema, {}), actions = Object.freeze([...options.actions ?? []]), theme = options.theme ?? "dark", density = options.density ?? "default", renderer = options.renderer ?? "auto", enabled = () => Boolean(options.ship) && (typeof options.enabled === "function" ? options.enabled() : options.enabled ?? (Boolean(options.ship) || typeof location < "u" && ["localhost", "127.0.0.1", "[::1]"].includes(location.hostname))), storage = () => options.storage === void 0 ? browserStorage() : options.storage, persistMode = options.persist ?? "all", valueStorage = () => persistMode === "all" ? storage() : null, variantStorage = () => persistMode === "none" ? null : storage(), draw = options.random ?? Math.random, hostHistory = options.history, unsubscribeHistory, listeners = /* @__PURE__ */ new Set, refreshes = 0, notify = () => {
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 = () => {
1446
1592
  for (let cb of [...listeners])
1447
1593
  cb();
1448
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([
@@ -1860,6 +2006,36 @@ function defineControls(id, input, options = {}) {
1860
2006
  for (let key of Object.keys(patch))
1861
2007
  options.onReset?.({ key });
1862
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
+ }
1863
2039
  let controls = {
1864
2040
  id,
1865
2041
  get schema() {
@@ -1879,9 +2055,10 @@ function defineControls(id, input, options = {}) {
1879
2055
  marks: options.marks !== !1,
1880
2056
  chip: Object.freeze({
1881
2057
  ...options.chip,
1882
- reveal: options.chip?.reveal ?? (devBuild() ? "hover" : "shortcut"),
2058
+ reveal: options.chip?.reveal ?? (devBuild() || !options.ship ? "hover" : "shortcut"),
1883
2059
  shortcut: options.chip?.shortcut ?? "Alt+Shift+D"
1884
2060
  }),
2061
+ toggleKey: options.toggleKey ?? (options.ship ? !1 : "`"),
1885
2062
  groupPresetDisplay: options.groupPresetDisplay ?? "auto",
1886
2063
  groupLabel: (group) => options.groupLabels?.[group] ?? fieldLabels().group.get(group),
1887
2064
  sectionLabel: (section) => options.sectionLabels?.[section] ?? fieldLabels().section.get(section),
@@ -1976,9 +2153,10 @@ function defineControls(id, input, options = {}) {
1976
2153
  subscribe(cb) {
1977
2154
  if (listeners.add(cb), hostHistory?.subscribe && !unsubscribeHistory)
1978
2155
  unsubscribeHistory = hostHistory.subscribe(notify);
1979
- return () => {
1980
- if (listeners.delete(cb), !listeners.size && unsubscribeHistory)
1981
- 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;
1982
2160
  };
1983
2161
  },
1984
2162
  reset(keys) {
@@ -2196,32 +2374,15 @@ function defineControls(id, input, options = {}) {
2196
2374
  code: (format, values) => formatCode(values ?? get(), format),
2197
2375
  variants,
2198
2376
  attach(element, chip) {
2199
- let load = options.ship ? () => options.ship().then((m) => m.attach) : void 0;
2200
- if (!load || !enabled())
2201
- return () => {};
2202
- let disposed = !1, detach;
2203
- return load().then((attach) => {
2204
- if (!disposed && enabled())
2205
- detach = attach(controls, element, chip);
2206
- }).catch((cause) => {
2207
- error = String(cause), notify();
2208
- }), () => {
2209
- disposed = !0, detach?.();
2210
- };
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());
2211
2379
  },
2212
2380
  mount(container, mountOptions) {
2213
- let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : void 0;
2214
- if (!load || !enabled())
2215
- return () => {};
2216
- let disposed = !1, panel;
2217
- return load().then((mountPanel) => {
2218
- if (!disposed && enabled())
2219
- panel = mountPanel(controls, container, mountOptions), mountOptions?.onReady?.(panel);
2220
- }).catch((cause) => {
2221
- error = String(cause), notify();
2222
- }), () => {
2223
- disposed = !0, panel?.dispose();
2224
- };
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());
2225
2386
  }
2226
2387
  };
2227
2388
  return Object.freeze(controls);
@@ -2501,6 +2662,7 @@ function createRemoteControls(id, descriptors, options = {}) {
2501
2662
  }, controls = defineControls(id, fromDescriptors(frozenDescriptors), {
2502
2663
  enabled: options.enabled ?? !0,
2503
2664
  ship: options.ship,
2665
+ toggleKey: options.toggleKey,
2504
2666
  echo: options.echo ?? ECHO_MS,
2505
2667
  storage: options.storage ?? null,
2506
2668
  persist: options.persist,
@@ -2594,6 +2756,7 @@ export {
2594
2756
  AROUND,
2595
2757
  CURVE_SHAPES,
2596
2758
  EASINGS,
2759
+ KONAMI,
2597
2760
  SPRING,
2598
2761
  action,
2599
2762
  actionShortcut,
@@ -2643,6 +2806,7 @@ export {
2643
2806
  toggle,
2644
2807
  toolbar,
2645
2808
  toolbarShortcut,
2809
+ unlock,
2646
2810
  urlFlag,
2647
2811
  vec2,
2648
2812
  vec3,