dialkit 0.2.0 → 0.2.2

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.d.ts CHANGED
@@ -33,10 +33,10 @@ type TextConfig = {
33
33
  };
34
34
  type DialValue = number | boolean | string | SpringConfig | ActionConfig | SelectConfig | ColorConfig | TextConfig;
35
35
  type DialConfig = {
36
- [key: string]: DialValue | [number, number, number] | DialConfig;
36
+ [key: string]: DialValue | [number, number, number, number?] | DialConfig;
37
37
  };
38
38
  type ResolvedValues<T extends DialConfig> = {
39
- [K in keyof T]: T[K] extends [number, number, number] ? number : T[K] extends SpringConfig ? SpringConfig : T[K] extends SelectConfig ? string : T[K] extends ColorConfig ? string : T[K] extends TextConfig ? string : T[K] extends DialConfig ? ResolvedValues<T[K]> : T[K];
39
+ [K in keyof T]: T[K] extends [number, number, number, number?] ? number : T[K] extends SpringConfig ? SpringConfig : T[K] extends SelectConfig ? string : T[K] extends ColorConfig ? string : T[K] extends TextConfig ? string : T[K] extends DialConfig ? ResolvedValues<T[K]> : T[K];
40
40
  };
41
41
  type ControlMeta = {
42
42
  type: 'slider' | 'toggle' | 'spring' | 'folder' | 'action' | 'select' | 'color' | 'text';
@@ -46,6 +46,7 @@ type ControlMeta = {
46
46
  max?: number;
47
47
  step?: number;
48
48
  children?: ControlMeta[];
49
+ defaultOpen?: boolean;
49
50
  options?: (string | {
50
51
  value: string;
51
52
  label: string;
package/dist/index.js CHANGED
@@ -160,16 +160,17 @@ var DialStoreClass = class {
160
160
  parseConfig(config, prefix) {
161
161
  const controls = [];
162
162
  for (const [key, value] of Object.entries(config)) {
163
+ if (key === "_collapsed") continue;
163
164
  const path = prefix ? `${prefix}.${key}` : key;
164
165
  const label = this.formatLabel(key);
165
- if (Array.isArray(value) && value.length === 3 && typeof value[0] === "number") {
166
+ if (Array.isArray(value) && value.length <= 4 && typeof value[0] === "number") {
166
167
  controls.push({
167
168
  type: "slider",
168
169
  path,
169
170
  label,
170
171
  min: value[1],
171
172
  max: value[2],
172
- step: this.inferStep(value[1], value[2])
173
+ step: value[3] ?? this.inferStep(value[1], value[2])
173
174
  });
174
175
  } else if (typeof value === "number") {
175
176
  const { min, max, step } = this.inferRange(value);
@@ -193,11 +194,14 @@ var DialStoreClass = class {
193
194
  controls.push({ type: "text", path, label });
194
195
  }
195
196
  } else if (typeof value === "object" && value !== null) {
197
+ const folderConfig = value;
198
+ const defaultOpen = "_collapsed" in folderConfig ? !folderConfig._collapsed : true;
196
199
  controls.push({
197
200
  type: "folder",
198
201
  path,
199
202
  label,
200
- children: this.parseConfig(value, path)
203
+ defaultOpen,
204
+ children: this.parseConfig(folderConfig, path)
201
205
  });
202
206
  }
203
207
  }
@@ -206,8 +210,9 @@ var DialStoreClass = class {
206
210
  flattenValues(config, prefix) {
207
211
  const values = {};
208
212
  for (const [key, value] of Object.entries(config)) {
213
+ if (key === "_collapsed") continue;
209
214
  const path = prefix ? `${prefix}.${key}` : key;
210
- if (Array.isArray(value) && value.length === 3 && typeof value[0] === "number") {
215
+ if (Array.isArray(value) && value.length <= 4 && typeof value[0] === "number") {
211
216
  values[path] = value[0];
212
217
  } else if (typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
213
218
  values[path] = value;
@@ -299,8 +304,9 @@ function useDialKit(name, config, options) {
299
304
  function buildResolvedValues(config, flatValues, prefix) {
300
305
  const result = {};
301
306
  for (const [key, configValue] of Object.entries(config)) {
307
+ if (key === "_collapsed") continue;
302
308
  const path = prefix ? `${prefix}.${key}` : key;
303
- if (Array.isArray(configValue) && configValue.length === 3 && typeof configValue[0] === "number") {
309
+ if (Array.isArray(configValue) && configValue.length <= 4 && typeof configValue[0] === "number") {
304
310
  result[key] = flatValues[path] ?? configValue[0];
305
311
  } else if (typeof configValue === "number" || typeof configValue === "boolean" || typeof configValue === "string") {
306
312
  result[key] = flatValues[path] ?? configValue;
@@ -346,7 +352,7 @@ function getFirstOptionValue(options) {
346
352
 
347
353
  // src/components/DialRoot.tsx
348
354
  import { useEffect as useEffect7, useState as useState8 } from "react";
349
- import { createPortal as createPortal2 } from "react-dom";
355
+ import { createPortal as createPortal3 } from "react-dom";
350
356
 
351
357
  // src/components/Panel.tsx
352
358
  import { useState as useState7, useSyncExternalStore as useSyncExternalStore3 } from "react";
@@ -361,8 +367,6 @@ function Folder({ title, children, defaultOpen = true, isRoot = false, onOpenCha
361
367
  const [isCollapsed, setIsCollapsed] = useState(!defaultOpen);
362
368
  const contentRef = useRef2(null);
363
369
  const [contentHeight, setContentHeight] = useState(void 0);
364
- const iconTransition = { type: "spring", visualDuration: 0.4, bounce: 0.1 };
365
- const panelTransition = { type: "spring", visualDuration: 0.4, bounce: 0.2 };
366
370
  useEffect2(() => {
367
371
  const el = contentRef.current;
368
372
  if (!el) return;
@@ -375,25 +379,20 @@ function Folder({ title, children, defaultOpen = true, isRoot = false, onOpenCha
375
379
  ro.observe(el);
376
380
  return () => ro.disconnect();
377
381
  }, [isOpen]);
382
+ const handleToggle = () => {
383
+ const next = !isOpen;
384
+ setIsOpen(next);
385
+ if (next) {
386
+ setIsCollapsed(false);
387
+ } else {
388
+ setIsCollapsed(true);
389
+ }
390
+ onOpenChange?.(next);
391
+ };
378
392
  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: [
393
+ /* @__PURE__ */ jsxs("div", { className: `dialkit-folder-header ${isRoot ? "dialkit-panel-header" : ""}`, onClick: handleToggle, children: [
385
394
  /* @__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,
388
- {
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 })
395
- }
396
- ) }) : /* @__PURE__ */ jsx("div", { className: "dialkit-folder-title-row", children: /* @__PURE__ */ jsx("span", { className: "dialkit-folder-title", children: title }) }),
395
+ isRoot ? isOpen && /* @__PURE__ */ jsx("div", { className: "dialkit-folder-title-row", children: /* @__PURE__ */ jsx("span", { className: "dialkit-folder-title dialkit-folder-title-root", children: title }) }) : /* @__PURE__ */ jsx("div", { className: "dialkit-folder-title-row", children: /* @__PURE__ */ jsx("span", { className: "dialkit-folder-title", children: title }) }),
397
396
  isRoot ? (
398
397
  // Root panel icon — fixed position, container morphs around it
399
398
  /* @__PURE__ */ jsxs(
@@ -430,53 +429,32 @@ function Folder({ title, children, defaultOpen = true, isRoot = false, onOpenCha
430
429
  )
431
430
  )
432
431
  ] }),
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
- ) })
432
+ isRoot && toolbar && isOpen && /* @__PURE__ */ jsx("div", { className: "dialkit-panel-toolbar", onClick: (e) => e.stopPropagation(), children: toolbar })
443
433
  ] }),
444
434
  /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: isOpen && /* @__PURE__ */ jsx(
445
435
  motion.div,
446
436
  {
447
437
  className: "dialkit-folder-content",
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" },
438
+ initial: isRoot ? void 0 : { height: 0, opacity: 0 },
439
+ animate: isRoot ? void 0 : { height: "auto", opacity: 1 },
440
+ exit: isRoot ? void 0 : { height: 0, opacity: 0 },
441
+ transition: isRoot ? void 0 : { type: "spring", visualDuration: 0.35, bounce: 0.1 },
442
+ style: isRoot ? void 0 : { clipPath: "inset(0 -20px)" },
453
443
  children: /* @__PURE__ */ jsx("div", { className: "dialkit-folder-inner", children })
454
444
  }
455
445
  ) })
456
446
  ] });
457
447
  if (isRoot) {
448
+ const panelStyle = isOpen ? { width: 280, height: contentHeight !== void 0 ? contentHeight + 24 : "auto", borderRadius: 14, boxShadow: "0 8px 32px rgba(0, 0, 0, 0.5)", cursor: void 0 } : { width: 42, height: 42, borderRadius: 21, boxShadow: "0 4px 16px rgba(0, 0, 0, 0.25)", overflow: "hidden", cursor: "pointer" };
458
449
  return /* @__PURE__ */ jsx(
459
450
  motion.div,
460
451
  {
461
452
  className: "dialkit-panel-inner",
462
- initial: false,
463
- animate: {
464
- width: isOpen ? 280 : 42,
465
- height: isOpen ? contentHeight !== void 0 ? contentHeight + 24 : "auto" : 42,
466
- borderRadius: isOpen ? 14 : 21,
467
- boxShadow: isOpen ? "0 8px 32px rgba(0, 0, 0, 0.5)" : "0 4px 16px rgba(0, 0, 0, 0.25)"
468
- },
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
- },
453
+ style: panelStyle,
454
+ onClick: !isOpen ? handleToggle : void 0,
479
455
  "data-collapsed": isCollapsed,
456
+ whileTap: !isOpen ? { scale: 0.9 } : void 0,
457
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
480
458
  children: folderContent
481
459
  }
482
460
  );
@@ -492,10 +470,14 @@ var CLICK_THRESHOLD = 3;
492
470
  var DEAD_ZONE = 32;
493
471
  var MAX_CURSOR_RANGE = 200;
494
472
  var MAX_STRETCH = 8;
473
+ function decimalsForStep(step) {
474
+ const s = step.toString();
475
+ const dot = s.indexOf(".");
476
+ return dot === -1 ? 0 : s.length - dot - 1;
477
+ }
495
478
  function roundValue(val, step) {
496
- const decimals = step >= 1 ? 0 : 2;
497
- const factor = 10 ** decimals;
498
- return Math.round(val * factor) / factor;
479
+ const raw = Math.round(val / step) * step;
480
+ return parseFloat(raw.toFixed(decimalsForStep(step)));
499
481
  }
500
482
  function snapToDecile(rawValue, min, max) {
501
483
  const normalized = (rawValue - min) / (max - min);
@@ -643,7 +625,8 @@ function Slider({
643
625
  if (!isInteracting) return;
644
626
  if (isClickRef.current) {
645
627
  const rawValue = positionToValue(e.clientX);
646
- const snappedValue = snapToDecile(rawValue, min, max);
628
+ const discreteSteps2 = (max - min) / step;
629
+ const snappedValue = discreteSteps2 <= 10 ? Math.max(min, Math.min(max, min + Math.round((rawValue - min) / step) * step)) : snapToDecile(rawValue, min, max);
647
630
  const newPct = percentFromValue(snappedValue);
648
631
  if (animRef.current) {
649
632
  animRef.current.stop();
@@ -723,7 +706,7 @@ function Slider({
723
706
  e.stopPropagation();
724
707
  e.preventDefault();
725
708
  setShowInput(true);
726
- setInputValue(step >= 1 ? value.toFixed(0) : value.toFixed(2));
709
+ setInputValue(value.toFixed(decimalsForStep(step)));
727
710
  }
728
711
  };
729
712
  const handleInputKeyDown = (e) => {
@@ -737,7 +720,7 @@ function Slider({
737
720
  const handleInputBlur = () => {
738
721
  handleInputSubmit();
739
722
  };
740
- const displayValue = step >= 1 ? value.toFixed(0) : value.toFixed(2);
723
+ const displayValue = value.toFixed(decimalsForStep(step));
741
724
  const HANDLE_BUFFER = 8;
742
725
  const LABEL_CSS_LEFT = 10;
743
726
  const VALUE_CSS_RIGHT = 10;
@@ -755,7 +738,18 @@ function Slider({
755
738
  const valueDodge = percentage < leftThreshold || percentage > rightThreshold;
756
739
  const handleOpacity = !isActive ? 0 : valueDodge ? 0.1 : isDragging ? 0.9 : 0.5;
757
740
  const fillBackground = isActive ? "rgba(255, 255, 255, 0.15)" : "rgba(255, 255, 255, 0.11)";
758
- const hashMarks = Array.from({ length: 9 }, (_, i) => {
741
+ const discreteSteps = (max - min) / step;
742
+ const hashMarks = discreteSteps <= 10 ? Array.from({ length: discreteSteps - 1 }, (_, i) => {
743
+ const pct = (i + 1) * step / (max - min) * 100;
744
+ return /* @__PURE__ */ jsx2(
745
+ "div",
746
+ {
747
+ className: "dialkit-slider-hashmark",
748
+ style: { left: `${pct}%` }
749
+ },
750
+ i
751
+ );
752
+ }) : Array.from({ length: 9 }, (_, i) => {
759
753
  const pct = (i + 1) * 10;
760
754
  return /* @__PURE__ */ jsx2(
761
755
  "div",
@@ -1144,33 +1138,64 @@ function TextControl({ label, value, onChange, placeholder }) {
1144
1138
  }
1145
1139
 
1146
1140
  // src/components/SelectControl.tsx
1147
- import { useState as useState4, useRef as useRef5, useEffect as useEffect4 } from "react";
1141
+ import { useState as useState4, useRef as useRef5, useEffect as useEffect4, useCallback as useCallback2 } from "react";
1142
+ import { createPortal } from "react-dom";
1148
1143
  import { motion as motion4, AnimatePresence as AnimatePresence2 } from "motion/react";
1149
1144
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1145
+ function toTitleCase(s) {
1146
+ return s.replace(/\b\w/g, (c) => c.toUpperCase());
1147
+ }
1150
1148
  function normalizeOptions(options) {
1151
1149
  return options.map(
1152
- (opt) => typeof opt === "string" ? { value: opt, label: opt } : opt
1150
+ (opt) => typeof opt === "string" ? { value: opt, label: toTitleCase(opt) } : opt
1153
1151
  );
1154
1152
  }
1155
1153
  function SelectControl({ label, value, options, onChange }) {
1156
1154
  const [isOpen, setIsOpen] = useState4(false);
1157
- const containerRef = useRef5(null);
1155
+ const triggerRef = useRef5(null);
1156
+ const dropdownRef = useRef5(null);
1157
+ const [portalTarget, setPortalTarget] = useState4(null);
1158
+ const [pos, setPos] = useState4(null);
1158
1159
  const normalized = normalizeOptions(options);
1159
1160
  const selectedOption = normalized.find((o) => o.value === value);
1161
+ const updatePos = useCallback2(() => {
1162
+ const el = triggerRef.current;
1163
+ if (!el) return;
1164
+ const rect = el.getBoundingClientRect();
1165
+ const dropdownHeight = 8 + normalized.length * 36;
1166
+ const spaceBelow = window.innerHeight - rect.bottom - 4;
1167
+ const above = spaceBelow < dropdownHeight && rect.top > spaceBelow;
1168
+ setPos({
1169
+ top: above ? rect.top - 4 : rect.bottom + 4,
1170
+ left: rect.left,
1171
+ width: rect.width,
1172
+ above
1173
+ });
1174
+ }, [normalized.length]);
1175
+ useEffect4(() => {
1176
+ const root = triggerRef.current?.closest(".dialkit-root");
1177
+ setPortalTarget(root ?? document.body);
1178
+ }, []);
1179
+ useEffect4(() => {
1180
+ if (!isOpen) return;
1181
+ updatePos();
1182
+ }, [isOpen, updatePos]);
1160
1183
  useEffect4(() => {
1161
1184
  if (!isOpen) return;
1162
1185
  const handleClick = (e) => {
1163
- if (containerRef.current && !containerRef.current.contains(e.target)) {
1186
+ const target = e.target;
1187
+ if (triggerRef.current && !triggerRef.current.contains(target) && dropdownRef.current && !dropdownRef.current.contains(target)) {
1164
1188
  setIsOpen(false);
1165
1189
  }
1166
1190
  };
1167
1191
  document.addEventListener("mousedown", handleClick);
1168
1192
  return () => document.removeEventListener("mousedown", handleClick);
1169
1193
  }, [isOpen]);
1170
- return /* @__PURE__ */ jsxs8("div", { ref: containerRef, className: "dialkit-select-row", children: [
1194
+ return /* @__PURE__ */ jsxs8("div", { className: "dialkit-select-row", children: [
1171
1195
  /* @__PURE__ */ jsxs8(
1172
1196
  "button",
1173
1197
  {
1198
+ ref: triggerRef,
1174
1199
  className: "dialkit-select-trigger",
1175
1200
  onClick: () => setIsOpen(!isOpen),
1176
1201
  "data-open": String(isOpen),
@@ -1197,29 +1222,39 @@ function SelectControl({ label, value, options, onChange }) {
1197
1222
  ]
1198
1223
  }
1199
1224
  ),
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
1225
+ portalTarget && createPortal(
1226
+ /* @__PURE__ */ jsx8(AnimatePresence2, { children: isOpen && pos && /* @__PURE__ */ jsx8(
1227
+ motion4.div,
1228
+ {
1229
+ ref: dropdownRef,
1230
+ className: "dialkit-select-dropdown",
1231
+ initial: { opacity: 0, y: pos.above ? 8 : -8, scale: 0.95 },
1232
+ animate: { opacity: 1, y: 0, scale: 1 },
1233
+ exit: { opacity: 0, y: pos.above ? 8 : -8, scale: 0.95 },
1234
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
1235
+ style: {
1236
+ position: "fixed",
1237
+ left: pos.left,
1238
+ width: pos.width,
1239
+ ...pos.above ? { bottom: window.innerHeight - pos.top, transformOrigin: "bottom" } : { top: pos.top, transformOrigin: "top" }
1218
1240
  },
1219
- option.value
1220
- ))
1221
- }
1222
- ) })
1241
+ children: normalized.map((option) => /* @__PURE__ */ jsx8(
1242
+ "button",
1243
+ {
1244
+ className: "dialkit-select-option",
1245
+ "data-selected": String(option.value === value),
1246
+ onClick: () => {
1247
+ onChange(option.value);
1248
+ setIsOpen(false);
1249
+ },
1250
+ children: option.label
1251
+ },
1252
+ option.value
1253
+ ))
1254
+ }
1255
+ ) }),
1256
+ portalTarget
1257
+ )
1223
1258
  ] });
1224
1259
  }
1225
1260
 
@@ -1302,8 +1337,8 @@ function expandShorthandHex(hex) {
1302
1337
  }
1303
1338
 
1304
1339
  // 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";
1340
+ import { useState as useState6, useRef as useRef7, useEffect as useEffect6, useCallback as useCallback3 } from "react";
1341
+ import { createPortal as createPortal2 } from "react-dom";
1307
1342
  import { motion as motion5, AnimatePresence as AnimatePresence3 } from "motion/react";
1308
1343
  import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1309
1344
  function PresetManager({ panelId, presets, activePresetId, onAdd }) {
@@ -1313,7 +1348,7 @@ function PresetManager({ panelId, presets, activePresetId, onAdd }) {
1313
1348
  const [pos, setPos] = useState6({ top: 0, left: 0, width: 0 });
1314
1349
  const hasPresets = presets.length > 0;
1315
1350
  const activePreset = presets.find((p) => p.id === activePresetId);
1316
- const open = useCallback2(() => {
1351
+ const open = useCallback3(() => {
1317
1352
  if (!hasPresets) return;
1318
1353
  const rect = triggerRef.current?.getBoundingClientRect();
1319
1354
  if (rect) {
@@ -1321,8 +1356,8 @@ function PresetManager({ panelId, presets, activePresetId, onAdd }) {
1321
1356
  }
1322
1357
  setIsOpen(true);
1323
1358
  }, [hasPresets]);
1324
- const close = useCallback2(() => setIsOpen(false), []);
1325
- const toggle = useCallback2(() => {
1359
+ const close = useCallback3(() => setIsOpen(false), []);
1360
+ const toggle = useCallback3(() => {
1326
1361
  if (isOpen) close();
1327
1362
  else open();
1328
1363
  }, [isOpen, open, close]);
@@ -1370,7 +1405,7 @@ function PresetManager({ panelId, presets, activePresetId, onAdd }) {
1370
1405
  strokeWidth: "2.5",
1371
1406
  strokeLinecap: "round",
1372
1407
  strokeLinejoin: "round",
1373
- animate: { rotate: isOpen ? 180 : 0 },
1408
+ animate: { rotate: isOpen ? 180 : 0, opacity: hasPresets ? 0.6 : 0.25 },
1374
1409
  transition: { type: "spring", visualDuration: 0.2, bounce: 0.15 },
1375
1410
  children: /* @__PURE__ */ jsx10("path", { d: "M6 9.5L12 15.5L18 9.5" })
1376
1411
  }
@@ -1378,12 +1413,12 @@ function PresetManager({ panelId, presets, activePresetId, onAdd }) {
1378
1413
  ]
1379
1414
  }
1380
1415
  ),
1381
- createPortal(
1416
+ createPortal2(
1382
1417
  /* @__PURE__ */ jsx10(AnimatePresence3, { children: isOpen && /* @__PURE__ */ jsxs10(
1383
1418
  motion5.div,
1384
1419
  {
1385
1420
  ref: dropdownRef,
1386
- className: "dialkit-preset-dropdown",
1421
+ className: "dialkit-root dialkit-preset-dropdown",
1387
1422
  style: { position: "fixed", top: pos.top, left: pos.left, minWidth: pos.width },
1388
1423
  initial: { opacity: 0, y: 4, scale: 0.97 },
1389
1424
  animate: { opacity: 1, y: 0, scale: 1 },
@@ -1502,7 +1537,7 @@ Apply these values as the new defaults in the useDialKit call.`;
1502
1537
  control.path
1503
1538
  );
1504
1539
  case "folder":
1505
- return /* @__PURE__ */ jsx11(Folder, { title: control.label, children: control.children?.map(renderControl) }, control.path);
1540
+ return /* @__PURE__ */ jsx11(Folder, { title: control.label, defaultOpen: control.defaultOpen ?? true, children: control.children?.map(renderControl) }, control.path);
1506
1541
  case "text":
1507
1542
  return /* @__PURE__ */ jsx11(
1508
1543
  TextControl,
@@ -1566,11 +1601,13 @@ Apply these values as the new defaults in the useDialKit call.`;
1566
1601
  const iconTransition = { type: "spring", visualDuration: 0.4, bounce: 0.1 };
1567
1602
  const toolbar = /* @__PURE__ */ jsxs11(Fragment2, { children: [
1568
1603
  /* @__PURE__ */ jsx11(
1569
- "button",
1604
+ motion6.button,
1570
1605
  {
1571
1606
  className: "dialkit-toolbar-add",
1572
1607
  onClick: handleAddPreset,
1573
1608
  title: "Add preset",
1609
+ whileTap: { scale: 0.9 },
1610
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
1574
1611
  children: /* @__PURE__ */ jsxs11("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1575
1612
  /* @__PURE__ */ jsx11("path", { d: "M4 6H20" }),
1576
1613
  /* @__PURE__ */ jsx11("path", { d: "M4 12H10" }),
@@ -1590,13 +1627,15 @@ Apply these values as the new defaults in the useDialKit call.`;
1590
1627
  }
1591
1628
  ),
1592
1629
  /* @__PURE__ */ jsxs11(
1593
- "button",
1630
+ motion6.button,
1594
1631
  {
1595
1632
  className: "dialkit-toolbar-copy",
1596
1633
  onClick: handleCopy,
1597
1634
  title: "Copy parameters",
1635
+ whileTap: { scale: 0.95 },
1636
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
1598
1637
  children: [
1599
- /* @__PURE__ */ jsx11("span", { className: "dialkit-toolbar-copy-icon-wrap", children: /* @__PURE__ */ jsx11(AnimatePresence4, { mode: "popLayout", children: copied ? /* @__PURE__ */ jsx11(
1638
+ /* @__PURE__ */ jsx11("span", { className: "dialkit-toolbar-copy-icon-wrap", children: /* @__PURE__ */ jsx11(AnimatePresence4, { initial: false, mode: "popLayout", children: copied ? /* @__PURE__ */ jsx11(
1600
1639
  motion6.svg,
1601
1640
  {
1602
1641
  className: "dialkit-toolbar-copy-icon",
@@ -1659,7 +1698,7 @@ function DialRoot({ position = "top-right" }) {
1659
1698
  return null;
1660
1699
  }
1661
1700
  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);
1701
+ return createPortal3(content, document.body);
1663
1702
  }
1664
1703
 
1665
1704
  // src/components/ButtonGroup.tsx