dialkit 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +220 -3
  2. package/dist/dropdown-position.d.ts +15 -0
  3. package/dist/dropdown-position.js +22 -0
  4. package/dist/dropdown-position.js.map +1 -0
  5. package/dist/index.cjs +697 -307
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +42 -5
  8. package/dist/index.d.ts +42 -5
  9. package/dist/index.js +720 -331
  10. package/dist/index.js.map +1 -1
  11. package/dist/panel-drag.d.ts +19 -0
  12. package/dist/panel-drag.js +72 -0
  13. package/dist/panel-drag.js.map +1 -0
  14. package/dist/solid/index.cjs +661 -204
  15. package/dist/solid/index.cjs.map +1 -1
  16. package/dist/solid/index.d.cts +40 -3
  17. package/dist/solid/index.d.ts +40 -3
  18. package/dist/solid/index.js +646 -190
  19. package/dist/solid/index.js.map +1 -1
  20. package/dist/store/index.cjs +272 -41
  21. package/dist/store/index.cjs.map +1 -1
  22. package/dist/store/index.d.cts +30 -3
  23. package/dist/store/index.d.ts +30 -3
  24. package/dist/store/index.js +269 -40
  25. package/dist/store/index.js.map +1 -1
  26. package/dist/styles.css +105 -7
  27. package/dist/svelte/components/DialRoot.svelte +176 -11
  28. package/dist/svelte/components/DialRoot.svelte.d.ts +1 -0
  29. package/dist/svelte/components/DialRoot.svelte.d.ts.map +1 -1
  30. package/dist/svelte/components/Folder.svelte +24 -13
  31. package/dist/svelte/components/Folder.svelte.d.ts +1 -0
  32. package/dist/svelte/components/Folder.svelte.d.ts.map +1 -1
  33. package/dist/svelte/components/Panel.svelte +98 -71
  34. package/dist/svelte/components/Panel.svelte.d.ts +2 -0
  35. package/dist/svelte/components/Panel.svelte.d.ts.map +1 -1
  36. package/dist/svelte/components/PresetManager.svelte +5 -5
  37. package/dist/svelte/components/PresetManager.svelte.d.ts.map +1 -1
  38. package/dist/svelte/components/SelectControl.svelte +6 -14
  39. package/dist/svelte/components/SelectControl.svelte.d.ts.map +1 -1
  40. package/dist/svelte/createDialKit.svelte.d.ts +11 -1
  41. package/dist/svelte/createDialKit.svelte.d.ts.map +1 -1
  42. package/dist/svelte/createDialKit.svelte.js +61 -34
  43. package/dist/svelte/index.d.ts +3 -3
  44. package/dist/svelte/index.d.ts.map +1 -1
  45. package/dist/svelte/index.js +1 -1
  46. package/dist/svelte/theme-css.d.ts +1 -1
  47. package/dist/svelte/theme-css.d.ts.map +1 -1
  48. package/dist/svelte/theme-css.js +105 -7
  49. package/dist/vue/index.cjs +573 -132
  50. package/dist/vue/index.cjs.map +1 -1
  51. package/dist/vue/index.d.cts +53 -6
  52. package/dist/vue/index.d.ts +53 -6
  53. package/dist/vue/index.js +573 -133
  54. package/dist/vue/index.js.map +1 -1
  55. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -36,7 +36,8 @@ __export(index_exports, {
36
36
  TextControl: () => TextControl,
37
37
  Toggle: () => Toggle,
38
38
  TransitionControl: () => TransitionControl,
39
- useDialKit: () => useDialKit
39
+ useDialKit: () => useDialKit,
40
+ useDialKitController: () => useDialKitController
40
41
  });
41
42
  module.exports = __toCommonJS(index_exports);
42
43
 
@@ -45,6 +46,89 @@ var import_react = require("react");
45
46
 
46
47
  // src/store/DialStore.ts
47
48
  var EMPTY_VALUES = Object.freeze({});
