@kitlangton/knobs 0.0.0-stage → 0.1.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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1270 -2
  3. package/dist/dev/chunk-e13znr7c.js +155 -0
  4. package/dist/dev/chunk-kdewv0mt.js +1334 -0
  5. package/dist/dev/chunk-ktsn42p7.js +15668 -0
  6. package/dist/dev/chunk-qtt698hp.js +1118 -0
  7. package/dist/dev/index.js +1389 -0
  8. package/dist/index.js +2367 -0
  9. package/dist/panel.js +23635 -0
  10. package/dist/react.js +326 -0
  11. package/dist/solid.js +336 -0
  12. package/dist/themes.js +235 -0
  13. package/dist/types/core/adapter.d.ts +21 -0
  14. package/dist/types/core/code.d.ts +3 -0
  15. package/dist/types/core/color.d.ts +27 -0
  16. package/dist/types/core/easing.d.ts +21 -0
  17. package/dist/types/core/internal.d.ts +18 -0
  18. package/dist/types/core/keys.d.ts +21 -0
  19. package/dist/types/core/mutate.d.ts +21 -0
  20. package/dist/types/core/ramp.d.ts +150 -0
  21. package/dist/types/core/random.d.ts +2 -0
  22. package/dist/types/core/readout.d.ts +73 -0
  23. package/dist/types/core/remote.d.ts +258 -0
  24. package/dist/types/core/schema.d.ts +526 -0
  25. package/dist/types/core/spring.d.ts +38 -0
  26. package/dist/types/core/storage.d.ts +8 -0
  27. package/dist/types/core/store.d.ts +474 -0
  28. package/dist/types/core/theme.d.ts +27 -0
  29. package/dist/types/core/variants.d.ts +17 -0
  30. package/dist/types/index.d.ts +13 -0
  31. package/dist/types/panel/attach.d.ts +5 -0
  32. package/dist/types/panel/choice.d.ts +56 -0
  33. package/dist/types/panel/die.d.ts +121 -0
  34. package/dist/types/panel/editors.d.ts +159 -0
  35. package/dist/types/panel/folds.d.ts +14 -0
  36. package/dist/types/panel/give.d.ts +73 -0
  37. package/dist/types/panel/gpu/atlas.d.ts +40 -0
  38. package/dist/types/panel/gpu/color.d.ts +128 -0
  39. package/dist/types/panel/gpu/die.wgsl.d.ts +1 -0
  40. package/dist/types/panel/gpu/renderer.d.ts +712 -0
  41. package/dist/types/panel/gpu/shader.wgsl.d.ts +2 -0
  42. package/dist/types/panel/haptics.d.ts +8 -0
  43. package/dist/types/panel/has.d.ts +14 -0
  44. package/dist/types/panel/hints.d.ts +45 -0
  45. package/dist/types/panel/icons.d.ts +30 -0
  46. package/dist/types/panel/index.d.ts +10 -0
  47. package/dist/types/panel/inspector.d.ts +20 -0
  48. package/dist/types/panel/measure.d.ts +6 -0
  49. package/dist/types/panel/menu.d.ts +38 -0
  50. package/dist/types/panel/morph.d.ts +38 -0
  51. package/dist/types/panel/motion.d.ts +77 -0
  52. package/dist/types/panel/page-hints.d.ts +82 -0
  53. package/dist/types/panel/page.d.ts +423 -0
  54. package/dist/types/panel/panel.d.ts +43 -0
  55. package/dist/types/panel/position.d.ts +5 -0
  56. package/dist/types/panel/prefs.d.ts +28 -0
  57. package/dist/types/panel/presets.d.ts +48 -0
  58. package/dist/types/panel/ramps.d.ts +14 -0
  59. package/dist/types/panel/readout-row.d.ts +21 -0
  60. package/dist/types/panel/reveal.d.ts +15 -0
  61. package/dist/types/panel/rolling-css.d.ts +2 -0
  62. package/dist/types/panel/search.d.ts +36 -0
  63. package/dist/types/panel/seg.d.ts +15 -0
  64. package/dist/types/panel/session.d.ts +21 -0
  65. package/dist/types/panel/slider.d.ts +92 -0
  66. package/dist/types/panel/smear.d.ts +7 -0
  67. package/dist/types/panel/spin.d.ts +13 -0
  68. package/dist/types/panel/styles.d.ts +11 -0
  69. package/dist/types/panel/swipe.d.ts +114 -0
  70. package/dist/types/panel/switcher.d.ts +20 -0
  71. package/dist/types/panel/tabs.d.ts +31 -0
  72. package/dist/types/panel/thumb.d.ts +36 -0
  73. package/dist/types/react/Knobs.d.ts +15 -0
  74. package/dist/types/react/VariantSwitcher.d.ts +10 -0
  75. package/dist/types/react/index.d.ts +3 -0
  76. package/dist/types/react/useControls.d.ts +11 -0
  77. package/dist/types/solid/index.d.ts +50 -0
  78. package/dist/types/themes/index.d.ts +1 -0
  79. package/dist/types/ui/bento-css.d.ts +1 -0
  80. package/dist/types/ui/bento-model.d.ts +96 -0
  81. package/dist/types/ui/bento.d.ts +77 -0
  82. package/dist/types/ui/commands.d.ts +11 -0
  83. package/dist/types/ui/confirm.d.ts +13 -0
  84. package/dist/types/ui/index.d.ts +20 -0
  85. package/dist/types/ui/layer.d.ts +70 -0
  86. package/dist/types/ui/look.d.ts +53 -0
  87. package/dist/types/ui/menu.d.ts +63 -0
  88. package/dist/types/ui/overlay-css.d.ts +1 -0
  89. package/dist/types/ui/overlay-model.d.ts +87 -0
  90. package/dist/types/ui/palette.d.ts +38 -0
  91. package/dist/types/ui/popover.d.ts +35 -0
  92. package/dist/types/ui/react.d.ts +45 -0
  93. package/dist/types/ui/readout-css.d.ts +1 -0
  94. package/dist/types/ui/readout-model.d.ts +137 -0
  95. package/dist/types/ui/readouts.d.ts +85 -0
  96. package/dist/types/ui/rgba.d.ts +6 -0
  97. package/dist/types/ui/solid.d.ts +40 -0
  98. package/dist/types/ui/split-model.d.ts +81 -0
  99. package/dist/types/ui/split.d.ts +75 -0
  100. package/dist/types/ui/timeline-model.d.ts +83 -0
  101. package/dist/types/ui/timeline.d.ts +76 -0
  102. package/dist/types/ui/toast.d.ts +19 -0
  103. package/dist/types/ui/tooltip.d.ts +19 -0
  104. package/dist/types/ui/tree-model.d.ts +125 -0
  105. package/dist/types/ui/tree.d.ts +95 -0
  106. package/dist/ui-react.js +6558 -0
  107. package/dist/ui-solid.js +6511 -0
  108. package/dist/ui.js +9238 -0
  109. package/package.json +99 -4
