dialkit 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -12,12 +12,16 @@ var DialStoreClass = class {
12
12
  this.globalListeners = /* @__PURE__ */ new Set();
13
13
  this.snapshots = /* @__PURE__ */ new Map();
14
14
  this.actionListeners = /* @__PURE__ */ new Map();
15
+ this.presets = /* @__PURE__ */ new Map();
16
+ this.activePreset = /* @__PURE__ */ new Map();
17
+ this.baseValues = /* @__PURE__ */ new Map();
15
18
  }
16
19
  registerPanel(id, name, config) {
17
20
  const controls = this.parseConfig(config, "");
18
21
  const values = this.flattenValues(config, "");
19
22
  this.panels.set(id, { id, name, controls, values });
20
23
  this.snapshots.set(id, { ...values });
24
+ this.baseValues.set(id, { ...values });
21
25
  this.notifyGlobal();
22
26
  }
23
27
  unregisterPanel(id) {
@@ -30,6 +34,15 @@ var DialStoreClass = class {
30
34
  const panel = this.panels.get(panelId);
31
35
  if (!panel) return;
32
36
  panel.values[path] = value;
37
+ const activeId = this.activePreset.get(panelId);
38
+ if (activeId) {
39
+ const presets = this.presets.get(panelId) ?? [];
40
+ const preset = presets.find((p) => p.id === activeId);
41
+ if (preset) preset.values[path] = value;
42
+ } else {
43
+ const base = this.baseValues.get(panelId);
44
+ if (base) base[path] = value;
45
+ }
33
46
  this.snapshots.set(panelId, { ...panel.values });
34
47
  this.notify(panelId);
35
48
  }
@@ -83,6 +96,61 @@ var DialStoreClass = class {
83
96
  triggerAction(panelId, path) {
84
97
  this.actionListeners.get(panelId)?.forEach((fn) => fn(path));
85
98
  }
99
+ savePreset(panelId, name) {
100
+ const panel = this.panels.get(panelId);
101
+ if (!panel) throw new Error(`Panel ${panelId} not found`);
102
+ const id = `preset-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
103
+ const preset = {
104
+ id,
105
+ name,
106
+ values: { ...panel.values }
107
+ };
108
+ const existing = this.presets.get(panelId) ?? [];
109
+ this.presets.set(panelId, [...existing, preset]);
110
+ this.activePreset.set(panelId, id);
111
+ this.snapshots.set(panelId, { ...panel.values });
112
+ this.notify(panelId);
113
+ return id;
114
+ }
115
+ loadPreset(panelId, presetId) {
116
+ const panel = this.panels.get(panelId);
117
+ if (!panel) return;
118
+ const presets = this.presets.get(panelId) ?? [];
119
+ const preset = presets.find((p) => p.id === presetId);
120
+ if (!preset) return;
121
+ panel.values = { ...preset.values };
122
+ this.snapshots.set(panelId, { ...panel.values });
123
+ this.activePreset.set(panelId, presetId);
124
+ this.notify(panelId);
125
+ }
126
+ deletePreset(panelId, presetId) {
127
+ const presets = this.presets.get(panelId) ?? [];
128
+ this.presets.set(panelId, presets.filter((p) => p.id !== presetId));
129
+ if (this.activePreset.get(panelId) === presetId) {
130
+ this.activePreset.set(panelId, null);
131
+ }
132
+ const panel = this.panels.get(panelId);
133
+ if (panel) {
134
+ this.snapshots.set(panelId, { ...panel.values });
135
+ }
136
+ this.notify(panelId);
137
+ }
138
+ getPresets(panelId) {
139
+ return this.presets.get(panelId) ?? [];
140
+ }
141
+ getActivePresetId(panelId) {
142
+ return this.activePreset.get(panelId) ?? null;
143
+ }
144
+ clearActivePreset(panelId) {
145
+ const panel = this.panels.get(panelId);
146
+ const base = this.baseValues.get(panelId);
147
+ if (panel && base) {
148
+ panel.values = { ...base };
149
+ this.snapshots.set(panelId, { ...panel.values });
150
+ }
151
+ this.activePreset.set(panelId, null);
152
+ this.notify(panelId);
153
+ }
86
154
  notify(panelId) {
87
155
  this.listeners.get(panelId)?.forEach((fn) => fn());
88
156
  }
@@ -112,6 +180,18 @@ var DialStoreClass = class {
112
180
  controls.push({ type: "spring", path, label });
113
181
  } else if (this.isActionConfig(value)) {
114
182
  controls.push({ type: "action", path, label: value.label || label });
183
+ } else if (this.isSelectConfig(value)) {
184
+ controls.push({ type: "select", path, label, options: value.options });
185
+ } else if (this.isColorConfig(value)) {
186
+ controls.push({ type: "color", path, label });
187
+ } else if (this.isTextConfig(value)) {
188
+ controls.push({ type: "text", path, label, placeholder: value.placeholder });
189
+ } else if (typeof value === "string") {
190
+ if (this.isHexColor(value)) {
191
+ controls.push({ type: "color", path, label });
192
+ } else {
193
+ controls.push({ type: "text", path, label });
194
+ }
115
195
  } else if (typeof value === "object" && value !== null) {
116
196
  controls.push({
117
197
  type: "folder",
@@ -135,6 +215,14 @@ var DialStoreClass = class {
135
215
  values[path] = value;
136
216
  } else if (this.isActionConfig(value)) {
137
217
  values[path] = value;
218
+ } else if (this.isSelectConfig(value)) {
219
+ const firstOption = value.options[0];
220
+ const firstValue = typeof firstOption === "string" ? firstOption : firstOption.value;
221
+ values[path] = value.default ?? firstValue;
222
+ } else if (this.isColorConfig(value)) {
223
+ values[path] = value.default ?? "#000000";
224
+ } else if (this.isTextConfig(value)) {
225
+ values[path] = value.default ?? "";
138
226
  } else if (typeof value === "object" && value !== null) {
139
227
  Object.assign(values, this.flattenValues(value, path));
140
228
  }
@@ -147,6 +235,18 @@ var DialStoreClass = class {
147
235
  isActionConfig(value) {
148
236
  return typeof value === "object" && value !== null && "type" in value && value.type === "action";
149
237
  }
238
+ isSelectConfig(value) {
239
+ return typeof value === "object" && value !== null && "type" in value && value.type === "select" && "options" in value && Array.isArray(value.options);
240
+ }
241
+ isColorConfig(value) {
242
+ return typeof value === "object" && value !== null && "type" in value && value.type === "color";
243
+ }
244
+ isTextConfig(value) {
245
+ return typeof value === "object" && value !== null && "type" in value && value.type === "text";
246
+ }
247
+ isHexColor(value) {
248
+ return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(value);
249
+ }
150
250
  formatLabel(key) {
151
251
  return key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
152
252
  }
@@ -154,13 +254,13 @@ var DialStoreClass = class {
154
254
  if (value >= 0 && value <= 1) {
155
255
  return { min: 0, max: 1, step: 0.01 };
156
256
  } else if (value >= 0 && value <= 10) {
157
- return { min: 0, max: value * 2 || 10, step: 0.1 };
257
+ return { min: 0, max: value * 3 || 10, step: 0.1 };
158
258
  } else if (value >= 0 && value <= 100) {
159
- return { min: 0, max: value * 2 || 100, step: 1 };
259
+ return { min: 0, max: value * 3 || 100, step: 1 };
160
260
  } else if (value >= 0) {
161
- return { min: 0, max: value * 2 || 1e3, step: 10 };
261
+ return { min: 0, max: value * 3 || 1e3, step: 10 };
162
262
  } else {
163
- return { min: value * 2, max: -value * 2, step: 1 };
263
+ return { min: value * 3, max: -value * 3, step: 1 };
164
264
  }
165
265
  }
166
266
  inferStep(min, max) {
@@ -206,188 +306,150 @@ function buildResolvedValues(config, flatValues, prefix) {
206
306
  result[key] = flatValues[path] ?? configValue;
207
307
  } else if (isSpringConfig(configValue)) {
208
308
  result[key] = flatValues[path] ?? configValue;
309
+ } else if (isActionConfig(configValue)) {
310
+ result[key] = flatValues[path] ?? configValue;
311
+ } else if (isSelectConfig(configValue)) {
312
+ const defaultValue = configValue.default ?? getFirstOptionValue(configValue.options);
313
+ result[key] = flatValues[path] ?? defaultValue;
314
+ } else if (isColorConfig(configValue)) {
315
+ result[key] = flatValues[path] ?? configValue.default ?? "#000000";
316
+ } else if (isTextConfig(configValue)) {
317
+ result[key] = flatValues[path] ?? configValue.default ?? "";
209
318
  } else if (typeof configValue === "object" && configValue !== null) {
210
319
  result[key] = buildResolvedValues(configValue, flatValues, path);
211
320
  }
212
321
  }
213
322
  return result;
214
323
  }
324
+ function hasType(value, type) {
325
+ return typeof value === "object" && value !== null && "type" in value && value.type === type;
326
+ }
215
327
  function isSpringConfig(value) {
216
- return typeof value === "object" && value !== null && "type" in value && value.type === "spring";
328
+ return hasType(value, "spring");
329
+ }
330
+ function isActionConfig(value) {
331
+ return hasType(value, "action");
332
+ }
333
+ function isSelectConfig(value) {
334
+ return hasType(value, "select") && "options" in value && Array.isArray(value.options);
335
+ }
336
+ function isColorConfig(value) {
337
+ return hasType(value, "color");
338
+ }
339
+ function isTextConfig(value) {
340
+ return hasType(value, "text");
341
+ }
342
+ function getFirstOptionValue(options) {
343
+ const first = options[0];
344
+ return typeof first === "string" ? first : first.value;
217
345
  }
218
346
 
219
347
  // src/components/DialRoot.tsx
220
- import { useEffect as useEffect3, useState as useState4 } from "react";
221
- import { createPortal } from "react-dom";
348
+ import { useEffect as useEffect7, useState as useState8 } from "react";
349
+ import { createPortal as createPortal2 } from "react-dom";
350
+
351
+ // src/components/Panel.tsx
352
+ import { useState as useState7, useSyncExternalStore as useSyncExternalStore3 } from "react";
353
+ import { motion as motion6, AnimatePresence as AnimatePresence4 } from "motion/react";
222
354
 
223
355
  // src/components/Folder.tsx
224
- import { useState } from "react";
356
+ import { useState, useRef as useRef2, useEffect as useEffect2 } from "react";
225
357
  import { motion, AnimatePresence } from "motion/react";
226
358
  import { jsx, jsxs } from "react/jsx-runtime";
227
- function Folder({ title, children, defaultOpen = true, isRoot = false, panelId }) {
359
+ function Folder({ title, children, defaultOpen = true, isRoot = false, onOpenChange, toolbar }) {
228
360
  const [isOpen, setIsOpen] = useState(defaultOpen);
229
- const [copied, setCopied] = useState(false);
230
- const handleCopy = (e) => {
231
- e.stopPropagation();
232
- if (!panelId) return;
233
- const values = DialStore.getValues(panelId);
234
- const jsonStr = JSON.stringify(values, null, 2);
235
- const instruction = `Update the useDialKit configuration for "${title}" with these values:
236
-
237
- \`\`\`json
238
- ${jsonStr}
239
- \`\`\`
240
-
241
- Apply these values as the new defaults in the useDialKit call.`;
242
- navigator.clipboard.writeText(instruction);
243
- setCopied(true);
244
- setTimeout(() => setCopied(false), 1500);
245
- };
361
+ const [isCollapsed, setIsCollapsed] = useState(!defaultOpen);
362
+ const contentRef = useRef2(null);
363
+ const [contentHeight, setContentHeight] = useState(void 0);
246
364
  const iconTransition = { type: "spring", visualDuration: 0.4, bounce: 0.1 };
247
- const folderContent = /* @__PURE__ */ jsxs("div", { className: `dialkit-folder ${isRoot ? "dialkit-folder-root" : ""}`, children: [
248
- /* @__PURE__ */ jsxs("div", { className: "dialkit-folder-header", onClick: () => setIsOpen(!isOpen), children: [
249
- /* @__PURE__ */ jsxs("div", { className: "dialkit-folder-title-row", children: [
250
- /* @__PURE__ */ jsx("span", { className: `dialkit-folder-title ${isRoot ? "dialkit-folder-title-root" : ""}`, children: title }),
251
- isRoot && panelId && /* @__PURE__ */ jsx(
252
- motion.button,
365
+ const panelTransition = { type: "spring", visualDuration: 0.4, bounce: 0.2 };
366
+ useEffect2(() => {
367
+ const el = contentRef.current;
368
+ if (!el) return;
369
+ const ro = new ResizeObserver(() => {
370
+ if (isOpen) {
371
+ const h = el.offsetHeight;
372
+ setContentHeight((prev) => prev === h ? prev : h);
373
+ }
374
+ });
375
+ ro.observe(el);
376
+ return () => ro.disconnect();
377
+ }, [isOpen]);
378
+ const folderContent = /* @__PURE__ */ jsxs("div", { ref: isRoot ? contentRef : void 0, className: `dialkit-folder ${isRoot ? "dialkit-folder-root" : ""}`, children: [
379
+ /* @__PURE__ */ jsxs("div", { className: `dialkit-folder-header ${isRoot ? "dialkit-panel-header" : ""}`, onClick: () => {
380
+ const next = !isOpen;
381
+ setIsOpen(next);
382
+ if (next) setIsCollapsed(false);
383
+ onOpenChange?.(next);
384
+ }, children: [
385
+ /* @__PURE__ */ jsxs("div", { className: "dialkit-folder-header-top", children: [
386
+ isRoot ? /* @__PURE__ */ jsx(AnimatePresence, { initial: false, mode: "popLayout", children: isOpen && /* @__PURE__ */ jsx(
387
+ motion.div,
253
388
  {
254
- className: "dialkit-folder-copy",
255
- onClick: handleCopy,
256
- title: "Copy parameters",
257
- initial: false,
258
- animate: {
259
- opacity: isOpen ? 1 : 0,
260
- scale: isOpen ? 1 : 0.7,
261
- filter: isOpen ? "blur(0px)" : "blur(6px)"
262
- },
263
- transition: iconTransition,
264
- style: { pointerEvents: isOpen ? "auto" : "none" },
265
- children: /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: 14, height: 14 }, children: [
266
- /* @__PURE__ */ jsxs(
267
- motion.svg,
268
- {
269
- viewBox: "0 0 24 24",
270
- fill: "none",
271
- stroke: "currentColor",
272
- strokeWidth: "2",
273
- strokeLinecap: "round",
274
- strokeLinejoin: "round",
275
- style: { position: "absolute", inset: 0 },
276
- initial: false,
277
- animate: {
278
- opacity: copied ? 0 : 1,
279
- scale: copied ? 0.7 : 1,
280
- filter: copied ? "blur(6px)" : "blur(0px)"
281
- },
282
- transition: iconTransition,
283
- children: [
284
- /* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
285
- /* @__PURE__ */ jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
286
- ]
287
- }
288
- ),
289
- /* @__PURE__ */ jsx(
290
- motion.svg,
291
- {
292
- viewBox: "0 0 24 24",
293
- fill: "none",
294
- stroke: "currentColor",
295
- strokeWidth: "2",
296
- strokeLinecap: "round",
297
- strokeLinejoin: "round",
298
- style: { position: "absolute", inset: 0 },
299
- initial: false,
300
- animate: {
301
- opacity: copied ? 1 : 0,
302
- scale: copied ? 1 : 0.7,
303
- filter: copied ? "blur(0px)" : "blur(6px)"
304
- },
305
- transition: iconTransition,
306
- children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
307
- }
308
- )
309
- ] })
389
+ className: "dialkit-folder-title-row",
390
+ initial: { opacity: 1 },
391
+ animate: { opacity: 1 },
392
+ exit: { opacity: 0 },
393
+ transition: { duration: 0.15 },
394
+ children: /* @__PURE__ */ jsx("span", { className: "dialkit-folder-title dialkit-folder-title-root", children: title })
310
395
  }
311
- )
312
- ] }),
313
- isRoot ? (
314
- // Root panel uses minus/plus icons with crossfade
315
- /* @__PURE__ */ jsxs("div", { className: "dialkit-folder-icon", style: { position: "relative" }, children: [
316
- /* @__PURE__ */ jsx(
317
- motion.svg,
396
+ ) }) : /* @__PURE__ */ jsx("div", { className: "dialkit-folder-title-row", children: /* @__PURE__ */ jsx("span", { className: "dialkit-folder-title", children: title }) }),
397
+ isRoot ? (
398
+ // Root panel icon — fixed position, container morphs around it
399
+ /* @__PURE__ */ jsxs(
400
+ "svg",
318
401
  {
319
- viewBox: "0 0 24 24",
402
+ className: "dialkit-panel-icon",
403
+ viewBox: "0 0 16 16",
320
404
  fill: "none",
321
- stroke: "currentColor",
322
- strokeWidth: "2",
323
- strokeLinecap: "round",
324
- strokeLinejoin: "round",
325
- style: { position: "absolute", inset: 0, width: "100%", height: "100%" },
326
- initial: false,
327
- animate: {
328
- opacity: isOpen ? 1 : 0,
329
- scale: isOpen ? 1 : 0.7,
330
- filter: isOpen ? "blur(0px)" : "blur(6px)"
331
- },
332
- transition: iconTransition,
333
- children: /* @__PURE__ */ jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
405
+ children: [
406
+ /* @__PURE__ */ jsx("path", { opacity: "0.5", d: "M6.84766 11.75C6.78583 11.9899 6.75 12.2408 6.75 12.5C6.75 12.7592 6.78583 13.0101 6.84766 13.25H2C1.58579 13.25 1.25 12.9142 1.25 12.5C1.25 12.0858 1.58579 11.75 2 11.75H6.84766ZM14 11.75C14.4142 11.75 14.75 12.0858 14.75 12.5C14.75 12.9142 14.4142 13.25 14 13.25H12.6523C12.7142 13.0101 12.75 12.7592 12.75 12.5C12.75 12.2408 12.7142 11.9899 12.6523 11.75H14ZM3.09766 7.25C3.03583 7.48994 3 7.74075 3 8C3 8.25925 3.03583 8.51006 3.09766 8.75H2C1.58579 8.75 1.25 8.41421 1.25 8C1.25 7.58579 1.58579 7.25 2 7.25H3.09766ZM14 7.25C14.4142 7.25 14.75 7.58579 14.75 8C14.75 8.41421 14.4142 8.75 14 8.75H8.90234C8.96417 8.51006 9 8.25925 9 8C9 7.74075 8.96417 7.48994 8.90234 7.25H14ZM7.59766 2.75C7.53583 2.98994 7.5 3.24075 7.5 3.5C7.5 3.75925 7.53583 4.01006 7.59766 4.25H2C1.58579 4.25 1.25 3.91421 1.25 3.5C1.25 3.08579 1.58579 2.75 2 2.75H7.59766ZM14 2.75C14.4142 2.75 14.75 3.08579 14.75 3.5C14.75 3.91421 14.4142 4.25 14 4.25H13.4023C13.4642 4.01006 13.5 3.75925 13.5 3.5C13.5 3.24075 13.4642 2.98994 13.4023 2.75H14Z", fill: "currentColor" }),
407
+ /* @__PURE__ */ jsx("circle", { cx: "6", cy: "8", r: "0.998596", fill: "currentColor", stroke: "currentColor", strokeWidth: "1.25" }),
408
+ /* @__PURE__ */ jsx("circle", { cx: "10.4999", cy: "3.5", r: "0.998657", fill: "currentColor", stroke: "currentColor", strokeWidth: "1.25" }),
409
+ /* @__PURE__ */ jsx("circle", { cx: "9.75015", cy: "12.5", r: "0.997986", fill: "currentColor", stroke: "currentColor", strokeWidth: "1.25" })
410
+ ]
334
411
  }
335
- ),
336
- /* @__PURE__ */ jsxs(
412
+ )
413
+ ) : (
414
+ // Section folders use rotating chevron with gentle spring
415
+ /* @__PURE__ */ jsx(
337
416
  motion.svg,
338
417
  {
418
+ className: "dialkit-folder-icon",
339
419
  viewBox: "0 0 24 24",
340
420
  fill: "none",
341
421
  stroke: "currentColor",
342
- strokeWidth: "2",
422
+ strokeWidth: "2.5",
343
423
  strokeLinecap: "round",
344
424
  strokeLinejoin: "round",
345
- style: { position: "absolute", inset: 0, width: "100%", height: "100%" },
346
425
  initial: false,
347
- animate: {
348
- opacity: isOpen ? 0 : 1,
349
- scale: isOpen ? 0.7 : 1,
350
- filter: isOpen ? "blur(6px)" : "blur(0px)"
351
- },
352
- transition: iconTransition,
353
- children: [
354
- /* @__PURE__ */ jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
355
- /* @__PURE__ */ jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
356
- ]
426
+ animate: { rotate: isOpen ? 0 : 180 },
427
+ transition: { type: "spring", visualDuration: 0.35, bounce: 0.15 },
428
+ children: /* @__PURE__ */ jsx("path", { d: "M6 9.5L12 15.5L18 9.5" })
357
429
  }
358
430
  )
359
- ] })
360
- ) : (
361
- // Section folders use rotating chevron with gentle spring
362
- /* @__PURE__ */ jsxs(
363
- motion.svg,
364
- {
365
- className: "dialkit-folder-icon",
366
- viewBox: "0 0 24 24",
367
- fill: "none",
368
- stroke: "currentColor",
369
- strokeWidth: "1.5",
370
- strokeLinecap: "round",
371
- strokeLinejoin: "round",
372
- initial: false,
373
- animate: { rotate: isOpen ? 0 : 180 },
374
- transition: { type: "spring", visualDuration: 0.35, bounce: 0.15 },
375
- children: [
376
- /* @__PURE__ */ jsx("line", { x1: "5", y1: "9", x2: "12", y2: "16" }),
377
- /* @__PURE__ */ jsx("line", { x1: "19", y1: "9", x2: "12", y2: "16" })
378
- ]
379
- }
380
431
  )
381
- )
432
+ ] }),
433
+ isRoot && toolbar && /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: isOpen && /* @__PURE__ */ jsx(
434
+ motion.div,
435
+ {
436
+ initial: { opacity: 1 },
437
+ animate: { opacity: 1 },
438
+ exit: { opacity: 0 },
439
+ transition: { duration: 0.15 },
440
+ children: /* @__PURE__ */ jsx("div", { className: "dialkit-panel-toolbar", onClick: (e) => e.stopPropagation(), children: toolbar })
441
+ }
442
+ ) })
382
443
  ] }),
383
444
  /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: isOpen && /* @__PURE__ */ jsx(
384
445
  motion.div,
385
446
  {
386
447
  className: "dialkit-folder-content",
387
- initial: { height: 0, opacity: 0 },
388
- animate: { height: "auto", opacity: 1 },
389
- exit: { height: 0, opacity: 0 },
390
- transition: { type: "spring", visualDuration: 0.25, bounce: 0 },
448
+ initial: isRoot ? { opacity: 1 } : { height: 0, opacity: 0 },
449
+ animate: isRoot ? { opacity: 1 } : { height: "auto", opacity: 1 },
450
+ exit: isRoot ? { opacity: 0 } : { height: 0, opacity: 0 },
451
+ transition: isRoot ? { duration: 0.15 } : { type: "spring", visualDuration: 0.35, bounce: 0.1 },
452
+ style: isRoot ? void 0 : { overflow: "hidden" },
391
453
  children: /* @__PURE__ */ jsx("div", { className: "dialkit-folder-inner", children })
392
454
  }
393
455
  ) })
@@ -399,11 +461,22 @@ Apply these values as the new defaults in the useDialKit call.`;
399
461
  className: "dialkit-panel-inner",
400
462
  initial: false,
401
463
  animate: {
402
- width: isOpen ? 280 : 160,
464
+ width: isOpen ? 280 : 42,
465
+ height: isOpen ? contentHeight !== void 0 ? contentHeight + 24 : "auto" : 42,
466
+ borderRadius: isOpen ? 14 : 21,
403
467
  boxShadow: isOpen ? "0 8px 32px rgba(0, 0, 0, 0.5)" : "0 4px 16px rgba(0, 0, 0, 0.25)"
404
468
  },
405
- transition: { type: "spring", visualDuration: 0.4, bounce: 0.05 },
406
- "data-collapsed": !isOpen,
469
+ transition: panelTransition,
470
+ style: { overflow: isOpen ? void 0 : "hidden", cursor: isOpen ? void 0 : "pointer" },
471
+ onClick: !isOpen ? () => {
472
+ setIsOpen(true);
473
+ setIsCollapsed(false);
474
+ onOpenChange?.(true);
475
+ } : void 0,
476
+ onAnimationComplete: () => {
477
+ if (!isOpen) setIsCollapsed(true);
478
+ },
479
+ "data-collapsed": isCollapsed,
407
480
  children: folderContent
408
481
  }
409
482
  );
@@ -412,9 +485,26 @@ Apply these values as the new defaults in the useDialKit call.`;
412
485
  }
413
486
 
414
487
  // src/components/Slider.tsx
415
- import { useRef as useRef2, useState as useState2, useCallback, useEffect as useEffect2 } from "react";
416
- import { motion as motion2 } from "motion/react";
488
+ import { useRef as useRef3, useState as useState2, useCallback, useEffect as useEffect3 } from "react";
489
+ import { motion as motion2, useMotionValue, useTransform, animate } from "motion/react";
417
490
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
491
+ var CLICK_THRESHOLD = 3;
492
+ var DEAD_ZONE = 32;
493
+ var MAX_CURSOR_RANGE = 200;
494
+ var MAX_STRETCH = 8;
495
+ function roundValue(val, step) {
496
+ const decimals = step >= 1 ? 0 : 2;
497
+ const factor = 10 ** decimals;
498
+ return Math.round(val * factor) / factor;
499
+ }
500
+ function snapToDecile(rawValue, min, max) {
501
+ const normalized = (rawValue - min) / (max - min);
502
+ const nearest = Math.round(normalized * 10) / 10;
503
+ if (Math.abs(normalized - nearest) <= 0.03125) {
504
+ return min + nearest * (max - min);
505
+ }
506
+ return rawValue;
507
+ }
418
508
  function Slider({
419
509
  label,
420
510
  value,
@@ -424,8 +514,11 @@ function Slider({
424
514
  step = 0.01,
425
515
  unit
426
516
  }) {
427
- const trackRef = useRef2(null);
428
- const inputRef = useRef2(null);
517
+ const wrapperRef = useRef3(null);
518
+ const trackRef = useRef3(null);
519
+ const inputRef = useRef3(null);
520
+ const labelRef = useRef3(null);
521
+ const valueSpanRef = useRef3(null);
429
522
  const [isInteracting, setIsInteracting] = useState2(false);
430
523
  const [isDragging, setIsDragging] = useState2(false);
431
524
  const [isHovered, setIsHovered] = useState2(false);
@@ -433,80 +526,162 @@ function Slider({
433
526
  const [isValueEditable, setIsValueEditable] = useState2(false);
434
527
  const [showInput, setShowInput] = useState2(false);
435
528
  const [inputValue, setInputValue] = useState2("");
436
- const hoverTimeoutRef = useRef2(null);
529
+ const hoverTimeoutRef = useRef3(null);
530
+ const pointerDownPos = useRef3(null);
531
+ const isClickRef = useRef3(true);
532
+ const animRef = useRef3(null);
533
+ const wrapperRectRef = useRef3(null);
534
+ const scaleRef = useRef3(1);
437
535
  const percentage = (value - min) / (max - min) * 100;
438
- const isAtMinimum = value <= min;
439
536
  const isActive = isInteracting || isHovered;
537
+ const fillPercent = useMotionValue(percentage);
538
+ const fillWidth = useTransform(fillPercent, (pct) => `${pct}%`);
539
+ const handleLeft = useTransform(
540
+ fillPercent,
541
+ (pct) => `max(5px, calc(${pct}% - 9px))`
542
+ );
543
+ const rubberStretchPx = useMotionValue(0);
544
+ const rubberBandWidth = useTransform(
545
+ rubberStretchPx,
546
+ (stretch) => `calc(100% + ${Math.abs(stretch)}px)`
547
+ );
548
+ const rubberBandX = useTransform(
549
+ rubberStretchPx,
550
+ (stretch) => stretch < 0 ? stretch : 0
551
+ );
552
+ useEffect3(() => {
553
+ if (!isInteracting && !animRef.current) {
554
+ fillPercent.jump(percentage);
555
+ }
556
+ }, [percentage, isInteracting, fillPercent]);
440
557
  const positionToValue = useCallback(
441
558
  (clientX) => {
442
- if (!trackRef.current) return value;
443
- const rect = trackRef.current.getBoundingClientRect();
444
- const x = clientX - rect.left;
445
- const percent = Math.max(0, Math.min(1, x / rect.width));
559
+ const rect = wrapperRectRef.current;
560
+ if (!rect) return value;
561
+ const screenX = clientX - rect.left;
562
+ const sceneX = screenX / scaleRef.current;
563
+ const nativeWidth = wrapperRef.current ? wrapperRef.current.offsetWidth : rect.width;
564
+ const percent = Math.max(0, Math.min(1, sceneX / nativeWidth));
446
565
  const rawValue = min + percent * (max - min);
447
566
  return Math.max(min, Math.min(max, rawValue));
448
567
  },
449
568
  [min, max, value]
450
569
  );
451
- const handleMouseDown = useCallback(
452
- (e) => {
453
- if (showInput) return;
454
- e.preventDefault();
455
- setIsInteracting(true);
456
- onChange(positionToValue(e.clientX));
457
- },
458
- [positionToValue, onChange, showInput]
570
+ const percentFromValue = useCallback(
571
+ (v) => (v - min) / (max - min) * 100,
572
+ [min, max]
459
573
  );
460
- const handleMouseMove = useCallback(
461
- (e) => {
462
- if (!isInteracting) return;
463
- if (!isDragging) setIsDragging(true);
464
- onChange(positionToValue(e.clientX));
574
+ const computeRubberStretch = useCallback(
575
+ (clientX, sign) => {
576
+ const rect = wrapperRectRef.current;
577
+ if (!rect) return 0;
578
+ const distancePast = sign < 0 ? rect.left - clientX : clientX - rect.right;
579
+ const overflow = Math.max(0, distancePast - DEAD_ZONE);
580
+ return sign * MAX_STRETCH * Math.sqrt(Math.min(overflow / MAX_CURSOR_RANGE, 1));
465
581
  },
466
- [isInteracting, isDragging, positionToValue, onChange]
582
+ []
467
583
  );
468
- const handleMouseUp = useCallback(() => {
469
- setIsInteracting(false);
470
- setIsDragging(false);
471
- }, []);
472
- const handleTouchStart = useCallback(
584
+ const handlePointerDown = useCallback(
473
585
  (e) => {
474
586
  if (showInput) return;
475
587
  e.preventDefault();
588
+ e.target.setPointerCapture(e.pointerId);
589
+ pointerDownPos.current = { x: e.clientX, y: e.clientY };
590
+ isClickRef.current = true;
476
591
  setIsInteracting(true);
477
- const touch = e.touches[0];
478
- onChange(positionToValue(touch.clientX));
592
+ if (wrapperRef.current) {
593
+ wrapperRectRef.current = wrapperRef.current.getBoundingClientRect();
594
+ const nativeWidth = wrapperRef.current.offsetWidth;
595
+ scaleRef.current = wrapperRectRef.current.width / nativeWidth;
596
+ }
479
597
  },
480
- [positionToValue, onChange, showInput]
598
+ [showInput]
481
599
  );
482
- const handleTouchMove = useCallback(
600
+ const handlePointerMove = useCallback(
601
+ (e) => {
602
+ if (!isInteracting || !pointerDownPos.current) return;
603
+ const dx = e.clientX - pointerDownPos.current.x;
604
+ const dy = e.clientY - pointerDownPos.current.y;
605
+ const distance = Math.sqrt(dx * dx + dy * dy);
606
+ if (isClickRef.current && distance > CLICK_THRESHOLD) {
607
+ isClickRef.current = false;
608
+ setIsDragging(true);
609
+ }
610
+ if (!isClickRef.current) {
611
+ const rect = wrapperRectRef.current;
612
+ if (rect) {
613
+ if (e.clientX < rect.left) {
614
+ rubberStretchPx.jump(computeRubberStretch(e.clientX, -1));
615
+ } else if (e.clientX > rect.right) {
616
+ rubberStretchPx.jump(computeRubberStretch(e.clientX, 1));
617
+ } else {
618
+ rubberStretchPx.jump(0);
619
+ }
620
+ }
621
+ const newValue = positionToValue(e.clientX);
622
+ const newPct = percentFromValue(newValue);
623
+ if (animRef.current) {
624
+ animRef.current.stop();
625
+ animRef.current = null;
626
+ }
627
+ fillPercent.jump(newPct);
628
+ onChange(roundValue(newValue, step));
629
+ }
630
+ },
631
+ [
632
+ isInteracting,
633
+ positionToValue,
634
+ percentFromValue,
635
+ onChange,
636
+ fillPercent,
637
+ rubberStretchPx,
638
+ computeRubberStretch
639
+ ]
640
+ );
641
+ const handlePointerUp = useCallback(
483
642
  (e) => {
484
643
  if (!isInteracting) return;
485
- if (!isDragging) setIsDragging(true);
486
- const touch = e.touches[0];
487
- onChange(positionToValue(touch.clientX));
644
+ if (isClickRef.current) {
645
+ const rawValue = positionToValue(e.clientX);
646
+ const snappedValue = snapToDecile(rawValue, min, max);
647
+ const newPct = percentFromValue(snappedValue);
648
+ if (animRef.current) {
649
+ animRef.current.stop();
650
+ }
651
+ animRef.current = animate(fillPercent, newPct, {
652
+ type: "spring",
653
+ stiffness: 300,
654
+ damping: 25,
655
+ mass: 0.8,
656
+ onComplete: () => {
657
+ animRef.current = null;
658
+ }
659
+ });
660
+ onChange(roundValue(snappedValue, step));
661
+ }
662
+ if (rubberStretchPx.get() !== 0) {
663
+ animate(rubberStretchPx, 0, {
664
+ type: "spring",
665
+ visualDuration: 0.35,
666
+ bounce: 0.15
667
+ });
668
+ }
669
+ setIsInteracting(false);
670
+ setIsDragging(false);
671
+ pointerDownPos.current = null;
488
672
  },
489
- [isInteracting, isDragging, positionToValue, onChange]
673
+ [
674
+ isInteracting,
675
+ positionToValue,
676
+ percentFromValue,
677
+ onChange,
678
+ min,
679
+ max,
680
+ fillPercent,
681
+ rubberStretchPx
682
+ ]
490
683
  );
491
- const handleTouchEnd = useCallback(() => {
492
- setIsInteracting(false);
493
- setIsDragging(false);
494
- }, []);
495
- useEffect2(() => {
496
- if (isInteracting) {
497
- document.addEventListener("mousemove", handleMouseMove);
498
- document.addEventListener("mouseup", handleMouseUp);
499
- document.addEventListener("touchmove", handleTouchMove, { passive: false });
500
- document.addEventListener("touchend", handleTouchEnd);
501
- return () => {
502
- document.removeEventListener("mousemove", handleMouseMove);
503
- document.removeEventListener("mouseup", handleMouseUp);
504
- document.removeEventListener("touchmove", handleTouchMove);
505
- document.removeEventListener("touchend", handleTouchEnd);
506
- };
507
- }
508
- }, [isInteracting, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
509
- useEffect2(() => {
684
+ useEffect3(() => {
510
685
  if (isValueHovered && !showInput && !isValueEditable) {
511
686
  hoverTimeoutRef.current = setTimeout(() => {
512
687
  setIsValueEditable(true);
@@ -524,7 +699,7 @@ function Slider({
524
699
  }
525
700
  };
526
701
  }, [isValueHovered, showInput, isValueEditable]);
527
- useEffect2(() => {
702
+ useEffect3(() => {
528
703
  if (showInput && inputRef.current) {
529
704
  inputRef.current.focus();
530
705
  inputRef.current.select();
@@ -537,7 +712,7 @@ function Slider({
537
712
  const parsed = parseFloat(inputValue);
538
713
  if (!isNaN(parsed)) {
539
714
  const clamped = Math.max(min, Math.min(max, parsed));
540
- onChange(clamped);
715
+ onChange(roundValue(clamped, step));
541
716
  }
542
717
  setShowInput(false);
543
718
  setIsValueHovered(false);
@@ -563,29 +738,56 @@ function Slider({
563
738
  handleInputSubmit();
564
739
  };
565
740
  const displayValue = step >= 1 ? value.toFixed(0) : value.toFixed(2);
566
- const shouldAnimate = !isDragging;
567
- const transition = shouldAnimate ? { type: "spring", visualDuration: 0.3, bounce: 0.15 } : { duration: 0 };
568
- const handleLeft = isAtMinimum ? 7 : `calc(${percentage}% - 9px)`;
741
+ const HANDLE_BUFFER = 8;
742
+ const LABEL_CSS_LEFT = 10;
743
+ const VALUE_CSS_RIGHT = 10;
744
+ let leftThreshold = 30;
745
+ let rightThreshold = 78;
746
+ const trackWidth = wrapperRef.current?.offsetWidth;
747
+ if (trackWidth && trackWidth > 0) {
748
+ if (labelRef.current) {
749
+ leftThreshold = (LABEL_CSS_LEFT + labelRef.current.offsetWidth + HANDLE_BUFFER) / trackWidth * 100;
750
+ }
751
+ if (valueSpanRef.current) {
752
+ rightThreshold = (trackWidth - VALUE_CSS_RIGHT - valueSpanRef.current.offsetWidth - HANDLE_BUFFER) / trackWidth * 100;
753
+ }
754
+ }
755
+ const valueDodge = percentage < leftThreshold || percentage > rightThreshold;
756
+ const handleOpacity = !isActive ? 0 : valueDodge ? 0.1 : isDragging ? 0.9 : 0.5;
569
757
  const fillBackground = isActive ? "rgba(255, 255, 255, 0.15)" : "rgba(255, 255, 255, 0.11)";
570
- const hashMarks = Array.from({ length: 7 }, (_, i) => /* @__PURE__ */ jsx2("span", { className: "dialkit-slider-hashmark", children: "\u2022" }, i));
571
- return /* @__PURE__ */ jsxs2(
572
- "div",
758
+ const hashMarks = Array.from({ length: 9 }, (_, i) => {
759
+ const pct = (i + 1) * 10;
760
+ return /* @__PURE__ */ jsx2(
761
+ "div",
762
+ {
763
+ className: "dialkit-slider-hashmark",
764
+ style: { left: `${pct}%` }
765
+ },
766
+ i
767
+ );
768
+ });
769
+ return /* @__PURE__ */ jsx2("div", { ref: wrapperRef, className: "dialkit-slider-wrapper", children: /* @__PURE__ */ jsxs2(
770
+ motion2.div,
573
771
  {
574
772
  ref: trackRef,
575
773
  className: `dialkit-slider ${isActive ? "dialkit-slider-active" : ""}`,
576
- onMouseDown: handleMouseDown,
577
- onTouchStart: handleTouchStart,
774
+ onPointerDown: handlePointerDown,
775
+ onPointerMove: handlePointerMove,
776
+ onPointerUp: handlePointerUp,
578
777
  onMouseEnter: () => setIsHovered(true),
579
778
  onMouseLeave: () => setIsHovered(false),
779
+ style: { width: rubberBandWidth, x: rubberBandX },
580
780
  children: [
581
781
  /* @__PURE__ */ jsx2("div", { className: "dialkit-slider-hashmarks", children: hashMarks }),
582
782
  /* @__PURE__ */ jsx2(
583
783
  motion2.div,
584
784
  {
585
785
  className: "dialkit-slider-fill",
586
- style: { background: fillBackground, width: `${percentage}%`, transition: "background 0.15s" },
587
- animate: { width: `${percentage}%` },
588
- transition
786
+ style: {
787
+ background: fillBackground,
788
+ width: fillWidth,
789
+ transition: "background 0.15s"
790
+ }
589
791
  }
590
792
  ),
591
793
  /* @__PURE__ */ jsx2(
@@ -593,15 +795,23 @@ function Slider({
593
795
  {
594
796
  className: "dialkit-slider-handle",
595
797
  style: {
596
- background: isInteracting ? "rgba(255, 255, 255, 1)" : "rgba(255, 255, 255, 0.5)",
597
798
  left: handleLeft,
598
- opacity: isActive ? 1 : 0
799
+ y: "-50%",
800
+ background: "rgba(255, 255, 255, 0.9)"
599
801
  },
600
- animate: { left: handleLeft, opacity: isActive ? 1 : 0 },
601
- transition
802
+ animate: {
803
+ opacity: handleOpacity,
804
+ scaleX: isActive ? 1 : 0.25,
805
+ scaleY: isActive && valueDodge ? 0.75 : 1
806
+ },
807
+ transition: {
808
+ scaleX: { type: "spring", visualDuration: 0.25, bounce: 0.15 },
809
+ scaleY: { type: "spring", visualDuration: 0.2, bounce: 0.1 },
810
+ opacity: { duration: 0.15 }
811
+ }
602
812
  }
603
813
  ),
604
- /* @__PURE__ */ jsx2("span", { className: "dialkit-slider-label", children: label }),
814
+ /* @__PURE__ */ jsx2("span", { ref: labelRef, className: "dialkit-slider-label", children: label }),
605
815
  showInput ? /* @__PURE__ */ jsx2(
606
816
  "input",
607
817
  {
@@ -615,28 +825,26 @@ function Slider({
615
825
  onClick: (e) => e.stopPropagation(),
616
826
  onMouseDown: (e) => e.stopPropagation()
617
827
  }
618
- ) : /* @__PURE__ */ jsxs2(
828
+ ) : /* @__PURE__ */ jsx2(
619
829
  "span",
620
830
  {
831
+ ref: valueSpanRef,
621
832
  className: `dialkit-slider-value ${isValueEditable ? "dialkit-slider-value-editable" : ""}`,
622
833
  onMouseEnter: () => setIsValueHovered(true),
623
834
  onMouseLeave: () => setIsValueHovered(false),
624
835
  onClick: handleValueClick,
625
836
  onMouseDown: (e) => isValueEditable && e.stopPropagation(),
626
837
  style: { cursor: isValueEditable ? "text" : "default" },
627
- children: [
628
- displayValue,
629
- unit
630
- ]
838
+ children: displayValue
631
839
  }
632
840
  )
633
841
  ]
634
842
  }
635
- );
843
+ ) });
636
844
  }
637
845
 
638
846
  // src/components/SegmentedControl.tsx
639
- import { useRef as useRef3, useState as useState3, useLayoutEffect } from "react";
847
+ import { useRef as useRef4, useState as useState3, useLayoutEffect } from "react";
640
848
  import { motion as motion3 } from "motion/react";
641
849
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
642
850
  function SegmentedControl({
@@ -644,10 +852,10 @@ function SegmentedControl({
644
852
  value,
645
853
  onChange
646
854
  }) {
647
- const containerRef = useRef3(null);
648
- const buttonRefs = useRef3(/* @__PURE__ */ new Map());
855
+ const containerRef = useRef4(null);
856
+ const buttonRefs = useRef4(/* @__PURE__ */ new Map());
649
857
  const [pillStyle, setPillStyle] = useState3(null);
650
- const hasAnimated = useRef3(false);
858
+ const hasAnimated = useRef4(false);
651
859
  useLayoutEffect(() => {
652
860
  const button = buttonRefs.current.get(value);
653
861
  const container = containerRef.current;
@@ -917,20 +1125,349 @@ function SpringControl({ panelId, path, label, spring, onChange }) {
917
1125
  ] }) });
918
1126
  }
919
1127
 
920
- // src/components/Panel.tsx
921
- import { useSyncExternalStore as useSyncExternalStore3 } from "react";
1128
+ // src/components/TextControl.tsx
922
1129
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1130
+ function TextControl({ label, value, onChange, placeholder }) {
1131
+ return /* @__PURE__ */ jsxs7("div", { className: "dialkit-text-control", children: [
1132
+ /* @__PURE__ */ jsx7("label", { className: "dialkit-text-label", children: label }),
1133
+ /* @__PURE__ */ jsx7(
1134
+ "input",
1135
+ {
1136
+ type: "text",
1137
+ className: "dialkit-text-input",
1138
+ value,
1139
+ onChange: (e) => onChange(e.target.value),
1140
+ placeholder
1141
+ }
1142
+ )
1143
+ ] });
1144
+ }
1145
+
1146
+ // src/components/SelectControl.tsx
1147
+ import { useState as useState4, useRef as useRef5, useEffect as useEffect4 } from "react";
1148
+ import { motion as motion4, AnimatePresence as AnimatePresence2 } from "motion/react";
1149
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1150
+ function normalizeOptions(options) {
1151
+ return options.map(
1152
+ (opt) => typeof opt === "string" ? { value: opt, label: opt } : opt
1153
+ );
1154
+ }
1155
+ function SelectControl({ label, value, options, onChange }) {
1156
+ const [isOpen, setIsOpen] = useState4(false);
1157
+ const containerRef = useRef5(null);
1158
+ const normalized = normalizeOptions(options);
1159
+ const selectedOption = normalized.find((o) => o.value === value);
1160
+ useEffect4(() => {
1161
+ if (!isOpen) return;
1162
+ const handleClick = (e) => {
1163
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
1164
+ setIsOpen(false);
1165
+ }
1166
+ };
1167
+ document.addEventListener("mousedown", handleClick);
1168
+ return () => document.removeEventListener("mousedown", handleClick);
1169
+ }, [isOpen]);
1170
+ return /* @__PURE__ */ jsxs8("div", { ref: containerRef, className: "dialkit-select-row", children: [
1171
+ /* @__PURE__ */ jsxs8(
1172
+ "button",
1173
+ {
1174
+ className: "dialkit-select-trigger",
1175
+ onClick: () => setIsOpen(!isOpen),
1176
+ "data-open": String(isOpen),
1177
+ children: [
1178
+ /* @__PURE__ */ jsx8("span", { className: "dialkit-select-label", children: label }),
1179
+ /* @__PURE__ */ jsxs8("div", { className: "dialkit-select-right", children: [
1180
+ /* @__PURE__ */ jsx8("span", { className: "dialkit-select-value", children: selectedOption?.label ?? value }),
1181
+ /* @__PURE__ */ jsx8(
1182
+ motion4.svg,
1183
+ {
1184
+ className: "dialkit-select-chevron",
1185
+ viewBox: "0 0 24 24",
1186
+ fill: "none",
1187
+ stroke: "currentColor",
1188
+ strokeWidth: "2.5",
1189
+ strokeLinecap: "round",
1190
+ strokeLinejoin: "round",
1191
+ animate: { rotate: isOpen ? 180 : 0 },
1192
+ transition: { type: "spring", visualDuration: 0.2, bounce: 0.15 },
1193
+ children: /* @__PURE__ */ jsx8("path", { d: "M6 9.5L12 15.5L18 9.5" })
1194
+ }
1195
+ )
1196
+ ] })
1197
+ ]
1198
+ }
1199
+ ),
1200
+ /* @__PURE__ */ jsx8(AnimatePresence2, { children: isOpen && /* @__PURE__ */ jsx8(
1201
+ motion4.div,
1202
+ {
1203
+ className: "dialkit-select-dropdown",
1204
+ initial: { opacity: 0, y: -8, scale: 0.95 },
1205
+ animate: { opacity: 1, y: 0, scale: 1 },
1206
+ exit: { opacity: 0, y: -8, scale: 0.95 },
1207
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
1208
+ children: normalized.map((option) => /* @__PURE__ */ jsx8(
1209
+ "button",
1210
+ {
1211
+ className: "dialkit-select-option",
1212
+ "data-selected": String(option.value === value),
1213
+ onClick: () => {
1214
+ onChange(option.value);
1215
+ setIsOpen(false);
1216
+ },
1217
+ children: option.label
1218
+ },
1219
+ option.value
1220
+ ))
1221
+ }
1222
+ ) })
1223
+ ] });
1224
+ }
1225
+
1226
+ // src/components/ColorControl.tsx
1227
+ import { useState as useState5, useRef as useRef6, useEffect as useEffect5 } from "react";
1228
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1229
+ var HEX_COLOR_REGEX = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/;
1230
+ function ColorControl({ label, value, onChange }) {
1231
+ const [isEditing, setIsEditing] = useState5(false);
1232
+ const [editValue, setEditValue] = useState5(value);
1233
+ const colorInputRef = useRef6(null);
1234
+ useEffect5(() => {
1235
+ if (!isEditing) {
1236
+ setEditValue(value);
1237
+ }
1238
+ }, [value, isEditing]);
1239
+ function handleTextSubmit() {
1240
+ setIsEditing(false);
1241
+ if (HEX_COLOR_REGEX.test(editValue)) {
1242
+ onChange(editValue);
1243
+ } else {
1244
+ setEditValue(value);
1245
+ }
1246
+ }
1247
+ function handleKeyDown(e) {
1248
+ if (e.key === "Enter") {
1249
+ handleTextSubmit();
1250
+ } else if (e.key === "Escape") {
1251
+ setIsEditing(false);
1252
+ setEditValue(value);
1253
+ }
1254
+ }
1255
+ return /* @__PURE__ */ jsxs9("div", { className: "dialkit-color-control", children: [
1256
+ /* @__PURE__ */ jsx9("span", { className: "dialkit-color-label", children: label }),
1257
+ /* @__PURE__ */ jsxs9("div", { className: "dialkit-color-inputs", children: [
1258
+ isEditing ? /* @__PURE__ */ jsx9(
1259
+ "input",
1260
+ {
1261
+ type: "text",
1262
+ className: "dialkit-color-hex-input",
1263
+ value: editValue,
1264
+ onChange: (e) => setEditValue(e.target.value),
1265
+ onBlur: handleTextSubmit,
1266
+ onKeyDown: handleKeyDown,
1267
+ autoFocus: true
1268
+ }
1269
+ ) : /* @__PURE__ */ jsx9(
1270
+ "span",
1271
+ {
1272
+ className: "dialkit-color-hex",
1273
+ onClick: () => setIsEditing(true),
1274
+ children: (value ?? "").toUpperCase()
1275
+ }
1276
+ ),
1277
+ /* @__PURE__ */ jsx9(
1278
+ "button",
1279
+ {
1280
+ className: "dialkit-color-swatch",
1281
+ style: { backgroundColor: value },
1282
+ onClick: () => colorInputRef.current?.click(),
1283
+ title: "Pick color"
1284
+ }
1285
+ ),
1286
+ /* @__PURE__ */ jsx9(
1287
+ "input",
1288
+ {
1289
+ ref: colorInputRef,
1290
+ type: "color",
1291
+ className: "dialkit-color-picker-native",
1292
+ value: value.length === 4 ? expandShorthandHex(value) : value.slice(0, 7),
1293
+ onChange: (e) => onChange(e.target.value)
1294
+ }
1295
+ )
1296
+ ] })
1297
+ ] });
1298
+ }
1299
+ function expandShorthandHex(hex) {
1300
+ if (hex.length !== 4) return hex;
1301
+ return `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;
1302
+ }
1303
+
1304
+ // src/components/PresetManager.tsx
1305
+ import { useState as useState6, useRef as useRef7, useEffect as useEffect6, useCallback as useCallback2 } from "react";
1306
+ import { createPortal } from "react-dom";
1307
+ import { motion as motion5, AnimatePresence as AnimatePresence3 } from "motion/react";
1308
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1309
+ function PresetManager({ panelId, presets, activePresetId, onAdd }) {
1310
+ const [isOpen, setIsOpen] = useState6(false);
1311
+ const triggerRef = useRef7(null);
1312
+ const dropdownRef = useRef7(null);
1313
+ const [pos, setPos] = useState6({ top: 0, left: 0, width: 0 });
1314
+ const hasPresets = presets.length > 0;
1315
+ const activePreset = presets.find((p) => p.id === activePresetId);
1316
+ const open = useCallback2(() => {
1317
+ if (!hasPresets) return;
1318
+ const rect = triggerRef.current?.getBoundingClientRect();
1319
+ if (rect) {
1320
+ setPos({ top: rect.bottom + 4, left: rect.left, width: rect.width });
1321
+ }
1322
+ setIsOpen(true);
1323
+ }, [hasPresets]);
1324
+ const close = useCallback2(() => setIsOpen(false), []);
1325
+ const toggle = useCallback2(() => {
1326
+ if (isOpen) close();
1327
+ else open();
1328
+ }, [isOpen, open, close]);
1329
+ useEffect6(() => {
1330
+ if (!isOpen) return;
1331
+ const handler = (e) => {
1332
+ const target = e.target;
1333
+ if (triggerRef.current?.contains(target) || dropdownRef.current?.contains(target)) return;
1334
+ close();
1335
+ };
1336
+ document.addEventListener("mousedown", handler);
1337
+ return () => document.removeEventListener("mousedown", handler);
1338
+ }, [isOpen, close]);
1339
+ const handleSelect = (presetId) => {
1340
+ if (presetId) {
1341
+ DialStore.loadPreset(panelId, presetId);
1342
+ } else {
1343
+ DialStore.clearActivePreset(panelId);
1344
+ }
1345
+ close();
1346
+ };
1347
+ const handleDelete = (e, presetId) => {
1348
+ e.stopPropagation();
1349
+ DialStore.deletePreset(panelId, presetId);
1350
+ };
1351
+ return /* @__PURE__ */ jsxs10("div", { className: "dialkit-preset-manager", children: [
1352
+ /* @__PURE__ */ jsxs10(
1353
+ "button",
1354
+ {
1355
+ ref: triggerRef,
1356
+ className: "dialkit-preset-trigger",
1357
+ onClick: toggle,
1358
+ "data-open": String(isOpen),
1359
+ "data-has-preset": String(!!activePreset),
1360
+ "data-disabled": String(!hasPresets),
1361
+ children: [
1362
+ /* @__PURE__ */ jsx10("span", { className: "dialkit-preset-label", children: activePreset ? activePreset.name : "Version 1" }),
1363
+ /* @__PURE__ */ jsx10(
1364
+ motion5.svg,
1365
+ {
1366
+ className: "dialkit-select-chevron",
1367
+ viewBox: "0 0 24 24",
1368
+ fill: "none",
1369
+ stroke: "currentColor",
1370
+ strokeWidth: "2.5",
1371
+ strokeLinecap: "round",
1372
+ strokeLinejoin: "round",
1373
+ animate: { rotate: isOpen ? 180 : 0 },
1374
+ transition: { type: "spring", visualDuration: 0.2, bounce: 0.15 },
1375
+ children: /* @__PURE__ */ jsx10("path", { d: "M6 9.5L12 15.5L18 9.5" })
1376
+ }
1377
+ )
1378
+ ]
1379
+ }
1380
+ ),
1381
+ createPortal(
1382
+ /* @__PURE__ */ jsx10(AnimatePresence3, { children: isOpen && /* @__PURE__ */ jsxs10(
1383
+ motion5.div,
1384
+ {
1385
+ ref: dropdownRef,
1386
+ className: "dialkit-preset-dropdown",
1387
+ style: { position: "fixed", top: pos.top, left: pos.left, minWidth: pos.width },
1388
+ initial: { opacity: 0, y: 4, scale: 0.97 },
1389
+ animate: { opacity: 1, y: 0, scale: 1 },
1390
+ exit: { opacity: 0, y: 4, scale: 0.97, pointerEvents: "none" },
1391
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
1392
+ children: [
1393
+ /* @__PURE__ */ jsx10(
1394
+ "div",
1395
+ {
1396
+ className: "dialkit-preset-item",
1397
+ "data-active": String(!activePresetId),
1398
+ onClick: () => handleSelect(null),
1399
+ children: /* @__PURE__ */ jsx10("span", { className: "dialkit-preset-name", children: "Version 1" })
1400
+ }
1401
+ ),
1402
+ presets.map((preset) => /* @__PURE__ */ jsxs10(
1403
+ "div",
1404
+ {
1405
+ className: "dialkit-preset-item",
1406
+ "data-active": String(preset.id === activePresetId),
1407
+ onClick: () => handleSelect(preset.id),
1408
+ children: [
1409
+ /* @__PURE__ */ jsx10("span", { className: "dialkit-preset-name", children: preset.name }),
1410
+ /* @__PURE__ */ jsx10(
1411
+ "button",
1412
+ {
1413
+ className: "dialkit-preset-delete",
1414
+ onClick: (e) => handleDelete(e, preset.id),
1415
+ title: "Delete preset",
1416
+ children: /* @__PURE__ */ jsxs10("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1417
+ /* @__PURE__ */ jsx10("path", { d: "M5 6.5L5.80734 18.2064C5.91582 19.7794 7.22348 21 8.80023 21H15.1998C16.7765 21 18.0842 19.7794 18.1927 18.2064L19 6.5" }),
1418
+ /* @__PURE__ */ jsx10("path", { d: "M10 11V16" }),
1419
+ /* @__PURE__ */ jsx10("path", { d: "M14 11V16" }),
1420
+ /* @__PURE__ */ jsx10("path", { d: "M3.5 6H20.5" }),
1421
+ /* @__PURE__ */ jsx10("path", { d: "M8.07092 5.74621C8.42348 3.89745 10.0485 2.5 12 2.5C13.9515 2.5 15.5765 3.89745 15.9291 5.74621" })
1422
+ ] })
1423
+ }
1424
+ )
1425
+ ]
1426
+ },
1427
+ preset.id
1428
+ ))
1429
+ ]
1430
+ }
1431
+ ) }),
1432
+ document.body
1433
+ )
1434
+ ] });
1435
+ }
1436
+
1437
+ // src/components/Panel.tsx
1438
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
923
1439
  function Panel({ panel }) {
1440
+ const [copied, setCopied] = useState7(false);
1441
+ const [isPanelOpen, setIsPanelOpen] = useState7(true);
924
1442
  const values = useSyncExternalStore3(
925
1443
  (cb) => DialStore.subscribe(panel.id, cb),
926
1444
  () => DialStore.getValues(panel.id),
927
1445
  () => DialStore.getValues(panel.id)
928
1446
  );
1447
+ const presets = DialStore.getPresets(panel.id);
1448
+ const activePresetId = DialStore.getActivePresetId(panel.id);
1449
+ const handleAddPreset = () => {
1450
+ const nextNum = presets.length + 2;
1451
+ DialStore.savePreset(panel.id, `Version ${nextNum}`);
1452
+ };
1453
+ const handleCopy = () => {
1454
+ const jsonStr = JSON.stringify(values, null, 2);
1455
+ const instruction = `Update the useDialKit configuration for "${panel.name}" with these values:
1456
+
1457
+ \`\`\`json
1458
+ ${jsonStr}
1459
+ \`\`\`
1460
+
1461
+ Apply these values as the new defaults in the useDialKit call.`;
1462
+ navigator.clipboard.writeText(instruction);
1463
+ setCopied(true);
1464
+ setTimeout(() => setCopied(false), 1500);
1465
+ };
929
1466
  const renderControl = (control) => {
930
1467
  const value = values[control.path];
931
1468
  switch (control.type) {
932
1469
  case "slider":
933
- return /* @__PURE__ */ jsx7(
1470
+ return /* @__PURE__ */ jsx11(
934
1471
  Slider,
935
1472
  {
936
1473
  label: control.label,
@@ -943,7 +1480,7 @@ function Panel({ panel }) {
943
1480
  control.path
944
1481
  );
945
1482
  case "toggle":
946
- return /* @__PURE__ */ jsx7(
1483
+ return /* @__PURE__ */ jsx11(
947
1484
  Toggle,
948
1485
  {
949
1486
  label: control.label,
@@ -953,7 +1490,7 @@ function Panel({ panel }) {
953
1490
  control.path
954
1491
  );
955
1492
  case "spring":
956
- return /* @__PURE__ */ jsx7(
1493
+ return /* @__PURE__ */ jsx11(
957
1494
  SpringControl,
958
1495
  {
959
1496
  panelId: panel.id,
@@ -965,7 +1502,39 @@ function Panel({ panel }) {
965
1502
  control.path
966
1503
  );
967
1504
  case "folder":
968
- return /* @__PURE__ */ jsx7(Folder, { title: control.label, children: control.children?.map(renderControl) }, control.path);
1505
+ return /* @__PURE__ */ jsx11(Folder, { title: control.label, children: control.children?.map(renderControl) }, control.path);
1506
+ case "text":
1507
+ return /* @__PURE__ */ jsx11(
1508
+ TextControl,
1509
+ {
1510
+ label: control.label,
1511
+ value,
1512
+ onChange: (v) => DialStore.updateValue(panel.id, control.path, v),
1513
+ placeholder: control.placeholder
1514
+ },
1515
+ control.path
1516
+ );
1517
+ case "select":
1518
+ return /* @__PURE__ */ jsx11(
1519
+ SelectControl,
1520
+ {
1521
+ label: control.label,
1522
+ value,
1523
+ options: control.options ?? [],
1524
+ onChange: (v) => DialStore.updateValue(panel.id, control.path, v)
1525
+ },
1526
+ control.path
1527
+ );
1528
+ case "color":
1529
+ return /* @__PURE__ */ jsx11(
1530
+ ColorControl,
1531
+ {
1532
+ label: control.label,
1533
+ value,
1534
+ onChange: (v) => DialStore.updateValue(panel.id, control.path, v)
1535
+ },
1536
+ control.path
1537
+ );
969
1538
  default:
970
1539
  return null;
971
1540
  }
@@ -976,24 +1545,16 @@ function Panel({ panel }) {
976
1545
  while (i < panel.controls.length) {
977
1546
  const control = panel.controls[i];
978
1547
  if (control.type === "action") {
979
- const actions = [control];
980
- while (i + 1 < panel.controls.length && panel.controls[i + 1].type === "action") {
981
- i++;
982
- actions.push(panel.controls[i]);
983
- }
984
1548
  result.push(
985
- /* @__PURE__ */ jsxs7("div", { className: "dialkit-labeled-control dialkit-actions-group", children: [
986
- /* @__PURE__ */ jsx7("span", { className: "dialkit-labeled-control-label", children: "Actions" }),
987
- /* @__PURE__ */ jsx7("div", { className: "dialkit-actions-stack", children: actions.map((action) => /* @__PURE__ */ jsx7(
988
- "button",
989
- {
990
- className: "dialkit-action-button",
991
- onClick: () => DialStore.triggerAction(panel.id, action.path),
992
- children: action.label
993
- },
994
- action.path
995
- )) })
996
- ] }, `actions-${actions[0].path}`)
1549
+ /* @__PURE__ */ jsx11(
1550
+ "button",
1551
+ {
1552
+ className: "dialkit-button",
1553
+ onClick: () => DialStore.triggerAction(panel.id, control.path),
1554
+ children: control.label
1555
+ },
1556
+ control.path
1557
+ )
997
1558
  );
998
1559
  } else {
999
1560
  result.push(renderControl(control));
@@ -1002,15 +1563,88 @@ function Panel({ panel }) {
1002
1563
  }
1003
1564
  return result;
1004
1565
  };
1005
- return /* @__PURE__ */ jsx7(Folder, { title: panel.name, defaultOpen: true, isRoot: true, panelId: panel.id, children: renderControls() });
1566
+ const iconTransition = { type: "spring", visualDuration: 0.4, bounce: 0.1 };
1567
+ const toolbar = /* @__PURE__ */ jsxs11(Fragment2, { children: [
1568
+ /* @__PURE__ */ jsx11(
1569
+ "button",
1570
+ {
1571
+ className: "dialkit-toolbar-add",
1572
+ onClick: handleAddPreset,
1573
+ title: "Add preset",
1574
+ children: /* @__PURE__ */ jsxs11("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1575
+ /* @__PURE__ */ jsx11("path", { d: "M4 6H20" }),
1576
+ /* @__PURE__ */ jsx11("path", { d: "M4 12H10" }),
1577
+ /* @__PURE__ */ jsx11("path", { d: "M15 15L21 15" }),
1578
+ /* @__PURE__ */ jsx11("path", { d: "M18 12V18" }),
1579
+ /* @__PURE__ */ jsx11("path", { d: "M4 18H10" })
1580
+ ] })
1581
+ }
1582
+ ),
1583
+ /* @__PURE__ */ jsx11(
1584
+ PresetManager,
1585
+ {
1586
+ panelId: panel.id,
1587
+ presets,
1588
+ activePresetId,
1589
+ onAdd: handleAddPreset
1590
+ }
1591
+ ),
1592
+ /* @__PURE__ */ jsxs11(
1593
+ "button",
1594
+ {
1595
+ className: "dialkit-toolbar-copy",
1596
+ onClick: handleCopy,
1597
+ title: "Copy parameters",
1598
+ children: [
1599
+ /* @__PURE__ */ jsx11("span", { className: "dialkit-toolbar-copy-icon-wrap", children: /* @__PURE__ */ jsx11(AnimatePresence4, { mode: "popLayout", children: copied ? /* @__PURE__ */ jsx11(
1600
+ motion6.svg,
1601
+ {
1602
+ className: "dialkit-toolbar-copy-icon",
1603
+ viewBox: "0 0 24 24",
1604
+ fill: "none",
1605
+ stroke: "currentColor",
1606
+ strokeWidth: "2",
1607
+ strokeLinecap: "round",
1608
+ strokeLinejoin: "round",
1609
+ initial: { scale: 0.5, opacity: 0, filter: "blur(4px)" },
1610
+ animate: { scale: 1, opacity: 1, filter: "blur(0px)" },
1611
+ exit: { scale: 0.5, opacity: 0, filter: "blur(4px)" },
1612
+ transition: { type: "spring", visualDuration: 0.3, bounce: 0.2 },
1613
+ children: /* @__PURE__ */ jsx11("path", { d: "M5 12.75L10 19L19 5" })
1614
+ },
1615
+ "check"
1616
+ ) : /* @__PURE__ */ jsxs11(
1617
+ motion6.svg,
1618
+ {
1619
+ className: "dialkit-toolbar-copy-icon",
1620
+ viewBox: "0 0 24 24",
1621
+ fill: "none",
1622
+ initial: { scale: 0.5, opacity: 0, filter: "blur(4px)" },
1623
+ animate: { scale: 1, opacity: 1, filter: "blur(0px)" },
1624
+ exit: { scale: 0.5, opacity: 0, filter: "blur(4px)" },
1625
+ transition: { type: "spring", visualDuration: 0.3, bounce: 0.2 },
1626
+ children: [
1627
+ /* @__PURE__ */ jsx11("path", { d: "M8 6C8 4.34315 9.34315 3 11 3H13C14.6569 3 16 4.34315 16 6V7H8V6Z", stroke: "currentColor", strokeWidth: "2", strokeLinejoin: "round" }),
1628
+ /* @__PURE__ */ jsx11("path", { d: "M19.2405 16.1852L18.5436 14.3733C18.4571 14.1484 18.241 14 18 14C17.759 14 17.5429 14.1484 17.4564 14.3733L16.7595 16.1852C16.658 16.4493 16.4493 16.658 16.1852 16.7595L14.3733 17.4564C14.1484 17.5429 14 17.759 14 18C14 18.241 14.1484 18.4571 14.3733 18.5436L16.1852 19.2405C16.4493 19.342 16.658 19.5507 16.7595 19.8148L17.4564 21.6267C17.5429 21.8516 17.759 22 18 22C18.241 22 18.4571 21.8516 18.5436 21.6267L19.2405 19.8148C19.342 19.5507 19.5507 19.342 19.8148 19.2405L21.6267 18.5436C21.8516 18.4571 22 18.241 22 18C22 17.759 21.8516 17.5429 21.6267 17.4564L19.8148 16.7595C19.5507 16.658 19.342 16.4493 19.2405 16.1852Z", fill: "currentColor" }),
1629
+ /* @__PURE__ */ jsx11("path", { d: "M16 5H17C18.6569 5 20 6.34315 20 8V11M8 5H7C5.34315 5 4 6.34315 4 8V18C4 19.6569 5.34315 21 7 21H12", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
1630
+ ]
1631
+ },
1632
+ "clipboard"
1633
+ ) }) }),
1634
+ "Copy"
1635
+ ]
1636
+ }
1637
+ )
1638
+ ] });
1639
+ return /* @__PURE__ */ jsx11("div", { className: "dialkit-panel-wrapper", children: /* @__PURE__ */ jsx11(Folder, { title: panel.name, defaultOpen: true, isRoot: true, onOpenChange: setIsPanelOpen, toolbar, children: renderControls() }) });
1006
1640
  }
1007
1641
 
1008
1642
  // src/components/DialRoot.tsx
1009
- import { jsx as jsx8 } from "react/jsx-runtime";
1643
+ import { jsx as jsx12 } from "react/jsx-runtime";
1010
1644
  function DialRoot({ position = "top-right" }) {
1011
- const [panels, setPanels] = useState4([]);
1012
- const [mounted, setMounted] = useState4(false);
1013
- useEffect3(() => {
1645
+ const [panels, setPanels] = useState8([]);
1646
+ const [mounted, setMounted] = useState8(false);
1647
+ useEffect7(() => {
1014
1648
  setMounted(true);
1015
1649
  setPanels(DialStore.getPanels());
1016
1650
  const unsubscribe = DialStore.subscribeGlobal(() => {
@@ -1024,14 +1658,14 @@ function DialRoot({ position = "top-right" }) {
1024
1658
  if (panels.length === 0) {
1025
1659
  return null;
1026
1660
  }
1027
- const content = /* @__PURE__ */ jsx8("div", { className: "dialkit-root", children: /* @__PURE__ */ jsx8("div", { className: "dialkit-panel", "data-position": position, children: panels.map((panel) => /* @__PURE__ */ jsx8(Panel, { panel }, panel.id)) }) });
1028
- return createPortal(content, document.body);
1661
+ const content = /* @__PURE__ */ jsx12("div", { className: "dialkit-root", children: /* @__PURE__ */ jsx12("div", { className: "dialkit-panel", "data-position": position, children: panels.map((panel) => /* @__PURE__ */ jsx12(Panel, { panel }, panel.id)) }) });
1662
+ return createPortal2(content, document.body);
1029
1663
  }
1030
1664
 
1031
1665
  // src/components/ButtonGroup.tsx
1032
- import { jsx as jsx9 } from "react/jsx-runtime";
1666
+ import { jsx as jsx13 } from "react/jsx-runtime";
1033
1667
  function ButtonGroup({ buttons }) {
1034
- return /* @__PURE__ */ jsx9("div", { className: "dialkit-button-group", children: buttons.map((button, index) => /* @__PURE__ */ jsx9(
1668
+ return /* @__PURE__ */ jsx13("div", { className: "dialkit-button-group", children: buttons.map((button, index) => /* @__PURE__ */ jsx13(
1035
1669
  "button",
1036
1670
  {
1037
1671
  className: "dialkit-button",
@@ -1043,13 +1677,16 @@ function ButtonGroup({ buttons }) {
1043
1677
  }
1044
1678
  export {
1045
1679
  ButtonGroup,
1680
+ ColorControl,
1046
1681
  DialRoot,
1047
1682
  DialStore,
1048
1683
  Folder,
1049
- SegmentedControl,
1684
+ PresetManager,
1685
+ SelectControl,
1050
1686
  Slider,
1051
1687
  SpringControl,
1052
1688
  SpringVisualization,
1689
+ TextControl,
1053
1690
  Toggle,
1054
1691
  useDialKit
1055
1692
  };