@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.
- package/LICENSE +21 -0
- package/README.md +1270 -2
- package/dist/dev/chunk-e13znr7c.js +155 -0
- package/dist/dev/chunk-kdewv0mt.js +1334 -0
- package/dist/dev/chunk-ktsn42p7.js +15668 -0
- package/dist/dev/chunk-qtt698hp.js +1118 -0
- package/dist/dev/index.js +1389 -0
- package/dist/index.js +2367 -0
- package/dist/panel.js +23635 -0
- package/dist/react.js +326 -0
- package/dist/solid.js +336 -0
- package/dist/themes.js +235 -0
- package/dist/types/core/adapter.d.ts +21 -0
- package/dist/types/core/code.d.ts +3 -0
- package/dist/types/core/color.d.ts +27 -0
- package/dist/types/core/easing.d.ts +21 -0
- package/dist/types/core/internal.d.ts +18 -0
- package/dist/types/core/keys.d.ts +21 -0
- package/dist/types/core/mutate.d.ts +21 -0
- package/dist/types/core/ramp.d.ts +150 -0
- package/dist/types/core/random.d.ts +2 -0
- package/dist/types/core/readout.d.ts +73 -0
- package/dist/types/core/remote.d.ts +258 -0
- package/dist/types/core/schema.d.ts +526 -0
- package/dist/types/core/spring.d.ts +38 -0
- package/dist/types/core/storage.d.ts +8 -0
- package/dist/types/core/store.d.ts +474 -0
- package/dist/types/core/theme.d.ts +27 -0
- package/dist/types/core/variants.d.ts +17 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/panel/attach.d.ts +5 -0
- package/dist/types/panel/choice.d.ts +56 -0
- package/dist/types/panel/die.d.ts +121 -0
- package/dist/types/panel/editors.d.ts +159 -0
- package/dist/types/panel/folds.d.ts +14 -0
- package/dist/types/panel/give.d.ts +73 -0
- package/dist/types/panel/gpu/atlas.d.ts +40 -0
- package/dist/types/panel/gpu/color.d.ts +128 -0
- package/dist/types/panel/gpu/die.wgsl.d.ts +1 -0
- package/dist/types/panel/gpu/renderer.d.ts +712 -0
- package/dist/types/panel/gpu/shader.wgsl.d.ts +2 -0
- package/dist/types/panel/haptics.d.ts +8 -0
- package/dist/types/panel/has.d.ts +14 -0
- package/dist/types/panel/hints.d.ts +45 -0
- package/dist/types/panel/icons.d.ts +30 -0
- package/dist/types/panel/index.d.ts +10 -0
- package/dist/types/panel/inspector.d.ts +20 -0
- package/dist/types/panel/measure.d.ts +6 -0
- package/dist/types/panel/menu.d.ts +38 -0
- package/dist/types/panel/morph.d.ts +38 -0
- package/dist/types/panel/motion.d.ts +77 -0
- package/dist/types/panel/page-hints.d.ts +82 -0
- package/dist/types/panel/page.d.ts +423 -0
- package/dist/types/panel/panel.d.ts +43 -0
- package/dist/types/panel/position.d.ts +5 -0
- package/dist/types/panel/prefs.d.ts +28 -0
- package/dist/types/panel/presets.d.ts +48 -0
- package/dist/types/panel/ramps.d.ts +14 -0
- package/dist/types/panel/readout-row.d.ts +21 -0
- package/dist/types/panel/reveal.d.ts +15 -0
- package/dist/types/panel/rolling-css.d.ts +2 -0
- package/dist/types/panel/search.d.ts +36 -0
- package/dist/types/panel/seg.d.ts +15 -0
- package/dist/types/panel/session.d.ts +21 -0
- package/dist/types/panel/slider.d.ts +92 -0
- package/dist/types/panel/smear.d.ts +7 -0
- package/dist/types/panel/spin.d.ts +13 -0
- package/dist/types/panel/styles.d.ts +11 -0
- package/dist/types/panel/swipe.d.ts +114 -0
- package/dist/types/panel/switcher.d.ts +20 -0
- package/dist/types/panel/tabs.d.ts +31 -0
- package/dist/types/panel/thumb.d.ts +36 -0
- package/dist/types/react/Knobs.d.ts +15 -0
- package/dist/types/react/VariantSwitcher.d.ts +10 -0
- package/dist/types/react/index.d.ts +3 -0
- package/dist/types/react/useControls.d.ts +11 -0
- package/dist/types/solid/index.d.ts +50 -0
- package/dist/types/themes/index.d.ts +1 -0
- package/dist/types/ui/bento-css.d.ts +1 -0
- package/dist/types/ui/bento-model.d.ts +96 -0
- package/dist/types/ui/bento.d.ts +77 -0
- package/dist/types/ui/commands.d.ts +11 -0
- package/dist/types/ui/confirm.d.ts +13 -0
- package/dist/types/ui/index.d.ts +20 -0
- package/dist/types/ui/layer.d.ts +70 -0
- package/dist/types/ui/look.d.ts +53 -0
- package/dist/types/ui/menu.d.ts +63 -0
- package/dist/types/ui/overlay-css.d.ts +1 -0
- package/dist/types/ui/overlay-model.d.ts +87 -0
- package/dist/types/ui/palette.d.ts +38 -0
- package/dist/types/ui/popover.d.ts +35 -0
- package/dist/types/ui/react.d.ts +45 -0
- package/dist/types/ui/readout-css.d.ts +1 -0
- package/dist/types/ui/readout-model.d.ts +137 -0
- package/dist/types/ui/readouts.d.ts +85 -0
- package/dist/types/ui/rgba.d.ts +6 -0
- package/dist/types/ui/solid.d.ts +40 -0
- package/dist/types/ui/split-model.d.ts +81 -0
- package/dist/types/ui/split.d.ts +75 -0
- package/dist/types/ui/timeline-model.d.ts +83 -0
- package/dist/types/ui/timeline.d.ts +76 -0
- package/dist/types/ui/toast.d.ts +19 -0
- package/dist/types/ui/tooltip.d.ts +19 -0
- package/dist/types/ui/tree-model.d.ts +125 -0
- package/dist/types/ui/tree.d.ts +95 -0
- package/dist/ui-react.js +6558 -0
- package/dist/ui-solid.js +6511 -0
- package/dist/ui.js +9238 -0
- 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
|
+
};
|