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/README.md CHANGED
@@ -72,12 +72,24 @@ Returns a fully typed object matching your config shape with live values. Updati
72
72
 
73
73
  ### Slider
74
74
 
75
+ Numbers create sliders. There are three ways to define them:
76
+
77
+ **Explicit range** — `[default, min, max]`:
78
+ ```tsx
79
+ blur: [24, 0, 100]
80
+ ```
81
+
82
+ **Explicit range + step** — `[default, min, max, step]`:
75
83
  ```tsx
76
- blur: [24, 0, 100] // [default, min, max] explicit range
77
- scale: 1.2 // auto-infers range from value
84
+ blur: [24, 0, 100, 5] // snaps in increments of 5
78
85
  ```
86
+ When `step` is omitted, it's inferred from the range (see table below).
79
87
 
80
- Numbers create sliders. With a tuple `[default, min, max]` you set the range explicitly. A bare number auto-infers a reasonable range:
88
+ **Auto-inferred** bare number:
89
+ ```tsx
90
+ scale: 1.2
91
+ ```
92
+ A single number auto-infers a reasonable min, max, and step:
81
93
 
82
94
  | Value range | Inferred min/max | Step |
83
95
  |-------------|-----------------|------|
@@ -197,19 +209,28 @@ Action buttons trigger callbacks without storing any value. The `label` defaults
197
209
 
198
210
  ### Folder
199
211
 
212
+ Any nested plain object becomes a collapsible folder. Folders can nest arbitrarily deep.
213
+
200
214
  ```tsx
201
215
  shadow: {
202
216
  blur: [10, 0, 50],
203
217
  opacity: [0.25, 0, 1],
204
218
  color: '#000000',
205
219
  }
220
+
221
+ // Access nested values:
222
+ params.shadow.blur // number
223
+ params.shadow.color // string
206
224
  ```
207
225
 
208
- Any nested plain object becomes a collapsible folder. Folders can nest arbitrarily deep. Access nested values with dot notation on the returned object:
226
+ Folders are open by default. Add `_collapsed: true` to start a folder closed. This is a reserved metadata key it controls the UI only and won't appear in your returned values.
209
227
 
210
228
  ```tsx
211
- params.shadow.blur // number
212
- params.shadow.color // string
229
+ shadow: {
230
+ _collapsed: true, // folder starts closed
231
+ blur: [10, 0, 50],
232
+ opacity: [0.25, 0, 1],
233
+ }
213
234
  ```
214
235
 
215
236
  ---
