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
@@ -38,6 +38,7 @@ __export(vue_exports, {
38
38
  Toggle: () => Toggle,
39
39
  TransitionControl: () => TransitionControl,
40
40
  useDialKit: () => useDialKit,
41
+ useDialKitController: () => useDialKitController,
41
42
  useShortcutContext: () => useShortcutContext,
42
43
  vDialKit: () => vDialKit
43
44
  });
@@ -48,6 +49,89 @@ var import_vue = require("vue");
48
49
 
49
50
  // src/store/DialStore.ts
50
51
  var EMPTY_VALUES = Object.freeze({});
52
+ function resolveDialValues(config, flatValues) {
53
+ return resolveConfigValues(config, flatValues, "");
54
+ }
55
+ function flattenDialValueUpdates(config, updates) {
56
+ const values = {};
57
+ if (typeof updates === "object" && updates !== null) {
58
+ flattenConfigUpdates(config, updates, "", values);
59
+ }
60
+ return values;
61
+ }
62
+ function resolveConfigValues(config, flatValues, prefix) {
63
+ const result = {};
64
+ for (const [key, configValue] of Object.entries(config)) {
65
+ if (key === "_collapsed") continue;
66
+ const path = prefix ? `${prefix}.${key}` : key;
67
+ if (Array.isArray(configValue) && configValue.length <= 4 && typeof configValue[0] === "number") {
68
+ result[key] = flatValues[path] ?? configValue[0];
69
+ } else if (typeof configValue === "number" || typeof configValue === "boolean" || typeof configValue === "string") {
70
+ result[key] = flatValues[path] ?? configValue;
71
+ } else if (isSpringConfigValue(configValue) || isEasingConfigValue(configValue)) {
72
+ result[key] = flatValues[path] ?? configValue;
73
+ } else if (isActionConfigValue(configValue)) {
74
+ result[key] = flatValues[path] ?? configValue;
75
+ } else if (isSelectConfigValue(configValue)) {
76
+ const defaultValue = configValue.default ?? getFirstOptionValue(configValue.options);
77
+ result[key] = flatValues[path] ?? defaultValue;
78
+ } else if (isColorConfigValue(configValue)) {
79
+ result[key] = flatValues[path] ?? configValue.default ?? "#000000";
80
+ } else if (isTextConfigValue(configValue)) {
81
+ result[key] = flatValues[path] ?? configValue.default ?? "";
82
+ } else if (typeof configValue === "object" && configValue !== null) {
83
+ result[key] = resolveConfigValues(configValue, flatValues, path);
84
+ }
85
+ }
86
+ return result;
87
+ }
88
+ function flattenConfigUpdates(config, updates, prefix, values) {
89
+ for (const [key, configValue] of Object.entries(config)) {
90
+ if (key === "_collapsed" || !(key in updates)) continue;
91
+ const nextValue = updates[key];
92
+ if (nextValue === void 0) continue;
93
+ const path = prefix ? `${prefix}.${key}` : key;
94
+ if (isActionConfigValue(configValue)) {
95
+ continue;
96
+ }
97
+ if (isLeafConfigValue(configValue)) {
98
+ values[path] = nextValue;
99
+ continue;
100
+ }
101
+ if (typeof configValue === "object" && configValue !== null && typeof nextValue === "object" && nextValue !== null && !Array.isArray(nextValue)) {
102
+ flattenConfigUpdates(configValue, nextValue, path, values);
103
+ }
104
+ }
105
+ }
106
+ function isLeafConfigValue(value) {
107
+ 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);
108
+ }
109
+ function hasType(value, type) {
110
+ return typeof value === "object" && value !== null && "type" in value && value.type === type;
111
+ }
112
+ function isSpringConfigValue(value) {
113
+ return hasType(value, "spring");
114
+ }
115
+ function isEasingConfigValue(value) {
116
+ return hasType(value, "easing");
117
+ }
118
+ function isActionConfigValue(value) {
119
+ return hasType(value, "action");
120
+ }
121
+ function isSelectConfigValue(value) {
122
+ return hasType(value, "select") && "options" in value && Array.isArray(value.options);
123
+ }
124
+ function isColorConfigValue(value) {
125
+ return hasType(value, "color");
126
+ }
127
+ function isTextConfigValue(value) {
128
+ return hasType(value, "text");
129
+ }
130
+ function getFirstOptionValue(options) {
131
+ const first = options[0];
132
+ if (first === void 0) return "";
133
+ return typeof first === "string" ? first : first.value;
134
+ }
51
135
  var DialStoreClass = class {
52
136
  constructor() {
53
137
  this.panels = /* @__PURE__ */ new Map();
@@ -58,87 +142,138 @@ var DialStoreClass = class {
58
142
  this.presets = /* @__PURE__ */ new Map();
59
143
  this.activePreset = /* @__PURE__ */ new Map();
60
144
  this.baseValues = /* @__PURE__ */ new Map();
61
- }
62
- registerPanel(id, name, config, shortcuts) {
145
+ this.defaultValues = /* @__PURE__ */ new Map();
146
+ this.registrationCounts = /* @__PURE__ */ new Map();
147
+ this.retainedPanels = /* @__PURE__ */ new Set();
148
+ this.persistConfigs = /* @__PURE__ */ new Map();
149
+ }
150
+ registerPanel(id, name, config, shortcuts, options = {}) {
151
+ this.configurePanelRetention(id, options);
152
+ this.registrationCounts.set(id, (this.registrationCounts.get(id) ?? 0) + 1);
63
153
  const controls = this.parseConfig(config, "", shortcuts);
64
- const values = this.flattenValues(config, "");
65
- this.initTransitionModes(config, "", values);
154
+ const controlsByPath = this.mapControlsByPath(controls);
155
+ const defaultValues = this.flattenValues(config, "");
156
+ this.initTransitionModes(config, "", defaultValues);
157
+ const persisted = this.loadPersistedPanel(id);
158
+ const previousValues = this.panels.get(id)?.values ?? this.snapshots.get(id) ?? persisted?.values ?? {};
159
+ const values = this.reconcileValues(defaultValues, previousValues, controlsByPath);
160
+ const previousBaseValues = this.baseValues.get(id) ?? persisted?.baseValues ?? persisted?.values ?? {};
161
+ const baseValues = this.reconcileValues(defaultValues, previousBaseValues, controlsByPath);
66
162
  this.panels.set(id, { id, name, controls, values, shortcuts: shortcuts ?? {} });
67
163
  this.snapshots.set(id, { ...values });
68
- this.baseValues.set(id, { ...values });
164
+ this.baseValues.set(id, baseValues);
165
+ this.defaultValues.set(id, { ...defaultValues });
166
+ const existingPresets = this.presets.get(id) ?? persisted?.presets;
167
+ if (existingPresets) {
168
+ this.presets.set(id, this.reconcilePresets(existingPresets, defaultValues, controlsByPath));
169
+ }
170
+ if (!this.activePreset.has(id) && persisted?.activePresetId !== void 0) {
171
+ this.activePreset.set(id, persisted.activePresetId);
172
+ }
173
+ this.persistPanel(id);
174
+ this.notify(id);
69
175
  this.notifyGlobal();
70
176
  }
71
- updatePanel(id, name, config, shortcuts) {
177
+ updatePanel(id, name, config, shortcuts, options = {}) {
178
+ this.configurePanelRetention(id, options);
72
179
  const existing = this.panels.get(id);
73
180
  if (!existing) {
74
- this.registerPanel(id, name, config, shortcuts);
181
+ this.registerPanel(id, name, config, shortcuts, options);
75
182
  return;
76
183
  }
77
184
  const controls = this.parseConfig(config, "", shortcuts);
78
185
  const controlsByPath = this.mapControlsByPath(controls);
79
186
  const defaultValues = this.flattenValues(config, "");
80
- const nextValues = {};
81
- for (const [path, defaultValue] of Object.entries(defaultValues)) {
82
- nextValues[path] = this.normalizePreservedValue(
83
- existing.values[path],
84
- defaultValue,
85
- controlsByPath.get(path)
86
- );
87
- }
88
- this.initTransitionModes(config, "", nextValues);
89
- for (const [path, mode] of Object.entries(existing.values)) {
90
- if (!path.endsWith(".__mode")) {
91
- continue;
92
- }
93
- const transitionPath = path.slice(0, -"__mode".length - 1);
94
- const transitionControl = controlsByPath.get(transitionPath);
95
- if (transitionControl?.type === "transition") {
96
- nextValues[path] = mode;
97
- }
98
- }
187
+ this.initTransitionModes(config, "", defaultValues);
188
+ const nextValues = this.reconcileValues(defaultValues, existing.values, controlsByPath);
99
189
  const nextPanel = { id, name, controls, values: nextValues, shortcuts: shortcuts ?? existing.shortcuts };
100
190
  this.panels.set(id, nextPanel);
101
191
  this.snapshots.set(id, { ...nextValues });
102
192
  const previousBaseValues = this.baseValues.get(id) ?? {};
103
- const nextBaseValues = {};
104
- for (const [path, defaultValue] of Object.entries(defaultValues)) {
105
- nextBaseValues[path] = this.normalizePreservedValue(
106
- previousBaseValues[path],
107
- defaultValue,
108
- controlsByPath.get(path)
109
- );
110
- }
193
+ const nextBaseValues = this.reconcileValues(defaultValues, previousBaseValues, controlsByPath);
111
194
  for (const [path, value] of Object.entries(nextValues)) {
112
195
  if (path.endsWith(".__mode")) {
113
196
  nextBaseValues[path] = value;
114
197
  }
115
198
  }
116
199
  this.baseValues.set(id, nextBaseValues);
200
+ this.defaultValues.set(id, { ...defaultValues });
201
+ this.presets.set(id, this.reconcilePresets(this.presets.get(id) ?? [], defaultValues, controlsByPath));
202
+ this.persistPanel(id);
117
203
  this.notify(id);
118
204
  this.notifyGlobal();
119
205
  }
120
206
  unregisterPanel(id) {
207
+ const nextCount = (this.registrationCounts.get(id) ?? 1) - 1;
208
+ if (nextCount > 0) {
209
+ this.registrationCounts.set(id, nextCount);
210
+ return;
211
+ }
212
+ this.registrationCounts.delete(id);
121
213
  this.panels.delete(id);
122
214
  this.listeners.delete(id);
123
- this.snapshots.delete(id);
124
215
  this.actionListeners.delete(id);
125
- this.baseValues.delete(id);
216
+ if (!this.retainedPanels.has(id)) {
217
+ this.snapshots.delete(id);
218
+ this.baseValues.delete(id);
219
+ this.defaultValues.delete(id);
220
+ this.presets.delete(id);
221
+ this.activePreset.delete(id);
222
+ this.persistConfigs.delete(id);
223
+ }
126
224
  this.notifyGlobal();
127
225
  }
128
226
  updateValue(panelId, path, value) {
227
+ this.updateValues(panelId, { [path]: value });
228
+ }
229
+ updateValues(panelId, updates) {
129
230
  const panel = this.panels.get(panelId);
130
231
  if (!panel) return;
131
- panel.values[path] = value;
232
+ const validUpdates = {};
233
+ for (const [path, value] of Object.entries(updates)) {
234
+ if (!Object.prototype.hasOwnProperty.call(panel.values, path)) {
235
+ continue;
236
+ }
237
+ const control = this.findControlByPath(panel.controls, path);
238
+ if (control?.type === "action") {
239
+ continue;
240
+ }
241
+ panel.values[path] = value;
242
+ validUpdates[path] = value;
243
+ }
244
+ if (Object.keys(validUpdates).length === 0) {
245
+ return;
246
+ }
132
247
  const activeId = this.activePreset.get(panelId);
133
248
  if (activeId) {
134
249
  const presets = this.presets.get(panelId) ?? [];
135
250
  const preset = presets.find((p) => p.id === activeId);
136
- if (preset) preset.values[path] = value;
251
+ if (preset) {
252
+ for (const [path, value] of Object.entries(validUpdates)) {
253
+ preset.values[path] = value;
254
+ }
255
+ }
137
256
  } else {
138
257
  const base = this.baseValues.get(panelId);
139
- if (base) base[path] = value;
258
+ if (base) {
259
+ for (const [path, value] of Object.entries(validUpdates)) {
260
+ base[path] = value;
261
+ }
262
+ }
140
263
  }
141
264
  this.snapshots.set(panelId, { ...panel.values });
265
+ this.persistPanel(panelId);
266
+ this.notify(panelId);
267
+ }
268
+ resetValues(panelId) {
269
+ const panel = this.panels.get(panelId);
270
+ const defaults = this.defaultValues.get(panelId);
271
+ if (!panel || !defaults) return;
272
+ panel.values = { ...defaults };
273
+ this.snapshots.set(panelId, { ...panel.values });
274
+ this.baseValues.set(panelId, { ...defaults });
275
+ this.activePreset.set(panelId, null);
276
+ this.persistPanel(panelId);
142
277
  this.notify(panelId);
143
278
  }
144
279
  updateSpringMode(panelId, path, mode) {
@@ -154,6 +289,7 @@ var DialStoreClass = class {
154
289
  if (!panel) return;
155
290
  panel.values[`${path}.__mode`] = mode;
156
291
  this.snapshots.set(panelId, { ...panel.values });
292
+ this.persistPanel(panelId);
157
293
  this.notify(panelId);
158
294
  }
159
295
  getTransitionMode(panelId, path) {
@@ -212,6 +348,7 @@ var DialStoreClass = class {
212
348
  this.presets.set(panelId, [...existing, preset]);
213
349
  this.activePreset.set(panelId, id);
214
350
  this.snapshots.set(panelId, { ...panel.values });
351
+ this.persistPanel(panelId);
215
352
  this.notify(panelId);
216
353
  return id;
217
354
  }
@@ -224,6 +361,7 @@ var DialStoreClass = class {
224
361
  panel.values = { ...preset.values };
225
362
  this.snapshots.set(panelId, { ...panel.values });
226
363
  this.activePreset.set(panelId, presetId);
364
+ this.persistPanel(panelId);
227
365
  this.notify(panelId);
228
366
  }
229
367
  deletePreset(panelId, presetId) {
@@ -236,6 +374,7 @@ var DialStoreClass = class {
236
374
  if (panel) {
237
375
  this.snapshots.set(panelId, { ...panel.values });
238
376
  }
377
+ this.persistPanel(panelId);
239
378
  this.notify(panelId);
240
379
  }
241
380
  getPresets(panelId) {
@@ -252,6 +391,7 @@ var DialStoreClass = class {
252
391
  this.snapshots.set(panelId, { ...panel.values });
253
392
  }
254
393
  this.activePreset.set(panelId, null);
394
+ this.persistPanel(panelId);
255
395
  this.notify(panelId);
256
396
  }
257
397
  resolveShortcutTarget(key, modifier) {
@@ -282,6 +422,94 @@ var DialStoreClass = class {
282
422
  }
283
423
  return results;
284
424
  }
425
+ configurePanelRetention(id, options) {
426
+ if (options.retainOnUnmount) {
427
+ this.retainedPanels.add(id);
428
+ }
429
+ const persistConfig = this.normalizePersistConfig(id, options.persist);
430
+ if (persistConfig) {
431
+ this.persistConfigs.set(id, persistConfig);
432
+ this.retainedPanels.add(id);
433
+ }
434
+ }
435
+ reconcileValues(defaultValues, previousValues, controlsByPath) {
436
+ const nextValues = {};
437
+ for (const [path, defaultValue] of Object.entries(defaultValues)) {
438
+ if (path.endsWith(".__mode")) {
439
+ const transitionPath = path.slice(0, -".__mode".length);
440
+ const transitionControl = controlsByPath.get(transitionPath);
441
+ nextValues[path] = transitionControl?.type === "transition" && previousValues[path] !== void 0 ? previousValues[path] : defaultValue;
442
+ continue;
443
+ }
444
+ nextValues[path] = this.normalizePreservedValue(
445
+ previousValues[path],
446
+ defaultValue,
447
+ controlsByPath.get(path)
448
+ );
449
+ }
450
+ return nextValues;
451
+ }
452
+ reconcilePresets(presets, defaultValues, controlsByPath) {
453
+ return presets.map((preset) => ({
454
+ ...preset,
455
+ values: this.reconcileValues(defaultValues, preset.values, controlsByPath)
456
+ }));
457
+ }
458
+ normalizePersistConfig(id, persist) {
459
+ if (!persist) return null;
460
+ const options = typeof persist === "object" ? persist : {};
461
+ return {
462
+ key: options.key ?? `dialkit:${id}`,
463
+ storage: options.storage ?? "localStorage",
464
+ presets: options.presets ?? true
465
+ };
466
+ }
467
+ loadPersistedPanel(id) {
468
+ const config = this.persistConfigs.get(id);
469
+ if (!config) return null;
470
+ const storage = this.getStorage(config.storage);
471
+ if (!storage) return null;
472
+ try {
473
+ const raw = storage.getItem(config.key);
474
+ if (!raw) return null;
475
+ const parsed = JSON.parse(raw);
476
+ if (parsed?.version !== 1 || typeof parsed !== "object") return null;
477
+ return parsed;
478
+ } catch {
479
+ return null;
480
+ }
481
+ }
482
+ persistPanel(id) {
483
+ const config = this.persistConfigs.get(id);
484
+ if (!config) return;
485
+ const storage = this.getStorage(config.storage);
486
+ if (!storage) return;
487
+ const values = this.snapshots.get(id) ?? this.panels.get(id)?.values;
488
+ if (!values) return;
489
+ const state = {
490
+ version: 1,
491
+ values,
492
+ baseValues: this.baseValues.get(id) ?? values,
493
+ activePresetId: this.activePreset.get(id) ?? null
494
+ };
495
+ if (config.presets) {
496
+ state.presets = this.presets.get(id) ?? [];
497
+ }
498
+ try {
499
+ storage.setItem(config.key, JSON.stringify(state));
500
+ } catch {
501
+ }
502
+ }
503
+ getStorage(kind) {
504
+ if (typeof globalThis === "undefined" || !("window" in globalThis)) {
505
+ return null;
506
+ }
507
+ try {
508
+ return kind === "sessionStorage" ? globalThis.window?.sessionStorage ?? null : globalThis.window?.localStorage ?? null;
509
+ } catch {
510
+ return null;
511
+ }
512
+ }
285
513
  findControlByPath(controls, path) {
286
514
  for (const control of controls) {
287
515
  if (control.path === path) return control;
@@ -511,21 +739,30 @@ var DialStore = new DialStoreClass();
511
739
  // src/vue/useDialKit.ts
512
740
  var dialKitInstance = 0;
513
741
  function useDialKit(name, config, options) {
514
- const panelId = `${name}-${++dialKitInstance}`;
742
+ return useDialKitController(name, config, options).values;
743
+ }
744
+ function useDialKitController(name, config, options) {
745
+ const hasStableId = options?.id !== void 0;
746
+ const panelId = options?.id ?? `${name}-${++dialKitInstance}`;
515
747
  const configRef = (0, import_vue.shallowRef)(config);
516
748
  const onActionRef = (0, import_vue.ref)(options?.onAction);
517
749
  const shortcutsRef = (0, import_vue.shallowRef)(options?.shortcuts);
518
- const values = (0, import_vue.ref)(DialStore.getValues(panelId));
750
+ const persistRef = (0, import_vue.shallowRef)(options?.persist);
751
+ const flatValues = (0, import_vue.ref)(DialStore.getValues(panelId));
519
752
  const mounted = (0, import_vue.ref)(false);
520
753
  const serializedConfig = (0, import_vue.computed)(() => JSON.stringify(config));
521
754
  const serializedShortcuts = (0, import_vue.computed)(() => JSON.stringify(options?.shortcuts));
755
+ const serializedPersist = (0, import_vue.computed)(() => JSON.stringify(options?.persist));
522
756
  let unsubscribeValues;
523
757
  let unsubscribeActions;
524
758
  const register = () => {
525
- DialStore.registerPanel(panelId, name, configRef.value, shortcutsRef.value);
526
- values.value = DialStore.getValues(panelId);
759
+ DialStore.registerPanel(panelId, name, configRef.value, shortcutsRef.value, {
760
+ retainOnUnmount: hasStableId,
761
+ persist: persistRef.value
762
+ });
763
+ flatValues.value = DialStore.getValues(panelId);
527
764
  unsubscribeValues = DialStore.subscribe(panelId, () => {
528
- values.value = DialStore.getValues(panelId);
765
+ flatValues.value = DialStore.getValues(panelId);
529
766
  });
530
767
  unsubscribeActions = DialStore.subscribeActions(panelId, (action) => {
531
768
  onActionRef.value?.(action);
@@ -537,12 +774,19 @@ function useDialKit(name, config, options) {
537
774
  (0, import_vue.watch)(() => options?.shortcuts, (next) => {
538
775
  shortcutsRef.value = next;
539
776
  });
540
- (0, import_vue.watch)([serializedConfig, serializedShortcuts], () => {
777
+ (0, import_vue.watch)(() => options?.persist, (next) => {
778
+ persistRef.value = next;
779
+ });
780
+ (0, import_vue.watch)([serializedConfig, serializedShortcuts, serializedPersist], () => {
541
781
  configRef.value = config;
542
782
  shortcutsRef.value = options?.shortcuts;
783
+ persistRef.value = options?.persist;
543
784
  if (mounted.value) {
544
- DialStore.updatePanel(panelId, name, configRef.value, shortcutsRef.value);
545
- values.value = DialStore.getValues(panelId);
785
+ DialStore.updatePanel(panelId, name, configRef.value, shortcutsRef.value, {
786
+ retainOnUnmount: hasStableId,
787
+ persist: persistRef.value
788
+ });
789
+ flatValues.value = DialStore.getValues(panelId);
546
790
  }
547
791
  });
548
792
  (0, import_vue.onMounted)(register);
@@ -554,58 +798,22 @@ function useDialKit(name, config, options) {
554
798
  unsubscribeActions?.();
555
799
  DialStore.unregisterPanel(panelId);
556
800
  });
557
- return (0, import_vue.computed)(() => buildResolvedValues(configRef.value, values.value, ""));
558
- }
559
- function buildResolvedValues(config, flatValues, prefix) {
560
- const result = {};
561
- for (const [key, configValue] of Object.entries(config)) {
562
- if (key === "_collapsed") continue;
563
- const path = prefix ? `${prefix}.${key}` : key;
564
- if (Array.isArray(configValue) && configValue.length <= 4 && typeof configValue[0] === "number") {
565
- result[key] = flatValues[path] ?? configValue[0];
566
- } else if (typeof configValue === "number" || typeof configValue === "boolean" || typeof configValue === "string") {
567
- result[key] = flatValues[path] ?? configValue;
568
- } else if (isSpringConfig(configValue) || isEasingConfig(configValue)) {
569
- result[key] = flatValues[path] ?? configValue;
570
- } else if (isActionConfig(configValue)) {
571
- result[key] = flatValues[path] ?? configValue;
572
- } else if (isSelectConfig(configValue)) {
573
- const defaultValue = configValue.default ?? getFirstOptionValue(configValue.options);
574
- result[key] = flatValues[path] ?? defaultValue;
575
- } else if (isColorConfig(configValue)) {
576
- result[key] = flatValues[path] ?? configValue.default ?? "#000000";
577
- } else if (isTextConfig(configValue)) {
578
- result[key] = flatValues[path] ?? configValue.default ?? "";
579
- } else if (typeof configValue === "object" && configValue !== null) {
580
- result[key] = buildResolvedValues(configValue, flatValues, path);
801
+ const values = (0, import_vue.computed)(() => resolveDialValues(configRef.value, flatValues.value));
802
+ return {
803
+ values,
804
+ setValue(path, value) {
805
+ DialStore.updateValue(panelId, path, value);
806
+ },
807
+ setValues(nextValues) {
808
+ DialStore.updateValues(panelId, flattenDialValueUpdates(configRef.value, nextValues));
809
+ },
810
+ resetValues() {
811
+ DialStore.resetValues(panelId);
812
+ },
813
+ getValues() {
814
+ return resolveDialValues(configRef.value, DialStore.getValues(panelId));
581
815
  }
582
- }
583
- return result;
584
- }
585
- function hasType(value, type) {
586
- return typeof value === "object" && value !== null && "type" in value && value.type === type;
587
- }
588
- function isSpringConfig(value) {
589
- return hasType(value, "spring");
590
- }
591
- function isEasingConfig(value) {
592
- return hasType(value, "easing");
593
- }
594
- function isActionConfig(value) {
595
- return hasType(value, "action");
596
- }
597
- function isSelectConfig(value) {
598
- return hasType(value, "select") && "options" in value && Array.isArray(value.options);
599
- }
600
- function isColorConfig(value) {
601
- return hasType(value, "color");
602
- }
603
- function isTextConfig(value) {
604
- return hasType(value, "text");
605
- }
606
- function getFirstOptionValue(options) {
607
- const first = options[0];
608
- return typeof first === "string" ? first : first.value;
816
+ };
609
817
  }
610
818
 
611
819
  // src/vue/directives/dialkit.ts
@@ -614,9 +822,9 @@ var import_vue17 = require("vue");
614
822
  // src/vue/components/DialRoot.ts
615
823
  var import_vue16 = require("vue");
616
824
 
617
- // src/vue/components/Panel.ts
618
- var import_vue15 = require("vue");
619
- var import_motion_v5 = require("motion-v");
825
+ // src/vue/components/Folder.ts
826
+ var import_vue2 = require("vue");
827
+ var import_motion_v = require("motion-v");
620
828
 
621
829
  // src/icons.ts
622
830
  var ICON_CHEVRON = "M6 9.5L12 15.5L18 9.5";
@@ -650,8 +858,6 @@ var ICON_PANEL = {
650
858
  };
651
859
 
652
860
  // src/vue/components/Folder.ts
653
- var import_vue2 = require("vue");
654
- var import_motion_v = require("motion-v");
655
861
  var Folder = (0, import_vue2.defineComponent)({
656
862
  name: "DialKitFolder",
657
863
  props: {
@@ -663,6 +869,10 @@ var Folder = (0, import_vue2.defineComponent)({
663
869
  type: null,
664
870
  required: false,
665
871
  default: null
872
+ },
873
+ panelHeightOffset: {
874
+ type: Number,
875
+ default: 10
666
876
  }
667
877
  },
668
878
  emits: ["openChange"],
@@ -762,7 +972,8 @@ var Folder = (0, import_vue2.defineComponent)({
762
972
  };
763
973
  const folderContent = () => (0, import_vue2.h)("div", {
764
974
  ref: props.isRoot ? contentRef : void 0,
765
- class: `dialkit-folder ${props.isRoot ? "dialkit-folder-root" : ""}`
975
+ class: `dialkit-folder ${props.isRoot ? "dialkit-folder-root" : ""}`,
976
+ "data-open": String(isOpen.value)
766
977
  }, [
767
978
  renderHeader(),
768
979
  renderContent()
@@ -774,7 +985,7 @@ var Folder = (0, import_vue2.defineComponent)({
774
985
  }
775
986
  const panelStyle = isOpen.value ? {
776
987
  width: 280,
777
- height: contentHeight.value !== void 0 ? Math.min(contentHeight.value + 10, windowHeight.value - 32) : "auto",
988
+ height: contentHeight.value !== void 0 ? Math.min(contentHeight.value + props.panelHeightOffset, windowHeight.value - 32) : "auto",
778
989
  borderRadius: 14,
779
990
  boxShadow: "var(--dial-shadow)",
780
991
  cursor: void 0,
@@ -801,6 +1012,10 @@ var Folder = (0, import_vue2.defineComponent)({
801
1012
  }
802
1013
  });
803
1014
 
1015
+ // src/vue/components/Panel.ts
1016
+ var import_vue15 = require("vue");
1017
+ var import_motion_v5 = require("motion-v");
1018
+
804
1019
  // src/vue/components/Slider.ts
805
1020
  var import_vue3 = require("vue");
806
1021
  var import_motion_v2 = require("motion-v");
@@ -1933,6 +2148,26 @@ var TextControl = (0, import_vue10.defineComponent)({
1933
2148
  // src/vue/components/SelectControl.ts
1934
2149
  var import_vue11 = require("vue");
1935
2150
  var import_motion_v3 = require("motion-v");
2151
+
2152
+ // src/dropdown-position.ts
2153
+ function getDropdownPosition(trigger, portalRoot, options = {}) {
2154
+ const { dropdownHeight = 0, gap = 4, allowAbove = true } = options;
2155
+ const triggerRect = trigger.getBoundingClientRect();
2156
+ const rootRect = portalRoot.getBoundingClientRect();
2157
+ const spaceBelow = window.innerHeight - triggerRect.bottom - gap;
2158
+ const above = allowAbove && spaceBelow < dropdownHeight && triggerRect.top > spaceBelow;
2159
+ return {
2160
+ top: above ? triggerRect.top - rootRect.top - dropdownHeight - gap : triggerRect.bottom - rootRect.top + gap,
2161
+ left: triggerRect.left - rootRect.left,
2162
+ width: triggerRect.width,
2163
+ above
2164
+ };
2165
+ }
2166
+ function getDialKitPortalRoot(trigger) {
2167
+ return trigger?.closest(".dialkit-root") ?? null;
2168
+ }
2169
+
2170
+ // src/vue/components/SelectControl.ts
1936
2171
  function toTitleCase(value) {
1937
2172
  return value.replace(/\b\w/g, (char) => char.toUpperCase());
1938
2173
  }
@@ -1961,17 +2196,9 @@ var SelectControl = (0, import_vue11.defineComponent)({
1961
2196
  const normalizedOptions = () => normalizeOptions(props.options);
1962
2197
  const selectedLabel = () => normalizedOptions().find((option) => option.value === props.value)?.label ?? props.value;
1963
2198
  const updatePos = () => {
1964
- if (!triggerRef.value) return;
1965
- const rect = triggerRef.value.getBoundingClientRect();
2199
+ if (!triggerRef.value || !portalTarget.value) return;
1966
2200
  const dropdownHeight = 8 + normalizedOptions().length * 36;
1967
- const spaceBelow = window.innerHeight - rect.bottom - 4;
1968
- const above = spaceBelow < dropdownHeight && rect.top > spaceBelow;
1969
- pos.value = {
1970
- top: above ? rect.top - 4 : rect.bottom + 4,
1971
- left: rect.left,
1972
- width: rect.width,
1973
- above
1974
- };
2201
+ pos.value = getDropdownPosition(triggerRef.value, portalTarget.value, { dropdownHeight });
1975
2202
  };
1976
2203
  const openDropdown = () => {
1977
2204
  updatePos();
@@ -2015,8 +2242,7 @@ var SelectControl = (0, import_vue11.defineComponent)({
2015
2242
  });
2016
2243
  });
2017
2244
  (0, import_vue11.onMounted)(() => {
2018
- const root = triggerRef.value?.closest(".dialkit-root");
2019
- portalTarget.value = root ?? document.body;
2245
+ portalTarget.value = getDialKitPortalRoot(triggerRef.value) ?? document.body;
2020
2246
  });
2021
2247
  return () => (0, import_vue11.h)("div", { class: "dialkit-select-row" }, [
2022
2248
  (0, import_vue11.h)("button", {
@@ -2052,16 +2278,11 @@ var SelectControl = (0, import_vue11.defineComponent)({
2052
2278
  exit: { opacity: 0, y: pos.value.above ? 8 : -8, scale: 0.95 },
2053
2279
  transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
2054
2280
  style: {
2055
- position: "fixed",
2281
+ position: "absolute",
2056
2282
  left: `${pos.value.left}px`,
2283
+ top: `${pos.value.top}px`,
2057
2284
  width: `${pos.value.width}px`,
2058
- ...pos.value.above ? {
2059
- bottom: `${window.innerHeight - pos.value.top}px`,
2060
- transformOrigin: "bottom"
2061
- } : {
2062
- top: `${pos.value.top}px`,
2063
- transformOrigin: "top"
2064
- }
2285
+ transformOrigin: pos.value.above ? "bottom" : "top"
2065
2286
  }
2066
2287
  }, normalizedOptions().map((option) => (0, import_vue11.h)("button", {
2067
2288
  key: option.value,
@@ -2518,9 +2739,14 @@ var Panel = (0, import_vue15.defineComponent)({
2518
2739
  inline: {
2519
2740
  type: Boolean,
2520
2741
  default: false
2742
+ },
2743
+ variant: {
2744
+ type: String,
2745
+ default: "root"
2521
2746
  }
2522
2747
  },
2523
- setup(props) {
2748
+ emits: ["openChange"],
2749
+ setup(props, { emit }) {
2524
2750
  const shortcutCtx = useShortcutContext();
2525
2751
  const values = (0, import_vue15.ref)(DialStore.getValues(props.panel.id));
2526
2752
  const presets = (0, import_vue15.ref)(DialStore.getPresets(props.panel.id));
@@ -2569,6 +2795,9 @@ Apply these values as the new defaults in the useDialKit call.`;
2569
2795
  copied.value = false;
2570
2796
  }, 1500);
2571
2797
  };
2798
+ const handleOpenChange = (open) => {
2799
+ emit("openChange", open);
2800
+ };
2572
2801
  const renderControl = (control) => {
2573
2802
  const value = values.value[control.path];
2574
2803
  switch (control.type) {
@@ -2737,13 +2966,29 @@ Apply these values as the new defaults in the useDialKit call.`;
2737
2966
  "Copy"
2738
2967
  ])
2739
2968
  ]);
2969
+ if (props.variant === "section") {
2970
+ return (0, import_vue15.h)(Folder, {
2971
+ title: props.panel.name,
2972
+ defaultOpen: props.defaultOpen,
2973
+ onOpenChange: handleOpenChange
2974
+ }, {
2975
+ default: () => [
2976
+ (0, import_vue15.h)("div", {
2977
+ class: "dialkit-panel-section-toolbar",
2978
+ onClick: (event) => event.stopPropagation()
2979
+ }, [toolbarNode]),
2980
+ ...props.panel.controls.map(renderControl)
2981
+ ]
2982
+ });
2983
+ }
2740
2984
  return (0, import_vue15.h)("div", { class: "dialkit-panel-wrapper" }, [
2741
2985
  (0, import_vue15.h)(Folder, {
2742
2986
  title: props.panel.name,
2743
2987
  defaultOpen: props.defaultOpen,
2744
2988
  isRoot: true,
2745
2989
  inline: props.inline,
2746
- toolbar: () => toolbarNode
2990
+ toolbar: () => toolbarNode,
2991
+ onOpenChange: handleOpenChange
2747
2992
  }, {
2748
2993
  default: () => props.panel.controls.map(renderControl)
2749
2994
  })
@@ -2752,6 +2997,70 @@ Apply these values as the new defaults in the useDialKit call.`;
2752
2997
  }
2753
2998
  });
2754
2999
 
3000
+ // src/panel-drag.ts
3001
+ var PANEL_DRAG_THRESHOLD = 8;
3002
+ var COLLAPSED_PANEL_SIZE = 42;
3003
+ var DRAG_EXCLUSION_SELECTOR = [
3004
+ ".dialkit-panel-icon",
3005
+ ".dialkit-panel-toolbar",
3006
+ "button",
3007
+ "input",
3008
+ "select",
3009
+ "textarea",
3010
+ "a",
3011
+ '[role="button"]',
3012
+ '[contenteditable="true"]'
3013
+ ].join(",");
3014
+ function getPanelDragHandle(target, panel) {
3015
+ if (!(target instanceof Element) || !panel) return null;
3016
+ const inner = target.closest(".dialkit-panel-inner");
3017
+ if (!inner || !panel.contains(inner)) return null;
3018
+ if (inner.getAttribute("data-collapsed") === "true") {
3019
+ return inner;
3020
+ }
3021
+ const header = target.closest(".dialkit-panel-header");
3022
+ if (!header || !inner.contains(header)) return null;
3023
+ if (target.closest(DRAG_EXCLUSION_SELECTOR)) return null;
3024
+ return header;
3025
+ }
3026
+ function getPanelDragStart(pointerX, pointerY, panel) {
3027
+ const rect = panel.getBoundingClientRect();
3028
+ return {
3029
+ pointerX,
3030
+ pointerY,
3031
+ elX: rect.left,
3032
+ elY: rect.top
3033
+ };
3034
+ }
3035
+ function getPanelDragOffset(start, pointerX, pointerY) {
3036
+ return {
3037
+ x: start.elX + pointerX - start.pointerX,
3038
+ y: start.elY + pointerY - start.pointerY
3039
+ };
3040
+ }
3041
+ function hasPanelDragMoved(start, pointerX, pointerY) {
3042
+ const dx = pointerX - start.pointerX;
3043
+ const dy = pointerY - start.pointerY;
3044
+ return Math.hypot(dx, dy) >= PANEL_DRAG_THRESHOLD;
3045
+ }
3046
+ function getPanelOriginX(position, offset, viewportWidth = typeof window !== "undefined" ? window.innerWidth : void 0) {
3047
+ if (offset && viewportWidth) {
3048
+ return offset.x + COLLAPSED_PANEL_SIZE / 2 < viewportWidth / 2 ? "left" : "right";
3049
+ }
3050
+ return position.endsWith("left") ? "left" : "right";
3051
+ }
3052
+ function blockPanelDragClick(handle) {
3053
+ const blocker = (event) => {
3054
+ event.preventDefault();
3055
+ event.stopImmediatePropagation();
3056
+ event.stopPropagation();
3057
+ };
3058
+ handle.addEventListener("click", blocker, { capture: true, once: true });
3059
+ window.setTimeout(() => {
3060
+ handle.removeEventListener("click", blocker, true);
3061
+ }, 0);
3062
+ }
3063
+
2755
3064
  // src/vue/components/DialRoot.ts
2756
3065
  var import_meta = {};
2757
3066
  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;
@@ -2779,31 +3088,162 @@ var DialRoot = (0, import_vue16.defineComponent)({
2779
3088
  default: isDevDefault
2780
3089
  }
2781
3090
  },
2782
- setup(props) {
3091
+ emits: ["openChange"],
3092
+ setup(props, { emit }) {
2783
3093
  const panels = (0, import_vue16.ref)([]);
2784
3094
  const mounted = (0, import_vue16.ref)(false);
3095
+ const panelRef = (0, import_vue16.ref)(null);
3096
+ const dragOffset = (0, import_vue16.ref)(null);
3097
+ const activePosition = (0, import_vue16.ref)(props.position);
2785
3098
  let unsubscribe;
3099
+ let observer;
3100
+ let lastDragOffset = null;
3101
+ let dragging = false;
3102
+ let dragStart = null;
3103
+ let didDrag = false;
3104
+ let dragTarget = null;
3105
+ let panelOpenStates = /* @__PURE__ */ new Map();
3106
+ let rootOpen;
3107
+ const syncPanelOpenStates = () => {
3108
+ const fallbackOpen = props.mode === "inline" || props.defaultOpen;
3109
+ const nextStates = /* @__PURE__ */ new Map();
3110
+ for (const panel of panels.value) {
3111
+ nextStates.set(panel.id, panelOpenStates.get(panel.id) ?? fallbackOpen);
3112
+ }
3113
+ panelOpenStates = nextStates;
3114
+ rootOpen = Array.from(nextStates.values()).some(Boolean);
3115
+ };
3116
+ const connectObserver = () => {
3117
+ if (observer || props.mode === "inline" || !panelRef.value) return;
3118
+ observer = new MutationObserver(() => {
3119
+ const inners = panelRef.value?.querySelectorAll(".dialkit-panel-inner");
3120
+ if (!inners || inners.length === 0) return;
3121
+ const collapsed = Array.from(inners).every((el) => el.getAttribute("data-collapsed") === "true");
3122
+ const currentDragOffset = dragOffset.value;
3123
+ if (!collapsed) {
3124
+ if (currentDragOffset) {
3125
+ lastDragOffset = currentDragOffset;
3126
+ const bubbleCenterX = currentDragOffset.x + 21;
3127
+ activePosition.value = bubbleCenterX < window.innerWidth / 2 ? "top-left" : "top-right";
3128
+ } else {
3129
+ activePosition.value = props.position;
3130
+ }
3131
+ dragOffset.value = null;
3132
+ } else if (currentDragOffset) {
3133
+ lastDragOffset = currentDragOffset;
3134
+ } else if (lastDragOffset) {
3135
+ dragOffset.value = lastDragOffset;
3136
+ }
3137
+ });
3138
+ observer.observe(panelRef.value, { subtree: true, attributes: true, attributeFilter: ["data-collapsed"] });
3139
+ };
3140
+ const handlePointerDown = (event) => {
3141
+ const panel = panelRef.value;
3142
+ const handle = getPanelDragHandle(event.target, panel);
3143
+ if (!panel || !handle) return;
3144
+ dragTarget = handle;
3145
+ dragStart = getPanelDragStart(event.clientX, event.clientY, panel);
3146
+ didDrag = false;
3147
+ dragging = true;
3148
+ handle.setPointerCapture(event.pointerId);
3149
+ };
3150
+ const handlePointerMove = (event) => {
3151
+ if (!dragging || !dragStart) return;
3152
+ if (!didDrag && !hasPanelDragMoved(dragStart, event.clientX, event.clientY)) return;
3153
+ didDrag = true;
3154
+ dragOffset.value = getPanelDragOffset(dragStart, event.clientX, event.clientY);
3155
+ };
3156
+ const handlePointerUp = (event) => {
3157
+ if (!dragging) return;
3158
+ dragging = false;
3159
+ dragStart = null;
3160
+ const handle = dragTarget;
3161
+ if (handle?.hasPointerCapture(event.pointerId)) {
3162
+ handle.releasePointerCapture(event.pointerId);
3163
+ }
3164
+ if (didDrag) {
3165
+ event.stopPropagation();
3166
+ if (handle) {
3167
+ blockPanelDragClick(handle);
3168
+ }
3169
+ }
3170
+ dragTarget = null;
3171
+ };
3172
+ const handlePanelOpenChange = (panelId, open) => {
3173
+ panelOpenStates.set(panelId, open);
3174
+ const fallbackOpen = props.mode === "inline" || props.defaultOpen;
3175
+ const nextRootOpen = panels.value.some((panel) => panelOpenStates.get(panel.id) ?? fallbackOpen);
3176
+ if (rootOpen === nextRootOpen) return;
3177
+ rootOpen = nextRootOpen;
3178
+ emit("openChange", nextRootOpen);
3179
+ };
3180
+ const handleRootOpenChange = (open) => {
3181
+ if (rootOpen === open) return;
3182
+ rootOpen = open;
3183
+ emit("openChange", open);
3184
+ };
3185
+ const getDragStyle = () => dragOffset.value ? {
3186
+ top: `${dragOffset.value.y}px`,
3187
+ left: `${dragOffset.value.x}px`,
3188
+ right: "auto",
3189
+ bottom: "auto"
3190
+ } : void 0;
3191
+ const originX = (0, import_vue16.computed)(() => props.mode === "inline" ? void 0 : getPanelOriginX(activePosition.value, dragOffset.value));
2786
3192
  (0, import_vue16.onMounted)(() => {
2787
3193
  mounted.value = true;
2788
3194
  panels.value = DialStore.getPanels();
3195
+ syncPanelOpenStates();
2789
3196
  unsubscribe = DialStore.subscribeGlobal(() => {
2790
3197
  panels.value = DialStore.getPanels();
3198
+ syncPanelOpenStates();
2791
3199
  });
3200
+ (0, import_vue16.nextTick)(connectObserver);
2792
3201
  });
2793
3202
  (0, import_vue16.onUnmounted)(() => {
2794
3203
  unsubscribe?.();
3204
+ observer?.disconnect();
2795
3205
  });
2796
3206
  const renderContent = () => (0, import_vue16.h)(ShortcutListener, null, {
2797
3207
  default: () => (0, import_vue16.h)("div", { class: "dialkit-root", "data-mode": props.mode, "data-theme": props.theme }, [
2798
3208
  (0, import_vue16.h)("div", {
3209
+ ref: (el) => {
3210
+ panelRef.value = el;
3211
+ connectObserver();
3212
+ },
2799
3213
  class: "dialkit-panel",
2800
- "data-position": props.mode === "inline" ? void 0 : props.position,
2801
- "data-mode": props.mode
2802
- }, panels.value.map((panel) => (0, import_vue16.h)(Panel, {
3214
+ "data-position": props.mode === "inline" ? void 0 : dragOffset.value ? void 0 : activePosition.value,
3215
+ "data-origin-x": originX.value,
3216
+ "data-mode": props.mode,
3217
+ "data-multiple": panels.value.length > 1 ? "true" : void 0,
3218
+ style: getDragStyle(),
3219
+ onPointerdown: props.mode === "inline" ? void 0 : handlePointerDown,
3220
+ onPointermove: props.mode === "inline" ? void 0 : handlePointerMove,
3221
+ onPointerup: props.mode === "inline" ? void 0 : handlePointerUp,
3222
+ onPointercancel: props.mode === "inline" ? void 0 : handlePointerUp
3223
+ }, panels.value.length > 1 ? [
3224
+ (0, import_vue16.h)("div", { class: "dialkit-panel-wrapper" }, [
3225
+ (0, import_vue16.h)(Folder, {
3226
+ title: "DialKit",
3227
+ defaultOpen: props.mode === "inline" || props.defaultOpen,
3228
+ isRoot: true,
3229
+ inline: props.mode === "inline",
3230
+ onOpenChange: handleRootOpenChange,
3231
+ panelHeightOffset: 2
3232
+ }, {
3233
+ default: () => panels.value.map((panel) => (0, import_vue16.h)(Panel, {
3234
+ key: panel.id,
3235
+ panel,
3236
+ defaultOpen: true,
3237
+ variant: "section"
3238
+ }))
3239
+ })
3240
+ ])
3241
+ ] : panels.value.map((panel) => (0, import_vue16.h)(Panel, {
2803
3242
  key: panel.id,
2804
3243
  panel,
2805
3244
  defaultOpen: props.mode === "inline" || props.defaultOpen,
2806
- inline: props.mode === "inline"
3245
+ inline: props.mode === "inline",
3246
+ onOpenChange: (open) => handlePanelOpenChange(panel.id, open)
2807
3247
  })))
2808
3248
  ])
2809
3249
  });
@@ -3050,6 +3490,7 @@ var ButtonGroup = (0, import_vue19.defineComponent)({
3050
3490
  Toggle,
3051
3491
  TransitionControl,
3052
3492
  useDialKit,
3493
+ useDialKitController,
3053
3494
  useShortcutContext,
3054
3495
  vDialKit
3055
3496
  });