49
+ function resolveDialValues(config, flatValues) {
50
+ return resolveConfigValues(config, flatValues, "");
51
+ }
52
+ function flattenDialValueUpdates(config, updates) {
53
+ const values = {};
54
+ if (typeof updates === "object" && updates !== null) {
55
+ flattenConfigUpdates(config, updates, "", values);
56
+ }
57
+ return values;
58
+ }
59
+ function resolveConfigValues(config, flatValues, prefix) {
60
+ const result = {};
61
+ for (const [key, configValue] of Object.entries(config)) {
62
+ if (key === "_collapsed") continue;
63
+ const path = prefix ? `${prefix}.${key}` : key;
64
+ if (Array.isArray(configValue) && configValue.length <= 4 && typeof configValue[0] === "number") {
65
+ result[key] = flatValues[path] ?? configValue[0];
66
+ } else if (typeof configValue === "number" || typeof configValue === "boolean" || typeof configValue === "string") {
67
+ result[key] = flatValues[path] ?? configValue;
68
+ } else if (isSpringConfigValue(configValue) || isEasingConfigValue(configValue)) {
69
+ result[key] = flatValues[path] ?? configValue;
70
+ } else if (isActionConfigValue(configValue)) {
71
+ result[key] = flatValues[path] ?? configValue;
72
+ } else if (isSelectConfigValue(configValue)) {
73
+ const defaultValue = configValue.default ?? getFirstOptionValue(configValue.options);
74
+ result[key] = flatValues[path] ?? defaultValue;
75
+ } else if (isColorConfigValue(configValue)) {
76
+ result[key] = flatValues[path] ?? configValue.default ?? "#000000";
77
+ } else if (isTextConfigValue(configValue)) {
78
+ result[key] = flatValues[path] ?? configValue.default ?? "";
79
+ } else if (typeof configValue === "object" && configValue !== null) {
80
+ result[key] = resolveConfigValues(configValue, flatValues, path);
81
+ }
82
+ }
83
+ return result;
84
+ }
85
+ function flattenConfigUpdates(config, updates, prefix, values) {
86
+ for (const [key, configValue] of Object.entries(config)) {
87
+ if (key === "_collapsed" || !(key in updates)) continue;
88
+ const nextValue = updates[key];
89
+ if (nextValue === void 0) continue;
90
+ const path = prefix ? `${prefix}.${key}` : key;
91
+ if (isActionConfigValue(configValue)) {
92
+ continue;
93
+ }
94
+ if (isLeafConfigValue(configValue)) {
95
+ values[path] = nextValue;
96
+ continue;
97
+ }
98
+ if (typeof configValue === "object" && configValue !== null && typeof nextValue === "object" && nextValue !== null && !Array.isArray(nextValue)) {
99
+ flattenConfigUpdates(configValue, nextValue, path, values);
100
+ }
101
+ }
102
+ }
103
+ function isLeafConfigValue(value) {
104
+ return Array.isArray(value) && value.length <= 4 && typeof value[0] === "number" || typeof value === "number" || typeof value === "boolean" || typeof value === "string" || isSpringConfigValue(value) || isEasingConfigValue(value) || isActionConfigValue(value) || isSelectConfigValue(value) || isColorConfigValue(value) || isTextConfigValue(value);
105
+ }
106
+ function hasType(value, type) {
107
+ return typeof value === "object" && value !== null && "type" in value && value.type === type;
108
+ }
109
+ function isSpringConfigValue(value) {
110
+ return hasType(value, "spring");
111
+ }
112
+ function isEasingConfigValue(value) {
113
+ return hasType(value, "easing");
114
+ }
115
+ function isActionConfigValue(value) {
116
+ return hasType(value, "action");
117
+ }
118
+ function isSelectConfigValue(value) {
119
+ return hasType(value, "select") && "options" in value && Array.isArray(value.options);
120
+ }
121
+ function isColorConfigValue(value) {
122
+ return hasType(value, "color");
123
+ }
124
+ function isTextConfigValue(value) {
125
+ return hasType(value, "text");
126
+ }
127
+ function getFirstOptionValue(options) {
128
+ const first = options[0];
129
+ if (first === void 0) return "";
130
+ return typeof first === "string" ? first : first.value;
131
+ }
48
132
  var DialStoreClass = class {
49
133
  constructor() {
50
134
  this.panels = /* @__PURE__ */ new Map();
@@ -55,87 +139,138 @@ var DialStoreClass = class {
55
139
  this.presets = /* @__PURE__ */ new Map();
56
140
  this.activePreset = /* @__PURE__ */ new Map();
57
141
  this.baseValues = /* @__PURE__ */ new Map();
58
- }
59
- registerPanel(id, name, config, shortcuts) {
142
+ this.defaultValues = /* @__PURE__ */ new Map();
143
+ this.registrationCounts = /* @__PURE__ */ new Map();
144
+ this.retainedPanels = /* @__PURE__ */ new Set();
145
+ this.persistConfigs = /* @__PURE__ */ new Map();
146
+ }
147
+ registerPanel(id, name, config, shortcuts, options = {}) {
148
+ this.configurePanelRetention(id, options);
149
+ this.registrationCounts.set(id, (this.registrationCounts.get(id) ?? 0) + 1);
60
150
  const controls = this.parseConfig(config, "", shortcuts);
61
- const values = this.flattenValues(config, "");
62
- this.initTransitionModes(config, "", values);
151
+ const controlsByPath = this.mapControlsByPath(controls);
152
+ const defaultValues = this.flattenValues(config, "");
153
+ this.initTransitionModes(config, "", defaultValues);
154
+ const persisted = this.loadPersistedPanel(id);
155
+ const previousValues = this.panels.get(id)?.values ?? this.snapshots.get(id) ?? persisted?.values ?? {};
156
+ const values = this.reconcileValues(defaultValues, previousValues, controlsByPath);
157
+ const previousBaseValues = this.baseValues.get(id) ?? persisted?.baseValues ?? persisted?.values ?? {};
158
+ const baseValues = this.reconcileValues(defaultValues, previousBaseValues, controlsByPath);
63
159
  this.panels.set(id, { id, name, controls, values, shortcuts: shortcuts ?? {} });
64
160
  this.snapshots.set(id, { ...values });
65
- this.baseValues.set(id, { ...values });
161
+ this.baseValues.set(id, baseValues);
162
+ this.defaultValues.set(id, { ...defaultValues });
163
+ const existingPresets = this.presets.get(id) ?? persisted?.presets;
164
+ if (existingPresets) {
165
+ this.presets.set(id, this.reconcilePresets(existingPresets, defaultValues, controlsByPath));
166
+ }
167
+ if (!this.activePreset.has(id) && persisted?.activePresetId !== void 0) {
168
+ this.activePreset.set(id, persisted.activePresetId);
169
+ }
170
+ this.persistPanel(id);
171
+ this.notify(id);
66
172
  this.notifyGlobal();
67
173
  }
68
- updatePanel(id, name, config, shortcuts) {
174
+ updatePanel(id, name, config, shortcuts, options = {}) {
175
+ this.configurePanelRetention(id, options);
69
176
  const existing = this.panels.get(id);
70
177
  if (!existing) {
71
- this.registerPanel(id, name, config, shortcuts);
178
+ this.registerPanel(id, name, config, shortcuts, options);
72
179
  return;
73
180
  }
74
181
  const controls = this.parseConfig(config, "", shortcuts);
75
182
  const controlsByPath = this.mapControlsByPath(controls);
76
183
  const defaultValues = this.flattenValues(config, "");
77
- const nextValues = {};
78
- for (const [path, defaultValue] of Object.entries(defaultValues)) {
79
- nextValues[path] = this.normalizePreservedValue(
80
- existing.values[path],
81
- defaultValue,
82
- controlsByPath.get(path)
83
- );
84
- }
85
- this.initTransitionModes(config, "", nextValues);
86
- for (const [path, mode] of Object.entries(existing.values)) {
87
- if (!path.endsWith(".__mode")) {
88
- continue;
89
- }
90
- const transitionPath = path.slice(0, -"__mode".length - 1);
91
- const transitionControl = controlsByPath.get(transitionPath);
92
- if (transitionControl?.type === "transition") {
93
- nextValues[path] = mode;
94
- }
95
- }
184
+ this.initTransitionModes(config, "", defaultValues);
185
+ const nextValues = this.reconcileValues(defaultValues, existing.values, controlsByPath);
96
186
  const nextPanel = { id, name, controls, values: nextValues, shortcuts: shortcuts ?? existing.shortcuts };
97
187
  this.panels.set(id, nextPanel);
98
188
  this.snapshots.set(id, { ...nextValues });
99
189
  const previousBaseValues = this.baseValues.get(id) ?? {};
100
- const nextBaseValues = {};
101
- for (const [path, defaultValue] of Object.entries(defaultValues)) {
102
- nextBaseValues[path] = this.normalizePreservedValue(
103
- previousBaseValues[path],
104
- defaultValue,
105
- controlsByPath.get(path)
106
- );
107
- }
190
+ const nextBaseValues = this.reconcileValues(defaultValues, previousBaseValues, controlsByPath);
108
191
  for (const [path, value] of Object.entries(nextValues)) {
109
192
  if (path.endsWith(".__mode")) {
110
193
  nextBaseValues[path] = value;
111
194
  }
112
195
  }
113
196
  this.baseValues.set(id, nextBaseValues);
197
+ this.defaultValues.set(id, { ...defaultValues });
198
+ this.presets.set(id, this.reconcilePresets(this.presets.get(id) ?? [], defaultValues, controlsByPath));
199
+ this.persistPanel(id);
114
200
  this.notify(id);
115
201
  this.notifyGlobal();
116
202
  }
117
203
  unregisterPanel(id) {
204
+ const nextCount = (this.registrationCounts.get(id) ?? 1) - 1;
205
+ if (nextCount > 0) {
206
+ this.registrationCounts.set(id, nextCount);
207
+ return;
208
+ }
209
+ this.registrationCounts.delete(id);
118
210
  this.panels.delete(id);
119
211
  this.listeners.delete(id);
120
- this.snapshots.delete(id);
121
212
  this.actionListeners.delete(id);
122
- this.baseValues.delete(id);
213
+ if (!this.retainedPanels.has(id)) {
214
+ this.snapshots.delete(id);
215
+ this.baseValues.delete(id);
216
+ this.defaultValues.delete(id);
217
+ this.presets.delete(id);
218
+ this.activePreset.delete(id);
219
+ this.persistConfigs.delete(id);
220
+ }
123
221
  this.notifyGlobal();
124
222
  }
125
223
  updateValue(panelId, path, value) {
224
+ this.updateValues(panelId, { [path]: value });
225
+ }
226
+ updateValues(panelId, updates) {
126
227
  const panel = this.panels.get(panelId);
127
228
  if (!panel) return;
128
- panel.values[path] = value;
229
+ const validUpdates = {};
230
+ for (const [path, value] of Object.entries(updates)) {
231
+ if (!Object.prototype.hasOwnProperty.call(panel.values, path)) {
232
+ continue;
233
+ }
234
+ const control = this.findControlByPath(panel.controls, path);
235
+ if (control?.type === "action") {
236
+ continue;
237
+ }
238
+ panel.values[path] = value;
239
+ validUpdates[path] = value;
240
+ }
241
+ if (Object.keys(validUpdates).length === 0) {
242
+ return;
243
+ }
129
244
  const activeId = this.activePreset.get(panelId);
130
245
  if (activeId) {
131
246
  const presets = this.presets.get(panelId) ?? [];
132
247
  const preset = presets.find((p) => p.id === activeId);
133
- if (preset) preset.values[path] = value;
248
+ if (preset) {
249
+ for (const [path, value] of Object.entries(validUpdates)) {
250
+ preset.values[path] = value;
251
+ }
252
+ }
134
253
  } else {
135
254
  const base = this.baseValues.get(panelId);
136
- if (base) base[path] = value;
255
+ if (base) {
256
+ for (const [path, value] of Object.entries(validUpdates)) {
257
+ base[path] = value;
258
+ }
259
+ }
137
260
  }
138
261
  this.snapshots.set(panelId, { ...panel.values });
262
+ this.persistPanel(panelId);
263
+ this.notify(panelId);
264
+ }
265
+ resetValues(panelId) {
266
+ const panel = this.panels.get(panelId);
267
+ const defaults = this.defaultValues.get(panelId);
268
+ if (!panel || !defaults) return;
269
+ panel.values = { ...defaults };
270
+ this.snapshots.set(panelId, { ...panel.values });
271
+ this.baseValues.set(panelId, { ...defaults });
272
+ this.activePreset.set(panelId, null);
273
+ this.persistPanel(panelId);
139
274
  this.notify(panelId);
140
275
  }
141
276
  updateSpringMode(panelId, path, mode) {
@@ -151,6 +286,7 @@ var DialStoreClass = class {
151
286
  if (!panel) return;
152
287
  panel.values[`${path}.__mode`] = mode;
153
288
  this.snapshots.set(panelId, { ...panel.values });
289
+ this.persistPanel(panelId);
154
290
  this.notify(panelId);
155
291
  }
156
292
  getTransitionMode(panelId, path) {
@@ -209,6 +345,7 @@ var DialStoreClass = class {
209
345
  this.presets.set(panelId, [...existing, preset]);
210
346
  this.activePreset.set(panelId, id);
211
347
  this.snapshots.set(panelId, { ...panel.values });
348
+ this.persistPanel(panelId);
212
349
  this.notify(panelId);
213
350
  return id;
214
351
  }
@@ -221,6 +358,7 @@ var DialStoreClass = class {
221
358
  panel.values = { ...preset.values };
222
359
  this.snapshots.set(panelId, { ...panel.values });
223
360
  this.activePreset.set(panelId, presetId);
361
+ this.persistPanel(panelId);
224
362
  this.notify(panelId);
225
363
  }
226
364
  deletePreset(panelId, presetId) {
@@ -233,6 +371,7 @@ var DialStoreClass = class {
233
371
  if (panel) {
234
372
  this.snapshots.set(panelId, { ...panel.values });
235
373
  }
374
+ this.persistPanel(panelId);
236
375
  this.notify(panelId);
237
376
  }
238
377
  getPresets(panelId) {
@@ -249,6 +388,7 @@ var DialStoreClass = class {
249
388
  this.snapshots.set(panelId, { ...panel.values });
250
389
  }
251
390
  this.activePreset.set(panelId, null);
391
+ this.persistPanel(panelId);
252
392
  this.notify(panelId);
253
393
  }
254
394
  resolveShortcutTarget(key, modifier) {
@@ -279,6 +419,94 @@ var DialStoreClass = class {
279
419
  }
280
420
  return results;
281
421
  }
422
+ configurePanelRetention(id, options) {
423
+ if (options.retainOnUnmount) {
424
+ this.retainedPanels.add(id);
425
+ }
426
+ const persistConfig = this.normalizePersistConfig(id, options.persist);
427
+ if (persistConfig) {
428
+ this.persistConfigs.set(id, persistConfig);
429
+ this.retainedPanels.add(id);
430
+ }
431
+ }
432
+ reconcileValues(defaultValues, previousValues, controlsByPath) {
433
+ const nextValues = {};
434
+ for (const [path, defaultValue] of Object.entries(defaultValues)) {
435
+ if (path.endsWith(".__mode")) {
436
+ const transitionPath = path.slice(0, -".__mode".length);
437
+ const transitionControl = controlsByPath.get(transitionPath);
438
+ nextValues[path] = transitionControl?.type === "transition" && previousValues[path] !== void 0 ? previousValues[path] : defaultValue;
439
+ continue;
440
+ }
441
+ nextValues[path] = this.normalizePreservedValue(
442
+ previousValues[path],
443
+ defaultValue,
444
+ controlsByPath.get(path)
445
+ );
446
+ }
447
+ return nextValues;
448
+ }
449
+ reconcilePresets(presets, defaultValues, controlsByPath) {
450
+ return presets.map((preset) => ({
451
+ ...preset,
452
+ values: this.reconcileValues(defaultValues, preset.values, controlsByPath)
453
+ }));
454
+ }
455
+ normalizePersistConfig(id, persist) {
456
+ if (!persist) return null;
457
+ const options = typeof persist === "object" ? persist : {};
458
+ return {
459
+ key: options.key ?? `dialkit:${id}`,
460
+ storage: options.storage ?? "localStorage",
461
+ presets: options.presets ?? true
462
+ };
463
+ }
464
+ loadPersistedPanel(id) {
465
+ const config = this.persistConfigs.get(id);
466
+ if (!config) return null;
467
+ const storage = this.getStorage(config.storage);
468
+ if (!storage) return null;
469
+ try {
470
+ const raw = storage.getItem(config.key);
471
+ if (!raw) return null;
472
+ const parsed = JSON.parse(raw);
473
+ if (parsed?.version !== 1 || typeof parsed !== "object") return null;
474
+ return parsed;
475
+ } catch {
476
+ return null;
477
+ }
478
+ }
479
+ persistPanel(id) {
480
+ const config = this.persistConfigs.get(id);
481
+ if (!config) return;
482
+ const storage = this.getStorage(config.storage);
483
+ if (!storage) return;
484
+ const values = this.snapshots.get(id) ?? this.panels.get(id)?.values;
485
+ if (!values) return;
486
+ const state = {
487
+ version: 1,
488
+ values,
489
+ baseValues: this.baseValues.get(id) ?? values,
490
+ activePresetId: this.activePreset.get(id) ?? null
491
+ };
492
+ if (config.presets) {
493
+ state.presets = this.presets.get(id) ?? [];
494
+ }
495
+ try {
496
+ storage.setItem(config.key, JSON.stringify(state));
497
+ } catch {
498
+ }
499
+ }
500
+ getStorage(kind) {
501
+ if (typeof globalThis === "undefined" || !("window" in globalThis)) {
502
+ return null;
503
+ }
504
+ try {
505
+ return kind === "sessionStorage" ? globalThis.window?.sessionStorage ?? null : globalThis.window?.localStorage ?? null;
506
+ } catch {
507
+ return null;
508
+ }
509
+ }
282
510
  findControlByPath(controls, path) {
283
511
  for (const control of controls) {
284
512
  if (control.path === path) return control;
@@ -507,8 +735,12 @@ var DialStore = new DialStoreClass();
507
735
 
508
736
  // src/hooks/useDialKit.ts
509
737
  function useDialKit(name, config, options) {
738
+ return useDialKitController(name, config, options).values;
739
+ }
740
+ function useDialKitController(name, config, options) {
510
741
  const instanceId = (0, import_react.useId)();
511
- const panelId = `${name}-${instanceId}`;
742
+ const hasStableId = options?.id !== void 0;
743
+ const panelId = options?.id ?? `${name}-${instanceId}`;
512
744
  const configRef = (0, import_react.useRef)(config);
513
745
  const serializedConfig = JSON.stringify(config);
514
746
  configRef.current = config;
@@ -516,93 +748,236 @@ function useDialKit(name, config, options) {
516
748
  onActionRef.current = options?.onAction;
517
749
  const shortcutsRef = (0, import_react.useRef)(options?.shortcuts);
518
750
  shortcutsRef.current = options?.shortcuts;
751
+ const persistRef = (0, import_react.useRef)(options?.persist);
752
+ persistRef.current = options?.persist;
519
753
  const serializedShortcuts = JSON.stringify(options?.shortcuts);
754
+ const serializedPersist = JSON.stringify(options?.persist);
520
755
  (0, import_react.useEffect)(() => {
521
- DialStore.registerPanel(panelId, name, configRef.current, shortcutsRef.current);
756
+ DialStore.registerPanel(panelId, name, configRef.current, shortcutsRef.current, {
757
+ retainOnUnmount: hasStableId,
758
+ persist: persistRef.current
759
+ });
522
760
  return () => DialStore.unregisterPanel(panelId);
523
- }, [panelId, name]);
761
+ }, [hasStableId, panelId, name]);
524
762
  const mountedRef = (0, import_react.useRef)(false);
525
763
  (0, import_react.useEffect)(() => {
526
764
  if (!mountedRef.current) {
527
765
  mountedRef.current = true;
528
766
  return;
529
767
  }
530
- DialStore.updatePanel(panelId, name, configRef.current, shortcutsRef.current);
531
- }, [panelId, name, serializedConfig, serializedShortcuts]);
768
+ DialStore.updatePanel(panelId, name, configRef.current, shortcutsRef.current, {
769
+ retainOnUnmount: hasStableId,
770
+ persist: persistRef.current
771
+ });
772
+ }, [hasStableId, panelId, name, serializedConfig, serializedShortcuts, serializedPersist]);
532
773
  (0, import_react.useEffect)(() => {
533
774
  return DialStore.subscribeActions(panelId, (action) => {
534
775
  onActionRef.current?.(action);
535
776
  });
536
777
  }, [panelId]);
537
- const values = (0, import_react.useSyncExternalStore)(
778
+ const subscribe = (0, import_react.useCallback)(
538
779
  (callback) => DialStore.subscribe(panelId, callback),
780
+ [panelId]
781
+ );
782
+ const getSnapshot = (0, import_react.useCallback)(
539
783
  () => DialStore.getValues(panelId),
540
- () => DialStore.getValues(panelId)
784
+ [panelId]
785
+ );
786
+ const flatValues = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
787
+ const values = (0, import_react.useMemo)(
788
+ () => resolveDialValues(configRef.current, flatValues),
789
+ [flatValues, serializedConfig]
790
+ );
791
+ const setValue = (0, import_react.useCallback)(
792
+ (path, value) => {
793
+ DialStore.updateValue(panelId, path, value);
794
+ },
795
+ [panelId]
796
+ );
797
+ const setValues = (0, import_react.useCallback)(
798
+ (nextValues) => {
799
+ DialStore.updateValues(panelId, flattenDialValueUpdates(configRef.current, nextValues));
800
+ },
801
+ [panelId]
802
+ );
803
+ const resetValues = (0, import_react.useCallback)(() => {
804
+ DialStore.resetValues(panelId);
805
+ }, [panelId]);
806
+ const getValues = (0, import_react.useCallback)(
807
+ () => resolveDialValues(configRef.current, DialStore.getValues(panelId)),
808
+ [panelId]
809
+ );
810
+ return (0, import_react.useMemo)(
811
+ () => ({
812
+ values,
813
+ setValue,
814
+ setValues,
815
+ resetValues,
816
+ getValues
817
+ }),
818
+ [getValues, resetValues, setValue, setValues, values]
541
819
  );
542
- return buildResolvedValues(config, values, "");
543
- }
544
- function buildResolvedValues(config, flatValues, prefix) {
545
- const result = {};
546
- for (const [key, configValue] of Object.entries(config)) {
547
- if (key === "_collapsed") continue;
548
- const path = prefix ? `${prefix}.${key}` : key;
549
- if (Array.isArray(configValue) && configValue.length <= 4 && typeof configValue[0] === "number") {
550
- result[key] = flatValues[path] ?? configValue[0];
551
- } else if (typeof configValue === "number" || typeof configValue === "boolean" || typeof configValue === "string") {
552
- result[key] = flatValues[path] ?? configValue;
553
- } else if (isSpringConfig(configValue) || isEasingConfig(configValue)) {
554
- result[key] = flatValues[path] ?? configValue;
555
- } else if (isActionConfig(configValue)) {
556
- result[key] = flatValues[path] ?? configValue;
557
- } else if (isSelectConfig(configValue)) {
558
- const defaultValue = configValue.default ?? getFirstOptionValue(configValue.options);
559
- result[key] = flatValues[path] ?? defaultValue;
560
- } else if (isColorConfig(configValue)) {
561
- result[key] = flatValues[path] ?? configValue.default ?? "#000000";
562
- } else if (isTextConfig(configValue)) {
563
- result[key] = flatValues[path] ?? configValue.default ?? "";
564
- } else if (typeof configValue === "object" && configValue !== null) {
565
- result[key] = buildResolvedValues(configValue, flatValues, path);
566
- }
567
- }
568
- return result;
569
- }
570
- function hasType(value, type) {
571
- return typeof value === "object" && value !== null && "type" in value && value.type === type;
572
- }
573
- function isSpringConfig(value) {
574
- return hasType(value, "spring");
575
- }
576
- function isEasingConfig(value) {
577
- return hasType(value, "easing");
578
- }
579
- function isActionConfig(value) {
580
- return hasType(value, "action");
581
- }
582
- function isSelectConfig(value) {
583
- return hasType(value, "select") && "options" in value && Array.isArray(value.options);
584
- }
585
- function isColorConfig(value) {
586
- return hasType(value, "color");
587
- }
588
- function isTextConfig(value) {
589
- return hasType(value, "text");
590
- }
591
- function getFirstOptionValue(options) {
592
- const first = options[0];
593
- return typeof first === "string" ? first : first.value;
594
820
  }
595
821
 
596
822
  // src/components/DialRoot.tsx
597
823
  var import_react17 = require("react");
598
824
  var import_react_dom3 = require("react-dom");
599
825
 
826
+ // src/components/Folder.tsx
827
+ var import_react2 = require("react");
828
+ var import_react3 = require("motion/react");
829
+
830
+ // src/icons.ts
831
+ var ICON_CHEVRON = "M6 9.5L12 15.5L18 9.5";
832
+ var ICON_CHECK = "M5 12.75L10 19L19 5";
833
+ var ICON_CLIPBOARD = {
834
+ board: "M8 6C8 4.34315 9.34315 3 11 3H13C14.6569 3 16 4.34315 16 6V7H8V6Z",
835
+ sparkle: "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",
836
+ body: "M16 5H17C18.6569 5 20 6.34315 20 8V11M8 5H7C5.34315 5 4 6.34315 4 8V18C4 19.6569 5.34315 21 7 21H12"
837
+ };
838
+ var ICON_ADD_PRESET = [
839
+ "M4 6H20",
840
+ "M4 12H10",
841
+ "M15 15L21 15",
842
+ "M18 12V18",
843
+ "M4 18H10"
844
+ ];
845
+ var ICON_TRASH = [
846
+ "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",
847
+ "M10 11V16",
848
+ "M14 11V16",
849
+ "M3.5 6H20.5",
850
+ "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"
851
+ ];
852
+ var ICON_PANEL = {
853
+ path: "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",
854
+ circles: [
855
+ { cx: "6", cy: "8", r: "0.998596" },
856
+ { cx: "10.4999", cy: "3.5", r: "0.998657" },
857
+ { cx: "9.75015", cy: "12.5", r: "0.997986" }
858
+ ]
859
+ };
860
+
861
+ // src/components/Folder.tsx
862
+ var import_jsx_runtime = require("react/jsx-runtime");
863
+ function Folder({ title, children, defaultOpen = true, isRoot = false, inline = false, onOpenChange, toolbar, panelHeightOffset = 10 }) {
864
+ const [isOpen, setIsOpen] = (0, import_react2.useState)(defaultOpen);
865
+ const [isCollapsed, setIsCollapsed] = (0, import_react2.useState)(!defaultOpen);
866
+ const contentRef = (0, import_react2.useRef)(null);
867
+ const [contentHeight, setContentHeight] = (0, import_react2.useState)(void 0);
868
+ const [windowHeight, setWindowHeight] = (0, import_react2.useState)(typeof window !== "undefined" ? window.innerHeight : 800);
869
+ (0, import_react2.useEffect)(() => {
870
+ if (!isRoot) return;
871
+ const onResize = () => setWindowHeight(window.innerHeight);
872
+ window.addEventListener("resize", onResize);
873
+ return () => window.removeEventListener("resize", onResize);
874
+ }, [isRoot]);
875
+ (0, import_react2.useEffect)(() => {
876
+ const el = contentRef.current;
877
+ if (!el) return;
878
+ const ro = new ResizeObserver(() => {
879
+ if (isOpen) {
880
+ const h = el.offsetHeight;
881
+ setContentHeight((prev) => prev === h ? prev : h);
882
+ }
883
+ });
884
+ ro.observe(el);
885
+ return () => ro.disconnect();
886
+ }, [isOpen]);
887
+ const handleToggle = () => {
888
+ if (inline && isRoot) return;
889
+ const next = !isOpen;
890
+ setIsOpen(next);
891
+ if (next) {
892
+ setIsCollapsed(false);
893
+ } else {
894
+ setIsCollapsed(true);
895
+ }
896
+ onOpenChange?.(next);
897
+ };
898
+ const folderContent = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
899
+ "div",
900
+ {
901
+ ref: isRoot ? contentRef : void 0,
902
+ className: `dialkit-folder ${isRoot ? "dialkit-folder-root" : ""}`,
903
+ "data-open": String(isOpen),
904
+ children: [
905
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `dialkit-folder-header ${isRoot ? "dialkit-panel-header" : ""}`, onClick: handleToggle, children: [
906
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dialkit-folder-header-top", children: [
907
+ 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 }) }),
908
+ isRoot && !inline && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
909
+ "svg",
910
+ {
911
+ className: "dialkit-panel-icon",
912
+ viewBox: "0 0 16 16",
913
+ fill: "none",
914
+ children: [
915
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { opacity: "0.5", d: ICON_PANEL.path, fill: "currentColor" }),
916
+ ICON_PANEL.circles.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: c.cx, cy: c.cy, r: c.r, fill: "currentColor", stroke: "currentColor", strokeWidth: "1.25" }, i))
917
+ ]
918
+ }
919
+ ),
920
+ !isRoot && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
921
+ import_react3.motion.svg,
922
+ {
923
+ className: "dialkit-folder-icon",
924
+ viewBox: "0 0 24 24",
925
+ fill: "none",
926
+ stroke: "currentColor",
927
+ strokeWidth: "2.5",
928
+ strokeLinecap: "round",
929
+ strokeLinejoin: "round",
930
+ initial: false,
931
+ animate: { rotate: isOpen ? 0 : 180 },
932
+ transition: { type: "spring", visualDuration: 0.35, bounce: 0.15 },
933
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: ICON_CHEVRON })
934
+ }
935
+ )
936
+ ] }),
937
+ isRoot && toolbar && isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dialkit-panel-toolbar", onClick: (e) => e.stopPropagation(), children: toolbar })
938
+ ] }),
939
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react3.AnimatePresence, { initial: false, children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
940
+ import_react3.motion.div,
941
+ {
942
+ className: "dialkit-folder-content",
943
+ initial: isRoot ? void 0 : { height: 0, opacity: 0 },
944
+ animate: isRoot ? void 0 : { height: "auto", opacity: 1 },
945
+ exit: isRoot ? void 0 : { height: 0, opacity: 0 },
946
+ transition: isRoot ? void 0 : { type: "spring", visualDuration: 0.35, bounce: 0.1 },
947
+ style: isRoot ? void 0 : { clipPath: "inset(0 -20px)" },
948
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dialkit-folder-inner", children })
949
+ }
950
+ ) })
951
+ ]
952
+ }
953
+ );
954
+ if (isRoot) {
955
+ if (inline) {
956
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dialkit-panel-inner dialkit-panel-inline", children: folderContent });
957
+ }
958
+ const panelStyle = isOpen ? { width: 280, height: contentHeight !== void 0 ? Math.min(contentHeight + panelHeightOffset, windowHeight - 32) : "auto", borderRadius: 14, boxShadow: "var(--dial-shadow)", cursor: void 0, overflowY: "auto" } : { width: 42, height: 42, borderRadius: "50%", boxSizing: "border-box", boxShadow: "var(--dial-shadow-collapsed)", overflow: "hidden", cursor: "pointer" };
959
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
960
+ import_react3.motion.div,
961
+ {
962
+ className: "dialkit-panel-inner",
963
+ style: panelStyle,
964
+ onClick: !isOpen ? handleToggle : void 0,
965
+ "data-collapsed": isCollapsed,
966
+ whileTap: !isOpen ? { scale: 0.9 } : void 0,
967
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
968
+ children: folderContent
969
+ }
970
+ );
971
+ }
972
+ return folderContent;
973
+ }
974
+
600
975
  // src/components/Panel.tsx