package/dist/react.js ADDED
@@ -0,0 +1,326 @@
1
+ "use client";
2
+
3
+ // src/react/useControls.ts
4
+ import { useSyncExternalStore } from "react";
5
+ "use client";
6
+ function useControls(controls) {
7
+ return useSyncExternalStore(controls.subscribe, controls.get, controls.getServerSnapshot);
8
+ }
9
+ function useValue(controls, key) {
10
+ return useSyncExternalStore(controls.subscribe, () => controls.get()[key], () => controls.getServerSnapshot()[key]);
11
+ }
12
+ function useModified(controls, key) {
13
+ return useSyncExternalStore(controls.subscribe, () => controls.isModified(key), () => false);
14
+ }
15
+ // src/react/Knobs.tsx
16
+ import {
17
+ cloneElement,
18
+ useCallback,
19
+ useEffect,
20
+ useRef,
21
+ useState,
22
+ useSyncExternalStore as useSyncExternalStore2
23
+ } from "react";
24
+
25
+ // src/core/adapter.ts
26
+ function syncPanelLook(host, controls, theme, density) {
27
+ const panel = host?.firstElementChild;
28
+ if (!panel)
29
+ return;
30
+ panel.dataset.theme = theme ?? controls.theme;
31
+ panel.dataset.density = density ?? controls.density;
32
+ }
33
+ function syncPanelValues(controls, descriptors, values, land = false) {
34
+ if (!controls.enabled || !descriptors && !values)
35
+ return;
36
+ if (descriptors && "update" in controls)
37
+ controls.update(descriptors, values, { land });
38
+ else if (values)
39
+ controls.sync(values, { replace: true, land });
40
+ }
41
+ function mountPanelAdapter(host, controls, getProps) {
42
+ const p = getProps();
43
+ return controls.mount(host, {
44
+ title: p.title,
45
+ theme: p.theme,
46
+ density: p.density,
47
+ renderer: p.renderer,
48
+ variants: p.variants,
49
+ history: p.history,
50
+ footer: p.footer,
51
+ header: p.header,
52
+ keys: p.keys,
53
+ marks: p.marks,
54
+ preset: p.preset,
55
+ copy: p.copy ? () => getProps().copy?.() ?? controls.code() : undefined,
56
+ actions: p.actions,
57
+ onClose: p.onClose ? () => getProps().onClose?.() : undefined,
58
+ onReady(panel) {
59
+ const cur = getProps();
60
+ syncPanelLook(host, controls, cur.theme, cur.density);
61
+ cur.onReady?.(panel);
62
+ }
63
+ });
64
+ }
65
+
66
+ // src/react/Knobs.tsx
67
+ import { jsx } from "react/jsx-runtime";
68
+ "use client";
69
+ function useKnobs(controls) {
70
+ const [node, setNode] = useState(null);
71
+ useEffect(() => node ? controls.attach(node) : undefined, [controls, node]);
72
+ return useCallback((element) => {
73
+ setNode(element);
74
+ }, []);
75
+ }
76
+ function Knobs({
77
+ controls,
78
+ children
79
+ }) {
80
+ const attach = useKnobs(controls);
81
+ const childRef = children.props.ref ?? Object.getOwnPropertyDescriptor(children, "ref")?.value;
82
+ const ref = useCallback((node) => {
83
+ attach(node);
84
+ if (typeof childRef === "function")
85
+ return childRef(node);
86
+ if (childRef)
87
+ childRef.current = node;
88
+ }, [attach, childRef]);
89
+ return cloneElement(children, { ref });
90
+ }
91
+ function KnobsPanel(props) {
92
+ const { controls, descriptors, values, land, theme, density } = props;
93
+ const enabled = useSyncExternalStore2(controls.subscribe, () => controls.enabled, () => false);
94
+ const ref = useRef(null);
95
+ const latest = useRef(props);
96
+ latest.current = props;
97
+ useEffect(() => {
98
+ if (!enabled || !ref.current)
99
+ return;
100
+ return mountPanelAdapter(ref.current, controls, () => latest.current);
101
+ }, [enabled, controls]);
102
+ useEffect(() => {
103
+ if (!enabled)
104
+ return;
105
+ syncPanelValues(controls, descriptors, values, Boolean(land));
106
+ }, [enabled, controls, descriptors, values, land]);
107
+ useEffect(() => {
108
+ syncPanelLook(ref.current, controls, theme, density);
109
+ }, [controls, theme, density]);
110
+ if (!enabled)
111
+ return null;
112
+ return /* @__PURE__ */ jsx("div", {
113
+ ref,
114
+ "data-knobs-mount": controls.id
115
+ });
116
+ }
117
+ // src/react/VariantSwitcher.tsx
118
+ import { useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore3 } from "react";
119
+
120
+ // src/core/schema.ts
121
+ var READOUT_KINDS = new Set(["watch", "sparkline", "graph", "meter", "table", "log"]);
122
+ function same(a, b) {
123
+ if (a === b)
124
+ return true;
125
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
126
+ return false;
127
+ if (Array.isArray(a) !== Array.isArray(b))
128
+ return false;
129
+ const ka = Object.keys(a);
130
+ return ka.length === Object.keys(b).length && ka.every((k) => same(a[k], b[k]));
131
+ }
132
+ function equal(a, b) {
133
+ return Object.keys(a).length === Object.keys(b).length && Object.keys(a).every((key) => same(a[key], b[key]));
134
+ }
135
+
136
+ // src/panel/switcher.ts
137
+ var hoveredPreview = null;
138
+ var lastAppliedByControls = new WeakMap;
139
+ var interactive = "a[href],button,input,select,textarea,[contenteditable=true]";
140
+ function getSwitcherOptions(controls, field) {
141
+ const values = controls.get();
142
+ const definition = field ? controls.schema[field] : undefined;
143
+ let variants = [];
144
+ if (!field && controls.enabled) {
145
+ try {
146
+ variants = controls.variants.list();
147
+ } catch {
148
+ variants = [];
149
+ }
150
+ }
151
+ const options = definition?.kind === "choice" ? definition.options.map((value) => ({
152
+ id: value,
153
+ name: value,
154
+ choose: () => controls.set({ [field]: value })
155
+ })) : [
156
+ ...Object.entries(controls.presets).map(([id, preset]) => ({
157
+ id: `p:${id}`,
158
+ name: id,
159
+ values: preset,
160
+ choose: () => {
161
+ lastAppliedByControls.set(controls, `p:${id}`);
162
+ controls.preset(id);
163
+ }
164
+ })),
165
+ ...variants.map((v) => ({
166
+ id: `v:${v.id}`,
167
+ name: v.name ?? v.id.slice(0, 8),
168
+ values: v.values,
169
+ choose: () => {
170
+ lastAppliedByControls.set(controls, `v:${v.id}`);
171
+ controls.variants.load(v.id);
172
+ }
173
+ }))
174
+ ];
175
+ let index = -1;
176
+ if (field) {
177
+ index = options.findIndex((o) => o.id === values[field]);
178
+ } else {
179
+ const pinnedId = lastAppliedByControls.get(controls);
180
+ if (pinnedId) {
181
+ const pinnedIdx = options.findIndex((o) => o.id === pinnedId && o.values !== undefined && equal(o.values, values));
182
+ if (pinnedIdx >= 0)
183
+ index = pinnedIdx;
184
+ }
185
+ if (index < 0) {
186
+ index = options.findIndex((o) => o.values !== undefined && equal(o.values, values));
187
+ }
188
+ }
189
+ return {
190
+ options,
191
+ index,
192
+ activeName: options[index]?.name ?? "custom"
193
+ };
194
+ }
195
+ function attachVariantSwitcher(element, controls, switcherOptions = {}) {
196
+ if (!controls.enabled)
197
+ return () => {};
198
+ const doc = element.ownerDocument;
199
+ const win = doc.defaultView;
200
+ const { field, label, onUpdate } = switcherOptions;
201
+ element.dataset.knobsSwitcher = controls.id;
202
+ if (!element.hasAttribute("role"))
203
+ element.setAttribute("role", "group");
204
+ const addedTabIndex = !element.hasAttribute("tabindex");
205
+ if (addedTabIndex)
206
+ element.tabIndex = 0;
207
+ const sync = () => {
208
+ const { activeName } = getSwitcherOptions(controls, field);
209
+ element.setAttribute("aria-label", label ?? `${controls.id} ${field ?? "variant"}: ${activeName}. Use 1–9 or left/right arrows.`);
210
+ onUpdate?.(activeName);
211
+ };
212
+ const cycle = (direction) => {
213
+ const { options, index } = getSwitcherOptions(controls, field);
214
+ if (!options.length)
215
+ return;
216
+ options[(index + direction + options.length) % options.length]?.choose();
217
+ };
218
+ const onPointerOver = (event) => {
219
+ if (event.target instanceof Element && event.target.closest("[data-knobs-switcher]") === element)
220
+ hoveredPreview = { element, target: event.target };
221
+ };
222
+ const onPointerLeave = (event) => {
223
+ if (hoveredPreview?.element !== element)
224
+ return;
225
+ const target = event.relatedTarget instanceof Element ? event.relatedTarget : null;
226
+ const parent = target?.closest("[data-knobs-switcher]");
227
+ hoveredPreview = parent && target ? { element: parent, target } : null;
228
+ };
229
+ const onElementKeyDown = (event) => {
230
+ if (event.target === element && (event.key === "Enter" || event.key === " ")) {
231
+ event.preventDefault();
232
+ cycle(1);
233
+ }
234
+ };
235
+ const onWindowKeyDown = (event) => {
236
+ if (event.defaultPrevented || event.isComposing || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey)
237
+ return;
238
+ const focused = doc.activeElement?.closest("[data-knobs-switcher]");
239
+ const active = hoveredPreview?.element ?? focused;
240
+ if (active !== element || event.target instanceof Element && event.target.closest(interactive) || hoveredPreview?.target.closest(interactive))
241
+ return;
242
+ if (win.getSelection()?.isCollapsed === false)
243
+ return;
244
+ const { options } = getSwitcherOptions(controls, field);
245
+ const selection = /^[1-9]$/.test(event.key) ? Number(event.key) - 1 : -1;
246
+ if (selection >= 0 && options[selection]) {
247
+ event.preventDefault();
248
+ options[selection].choose();
249
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
250
+ event.preventDefault();
251
+ cycle(event.key === "ArrowLeft" ? -1 : 1);
252
+ }
253
+ };
254
+ element.addEventListener("pointerover", onPointerOver);
255
+ element.addEventListener("pointerleave", onPointerLeave);
256
+ element.addEventListener("keydown", onElementKeyDown);
257
+ win.addEventListener("keydown", onWindowKeyDown);
258
+ const unsubscribe = controls.subscribe(sync);
259
+ sync();
260
+ return () => {
261
+ unsubscribe();
262
+ element.removeEventListener("pointerover", onPointerOver);
263
+ element.removeEventListener("pointerleave", onPointerLeave);
264
+ element.removeEventListener("keydown", onElementKeyDown);
265
+ win.removeEventListener("keydown", onWindowKeyDown);
266
+ if (hoveredPreview?.element === element)
267
+ hoveredPreview = null;
268
+ delete element.dataset.knobsSwitcher;
269
+ if (addedTabIndex && element.getAttribute("tabindex") === "0")
270
+ element.removeAttribute("tabindex");
271
+ };
272
+ }
273
+
274
+ // src/react/VariantSwitcher.tsx
275
+ import { jsx as jsx2, Fragment } from "react/jsx-runtime";
276
+ "use client";
277
+ var empty = Object.freeze([]);
278
+ function VariantSwitcher({
279
+ controls,
280
+ field,
281
+ children,
282
+ label
283
+ }) {
284
+ const values = useControls(controls);
285
+ const enabled = useSyncExternalStore3(controls.subscribe, () => controls.enabled, () => false);
286
+ useSyncExternalStore3(controls.subscribe, () => {
287
+ if (field || !controls.enabled)
288
+ return empty;
289
+ try {
290
+ return controls.variants.list();
291
+ } catch {
292
+ return empty;
293
+ }
294
+ }, () => empty);
295
+ const ref = useRef2(null);
296
+ const { activeName } = getSwitcherOptions(controls, field);
297
+ useEffect2(() => {
298
+ if (!ref.current || !enabled)
299
+ return;
300
+ return attachVariantSwitcher(ref.current, controls, { field, label });
301
+ }, [controls, enabled, field, label]);
302
+ if (!enabled)
303
+ return /* @__PURE__ */ jsx2(Fragment, {
304
+ children: children ?? (field ? String(values[field]) : "default")
305
+ });
306
+ return /* @__PURE__ */ jsx2("div", {
307
+ ref,
308
+ "data-knobs-switcher": controls.id,
309
+ role: "group",
310
+ tabIndex: 0,
311
+ "aria-label": label ?? `${controls.id} ${field ?? "variant"}: ${activeName}. Use 1–9 or left/right arrows.`,
312
+ children: children ?? activeName
313
+ });
314
+ }
315
+
316
+ // src/react/index.ts
317
+ "use client";
318
+ export {
319
+ Knobs,
320
+ KnobsPanel,
321
+ VariantSwitcher,
322
+ useControls,
323
+ useKnobs,
324
+ useModified,
325
+ useValue
326
+ };
package/dist/solid.js ADDED
@@ -0,0 +1,336 @@
1
+ var __esm = (fn, res, err) => () => {
2
+ if (fn)
3
+ try {
4
+ res = fn(fn = 0);
5
+ } catch (e) {
6
+ err = [e];
7
+ }
8
+ if (err)
9
+ throw err[0];
10
+ return res;
11
+ };
12
+
13
+ // src/core/schema.ts
14
+ function same(a, b) {
15
+ if (a === b)
16
+ return true;
17
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
18
+ return false;
19
+ if (Array.isArray(a) !== Array.isArray(b))
20
+ return false;
21
+ const ka = Object.keys(a);
22
+ return ka.length === Object.keys(b).length && ka.every((k) => same(a[k], b[k]));
23
+ }
24
+ function equal(a, b) {
25
+ return Object.keys(a).length === Object.keys(b).length && Object.keys(a).every((key) => same(a[key], b[key]));
26
+ }
27
+ var READOUT_KINDS;
28
+ var init_schema = __esm(() => {
29
+ READOUT_KINDS = new Set(["watch", "sparkline", "graph", "meter", "table", "log"]);
30
+ });
31
+
32
+ // src/panel/switcher.ts
33
+ function getSwitcherOptions(controls, field) {
34
+ const values = controls.get();
35
+ const definition = field ? controls.schema[field] : undefined;
36
+ let variants = [];
37
+ if (!field && controls.enabled) {
38
+ try {
39
+ variants = controls.variants.list();
40
+ } catch {
41
+ variants = [];
42
+ }
43
+ }
44
+ const options = definition?.kind === "choice" ? definition.options.map((value) => ({
45
+ id: value,
46
+ name: value,
47
+ choose: () => controls.set({ [field]: value })
48
+ })) : [
49
+ ...Object.entries(controls.presets).map(([id, preset]) => ({
50
+ id: `p:${id}`,
51
+ name: id,
52
+ values: preset,
53
+ choose: () => {
54
+ lastAppliedByControls.set(controls, `p:${id}`);
55
+ controls.preset(id);
56
+ }
57
+ })),
58
+ ...variants.map((v) => ({
59
+ id: `v:${v.id}`,
60
+ name: v.name ?? v.id.slice(0, 8),
61
+ values: v.values,
62
+ choose: () => {
63
+ lastAppliedByControls.set(controls, `v:${v.id}`);
64
+ controls.variants.load(v.id);
65
+ }
66
+ }))
67
+ ];
68
+ let index = -1;
69
+ if (field) {
70
+ index = options.findIndex((o) => o.id === values[field]);
71
+ } else {
72
+ const pinnedId = lastAppliedByControls.get(controls);
73
+ if (pinnedId) {
74
+ const pinnedIdx = options.findIndex((o) => o.id === pinnedId && o.values !== undefined && equal(o.values, values));
75
+ if (pinnedIdx >= 0)
76
+ index = pinnedIdx;
77
+ }
78
+ if (index < 0) {
79
+ index = options.findIndex((o) => o.values !== undefined && equal(o.values, values));
80
+ }
81
+ }
82
+ return {
83
+ options,
84
+ index,
85
+ activeName: options[index]?.name ?? "custom"
86
+ };
87
+ }
88
+ function attachVariantSwitcher(element, controls, switcherOptions = {}) {
89
+ if (!controls.enabled)
90
+ return () => {};
91
+ const doc = element.ownerDocument;
92
+ const win = doc.defaultView;
93
+ const { field, label, onUpdate } = switcherOptions;
94
+ element.dataset.knobsSwitcher = controls.id;
95
+ if (!element.hasAttribute("role"))
96
+ element.setAttribute("role", "group");
97
+ const addedTabIndex = !element.hasAttribute("tabindex");
98
+ if (addedTabIndex)
99
+ element.tabIndex = 0;
100
+ const sync = () => {
101
+ const { activeName } = getSwitcherOptions(controls, field);
102
+ element.setAttribute("aria-label", label ?? `${controls.id} ${field ?? "variant"}: ${activeName}. Use 1–9 or left/right arrows.`);
103
+ onUpdate?.(activeName);
104
+ };
105
+ const cycle = (direction) => {
106
+ const { options, index } = getSwitcherOptions(controls, field);
107
+ if (!options.length)
108
+ return;
109
+ options[(index + direction + options.length) % options.length]?.choose();
110
+ };
111
+ const onPointerOver = (event) => {
112
+ if (event.target instanceof Element && event.target.closest("[data-knobs-switcher]") === element)
113
+ hoveredPreview = { element, target: event.target };
114
+ };
115
+ const onPointerLeave = (event) => {
116
+ if (hoveredPreview?.element !== element)
117
+ return;
118
+ const target = event.relatedTarget instanceof Element ? event.relatedTarget : null;
119
+ const parent = target?.closest("[data-knobs-switcher]");
120
+ hoveredPreview = parent && target ? { element: parent, target } : null;
121
+ };
122
+ const onElementKeyDown = (event) => {
123
+ if (event.target === element && (event.key === "Enter" || event.key === " ")) {
124
+ event.preventDefault();
125
+ cycle(1);
126
+ }
127
+ };
128
+ const onWindowKeyDown = (event) => {
129
+ if (event.defaultPrevented || event.isComposing || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey)
130
+ return;
131
+ const focused = doc.activeElement?.closest("[data-knobs-switcher]");
132
+ const active = hoveredPreview?.element ?? focused;
133
+ if (active !== element || event.target instanceof Element && event.target.closest(interactive) || hoveredPreview?.target.closest(interactive))
134
+ return;
135
+ if (win.getSelection()?.isCollapsed === false)
136
+ return;
137
+ const { options } = getSwitcherOptions(controls, field);
138
+ const selection = /^[1-9]$/.test(event.key) ? Number(event.key) - 1 : -1;
139
+ if (selection >= 0 && options[selection]) {
140
+ event.preventDefault();
141
+ options[selection].choose();
142
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
143
+ event.preventDefault();
144
+ cycle(event.key === "ArrowLeft" ? -1 : 1);
145
+ }
146
+ };
147
+ element.addEventListener("pointerover", onPointerOver);
148
+ element.addEventListener("pointerleave", onPointerLeave);
149
+ element.addEventListener("keydown", onElementKeyDown);
150
+ win.addEventListener("keydown", onWindowKeyDown);
151
+ const unsubscribe = controls.subscribe(sync);
152
+ sync();
153
+ return () => {
154
+ unsubscribe();
155
+ element.removeEventListener("pointerover", onPointerOver);
156
+ element.removeEventListener("pointerleave", onPointerLeave);
157
+ element.removeEventListener("keydown", onElementKeyDown);
158
+ win.removeEventListener("keydown", onWindowKeyDown);
159
+ if (hoveredPreview?.element === element)
160
+ hoveredPreview = null;
161
+ delete element.dataset.knobsSwitcher;
162
+ if (addedTabIndex && element.getAttribute("tabindex") === "0")
163
+ element.removeAttribute("tabindex");
164
+ };
165
+ }
166
+ var hoveredPreview = null, lastAppliedByControls, interactive = "a[href],button,input,select,textarea,[contenteditable=true]";
167
+ var init_switcher = __esm(() => {
168
+ init_schema();
169
+ lastAppliedByControls = new WeakMap;
170
+ });
171
+
172
+ // src/solid/index.ts
173
+ import {
174
+ children as resolveChildren,
175
+ createEffect,
176
+ createMemo,
177
+ createSignal,
178
+ onCleanup,
179
+ untrack
180
+ } from "solid-js";
181
+
182
+ // src/core/adapter.ts
183
+ function syncPanelLook(host, controls, theme, density) {
184
+ const panel = host?.firstElementChild;
185
+ if (!panel)
186
+ return;
187
+ panel.dataset.theme = theme ?? controls.theme;
188
+ panel.dataset.density = density ?? controls.density;
189
+ }
190
+ function syncPanelValues(controls, descriptors, values, land = false) {
191
+ if (!controls.enabled || !descriptors && !values)
192
+ return;
193
+ if (descriptors && "update" in controls)
194
+ controls.update(descriptors, values, { land });
195
+ else if (values)
196
+ controls.sync(values, { replace: true, land });
197
+ }
198
+ function mountPanelAdapter(host, controls, getProps) {
199
+ const p = getProps();
200
+ return controls.mount(host, {
201
+ title: p.title,
202
+ theme: p.theme,
203
+ density: p.density,
204
+ renderer: p.renderer,
205
+ variants: p.variants,
206
+ history: p.history,
207
+ footer: p.footer,
208
+ header: p.header,
209
+ keys: p.keys,
210
+ marks: p.marks,
211
+ preset: p.preset,
212
+ copy: p.copy ? () => getProps().copy?.() ?? controls.code() : undefined,
213
+ actions: p.actions,
214
+ onClose: p.onClose ? () => getProps().onClose?.() : undefined,
215
+ onReady(panel) {
216
+ const cur = getProps();
217
+ syncPanelLook(host, controls, cur.theme, cur.density);
218
+ cur.onReady?.(panel);
219
+ }
220
+ });
221
+ }
222
+
223
+ // src/solid/index.ts
224
+ init_schema();
225
+ function useControls(controls) {
226
+ const [values, setValues] = createSignal(controls.get());
227
+ const unsubscribe = controls.subscribe(() => {
228
+ setValues(() => controls.get());
229
+ });
230
+ onCleanup(unsubscribe);
231
+ return values;
232
+ }
233
+ function useValue(controls, key) {
234
+ const [value, setValue] = createSignal(controls.get()[key], { equals: same });
235
+ onCleanup(controls.subscribe(() => setValue(() => controls.get()[key])));
236
+ return value;
237
+ }
238
+ function useModified(controls, key) {
239
+ const [modified, setModified] = createSignal(controls.isModified(key));
240
+ onCleanup(controls.subscribe(() => setModified(controls.isModified(key))));
241
+ return modified;
242
+ }
243
+ function useKnobs(controls) {
244
+ return (element) => {
245
+ createEffect(() => {
246
+ const detach = controls.attach(element);
247
+ onCleanup(detach);
248
+ });
249
+ };
250
+ }
251
+ function knobs(element, accessor) {
252
+ createEffect(() => {
253
+ const detach = accessor().attach(element);
254
+ onCleanup(detach);
255
+ });
256
+ }
257
+ function Knobs(props) {
258
+ const resolved = resolveChildren(() => props.children);
259
+ createEffect(() => {
260
+ const c = props.controls;
261
+ const node = resolved();
262
+ const target = Array.isArray(node) ? node[0] : node;
263
+ if (typeof HTMLElement !== "undefined" && target instanceof HTMLElement) {
264
+ const detach = c.attach(target);
265
+ onCleanup(detach);
266
+ }
267
+ });
268
+ return resolved;
269
+ }
270
+ function KnobsPanel(props) {
271
+ if (typeof document === "undefined")
272
+ return null;
273
+ const host = document.createElement("div");
274
+ createEffect(() => {
275
+ const c = props.controls;
276
+ host.dataset.knobsMount = c.id;
277
+ if (!c.enabled) {
278
+ host.replaceChildren();
279
+ return;
280
+ }
281
+ const dispose = untrack(() => mountPanelAdapter(host, c, () => props));
282
+ onCleanup(dispose);
283
+ });
284
+ createEffect(() => {
285
+ syncPanelValues(props.controls, props.descriptors, props.values, Boolean(props.land));
286
+ });
287
+ createEffect(() => {
288
+ syncPanelLook(host, props.controls, props.theme, props.density);
289
+ });
290
+ return host;
291
+ }
292
+ function VariantSwitcher(props) {
293
+ const values = useControls(props.controls);
294
+ const fallbackText = createMemo(() => {
295
+ const v = values();
296
+ return props.field ? String(v[props.field]) : props.controls.matchingPreset();
297
+ });
298
+ if (typeof document === "undefined" || !props.controls.enabled) {
299
+ return () => props.children ?? fallbackText();
300
+ }
301
+ const wrapper = document.createElement("div");
302
+ const resolved = resolveChildren(() => props.children ?? fallbackText());
303
+ createEffect(() => {
304
+ const nodes = resolved.toArray();
305
+ wrapper.replaceChildren(...nodes.map((n) => typeof Node !== "undefined" && n instanceof Node ? n : document.createTextNode(String(n ?? ""))));
306
+ });
307
+ createEffect(() => {
308
+ const c = props.controls;
309
+ const field = props.field;
310
+ const label = props.label;
311
+ if (!c.enabled)
312
+ return;
313
+ let disposed = false;
314
+ let detach;
315
+ Promise.resolve().then(() => (init_switcher(), {})).then((mod) => {
316
+ if (!disposed && c.enabled) {
317
+ detach = attachVariantSwitcher(wrapper, c, { field, label });
318
+ }
319
+ });
320
+ onCleanup(() => {
321
+ disposed = true;
322
+ detach?.();
323
+ });
324
+ });
325
+ return wrapper;
326
+ }
327
+ export {
328
+ Knobs,
329
+ KnobsPanel,
330
+ VariantSwitcher,
331
+ knobs,
332
+ useControls,
333
+ useKnobs,
334
+ useModified,
335
+ useValue
336
+ };