package/dist/index.cjs CHANGED
@@ -197,16 +197,17 @@ var DialStoreClass = class {
197
197
  parseConfig(config, prefix) {
198
198
  const controls = [];
199
199
  for (const [key, value] of Object.entries(config)) {
200
+ if (key === "_collapsed") continue;
200
201
  const path = prefix ? `${prefix}.${key}` : key;
201
202
  const label = this.formatLabel(key);
202
- if (Array.isArray(value) && value.length === 3 && typeof value[0] === "number") {
203
+ if (Array.isArray(value) && value.length <= 4 && typeof value[0] === "number") {
203
204
  controls.push({
204
205
  type: "slider",
205
206
  path,
206
207
  label,
207
208
  min: value[1],
208
209
  max: value[2],
209
- step: this.inferStep(value[1], value[2])
210
+ step: value[3] ?? this.inferStep(value[1], value[2])
210
211
  });
211
212
  } else if (typeof value === "number") {
212
213
  const { min, max, step } = this.inferRange(value);
@@ -230,11 +231,14 @@ var DialStoreClass = class {
230
231
  controls.push({ type: "text", path, label });
231
232
  }
232
233
  } else if (typeof value === "object" && value !== null) {
234
+ const folderConfig = value;
235
+ const defaultOpen = "_collapsed" in folderConfig ? !folderConfig._collapsed : true;
233
236
  controls.push({
234
237
  type: "folder",
235
238
  path,
236
239
  label,
237
- children: this.parseConfig(value, path)
240
+ defaultOpen,
241
+ children: this.parseConfig(folderConfig, path)
238
242
  });
239
243
  }
240
244
  }
@@ -243,8 +247,9 @@ var DialStoreClass = class {
243
247
  flattenValues(config, prefix) {
244
248
  const values = {};
245
249
  for (const [key, value] of Object.entries(config)) {
250
+ if (key === "_collapsed") continue;
246
251
  const path = prefix ? `${prefix}.${key}` : key;
247
- if (Array.isArray(value) && value.length === 3 && typeof value[0] === "number") {
252
+ if (Array.isArray(value) && value.length <= 4 && typeof value[0] === "number") {
248
253
  values[path] = value[0];
249
254
  } else if (typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
250
255
  values[path] = value;
@@ -336,8 +341,9 @@ function useDialKit(name, config, options) {
336
341
  function buildResolvedValues(config, flatValues, prefix) {
337
342
  const result = {};
338
343
  for (const [key, configValue] of Object.entries(config)) {
344
+ if (key === "_collapsed") continue;
339
345
  const path = prefix ? `${prefix}.${key}` : key;
340
- if (Array.isArray(configValue) && configValue.length === 3 && typeof configValue[0] === "number") {
346
+ if (Array.isArray(configValue) && configValue.length <= 4 && typeof configValue[0] === "number") {
341
347
  result[key] = flatValues[path] ?? configValue[0];
342
348
  } else if (typeof configValue === "number" || typeof configValue === "boolean" || typeof configValue === "string") {
343
349
  result[key] = flatValues[path] ?? configValue;
@@ -383,7 +389,7 @@ function getFirstOptionValue(options) {
383
389
 
384
390
  // src/components/DialRoot.tsx
385
391
  var import_react16 = require("react");
386
- var import_react_dom2 = require("react-dom");
392
+ var import_react_dom3 = require("react-dom");
387
393
 
388
394
  // src/components/Panel.tsx
389
395
  var import_react14 = require("react");
@@ -398,8 +404,6 @@ function Folder({ title, children, defaultOpen = true, isRoot = false, onOpenCha
398
404
  const [isCollapsed, setIsCollapsed] = (0, import_react2.useState)(!defaultOpen);
399
405
  const contentRef = (0, import_react2.useRef)(null);
400
406
  const [contentHeight, setContentHeight] = (0, import_react2.useState)(void 0);
401
- const iconTransition = { type: "spring", visualDuration: 0.4, bounce: 0.1 };
402
- const panelTransition = { type: "spring", visualDuration: 0.4, bounce: 0.2 };
403
407
  (0, import_react2.useEffect)(() => {
404
408
  const el = contentRef.current;
405
409
  if (!el) return;
@@ -412,25 +416,20 @@ function Folder({ title, children, defaultOpen = true, isRoot = false, onOpenCha
412
416
  ro.observe(el);
413
417
  return () => ro.disconnect();
414
418
  }, [isOpen]);
419
+ const handleToggle = () => {
420
+ const next = !isOpen;
421
+ setIsOpen(next);
422
+ if (next) {
423
+ setIsCollapsed(false);
424
+ } else {
425
+ setIsCollapsed(true);
426
+ }
427
+ onOpenChange?.(next);
428
+ };
415
429
  const folderContent = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ref: isRoot ? contentRef : void 0, className: `dialkit-folder ${isRoot ? "dialkit-folder-root" : ""}`, children: [
416
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `dialkit-folder-header ${isRoot ? "dialkit-panel-header" : ""}`, onClick: () => {
417
- const next = !isOpen;
418
- setIsOpen(next);
419
- if (next) setIsCollapsed(false);
420
- onOpenChange?.(next);
421
- }, children: [
430
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `dialkit-folder-header ${isRoot ? "dialkit-panel-header" : ""}`, onClick: handleToggle, children: [
422
431
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dialkit-folder-header-top", children: [
423
- isRoot ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react3.AnimatePresence, { initial: false, mode: "popLayout", children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
424
- import_react3.motion.div,
425
- {
426
- className: "dialkit-folder-title-row",
427
- initial: { opacity: 1 },
428
- animate: { opacity: 1 },
429
- exit: { opacity: 0 },
430
- transition: { duration: 0.15 },
431
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dialkit-folder-title dialkit-folder-title-root", children: title })
432
- }
433
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dialkit-folder-title-row", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dialkit-folder-title", children: title }) }),
432
+ isRoot ? isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dialkit-folder-title-row", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dialkit-folder-title dialkit-folder-title-root", children: title }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dialkit-folder-title-row", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dialkit-folder-title", children: title }) }),
434
433
  isRoot ? (
435
434
  // Root panel icon — fixed position, container morphs around it
436
435
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -467,53 +466,32 @@ function Folder({ title, children, defaultOpen = true, isRoot = false, onOpenCha
467
466
  )
468
467
  )
469
468
  ] }),
470
- isRoot && toolbar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react3.AnimatePresence, { initial: false, children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
471
- import_react3.motion.div,
472
- {
473
- initial: { opacity: 1 },
474
- animate: { opacity: 1 },
475
- exit: { opacity: 0 },
476
- transition: { duration: 0.15 },
477
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dialkit-panel-toolbar", onClick: (e) => e.stopPropagation(), children: toolbar })
478
- }
479
- ) })
469
+ isRoot && toolbar && isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dialkit-panel-toolbar", onClick: (e) => e.stopPropagation(), children: toolbar })
480
470
  ] }),
481
471
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react3.AnimatePresence, { initial: false, children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
482
472
  import_react3.motion.div,
483
473
  {
484
474
  className: "dialkit-folder-content",
485
- initial: isRoot ? { opacity: 1 } : { height: 0, opacity: 0 },
486
- animate: isRoot ? { opacity: 1 } : { height: "auto", opacity: 1 },
487
- exit: isRoot ? { opacity: 0 } : { height: 0, opacity: 0 },
488
- transition: isRoot ? { duration: 0.15 } : { type: "spring", visualDuration: 0.35, bounce: 0.1 },
489
- style: isRoot ? void 0 : { overflow: "hidden" },
475
+ initial: isRoot ? void 0 : { height: 0, opacity: 0 },
476
+ animate: isRoot ? void 0 : { height: "auto", opacity: 1 },
477
+ exit: isRoot ? void 0 : { height: 0, opacity: 0 },
478
+ transition: isRoot ? void 0 : { type: "spring", visualDuration: 0.35, bounce: 0.1 },
479
+ style: isRoot ? void 0 : { clipPath: "inset(0 -20px)" },
490
480
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dialkit-folder-inner", children })
491
481
  }
492
482
  ) })
493
483
  ] });
494
484
  if (isRoot) {
485
+ 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" };
495
486
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
496
487
  import_react3.motion.div,
497
488
  {
498
489
  className: "dialkit-panel-inner",
499
- initial: false,
500
- animate: {
501
- width: isOpen ? 280 : 42,
502
- height: isOpen ? contentHeight !== void 0 ? contentHeight + 24 : "auto" : 42,
503
- borderRadius: isOpen ? 14 : 21,
504
- boxShadow: isOpen ? "0 8px 32px rgba(0, 0, 0, 0.5)" : "0 4px 16px rgba(0, 0, 0, 0.25)"
505
- },
506
- transition: panelTransition,
507
- style: { overflow: isOpen ? void 0 : "hidden", cursor: isOpen ? void 0 : "pointer" },
508
- onClick: !isOpen ? () => {
509
- setIsOpen(true);
510
- setIsCollapsed(false);
511
- onOpenChange?.(true);
512
- } : void 0,
513
- onAnimationComplete: () => {
514
- if (!isOpen) setIsCollapsed(true);
515
- },
490
+ style: panelStyle,
491
+ onClick: !isOpen ? handleToggle : void 0,
516
492
  "data-collapsed": isCollapsed,
493
+ whileTap: !isOpen ? { scale: 0.9 } : void 0,
494
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
517
495
  children: folderContent
518
496
  }
519
497
  );
@@ -529,10 +507,14 @@ var CLICK_THRESHOLD = 3;
529
507
  var DEAD_ZONE = 32;
530
508
  var MAX_CURSOR_RANGE = 200;
531
509
  var MAX_STRETCH = 8;
510
+ function decimalsForStep(step) {
511
+ const s = step.toString();
512
+ const dot = s.indexOf(".");
513
+ return dot === -1 ? 0 : s.length - dot - 1;
514
+ }
532
515
  function roundValue(val, step) {
533
- const decimals = step >= 1 ? 0 : 2;
534
- const factor = 10 ** decimals;
535
- return Math.round(val * factor) / factor;
516
+ const raw = Math.round(val / step) * step;
517
+ return parseFloat(raw.toFixed(decimalsForStep(step)));
536
518
  }
537
519
  function snapToDecile(rawValue, min, max) {
538
520
  const normalized = (rawValue - min) / (max - min);
@@ -680,7 +662,8 @@ function Slider({
680
662
  if (!isInteracting) return;
681
663
  if (isClickRef.current) {
682
664
  const rawValue = positionToValue(e.clientX);
683
- const snappedValue = snapToDecile(rawValue, min, max);
665
+ const discreteSteps2 = (max - min) / step;
666
+ const snappedValue = discreteSteps2 <= 10 ? Math.max(min, Math.min(max, min + Math.round((rawValue - min) / step) * step)) : snapToDecile(rawValue, min, max);
684
667
  const newPct = percentFromValue(snappedValue);
685
668
  if (animRef.current) {
686
669
  animRef.current.stop();
@@ -760,7 +743,7 @@ function Slider({
760
743
  e.stopPropagation();
761
744
  e.preventDefault();
762
745
  setShowInput(true);
763
- setInputValue(step >= 1 ? value.toFixed(0) : value.toFixed(2));
746
+ setInputValue(value.toFixed(decimalsForStep(step)));
764
747
  }
765
748
  };
766
749
  const handleInputKeyDown = (e) => {
@@ -774,7 +757,7 @@ function Slider({
774
757
  const handleInputBlur = () => {
775
758
  handleInputSubmit();
776
759
  };
777
- const displayValue = step >= 1 ? value.toFixed(0) : value.toFixed(2);
760
+ const displayValue = value.toFixed(decimalsForStep(step));
778
761
  const HANDLE_BUFFER = 8;
779
762
  const LABEL_CSS_LEFT = 10;
780
763
  const VALUE_CSS_RIGHT = 10;
@@ -792,7 +775,18 @@ function Slider({
792
775
  const valueDodge = percentage < leftThreshold || percentage > rightThreshold;
793
776
  const handleOpacity = !isActive ? 0 : valueDodge ? 0.1 : isDragging ? 0.9 : 0.5;
794
777
  const fillBackground = isActive ? "rgba(255, 255, 255, 0.15)" : "rgba(255, 255, 255, 0.11)";
795
- const hashMarks = Array.from({ length: 9 }, (_, i) => {
778
+ const discreteSteps = (max - min) / step;
779
+ const hashMarks = discreteSteps <= 10 ? Array.from({ length: discreteSteps - 1 }, (_, i) => {
780
+ const pct = (i + 1) * step / (max - min) * 100;
781
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
782
+ "div",
783
+ {
784
+ className: "dialkit-slider-hashmark",
785
+ style: { left: `${pct}%` }
786
+ },
787
+ i
788
+ );
789
+ }) : Array.from({ length: 9 }, (_, i) => {
796
790
  const pct = (i + 1) * 10;
797
791
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
798
792
  "div",
@@ -1182,32 +1176,63 @@ function TextControl({ label, value, onChange, placeholder }) {
1182
1176
 
1183
1177
  // src/components/SelectControl.tsx
1184
1178
  var import_react9 = require("react");
1179
+ var import_react_dom = require("react-dom");
1185
1180
  var import_react10 = require("motion/react");
1186
1181
  var import_jsx_runtime8 = require("react/jsx-runtime");
1182
+ function toTitleCase(s) {
1183
+ return s.replace(/\b\w/g, (c) => c.toUpperCase());
1184
+ }
1187
1185
  function normalizeOptions(options) {
1188
1186
  return options.map(
1189
- (opt) => typeof opt === "string" ? { value: opt, label: opt } : opt
1187
+ (opt) => typeof opt === "string" ? { value: opt, label: toTitleCase(opt) } : opt
1190
1188
  );
1191
1189
  }
1192
1190
  function SelectControl({ label, value, options, onChange }) {
1193
1191
  const [isOpen, setIsOpen] = (0, import_react9.useState)(false);
1194
- const containerRef = (0, import_react9.useRef)(null);
1192
+ const triggerRef = (0, import_react9.useRef)(null);
1193
+ const dropdownRef = (0, import_react9.useRef)(null);
1194
+ const [portalTarget, setPortalTarget] = (0, import_react9.useState)(null);
1195
+ const [pos, setPos] = (0, import_react9.useState)(null);
1195
1196
  const normalized = normalizeOptions(options);
1196
1197
  const selectedOption = normalized.find((o) => o.value === value);
1198
+ const updatePos = (0, import_react9.useCallback)(() => {
1199
+ const el = triggerRef.current;
1200
+ if (!el) return;
1201
+ const rect = el.getBoundingClientRect();
1202
+ const dropdownHeight = 8 + normalized.length * 36;
1203
+ const spaceBelow = window.innerHeight - rect.bottom - 4;
1204
+ const above = spaceBelow < dropdownHeight && rect.top > spaceBelow;
1205
+ setPos({
1206
+ top: above ? rect.top - 4 : rect.bottom + 4,
1207
+ left: rect.left,
1208
+ width: rect.width,
1209
+ above
1210
+ });
1211
+ }, [normalized.length]);
1212
+ (0, import_react9.useEffect)(() => {
1213
+ const root = triggerRef.current?.closest(".dialkit-root");
1214
+ setPortalTarget(root ?? document.body);
1215
+ }, []);
1216
+ (0, import_react9.useEffect)(() => {
1217
+ if (!isOpen) return;
1218
+ updatePos();
1219
+ }, [isOpen, updatePos]);
1197
1220
  (0, import_react9.useEffect)(() => {
1198
1221
  if (!isOpen) return;
1199
1222
  const handleClick = (e) => {
1200
- if (containerRef.current && !containerRef.current.contains(e.target)) {
1223
+ const target = e.target;
1224
+ if (triggerRef.current && !triggerRef.current.contains(target) && dropdownRef.current && !dropdownRef.current.contains(target)) {
1201
1225
  setIsOpen(false);
1202
1226
  }
1203
1227
  };
1204
1228
  document.addEventListener("mousedown", handleClick);
1205
1229
  return () => document.removeEventListener("mousedown", handleClick);
1206
1230
  }, [isOpen]);
1207
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { ref: containerRef, className: "dialkit-select-row", children: [
1231
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dialkit-select-row", children: [
1208
1232
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1209
1233
  "button",
1210
1234
  {
1235
+ ref: triggerRef,
1211
1236
  className: "dialkit-select-trigger",
1212
1237
  onClick: () => setIsOpen(!isOpen),
1213
1238
  "data-open": String(isOpen),
@@ -1234,29 +1259,39 @@ function SelectControl({ label, value, options, onChange }) {
1234
1259
  ]
1235
1260
  }
1236
1261
  ),
1237
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react10.AnimatePresence, { children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1238
- import_react10.motion.div,
1239
- {
1240
- className: "dialkit-select-dropdown",
1241
- initial: { opacity: 0, y: -8, scale: 0.95 },
1242
- animate: { opacity: 1, y: 0, scale: 1 },
1243
- exit: { opacity: 0, y: -8, scale: 0.95 },
1244
- transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
1245
- children: normalized.map((option) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1246
- "button",
1247
- {
1248
- className: "dialkit-select-option",
1249
- "data-selected": String(option.value === value),
1250
- onClick: () => {
1251
- onChange(option.value);
1252
- setIsOpen(false);
1253
- },
1254
- children: option.label
1262
+ portalTarget && (0, import_react_dom.createPortal)(
1263
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react10.AnimatePresence, { children: isOpen && pos && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1264
+ import_react10.motion.div,
1265
+ {
1266
+ ref: dropdownRef,
1267
+ className: "dialkit-select-dropdown",
1268
+ initial: { opacity: 0, y: pos.above ? 8 : -8, scale: 0.95 },
1269
+ animate: { opacity: 1, y: 0, scale: 1 },
1270
+ exit: { opacity: 0, y: pos.above ? 8 : -8, scale: 0.95 },
1271
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
1272
+ style: {
1273
+ position: "fixed",
1274
+ left: pos.left,
1275
+ width: pos.width,
1276
+ ...pos.above ? { bottom: window.innerHeight - pos.top, transformOrigin: "bottom" } : { top: pos.top, transformOrigin: "top" }
1255
1277
  },
1256
- option.value
1257
- ))
1258
- }
1259
- ) })
1278
+ children: normalized.map((option) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1279
+ "button",
1280
+ {
1281
+ className: "dialkit-select-option",
1282
+ "data-selected": String(option.value === value),
1283
+ onClick: () => {
1284
+ onChange(option.value);
1285
+ setIsOpen(false);
1286
+ },
1287
+ children: option.label
1288
+ },
1289
+ option.value
1290
+ ))
1291
+ }
1292
+ ) }),
1293
+ portalTarget
1294
+ )
1260
1295
  ] });
1261
1296
  }
1262
1297
 
@@ -1340,7 +1375,7 @@ function expandShorthandHex(hex) {
1340
1375
 
1341
1376
  // src/components/PresetManager.tsx
1342
1377
  var import_react12 = require("react");
1343
- var import_react_dom = require("react-dom");
1378
+ var import_react_dom2 = require("react-dom");
1344
1379
  var import_react13 = require("motion/react");
1345
1380
  var import_jsx_runtime10 = require("react/jsx-runtime");
1346
1381
  function PresetManager({ panelId, presets, activePresetId, onAdd }) {
@@ -1407,7 +1442,7 @@ function PresetManager({ panelId, presets, activePresetId, onAdd }) {
1407
1442
  strokeWidth: "2.5",
1408
1443
  strokeLinecap: "round",
1409
1444
  strokeLinejoin: "round",
1410
- animate: { rotate: isOpen ? 180 : 0 },
1445
+ animate: { rotate: isOpen ? 180 : 0, opacity: hasPresets ? 0.6 : 0.25 },
1411
1446
  transition: { type: "spring", visualDuration: 0.2, bounce: 0.15 },
1412
1447
  children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M6 9.5L12 15.5L18 9.5" })
1413
1448
  }
@@ -1415,12 +1450,12 @@ function PresetManager({ panelId, presets, activePresetId, onAdd }) {
1415
1450
  ]
1416
1451
  }
1417
1452
  ),
1418
- (0, import_react_dom.createPortal)(
1453
+ (0, import_react_dom2.createPortal)(
1419
1454
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react13.AnimatePresence, { children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1420
1455
  import_react13.motion.div,
1421
1456
  {
1422
1457
  ref: dropdownRef,
1423
- className: "dialkit-preset-dropdown",
1458
+ className: "dialkit-root dialkit-preset-dropdown",
1424
1459
  style: { position: "fixed", top: pos.top, left: pos.left, minWidth: pos.width },
1425
1460
  initial: { opacity: 0, y: 4, scale: 0.97 },
1426
1461
  animate: { opacity: 1, y: 0, scale: 1 },
@@ -1539,7 +1574,7 @@ Apply these values as the new defaults in the useDialKit call.`;
1539
1574
  control.path
1540
1575
  );
1541
1576
  case "folder":
1542
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Folder, { title: control.label, children: control.children?.map(renderControl) }, control.path);
1577
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Folder, { title: control.label, defaultOpen: control.defaultOpen ?? true, children: control.children?.map(renderControl) }, control.path);
1543
1578
  case "text":
1544
1579
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1545
1580
  TextControl,
@@ -1603,11 +1638,13 @@ Apply these values as the new defaults in the useDialKit call.`;
1603
1638
  const iconTransition = { type: "spring", visualDuration: 0.4, bounce: 0.1 };
1604
1639
  const toolbar = /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1605
1640
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1606
- "button",
1641
+ import_react15.motion.button,
1607
1642
  {
1608
1643
  className: "dialkit-toolbar-add",
1609
1644
  onClick: handleAddPreset,
1610
1645
  title: "Add preset",
1646
+ whileTap: { scale: 0.9 },
1647
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
1611
1648
  children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1612
1649
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M4 6H20" }),
1613
1650
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M4 12H10" }),
@@ -1627,13 +1664,15 @@ Apply these values as the new defaults in the useDialKit call.`;
1627
1664
  }
1628
1665
  ),
1629
1666
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1630
- "button",
1667
+ import_react15.motion.button,
1631
1668
  {
1632
1669
  className: "dialkit-toolbar-copy",
1633
1670
  onClick: handleCopy,
1634
1671
  title: "Copy parameters",
1672
+ whileTap: { scale: 0.95 },
1673
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
1635
1674
  children: [
1636
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "dialkit-toolbar-copy-icon-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react15.AnimatePresence, { mode: "popLayout", children: copied ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1675
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "dialkit-toolbar-copy-icon-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react15.AnimatePresence, { initial: false, mode: "popLayout", children: copied ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1637
1676
  import_react15.motion.svg,
1638
1677
  {
1639
1678
  className: "dialkit-toolbar-copy-icon",
@@ -1696,7 +1735,7 @@ function DialRoot({ position = "top-right" }) {
1696
1735
  return null;
1697
1736
  }
1698
1737
  const content = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "dialkit-root", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "dialkit-panel", "data-position": position, children: panels.map((panel) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Panel, { panel }, panel.id)) }) });
1699
- return (0, import_react_dom2.createPortal)(content, document.body);
1738
+ return (0, import_react_dom3.createPortal)(content, document.body);
1700
1739
  }
1701
1740
 
1702
1741
  // src/components/ButtonGroup.tsx