601
976
  var import_react15 = require("react");
602
977
  var import_react16 = require("motion/react");
603
978
 
604
979
  // src/components/ShortcutListener.tsx
605
- var import_react2 = require("react");
980
+ var import_react4 = require("react");
606
981
 
607
982
  // src/shortcut-utils.ts
608
983
  function decimalsForStep(step) {
@@ -690,15 +1065,15 @@ function formatModifier(modifier) {
690
1065
  }
691
1066
 
692
1067
  // src/components/ShortcutListener.tsx
693
- var import_jsx_runtime = require("react/jsx-runtime");
694
- var ShortcutContext = (0, import_react2.createContext)({ activePanelId: null, activePath: null });
1068
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1069
+ var ShortcutContext = (0, import_react4.createContext)({ activePanelId: null, activePath: null });
695
1070
  function ShortcutListener({ children }) {
696
- const [activeShortcut, setActiveShortcut] = (0, import_react2.useState)({ activePanelId: null, activePath: null });
697
- const activeKeysRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
698
- const isDraggingRef = (0, import_react2.useRef)(false);
699
- const lastMouseXRef = (0, import_react2.useRef)(null);
700
- const dragAccumulatorRef = (0, import_react2.useRef)(0);
701
- const resolveActiveTarget = (0, import_react2.useCallback)((interaction) => {
1071
+ const [activeShortcut, setActiveShortcut] = (0, import_react4.useState)({ activePanelId: null, activePath: null });
1072
+ const activeKeysRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
1073
+ const isDraggingRef = (0, import_react4.useRef)(false);
1074
+ const lastMouseXRef = (0, import_react4.useRef)(null);
1075
+ const dragAccumulatorRef = (0, import_react4.useRef)(0);
1076
+ const resolveActiveTarget = (0, import_react4.useCallback)((interaction) => {
702
1077
  for (const key of activeKeysRef.current) {
703
1078
  const panels = DialStore.getPanels();
704
1079
  for (const panel of panels) {
@@ -715,7 +1090,7 @@ function ShortcutListener({ children }) {
715
1090
  }
716
1091
  return null;
717
1092
  }, []);
718
- (0, import_react2.useEffect)(() => {
1093
+ (0, import_react4.useEffect)(() => {
719
1094
  const handleKeyDown = (e) => {
720
1095
  if (isInputFocused()) return;
721
1096
  const key = e.key.toLowerCase();
@@ -873,146 +1248,7 @@ function ShortcutListener({ children }) {
873
1248
  window.removeEventListener("blur", handleWindowBlur);
874
1249
  };
875
1250
  }, [resolveActiveTarget]);
876
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ShortcutContext.Provider, { value: activeShortcut, children });
877
- }
878
-
879
- // src/icons.ts
880
- var ICON_CHEVRON = "M6 9.5L12 15.5L18 9.5";
881
- var ICON_CHECK = "M5 12.75L10 19L19 5";
882
- var ICON_CLIPBOARD = {
883
- board: "M8 6C8 4.34315 9.34315 3 11 3H13C14.6569 3 16 4.34315 16 6V7H8V6Z",
884
- sparkle: "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",
885
- body: "M16 5H17C18.6569 5 20 6.34315 20 8V11M8 5H7C5.34315 5 4 6.34315 4 8V18C4 19.6569 5.34315 21 7 21H12"
886
- };
887
- var ICON_ADD_PRESET = [
888
- "M4 6H20",
889
- "M4 12H10",
890
- "M15 15L21 15",
891
- "M18 12V18",
892
- "M4 18H10"
893
- ];
894
- var ICON_TRASH = [
895
- "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",
896
- "M10 11V16",
897
- "M14 11V16",
898
- "M3.5 6H20.5",
899
- "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"
900
- ];
901
- var ICON_PANEL = {
902
- path: "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",
903
- circles: [
904
- { cx: "6", cy: "8", r: "0.998596" },
905
- { cx: "10.4999", cy: "3.5", r: "0.998657" },
906
- { cx: "9.75015", cy: "12.5", r: "0.997986" }
907
- ]
908
- };
909
-
910
- // src/components/Folder.tsx
911
- var import_react3 = require("react");
912
- var import_react4 = require("motion/react");
913
- var import_jsx_runtime2 = require("react/jsx-runtime");
914
- function Folder({ title, children, defaultOpen = true, isRoot = false, inline = false, onOpenChange, toolbar }) {
915
- const [isOpen, setIsOpen] = (0, import_react3.useState)(defaultOpen);
916
- const [isCollapsed, setIsCollapsed] = (0, import_react3.useState)(!defaultOpen);
917
- const contentRef = (0, import_react3.useRef)(null);
918
- const [contentHeight, setContentHeight] = (0, import_react3.useState)(void 0);
919
- const [windowHeight, setWindowHeight] = (0, import_react3.useState)(typeof window !== "undefined" ? window.innerHeight : 800);
920
- (0, import_react3.useEffect)(() => {
921
- if (!isRoot) return;
922
- const onResize = () => setWindowHeight(window.innerHeight);
923
- window.addEventListener("resize", onResize);
924
- return () => window.removeEventListener("resize", onResize);
925
- }, [isRoot]);
926
- (0, import_react3.useEffect)(() => {
927
- const el = contentRef.current;
928
- if (!el) return;
929
- const ro = new ResizeObserver(() => {
930
- if (isOpen) {
931
- const h = el.offsetHeight;
932
- setContentHeight((prev) => prev === h ? prev : h);
933
- }
934
- });
935
- ro.observe(el);
936
- return () => ro.disconnect();
937
- }, [isOpen]);
938
- const handleToggle = () => {
939
- if (inline && isRoot) return;
940
- const next = !isOpen;
941
- setIsOpen(next);
942
- if (next) {
943
- setIsCollapsed(false);
944
- } else {
945
- setIsCollapsed(true);
946
- }
947
- onOpenChange?.(next);
948
- };
949
- const folderContent = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ref: isRoot ? contentRef : void 0, className: `dialkit-folder ${isRoot ? "dialkit-folder-root" : ""}`, children: [
950
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `dialkit-folder-header ${isRoot ? "dialkit-panel-header" : ""}`, onClick: handleToggle, children: [
951
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dialkit-folder-header-top", children: [
952
- isRoot ? isOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dialkit-folder-title-row", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dialkit-folder-title dialkit-folder-title-root", children: title }) }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dialkit-folder-title-row", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dialkit-folder-title", children: title }) }),
953
- isRoot && !inline && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
954
- "svg",
955
- {
956
- className: "dialkit-panel-icon",
957
- viewBox: "0 0 16 16",
958
- fill: "none",
959
- children: [
960
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { opacity: "0.5", d: ICON_PANEL.path, fill: "currentColor" }),
961
- ICON_PANEL.circles.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: c.cx, cy: c.cy, r: c.r, fill: "currentColor", stroke: "currentColor", strokeWidth: "1.25" }, i))
962
- ]
963
- }
964
- ),
965
- !isRoot && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
966
- import_react4.motion.svg,
967
- {
968
- className: "dialkit-folder-icon",
969
- viewBox: "0 0 24 24",
970
- fill: "none",
971
- stroke: "currentColor",
972
- strokeWidth: "2.5",
973
- strokeLinecap: "round",
974
- strokeLinejoin: "round",
975
- initial: false,
976
- animate: { rotate: isOpen ? 0 : 180 },
977
- transition: { type: "spring", visualDuration: 0.35, bounce: 0.15 },
978
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: ICON_CHEVRON })
979
- }
980
- )
981
- ] }),
982
- isRoot && toolbar && isOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dialkit-panel-toolbar", onClick: (e) => e.stopPropagation(), children: toolbar })
983
- ] }),
984
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.AnimatePresence, { initial: false, children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
985
- import_react4.motion.div,
986
- {
987
- className: "dialkit-folder-content",
988
- initial: isRoot ? void 0 : { height: 0, opacity: 0 },
989
- animate: isRoot ? void 0 : { height: "auto", opacity: 1 },
990
- exit: isRoot ? void 0 : { height: 0, opacity: 0 },
991
- transition: isRoot ? void 0 : { type: "spring", visualDuration: 0.35, bounce: 0.1 },
992
- style: isRoot ? void 0 : { clipPath: "inset(0 -20px)" },
993
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dialkit-folder-inner", children })
994
- }
995
- ) })
996
- ] });
997
- if (isRoot) {
998
- if (inline) {
999
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dialkit-panel-inner dialkit-panel-inline", children: folderContent });
1000
- }
1001
- const panelStyle = isOpen ? { width: 280, height: contentHeight !== void 0 ? Math.min(contentHeight + 10, windowHeight - 32) : "auto", borderRadius: 14, boxShadow: "var(--dial-shadow)", cursor: void 0, overflowY: "auto" } : { width: 42, height: 42, borderRadius: "50%", boxSizing: "border-box", boxShadow: "var(--dial-shadow-collapsed)", overflow: "hidden", cursor: "pointer" };
1002
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1003
- import_react4.motion.div,
1004
- {
1005
- className: "dialkit-panel-inner",
1006
- style: panelStyle,
1007
- onClick: !isOpen ? handleToggle : void 0,
1008
- "data-collapsed": isCollapsed,
1009
- whileTap: !isOpen ? { scale: 0.9 } : void 0,
1010
- transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
1011
- children: folderContent
1012
- }
1013
- );
1014
- }
1015
- return folderContent;
1251
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ShortcutContext.Provider, { value: activeShortcut, children });
1016
1252
  }
1017
1253
 
1018
1254
  // src/components/Slider.tsx
@@ -1542,11 +1778,15 @@ function SpringVisualization({ spring, isSimpleMode }) {
1542
1778
  var import_react8 = require("react");
1543
1779
  var import_jsx_runtime7 = require("react/jsx-runtime");
1544
1780
  function SpringControl({ panelId, path, label, spring, onChange }) {
1545
- const mode = (0, import_react8.useSyncExternalStore)(
1546
- (cb) => DialStore.subscribe(panelId, cb),
1781
+ const subscribe = (0, import_react8.useCallback)(
1782
+ (callback) => DialStore.subscribe(panelId, callback),
1783
+ [panelId]
1784
+ );
1785
+ const getSnapshot = (0, import_react8.useCallback)(
1547
1786
  () => DialStore.getSpringMode(panelId, path),
1548
- () => DialStore.getSpringMode(panelId, path)
1787
+ [panelId, path]
1549
1788
  );
1789
+ const mode = (0, import_react8.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1550
1790
  const isSimpleMode = mode === "simple";
1551
1791
  const cache = (0, import_react8.useRef)({
1552
1792
  simple: spring.visualDuration !== void 0 ? spring : { type: "spring", visualDuration: 0.3, bounce: 0.2 },
@@ -1698,11 +1938,15 @@ function EasingVisualization({ easing }) {
1698
1938
  var import_react9 = require("react");
1699
1939
  var import_jsx_runtime9 = require("react/jsx-runtime");
1700
1940
  function TransitionControl({ panelId, path, label, value, onChange }) {
1701
- const mode = (0, import_react9.useSyncExternalStore)(
1702
- (cb) => DialStore.subscribe(panelId, cb),
1941
+ const subscribe = (0, import_react9.useCallback)(
1942
+ (callback) => DialStore.subscribe(panelId, callback),
1943
+ [panelId]
1944
+ );
1945
+ const getSnapshot = (0, import_react9.useCallback)(
1703
1946
  () => DialStore.getTransitionMode(panelId, path),
1704
- () => DialStore.getTransitionMode(panelId, path)
1947
+ [panelId, path]
1705
1948
  );
1949
+ const mode = (0, import_react9.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1706
1950
  const isEasing = mode === "easing";
1707
1951
  const isSimpleSpring = mode === "simple";
1708
1952
  const cache = (0, import_react9.useRef)({
@@ -1844,6 +2088,26 @@ function TextControl({ label, value, onChange, placeholder }) {
1844
2088
  var import_react10 = require("react");
1845
2089
  var import_react_dom = require("react-dom");
1846
2090
  var import_react11 = require("motion/react");
2091
+
2092
+ // src/dropdown-position.ts
2093
+ function getDropdownPosition(trigger, portalRoot, options = {}) {
2094
+ const { dropdownHeight = 0, gap = 4, allowAbove = true } = options;
2095
+ const triggerRect = trigger.getBoundingClientRect();
2096
+ const rootRect = portalRoot.getBoundingClientRect();
2097
+ const spaceBelow = window.innerHeight - triggerRect.bottom - gap;
2098
+ const above = allowAbove && spaceBelow < dropdownHeight && triggerRect.top > spaceBelow;
2099
+ return {
2100
+ top: above ? triggerRect.top - rootRect.top - dropdownHeight - gap : triggerRect.bottom - rootRect.top + gap,
2101
+ left: triggerRect.left - rootRect.left,
2102
+ width: triggerRect.width,
2103
+ above
2104
+ };
2105
+ }
2106
+ function getDialKitPortalRoot(trigger) {
2107
+ return trigger?.closest(".dialkit-root") ?? null;
2108
+ }
2109
+
2110
+ // src/components/SelectControl.tsx
1847
2111
  var import_jsx_runtime11 = require("react/jsx-runtime");
1848
2112
  function toTitleCase(s) {
1849
2113
  return s.replace(/\b\w/g, (c) => c.toUpperCase());
@@ -1863,21 +2127,12 @@ function SelectControl({ label, value, options, onChange }) {
1863
2127
  const selectedOption = normalized.find((o) => o.value === value);
1864
2128
  const updatePos = (0, import_react10.useCallback)(() => {
1865
2129
  const el = triggerRef.current;
1866
- if (!el) return;
1867
- const rect = el.getBoundingClientRect();
2130
+ if (!el || !portalTarget) return;
1868
2131
  const dropdownHeight = 8 + normalized.length * 36;
1869
- const spaceBelow = window.innerHeight - rect.bottom - 4;
1870
- const above = spaceBelow < dropdownHeight && rect.top > spaceBelow;
1871
- setPos({
1872
- top: above ? rect.top - 4 : rect.bottom + 4,
1873
- left: rect.left,
1874
- width: rect.width,
1875
- above
1876
- });
1877
- }, [normalized.length]);
2132
+ setPos(getDropdownPosition(el, portalTarget, { dropdownHeight }));
2133
+ }, [normalized.length, portalTarget]);
1878
2134
  (0, import_react10.useEffect)(() => {
1879
- const root = triggerRef.current?.closest(".dialkit-root");
1880
- setPortalTarget(root ?? document.body);
2135
+ setPortalTarget(getDialKitPortalRoot(triggerRef.current) ?? document.body);
1881
2136
  }, []);
1882
2137
  (0, import_react10.useEffect)(() => {
1883
2138
  if (!isOpen) return;
@@ -1936,10 +2191,11 @@ function SelectControl({ label, value, options, onChange }) {
1936
2191
  exit: { opacity: 0, y: pos.above ? 8 : -8, scale: 0.95 },
1937
2192
  transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
1938
2193
  style: {
1939
- position: "fixed",
2194
+ position: "absolute",
1940
2195
  left: pos.left,
2196
+ top: pos.top,
1941
2197
  width: pos.width,
1942
- ...pos.above ? { bottom: window.innerHeight - pos.top, transformOrigin: "bottom" } : { top: pos.top, transformOrigin: "top" }
2198
+ transformOrigin: pos.above ? "bottom" : "top"
1943
2199
  },
1944
2200
  children: normalized.map((option) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1945
2201
  "button",
@@ -2168,16 +2424,20 @@ function PresetManager({ panelId, presets, activePresetId, onAdd }) {
2168
2424
 
2169
2425
  // src/components/Panel.tsx
2170
2426
  var import_jsx_runtime14 = require("react/jsx-runtime");
2171
- function Panel({ panel, defaultOpen = true, inline = false }) {
2427
+ function Panel({ panel, defaultOpen = true, inline = false, onOpenChange, variant = "root" }) {
2172
2428
  const [copied, setCopied] = (0, import_react15.useState)(false);
2173
2429
  const [isPanelOpen, setIsPanelOpen] = (0, import_react15.useState)(defaultOpen);
2174
2430
  const shortcutCtx = (0, import_react15.useContext)(ShortcutContext);
2175
2431
  const hasShortcuts = Object.keys(panel.shortcuts).length > 0;
2176
- const values = (0, import_react15.useSyncExternalStore)(
2177
- (cb) => DialStore.subscribe(panel.id, cb),
2432
+ const subscribe = (0, import_react15.useCallback)(
2433
+ (callback) => DialStore.subscribe(panel.id, callback),
2434
+ [panel.id]
2435
+ );
2436
+ const getSnapshot = (0, import_react15.useCallback)(
2178
2437
  () => DialStore.getValues(panel.id),
2179
- () => DialStore.getValues(panel.id)
2438
+ [panel.id]
2180
2439
  );
2440
+ const values = (0, import_react15.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
2181
2441
  const presets = DialStore.getPresets(panel.id);
2182
2442
  const activePresetId = DialStore.getActivePresetId(panel.id);
2183
2443
  const handleAddPreset = () => {
@@ -2197,6 +2457,10 @@ Apply these values as the new defaults in the useDialKit call.`;
2197
2457
  setCopied(true);
2198
2458
  setTimeout(() => setCopied(false), 1500);
2199
2459
  };
2460
+ const handleOpenChange = (0, import_react15.useCallback)((open) => {
2461
+ setIsPanelOpen(open);
2462
+ onOpenChange?.(open);
2463
+ }, [onOpenChange]);
2200
2464
  const renderControl = (control) => {
2201
2465
  const value = values[control.path];
2202
2466
  switch (control.type) {
@@ -2370,14 +2634,84 @@ Apply these values as the new defaults in the useDialKit call.`;
2370
2634
  }
2371
2635
  )
2372
2636
  ] });
2373
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "dialkit-panel-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Folder, { title: panel.name, defaultOpen, isRoot: true, inline, onOpenChange: setIsPanelOpen, toolbar, children: renderControls() }) });
2637
+ if (variant === "section") {
2638
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Folder, { title: panel.name, defaultOpen, onOpenChange: handleOpenChange, children: [
2639
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "dialkit-panel-section-toolbar", onClick: (e) => e.stopPropagation(), children: toolbar }),
2640
+ renderControls()
2641
+ ] });
2642
+ }
2643
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "dialkit-panel-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Folder, { title: panel.name, defaultOpen, isRoot: true, inline, onOpenChange: handleOpenChange, toolbar, children: renderControls() }) });
2644
+ }
2645
+
2646
+ // src/panel-drag.ts
2647
+ var PANEL_DRAG_THRESHOLD = 8;
2648
+ var COLLAPSED_PANEL_SIZE = 42;
2649
+ var DRAG_EXCLUSION_SELECTOR = [
2650
+ ".dialkit-panel-icon",
2651
+ ".dialkit-panel-toolbar",
2652
+ "button",
2653
+ "input",
2654
+ "select",
2655
+ "textarea",
2656
+ "a",
2657
+ '[role="button"]',
2658
+ '[contenteditable="true"]'
2659
+ ].join(",");
2660
+ function getPanelDragHandle(target, panel) {
2661
+ if (!(target instanceof Element) || !panel) return null;
2662
+ const inner = target.closest(".dialkit-panel-inner");
2663
+ if (!inner || !panel.contains(inner)) return null;
2664
+ if (inner.getAttribute("data-collapsed") === "true") {
2665
+ return inner;
2666
+ }
2667
+ const header = target.closest(".dialkit-panel-header");
2668
+ if (!header || !inner.contains(header)) return null;
2669
+ if (target.closest(DRAG_EXCLUSION_SELECTOR)) return null;
2670
+ return header;
2671
+ }
2672
+ function getPanelDragStart(pointerX, pointerY, panel) {
2673
+ const rect = panel.getBoundingClientRect();
2674
+ return {
2675
+ pointerX,
2676
+ pointerY,
2677
+ elX: rect.left,
2678
+ elY: rect.top
2679
+ };
2680
+ }
2681
+ function getPanelDragOffset(start, pointerX, pointerY) {
2682
+ return {
2683
+ x: start.elX + pointerX - start.pointerX,
2684
+ y: start.elY + pointerY - start.pointerY
2685
+ };
2686
+ }
2687
+ function hasPanelDragMoved(start, pointerX, pointerY) {
2688
+ const dx = pointerX - start.pointerX;
2689
+ const dy = pointerY - start.pointerY;
2690
+ return Math.hypot(dx, dy) >= PANEL_DRAG_THRESHOLD;
2691
+ }
2692
+ function getPanelOriginX(position, offset, viewportWidth = typeof window !== "undefined" ? window.innerWidth : void 0) {
2693
+ if (offset && viewportWidth) {
2694
+ return offset.x + COLLAPSED_PANEL_SIZE / 2 < viewportWidth / 2 ? "left" : "right";
2695
+ }
2696
+ return position.endsWith("left") ? "left" : "right";
2697
+ }
2698
+ function blockPanelDragClick(handle) {
2699
+ const blocker = (event) => {
2700
+ event.preventDefault();
2701
+ event.stopImmediatePropagation();
2702
+ event.stopPropagation();
2703
+ };
2704
+ handle.addEventListener("click", blocker, { capture: true, once: true });
2705
+ window.setTimeout(() => {
2706
+ handle.removeEventListener("click", blocker, true);
2707
+ }, 0);
2374
2708
  }
2375
2709
 
2376
2710
  // src/components/DialRoot.tsx
2377
2711
  var import_jsx_runtime15 = require("react/jsx-runtime");
2378
2712
  var import_meta = {};
2379
2713
  var isDevDefault = typeof process !== "undefined" && process?.env?.NODE_ENV ? process.env.NODE_ENV !== "production" : typeof import_meta !== "undefined" && import_meta.env?.MODE ? import_meta.env.MODE !== "production" : true;
2380
- function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover", theme = "system", productionEnabled = isDevDefault }) {
2714
+ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover", theme = "system", productionEnabled = isDevDefault, onOpenChange }) {
2381
2715
  if (!productionEnabled) return null;
2382
2716
  const [panels, setPanels] = (0, import_react17.useState)([]);
2383
2717
  const [mounted, setMounted] = (0, import_react17.useState)(false);
@@ -2389,6 +2723,9 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2389
2723
  const draggingRef = (0, import_react17.useRef)(false);
2390
2724
  const dragStartRef = (0, import_react17.useRef)(null);
2391
2725
  const didDragRef = (0, import_react17.useRef)(false);
2726
+ const dragTargetRef = (0, import_react17.useRef)(null);
2727
+ const panelOpenStatesRef = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
2728
+ const rootOpenRef = (0, import_react17.useRef)(null);
2392
2729
  (0, import_react17.useEffect)(() => {
2393
2730
  setMounted(true);
2394
2731
  setPanels(DialStore.getPanels());
@@ -2397,22 +2734,36 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2397
2734
  });
2398
2735
  return unsubscribe;
2399
2736
  }, []);
2737
+ (0, import_react17.useEffect)(() => {
2738
+ const fallbackOpen = inline || defaultOpen;
2739
+ const nextStates = /* @__PURE__ */ new Map();
2740
+ for (const panel of panels) {
2741
+ nextStates.set(panel.id, panelOpenStatesRef.current.get(panel.id) ?? fallbackOpen);
2742
+ }
2743
+ panelOpenStatesRef.current = nextStates;
2744
+ rootOpenRef.current = Array.from(nextStates.values()).some(Boolean);
2745
+ }, [defaultOpen, inline, panels]);
2400
2746
  (0, import_react17.useEffect)(() => {
2401
2747
  if (!panelRef.current || inline) return;
2402
2748
  const observer = new MutationObserver(() => {
2403
- const inner = panelRef.current?.querySelector(".dialkit-panel-inner");
2404
- if (!inner) return;
2405
- const collapsed = inner.getAttribute("data-collapsed") === "true";
2749
+ const inners = panelRef.current?.querySelectorAll(".dialkit-panel-inner");
2750
+ if (!inners || inners.length === 0) return;
2751
+ const collapsed = Array.from(inners).every(
2752
+ (el) => el.getAttribute("data-collapsed") === "true"
2753
+ );
2754
+ const currentDragOffset = dragOffset;
2406
2755
  if (!collapsed) {
2407
- if (dragOffset) {
2408
- lastDragOffset.current = dragOffset;
2409
- const bubbleCenterX = dragOffset.x + 21;
2756
+ if (currentDragOffset) {
2757
+ lastDragOffset.current = currentDragOffset;
2758
+ const bubbleCenterX = currentDragOffset.x + 21;
2410
2759
  const midX = window.innerWidth / 2;
2411
2760
  setActivePosition(bubbleCenterX < midX ? "top-left" : "top-right");
2412
2761
  } else {
2413
2762
  setActivePosition(position);
2414
2763
  }
2415
2764
  setDragOffset(null);
2765
+ } else if (currentDragOffset) {
2766
+ lastDragOffset.current = currentDragOffset;
2416
2767
  } else if (lastDragOffset.current) {
2417
2768
  setDragOffset(lastDragOffset.current);
2418
2769
  }
@@ -2421,45 +2772,50 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2421
2772
  return () => observer.disconnect();
2422
2773
  }, [inline, dragOffset, position]);
2423
2774
  const handlePointerDown = (0, import_react17.useCallback)((e) => {
2424
- const inner = panelRef.current?.querySelector(".dialkit-panel-inner");
2425
- if (!inner || inner.getAttribute("data-collapsed") !== "true") return;
2426
- const rect = panelRef.current.getBoundingClientRect();
2427
- dragStartRef.current = {
2428
- pointerX: e.clientX,
2429
- pointerY: e.clientY,
2430
- elX: rect.left,
2431
- elY: rect.top
2432
- };
2775
+ const panel = panelRef.current;
2776
+ const handle = getPanelDragHandle(e.target, panel);
2777
+ if (!panel || !handle) return;
2778
+ dragTargetRef.current = handle;
2779
+ dragStartRef.current = getPanelDragStart(e.clientX, e.clientY, panel);
2433
2780
  didDragRef.current = false;
2434
2781
  draggingRef.current = true;
2435
- e.target.setPointerCapture(e.pointerId);
2782
+ handle.setPointerCapture(e.pointerId);
2436
2783
  }, []);
2437
2784
  const handlePointerMove = (0, import_react17.useCallback)((e) => {
2438
2785
  if (!draggingRef.current || !dragStartRef.current) return;
2439
- const dx = e.clientX - dragStartRef.current.pointerX;
2440
- const dy = e.clientY - dragStartRef.current.pointerY;
2441
- if (!didDragRef.current && Math.abs(dx) + Math.abs(dy) < 4) return;
2786
+ if (!didDragRef.current && !hasPanelDragMoved(dragStartRef.current, e.clientX, e.clientY)) return;
2442
2787
  didDragRef.current = true;
2443
- setDragOffset({
2444
- x: dragStartRef.current.elX + dx,
2445
- y: dragStartRef.current.elY + dy
2446
- });
2788
+ setDragOffset(getPanelDragOffset(dragStartRef.current, e.clientX, e.clientY));
2447
2789
  }, []);
2448
2790
  const handlePointerUp = (0, import_react17.useCallback)((e) => {
2449
2791
  if (!draggingRef.current) return;
2450
2792
  draggingRef.current = false;
2451
2793
  dragStartRef.current = null;
2794
+ const dragTarget = dragTargetRef.current;
2795
+ if (dragTarget?.hasPointerCapture(e.pointerId)) {
2796
+ dragTarget.releasePointerCapture(e.pointerId);
2797
+ }
2452
2798
  if (didDragRef.current) {
2453
2799
  e.stopPropagation();
2454
- const inner = panelRef.current?.querySelector(".dialkit-panel-inner");
2455
- if (inner) {
2456
- const blocker = (ev) => {
2457
- ev.stopPropagation();
2458
- };
2459
- inner.addEventListener("click", blocker, { capture: true, once: true });
2800
+ if (dragTarget) {
2801
+ blockPanelDragClick(dragTarget);
2460
2802
  }
2461
2803
  }
2804
+ dragTargetRef.current = null;
2462
2805
  }, []);
2806
+ const handlePanelOpenChange = (0, import_react17.useCallback)((panelId, open) => {
2807
+ panelOpenStatesRef.current.set(panelId, open);
2808
+ const fallbackOpen = inline || defaultOpen;
2809
+ const nextRootOpen = panels.some((panel) => panelOpenStatesRef.current.get(panel.id) ?? fallbackOpen);
2810
+ if (rootOpenRef.current === nextRootOpen) return;
2811
+ rootOpenRef.current = nextRootOpen;
2812
+ onOpenChange?.(nextRootOpen);
2813
+ }, [defaultOpen, inline, onOpenChange, panels]);
2814
+ const handleRootOpenChange = (0, import_react17.useCallback)((open) => {
2815
+ if (rootOpenRef.current === open) return;
2816
+ rootOpenRef.current = open;
2817
+ onOpenChange?.(open);
2818
+ }, [onOpenChange]);
2463
2819
  if (!mounted || typeof window === "undefined") {
2464
2820
  return null;
2465
2821
  }
@@ -2472,18 +2828,51 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2472
2828
  right: "auto",
2473
2829
  bottom: "auto"
2474
2830
  } : void 0;
2831
+ const originX = getPanelOriginX(activePosition, dragOffset);
2832
+ const hasMultiplePanels = panels.length > 1;
2475
2833
  const content = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ShortcutListener, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dialkit-root", "data-mode": mode, "data-theme": theme, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2476
2834
  "div",
2477
2835
  {
2478
2836
  ref: panelRef,
2479
2837
  className: "dialkit-panel",
2480
2838
  "data-position": inline ? void 0 : dragOffset ? void 0 : activePosition,
2839
+ "data-origin-x": inline ? void 0 : originX,
2481
2840
  "data-mode": mode,
2841
+ "data-multiple": hasMultiplePanels ? "true" : void 0,
2482
2842
  style: dragStyle,
2483
2843
  onPointerDown: !inline ? handlePointerDown : void 0,
2484
2844
  onPointerMove: !inline ? handlePointerMove : void 0,
2485
2845
  onPointerUp: !inline ? handlePointerUp : void 0,
2486
- children: panels.map((panel) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Panel, { panel, defaultOpen: inline || defaultOpen, inline }, panel.id))
2846
+ onPointerCancel: !inline ? handlePointerUp : void 0,
2847
+ children: hasMultiplePanels ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dialkit-panel-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2848
+ Folder,
2849
+ {
2850
+ title: "DialKit",
2851
+ defaultOpen: inline || defaultOpen,
2852
+ isRoot: true,
2853
+ inline,
2854
+ onOpenChange: handleRootOpenChange,
2855
+ panelHeightOffset: 2,
2856
+ children: panels.map((panel) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2857
+ Panel,
2858
+ {
2859
+ panel,
2860
+ defaultOpen: true,
2861
+ variant: "section"
2862
+ },
2863
+ panel.id
2864
+ ))
2865
+ }
2866
+ ) }) : panels.map((panel) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2867
+ Panel,
2868
+ {
2869
+ panel,
2870
+ defaultOpen: inline || defaultOpen,
2871
+ inline,
2872
+ onOpenChange: (open) => handlePanelOpenChange(panel.id, open)
2873
+ },
2874
+ panel.id
2875
+ ))
2487
2876
  }
2488
2877
  ) }) });
2489
2878
  if (inline) {
@@ -2640,6 +3029,7 @@ function ShortcutsMenu({ panelId }) {
2640
3029
  TextControl,
2641
3030
  Toggle,
2642
3031
  TransitionControl,
2643
- useDialKit
3032
+ useDialKit,
3033
+ useDialKitController
2644
3034
  });
2645
3035
  //# sourceMappingURL=index.cjs.map