dialkit 1.3.0 → 1.4.1

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 (71) hide show
  1. package/README.md +433 -3
  2. package/dist/icons.d.ts +5 -1
  3. package/dist/icons.js +18 -0
  4. package/dist/icons.js.map +1 -1
  5. package/dist/index.cjs +3222 -479
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +274 -6
  8. package/dist/index.d.ts +274 -6
  9. package/dist/index.js +3154 -416
  10. package/dist/index.js.map +1 -1
  11. package/dist/solid/index.d.ts +225 -3
  12. package/dist/solid/index.js +5761 -2500
  13. package/dist/solid/index.js.map +1 -1
  14. package/dist/store/index.cjs +57 -16
  15. package/dist/store/index.cjs.map +1 -1
  16. package/dist/store/index.d.cts +14 -2
  17. package/dist/store/index.d.ts +14 -2
  18. package/dist/store/index.js +52 -16
  19. package/dist/store/index.js.map +1 -1
  20. package/dist/styles.css +788 -0
  21. package/dist/svelte/components/ControlRenderer.svelte +5 -2
  22. package/dist/svelte/components/ControlRenderer.svelte.d.ts +2 -0
  23. package/dist/svelte/components/ControlRenderer.svelte.d.ts.map +1 -1
  24. package/dist/svelte/components/DialRoot.svelte +43 -6
  25. package/dist/svelte/components/DialRoot.svelte.d.ts.map +1 -1
  26. package/dist/svelte/components/Panel.svelte +7 -1
  27. package/dist/svelte/components/Panel.svelte.d.ts +2 -0
  28. package/dist/svelte/components/Panel.svelte.d.ts.map +1 -1
  29. package/dist/svelte/components/Timeline/ClipPopover.svelte +206 -0
  30. package/dist/svelte/components/Timeline/ClipPopover.svelte.d.ts +26 -0
  31. package/dist/svelte/components/Timeline/ClipPopover.svelte.d.ts.map +1 -0
  32. package/dist/svelte/components/Timeline/DialTimeline.svelte +125 -0
  33. package/dist/svelte/components/Timeline/DialTimeline.svelte.d.ts +13 -0
  34. package/dist/svelte/components/Timeline/DialTimeline.svelte.d.ts.map +1 -0
  35. package/dist/svelte/components/Timeline/TimelineClip.svelte +233 -0
  36. package/dist/svelte/components/Timeline/TimelineClip.svelte.d.ts +24 -0
  37. package/dist/svelte/components/Timeline/TimelineClip.svelte.d.ts.map +1 -0
  38. package/dist/svelte/components/Timeline/TimelineSection.svelte +803 -0
  39. package/dist/svelte/components/Timeline/TimelineSection.svelte.d.ts +12 -0
  40. package/dist/svelte/components/Timeline/TimelineSection.svelte.d.ts.map +1 -0
  41. package/dist/svelte/components/Timeline/TimelineToggleButton.svelte +25 -0
  42. package/dist/svelte/components/Timeline/TimelineToggleButton.svelte.d.ts +4 -0
  43. package/dist/svelte/components/Timeline/TimelineToggleButton.svelte.d.ts.map +1 -0
  44. package/dist/svelte/components/TransitionControl.svelte +26 -11
  45. package/dist/svelte/components/TransitionControl.svelte.d.ts +9 -0
  46. package/dist/svelte/components/TransitionControl.svelte.d.ts.map +1 -1
  47. package/dist/svelte/createDialTimeline.svelte.d.ts +4 -0
  48. package/dist/svelte/createDialTimeline.svelte.d.ts.map +1 -0
  49. package/dist/svelte/createDialTimeline.svelte.js +73 -0
  50. package/dist/svelte/index.d.ts +4 -0
  51. package/dist/svelte/index.d.ts.map +1 -1
  52. package/dist/svelte/index.js +3 -0
  53. package/dist/svelte/theme-css.d.ts +1 -1
  54. package/dist/svelte/theme-css.d.ts.map +1 -1
  55. package/dist/svelte/theme-css.js +788 -0
  56. package/dist/timeline/index.cjs +1288 -0
  57. package/dist/timeline/index.cjs.map +1 -0
  58. package/dist/timeline/index.d.cts +443 -0
  59. package/dist/timeline/index.d.ts +443 -0
  60. package/dist/timeline/index.js +1233 -0
  61. package/dist/timeline/index.js.map +1 -0
  62. package/dist/vue/index.d.ts +273 -7
  63. package/dist/vue/index.js +4116 -1517
  64. package/dist/vue/index.js.map +1 -1
  65. package/package.json +23 -13
  66. package/dist/solid/index.cjs +0 -3536
  67. package/dist/solid/index.cjs.map +0 -1
  68. package/dist/solid/index.d.cts +0 -295
  69. package/dist/vue/index.cjs +0 -3497
  70. package/dist/vue/index.cjs.map +0 -1
  71. package/dist/vue/index.d.cts +0 -722
@@ -1,3497 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/vue/index.ts
21
- var vue_exports = {};
22
- __export(vue_exports, {
23
- ButtonGroup: () => ButtonGroup,
24
- ColorControl: () => ColorControl,
25
- DialRoot: () => DialRoot,
26
- DialStore: () => DialStore,
27
- EasingVisualization: () => EasingVisualization,
28
- Folder: () => Folder,
29
- PresetManager: () => PresetManager,
30
- SelectControl: () => SelectControl,
31
- ShortcutKey: () => ShortcutKey,
32
- ShortcutListener: () => ShortcutListener,
33
- ShortcutsMenu: () => ShortcutsMenu,
34
- Slider: () => Slider,
35
- SpringControl: () => SpringControl,
36
- SpringVisualization: () => SpringVisualization,
37
- TextControl: () => TextControl,
38
- Toggle: () => Toggle,
39
- TransitionControl: () => TransitionControl,
40
- useDialKit: () => useDialKit,
41
- useDialKitController: () => useDialKitController,
42
- useShortcutContext: () => useShortcutContext,
43
- vDialKit: () => vDialKit
44
- });
45
- module.exports = __toCommonJS(vue_exports);
46
-
47
- // src/vue/useDialKit.ts
48
- var import_vue = require("vue");
49
-
50
- // src/store/DialStore.ts
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
- }
135
- var DialStoreClass = class {
136
- constructor() {
137
- this.panels = /* @__PURE__ */ new Map();
138
- this.listeners = /* @__PURE__ */ new Map();
139
- this.globalListeners = /* @__PURE__ */ new Set();
140
- this.snapshots = /* @__PURE__ */ new Map();
141
- this.actionListeners = /* @__PURE__ */ new Map();
142
- this.presets = /* @__PURE__ */ new Map();
143
- this.activePreset = /* @__PURE__ */ new Map();
144
- this.baseValues = /* @__PURE__ */ new Map();
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);
153
- const controls = this.parseConfig(config, "", shortcuts);
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);
162
- this.panels.set(id, { id, name, controls, values, shortcuts: shortcuts ?? {} });
163
- this.snapshots.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);
175
- this.notifyGlobal();
176
- }
177
- updatePanel(id, name, config, shortcuts, options = {}) {
178
- this.configurePanelRetention(id, options);
179
- const existing = this.panels.get(id);
180
- if (!existing) {
181
- this.registerPanel(id, name, config, shortcuts, options);
182
- return;
183
- }
184
- const controls = this.parseConfig(config, "", shortcuts);
185
- const controlsByPath = this.mapControlsByPath(controls);
186
- const defaultValues = this.flattenValues(config, "");
187
- this.initTransitionModes(config, "", defaultValues);
188
- const nextValues = this.reconcileValues(defaultValues, existing.values, controlsByPath);
189
- const nextPanel = { id, name, controls, values: nextValues, shortcuts: shortcuts ?? existing.shortcuts };
190
- this.panels.set(id, nextPanel);
191
- this.snapshots.set(id, { ...nextValues });
192
- const previousBaseValues = this.baseValues.get(id) ?? {};
193
- const nextBaseValues = this.reconcileValues(defaultValues, previousBaseValues, controlsByPath);
194
- for (const [path, value] of Object.entries(nextValues)) {
195
- if (path.endsWith(".__mode")) {
196
- nextBaseValues[path] = value;
197
- }
198
- }
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);
203
- this.notify(id);
204
- this.notifyGlobal();
205
- }
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);
213
- this.panels.delete(id);
214
- this.listeners.delete(id);
215
- this.actionListeners.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
- }
224
- this.notifyGlobal();
225
- }
226
- updateValue(panelId, path, value) {
227
- this.updateValues(panelId, { [path]: value });
228
- }
229
- updateValues(panelId, updates) {
230
- const panel = this.panels.get(panelId);
231
- if (!panel) return;
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
- }
247
- const activeId = this.activePreset.get(panelId);
248
- if (activeId) {
249
- const presets = this.presets.get(panelId) ?? [];
250
- const preset = presets.find((p) => p.id === activeId);
251
- if (preset) {
252
- for (const [path, value] of Object.entries(validUpdates)) {
253
- preset.values[path] = value;
254
- }
255
- }
256
- } else {
257
- const base = this.baseValues.get(panelId);
258
- if (base) {
259
- for (const [path, value] of Object.entries(validUpdates)) {
260
- base[path] = value;
261
- }
262
- }
263
- }
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);
277
- this.notify(panelId);
278
- }
279
- updateSpringMode(panelId, path, mode) {
280
- this.updateTransitionMode(panelId, path, mode);
281
- }
282
- getSpringMode(panelId, path) {
283
- const mode = this.getTransitionMode(panelId, path);
284
- if (mode === "easing") return "simple";
285
- return mode;
286
- }
287
- updateTransitionMode(panelId, path, mode) {
288
- const panel = this.panels.get(panelId);
289
- if (!panel) return;
290
- panel.values[`${path}.__mode`] = mode;
291
- this.snapshots.set(panelId, { ...panel.values });
292
- this.persistPanel(panelId);
293
- this.notify(panelId);
294
- }
295
- getTransitionMode(panelId, path) {
296
- const panel = this.panels.get(panelId);
297
- if (!panel) return "simple";
298
- return panel.values[`${path}.__mode`] || "simple";
299
- }
300
- getValue(panelId, path) {
301
- const panel = this.panels.get(panelId);
302
- return panel?.values[path];
303
- }
304
- getValues(panelId) {
305
- return this.snapshots.get(panelId) ?? EMPTY_VALUES;
306
- }
307
- getPanels() {
308
- return Array.from(this.panels.values());
309
- }
310
- getPanel(id) {
311
- return this.panels.get(id);
312
- }
313
- subscribe(panelId, listener) {
314
- if (!this.listeners.has(panelId)) {
315
- this.listeners.set(panelId, /* @__PURE__ */ new Set());
316
- }
317
- this.listeners.get(panelId).add(listener);
318
- return () => {
319
- this.listeners.get(panelId)?.delete(listener);
320
- };
321
- }
322
- subscribeGlobal(listener) {
323
- this.globalListeners.add(listener);
324
- return () => this.globalListeners.delete(listener);
325
- }
326
- subscribeActions(panelId, listener) {
327
- if (!this.actionListeners.has(panelId)) {
328
- this.actionListeners.set(panelId, /* @__PURE__ */ new Set());
329
- }
330
- this.actionListeners.get(panelId).add(listener);
331
- return () => {
332
- this.actionListeners.get(panelId)?.delete(listener);
333
- };
334
- }
335
- triggerAction(panelId, path) {
336
- this.actionListeners.get(panelId)?.forEach((fn) => fn(path));
337
- }
338
- savePreset(panelId, name) {
339
- const panel = this.panels.get(panelId);
340
- if (!panel) throw new Error(`Panel ${panelId} not found`);
341
- const id = `preset-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
342
- const preset = {
343
- id,
344
- name,
345
- values: { ...panel.values }
346
- };
347
- const existing = this.presets.get(panelId) ?? [];
348
- this.presets.set(panelId, [...existing, preset]);
349
- this.activePreset.set(panelId, id);
350
- this.snapshots.set(panelId, { ...panel.values });
351
- this.persistPanel(panelId);
352
- this.notify(panelId);
353
- return id;
354
- }
355
- loadPreset(panelId, presetId) {
356
- const panel = this.panels.get(panelId);
357
- if (!panel) return;
358
- const presets = this.presets.get(panelId) ?? [];
359
- const preset = presets.find((p) => p.id === presetId);
360
- if (!preset) return;
361
- panel.values = { ...preset.values };
362
- this.snapshots.set(panelId, { ...panel.values });
363
- this.activePreset.set(panelId, presetId);
364
- this.persistPanel(panelId);
365
- this.notify(panelId);
366
- }
367
- deletePreset(panelId, presetId) {
368
- const presets = this.presets.get(panelId) ?? [];
369
- this.presets.set(panelId, presets.filter((p) => p.id !== presetId));
370
- if (this.activePreset.get(panelId) === presetId) {
371
- this.activePreset.set(panelId, null);
372
- }
373
- const panel = this.panels.get(panelId);
374
- if (panel) {
375
- this.snapshots.set(panelId, { ...panel.values });
376
- }
377
- this.persistPanel(panelId);
378
- this.notify(panelId);
379
- }
380
- getPresets(panelId) {
381
- return this.presets.get(panelId) ?? [];
382
- }
383
- getActivePresetId(panelId) {
384
- return this.activePreset.get(panelId) ?? null;
385
- }
386
- clearActivePreset(panelId) {
387
- const panel = this.panels.get(panelId);
388
- const base = this.baseValues.get(panelId);
389
- if (panel && base) {
390
- panel.values = { ...base };
391
- this.snapshots.set(panelId, { ...panel.values });
392
- }
393
- this.activePreset.set(panelId, null);
394
- this.persistPanel(panelId);
395
- this.notify(panelId);
396
- }
397
- resolveShortcutTarget(key, modifier) {
398
- for (const panel of this.panels.values()) {
399
- for (const [path, shortcut] of Object.entries(panel.shortcuts)) {
400
- if (!shortcut.key) continue;
401
- if (shortcut.key.toLowerCase() !== key.toLowerCase()) continue;
402
- const scMod = shortcut.modifier ?? void 0;
403
- if (scMod !== modifier) continue;
404
- const control = this.findControlByPath(panel.controls, path);
405
- if (control) {
406
- return { panelId: panel.id, path, control };
407
- }
408
- }
409
- }
410
- return null;
411
- }
412
- resolveScrollOnlyTargets() {
413
- const results = [];
414
- for (const panel of this.panels.values()) {
415
- for (const [path, shortcut] of Object.entries(panel.shortcuts)) {
416
- if ((shortcut.interaction ?? "scroll") !== "scroll-only") continue;
417
- const control = this.findControlByPath(panel.controls, path);
418
- if (control) {
419
- results.push({ panelId: panel.id, path, control, shortcut });
420
- }
421
- }
422
- }
423
- return results;
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
- }
513
- findControlByPath(controls, path) {
514
- for (const control of controls) {
515
- if (control.path === path) return control;
516
- if (control.type === "folder" && control.children) {
517
- const found = this.findControlByPath(control.children, path);
518
- if (found) return found;
519
- }
520
- }
521
- return null;
522
- }
523
- notify(panelId) {
524
- this.listeners.get(panelId)?.forEach((fn) => fn());
525
- }
526
- notifyGlobal() {
527
- this.globalListeners.forEach((fn) => fn());
528
- }
529
- initTransitionModes(config, prefix, values) {
530
- for (const [key, value] of Object.entries(config)) {
531
- if (key === "_collapsed") continue;
532
- const path = prefix ? `${prefix}.${key}` : key;
533
- if (this.isEasingConfig(value)) {
534
- values[`${path}.__mode`] = "easing";
535
- } else if (this.isSpringConfig(value)) {
536
- const hasPhysics = value.stiffness !== void 0 || value.damping !== void 0 || value.mass !== void 0;
537
- const hasTime = value.visualDuration !== void 0 || value.bounce !== void 0;
538
- values[`${path}.__mode`] = hasPhysics && !hasTime ? "advanced" : "simple";
539
- } else if (typeof value === "object" && value !== null && !Array.isArray(value) && !this.isActionConfig(value) && !this.isSelectConfig(value) && !this.isColorConfig(value) && !this.isTextConfig(value)) {
540
- this.initTransitionModes(value, path, values);
541
- }
542
- }
543
- }
544
- parseConfig(config, prefix, shortcuts) {
545
- const controls = [];
546
- for (const [key, value] of Object.entries(config)) {
547
- if (key === "_collapsed") continue;
548
- const path = prefix ? `${prefix}.${key}` : key;
549
- const label = this.formatLabel(key);
550
- const shortcut = shortcuts?.[path];
551
- if (Array.isArray(value) && value.length <= 4 && typeof value[0] === "number") {
552
- controls.push({
553
- type: "slider",
554
- path,
555
- label,
556
- min: value[1],
557
- max: value[2],
558
- step: value[3] ?? this.inferStep(value[1], value[2]),
559
- shortcut
560
- });
561
- } else if (typeof value === "number") {
562
- const { min, max, step } = this.inferRange(value);
563
- controls.push({ type: "slider", path, label, min, max, step, shortcut });
564
- } else if (typeof value === "boolean") {
565
- controls.push({ type: "toggle", path, label, shortcut });
566
- } else if (this.isSpringConfig(value) || this.isEasingConfig(value)) {
567
- controls.push({ type: "transition", path, label });
568
- } else if (this.isActionConfig(value)) {
569
- controls.push({ type: "action", path, label: value.label || label });
570
- } else if (this.isSelectConfig(value)) {
571
- controls.push({ type: "select", path, label, options: value.options });
572
- } else if (this.isColorConfig(value)) {
573
- controls.push({ type: "color", path, label });
574
- } else if (this.isTextConfig(value)) {
575
- controls.push({ type: "text", path, label, placeholder: value.placeholder });
576
- } else if (typeof value === "string") {
577
- if (this.isHexColor(value)) {
578
- controls.push({ type: "color", path, label });
579
- } else {
580
- controls.push({ type: "text", path, label });
581
- }
582
- } else if (typeof value === "object" && value !== null) {
583
- const folderConfig = value;
584
- const defaultOpen = "_collapsed" in folderConfig ? !folderConfig._collapsed : true;
585
- controls.push({
586
- type: "folder",
587
- path,
588
- label,
589
- defaultOpen,
590
- children: this.parseConfig(folderConfig, path, shortcuts)
591
- });
592
- }
593
- }
594
- return controls;
595
- }
596
- flattenValues(config, prefix) {
597
- const values = {};
598
- for (const [key, value] of Object.entries(config)) {
599
- if (key === "_collapsed") continue;
600
- const path = prefix ? `${prefix}.${key}` : key;
601
- if (Array.isArray(value) && value.length <= 4 && typeof value[0] === "number") {
602
- values[path] = value[0];
603
- } else if (typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
604
- values[path] = value;
605
- } else if (this.isSpringConfig(value) || this.isEasingConfig(value)) {
606
- values[path] = value;
607
- } else if (this.isActionConfig(value)) {
608
- values[path] = value;
609
- } else if (this.isSelectConfig(value)) {
610
- const firstOption = value.options[0];
611
- const firstValue = typeof firstOption === "string" ? firstOption : firstOption.value;
612
- values[path] = value.default ?? firstValue;
613
- } else if (this.isColorConfig(value)) {
614
- values[path] = value.default ?? "#000000";
615
- } else if (this.isTextConfig(value)) {
616
- values[path] = value.default ?? "";
617
- } else if (typeof value === "object" && value !== null) {
618
- Object.assign(values, this.flattenValues(value, path));
619
- }
620
- }
621
- return values;
622
- }
623
- isSpringConfig(value) {
624
- return typeof value === "object" && value !== null && "type" in value && value.type === "spring";
625
- }
626
- isEasingConfig(value) {
627
- return typeof value === "object" && value !== null && "type" in value && value.type === "easing";
628
- }
629
- isActionConfig(value) {
630
- return typeof value === "object" && value !== null && "type" in value && value.type === "action";
631
- }
632
- isSelectConfig(value) {
633
- return typeof value === "object" && value !== null && "type" in value && value.type === "select" && "options" in value && Array.isArray(value.options);
634
- }
635
- isColorConfig(value) {
636
- return typeof value === "object" && value !== null && "type" in value && value.type === "color";
637
- }
638
- isTextConfig(value) {
639
- return typeof value === "object" && value !== null && "type" in value && value.type === "text";
640
- }
641
- isHexColor(value) {
642
- return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(value);
643
- }
644
- formatLabel(key) {
645
- return key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
646
- }
647
- inferRange(value) {
648
- if (value >= 0 && value <= 1) {
649
- return { min: 0, max: 1, step: 0.01 };
650
- } else if (value >= 0 && value <= 10) {
651
- return { min: 0, max: value * 3 || 10, step: 0.1 };
652
- } else if (value >= 0 && value <= 100) {
653
- return { min: 0, max: value * 3 || 100, step: 1 };
654
- } else if (value >= 0) {
655
- return { min: 0, max: value * 3 || 1e3, step: 10 };
656
- } else {
657
- return { min: value * 3, max: -value * 3, step: 1 };
658
- }
659
- }
660
- inferStep(min, max) {
661
- const range = max - min;
662
- if (range <= 1) return 0.01;
663
- if (range <= 10) return 0.1;
664
- if (range <= 100) return 1;
665
- return 10;
666
- }
667
- normalizePreservedValue(existingValue, defaultValue, control) {
668
- if (existingValue === void 0 || !control) {
669
- return defaultValue;
670
- }
671
- switch (control.type) {
672
- case "slider": {
673
- if (typeof existingValue !== "number" || typeof defaultValue !== "number") {
674
- return defaultValue;
675
- }
676
- const min = control.min ?? Number.NEGATIVE_INFINITY;
677
- const max = control.max ?? Number.POSITIVE_INFINITY;
678
- const clamped = Math.min(max, Math.max(min, existingValue));
679
- if (typeof control.step !== "number" || control.step <= 0) {
680
- return clamped;
681
- }
682
- return this.roundToStep(clamped, min, max, control.step);
683
- }
684
- case "toggle":
685
- return typeof existingValue === "boolean" ? existingValue : defaultValue;
686
- case "select": {
687
- if (typeof existingValue !== "string") {
688
- return defaultValue;
689
- }
690
- const options = control.options ?? [];
691
- const validValues = new Set(options.map((option) => typeof option === "string" ? option : option.value));
692
- return validValues.has(existingValue) ? existingValue : defaultValue;
693
- }
694
- case "color":
695
- case "text":
696
- return typeof existingValue === "string" ? existingValue : defaultValue;
697
- case "transition":
698
- if (this.isSpringConfig(defaultValue)) {
699
- return this.isSpringConfig(existingValue) ? existingValue : defaultValue;
700
- }
701
- if (this.isEasingConfig(defaultValue)) {
702
- return this.isEasingConfig(existingValue) ? existingValue : defaultValue;
703
- }
704
- return defaultValue;
705
- case "action":
706
- return defaultValue;
707
- default:
708
- return defaultValue;
709
- }
710
- }
711
- roundToStep(value, min, max, step) {
712
- const snapped = min + Math.round((value - min) / step) * step;
713
- const clamped = Math.min(max, Math.max(min, snapped));
714
- const precision = this.stepPrecision(step);
715
- return Number(clamped.toFixed(precision));
716
- }
717
- stepPrecision(step) {
718
- const text = String(step);
719
- const decimalIndex = text.indexOf(".");
720
- return decimalIndex === -1 ? 0 : text.length - decimalIndex - 1;
721
- }
722
- mapControlsByPath(controls) {
723
- const map = /* @__PURE__ */ new Map();
724
- const visit = (nodes) => {
725
- for (const node of nodes) {
726
- if (node.type === "folder" && node.children) {
727
- visit(node.children);
728
- continue;
729
- }
730
- map.set(node.path, node);
731
- }
732
- };
733
- visit(controls);
734
- return map;
735
- }
736
- };
737
- var DialStore = new DialStoreClass();
738
-
739
- // src/vue/useDialKit.ts
740
- var dialKitInstance = 0;
741
- function useDialKit(name, config, options) {
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}`;
747
- const configRef = (0, import_vue.shallowRef)(config);
748
- const onActionRef = (0, import_vue.ref)(options?.onAction);
749
- const shortcutsRef = (0, import_vue.shallowRef)(options?.shortcuts);
750
- const persistRef = (0, import_vue.shallowRef)(options?.persist);
751
- const flatValues = (0, import_vue.ref)(DialStore.getValues(panelId));
752
- const mounted = (0, import_vue.ref)(false);
753
- const serializedConfig = (0, import_vue.computed)(() => JSON.stringify(config));
754
- const serializedShortcuts = (0, import_vue.computed)(() => JSON.stringify(options?.shortcuts));
755
- const serializedPersist = (0, import_vue.computed)(() => JSON.stringify(options?.persist));
756
- let unsubscribeValues;
757
- let unsubscribeActions;
758
- const register = () => {
759
- DialStore.registerPanel(panelId, name, configRef.value, shortcutsRef.value, {
760
- retainOnUnmount: hasStableId,
761
- persist: persistRef.value
762
- });
763
- flatValues.value = DialStore.getValues(panelId);
764
- unsubscribeValues = DialStore.subscribe(panelId, () => {
765
- flatValues.value = DialStore.getValues(panelId);
766
- });
767
- unsubscribeActions = DialStore.subscribeActions(panelId, (action) => {
768
- onActionRef.value?.(action);
769
- });
770
- };
771
- (0, import_vue.watch)(() => options?.onAction, (next) => {
772
- onActionRef.value = next;
773
- });
774
- (0, import_vue.watch)(() => options?.shortcuts, (next) => {
775
- shortcutsRef.value = next;
776
- });
777
- (0, import_vue.watch)(() => options?.persist, (next) => {
778
- persistRef.value = next;
779
- });
780
- (0, import_vue.watch)([serializedConfig, serializedShortcuts, serializedPersist], () => {
781
- configRef.value = config;
782
- shortcutsRef.value = options?.shortcuts;
783
- persistRef.value = options?.persist;
784
- if (mounted.value) {
785
- DialStore.updatePanel(panelId, name, configRef.value, shortcutsRef.value, {
786
- retainOnUnmount: hasStableId,
787
- persist: persistRef.value
788
- });
789
- flatValues.value = DialStore.getValues(panelId);
790
- }
791
- });
792
- (0, import_vue.onMounted)(register);
793
- (0, import_vue.onMounted)(() => {
794
- mounted.value = true;
795
- });
796
- (0, import_vue.onUnmounted)(() => {
797
- unsubscribeValues?.();
798
- unsubscribeActions?.();
799
- DialStore.unregisterPanel(panelId);
800
- });
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));
815
- }
816
- };
817
- }
818
-
819
- // src/vue/directives/dialkit.ts
820
- var import_vue17 = require("vue");
821
-
822
- // src/vue/components/DialRoot.ts
823
- var import_vue16 = require("vue");
824
-
825
- // src/vue/components/Folder.ts
826
- var import_vue2 = require("vue");
827
- var import_motion_v = require("motion-v");
828
-
829
- // src/icons.ts
830
- var ICON_CHEVRON = "M6 9.5L12 15.5L18 9.5";
831
- var ICON_CHECK = "M5 12.75L10 19L19 5";
832
- var ICON_CLIPBOARD = {
833
- board: "M8 6C8 4.34315 9.34315 3 11 3H13C14.6569 3 16 4.34315 16 6V7H8V6Z",
834
- 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",
835
- 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"
836
- };
837
- var ICON_ADD_PRESET = [
838
- "M4 6H20",
839
- "M4 12H10",
840
- "M15 15L21 15",
841
- "M18 12V18",
842
- "M4 18H10"
843
- ];
844
- var ICON_TRASH = [
845
- "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",
846
- "M10 11V16",
847
- "M14 11V16",
848
- "M3.5 6H20.5",
849
- "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"
850
- ];
851
- var ICON_PANEL = {
852
- 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",
853
- circles: [
854
- { cx: "6", cy: "8", r: "0.998596" },
855
- { cx: "10.4999", cy: "3.5", r: "0.998657" },
856
- { cx: "9.75015", cy: "12.5", r: "0.997986" }
857
- ]
858
- };
859
-
860
- // src/vue/components/Folder.ts
861
- var Folder = (0, import_vue2.defineComponent)({
862
- name: "DialKitFolder",
863
- props: {
864
- title: { type: String, required: true },
865
- defaultOpen: { type: Boolean, default: true },
866
- isRoot: { type: Boolean, default: false },
867
- inline: { type: Boolean, default: false },
868
- toolbar: {
869
- type: null,
870
- required: false,
871
- default: null
872
- },
873
- panelHeightOffset: {
874
- type: Number,
875
- default: 10
876
- }
877
- },
878
- emits: ["openChange"],
879
- setup(props, { emit, slots }) {
880
- const isOpen = (0, import_vue2.ref)(props.defaultOpen);
881
- const isCollapsed = (0, import_vue2.ref)(!props.defaultOpen);
882
- const contentRef = (0, import_vue2.ref)(null);
883
- const contentHeight = (0, import_vue2.ref)(void 0);
884
- const windowHeight = (0, import_vue2.ref)(typeof window !== "undefined" ? window.innerHeight : 800);
885
- let resizeHandler = null;
886
- if (props.isRoot) {
887
- resizeHandler = () => {
888
- windowHeight.value = window.innerHeight;
889
- };
890
- window.addEventListener("resize", resizeHandler);
891
- }
892
- (0, import_vue2.onUnmounted)(() => {
893
- if (resizeHandler) window.removeEventListener("resize", resizeHandler);
894
- });
895
- const handleToggle = () => {
896
- if (props.inline && props.isRoot) return;
897
- const next = !isOpen.value;
898
- isOpen.value = next;
899
- isCollapsed.value = !next;
900
- emit("openChange", next);
901
- };
902
- let ro = null;
903
- (0, import_vue2.onMounted)(() => {
904
- if (!props.isRoot || typeof ResizeObserver === "undefined") return;
905
- const el = contentRef.value;
906
- if (!el) return;
907
- ro = new ResizeObserver(() => {
908
- if (isOpen.value) {
909
- const next = el.offsetHeight;
910
- if (contentHeight.value !== next) {
911
- contentHeight.value = next;
912
- }
913
- }
914
- });
915
- ro.observe(el);
916
- if (isOpen.value) {
917
- contentHeight.value = el.offsetHeight;
918
- }
919
- });
920
- (0, import_vue2.onUnmounted)(() => {
921
- ro?.disconnect();
922
- });
923
- const renderHeader = () => (0, import_vue2.h)("div", {
924
- class: `dialkit-folder-header ${props.isRoot ? "dialkit-panel-header" : ""}`,
925
- onClick: handleToggle
926
- }, [
927
- (0, import_vue2.h)("div", { class: "dialkit-folder-header-top" }, [
928
- props.isRoot ? isOpen.value ? (0, import_vue2.h)("div", { class: "dialkit-folder-title-row" }, [
929
- (0, import_vue2.h)("span", { class: "dialkit-folder-title dialkit-folder-title-root" }, props.title)
930
- ]) : null : (0, import_vue2.h)("div", { class: "dialkit-folder-title-row" }, [
931
- (0, import_vue2.h)("span", { class: "dialkit-folder-title" }, props.title)
932
- ]),
933
- props.isRoot && !props.inline ? (0, import_vue2.h)("svg", { class: "dialkit-panel-icon", viewBox: "0 0 16 16", fill: "none" }, [
934
- (0, import_vue2.h)("path", {
935
- opacity: "0.5",
936
- d: ICON_PANEL.path,
937
- fill: "currentColor"
938
- }),
939
- ...ICON_PANEL.circles.map((c) => (0, import_vue2.h)("circle", { cx: c.cx, cy: c.cy, r: c.r, fill: "currentColor", stroke: "currentColor", "stroke-width": "1.25" }))
940
- ]) : null,
941
- !props.isRoot ? (0, import_vue2.h)(import_motion_v.motion.svg, {
942
- class: "dialkit-folder-icon",
943
- viewBox: "0 0 24 24",
944
- fill: "none",
945
- stroke: "currentColor",
946
- "stroke-width": "2.5",
947
- "stroke-linecap": "round",
948
- "stroke-linejoin": "round",
949
- initial: false,
950
- animate: { rotate: isOpen.value ? 0 : 180 },
951
- transition: { type: "spring", visualDuration: 0.35, bounce: 0.15 }
952
- }, [(0, import_vue2.h)("path", { d: ICON_CHEVRON })]) : null
953
- ]),
954
- props.isRoot && props.toolbar && isOpen.value ? (0, import_vue2.h)("div", { class: "dialkit-panel-toolbar", onClick: (event) => event.stopPropagation() }, [props.toolbar()]) : null
955
- ]);
956
- const renderChildren = () => (0, import_vue2.h)("div", { class: "dialkit-folder-inner" }, slots.default ? slots.default() : []);
957
- const renderContent = () => {
958
- if (props.isRoot) {
959
- return isOpen.value ? (0, import_vue2.h)("div", { class: "dialkit-folder-content" }, [renderChildren()]) : null;
960
- }
961
- return (0, import_vue2.h)(import_motion_v.AnimatePresence, { initial: false }, {
962
- default: () => isOpen.value ? [(0, import_vue2.h)(import_motion_v.motion.div, {
963
- key: "dialkit-folder-content",
964
- class: "dialkit-folder-content",
965
- initial: { height: 0, opacity: 0 },
966
- animate: { height: "auto", opacity: 1 },
967
- exit: { height: 0, opacity: 0 },
968
- transition: { type: "spring", visualDuration: 0.35, bounce: 0.1 },
969
- style: { clipPath: "inset(0 -20px)" }
970
- }, [renderChildren()])] : []
971
- });
972
- };
973
- const folderContent = () => (0, import_vue2.h)("div", {
974
- ref: props.isRoot ? contentRef : void 0,
975
- class: `dialkit-folder ${props.isRoot ? "dialkit-folder-root" : ""}`,
976
- "data-open": String(isOpen.value)
977
- }, [
978
- renderHeader(),
979
- renderContent()
980
- ]);
981
- return () => {
982
- if (props.isRoot) {
983
- if (props.inline) {
984
- return (0, import_vue2.h)("div", { class: "dialkit-panel-inner dialkit-panel-inline" }, [folderContent()]);
985
- }
986
- const panelStyle = isOpen.value ? {
987
- width: 280,
988
- height: contentHeight.value !== void 0 ? Math.min(contentHeight.value + props.panelHeightOffset, windowHeight.value - 32) : "auto",
989
- borderRadius: 14,
990
- boxShadow: "var(--dial-shadow)",
991
- cursor: void 0,
992
- overflowY: "auto"
993
- } : {
994
- width: 42,
995
- height: 42,
996
- borderRadius: 21,
997
- boxShadow: "var(--dial-shadow-collapsed)",
998
- overflow: "hidden",
999
- cursor: "pointer"
1000
- };
1001
- return (0, import_vue2.h)(import_motion_v.motion.div, {
1002
- class: "dialkit-panel-inner",
1003
- style: panelStyle,
1004
- onClick: !isOpen.value ? handleToggle : void 0,
1005
- "data-collapsed": String(isCollapsed.value),
1006
- whilePress: !isOpen.value ? { scale: 0.9 } : void 0,
1007
- transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 }
1008
- }, [folderContent()]);
1009
- }
1010
- return folderContent();
1011
- };
1012
- }
1013
- });
1014
-
1015
- // src/vue/components/Panel.ts
1016
- var import_vue15 = require("vue");
1017
- var import_motion_v5 = require("motion-v");
1018
-
1019
- // src/vue/components/Slider.ts
1020
- var import_vue3 = require("vue");
1021
- var import_motion_v2 = require("motion-v");
1022
-
1023
- // src/shortcut-utils.ts
1024
- function decimalsForStep(step) {
1025
- const s = step.toString();
1026
- const dot = s.indexOf(".");
1027
- return dot === -1 ? 0 : s.length - dot - 1;
1028
- }
1029
- function roundValue(val, step) {
1030
- const raw = Math.round(val / step) * step;
1031
- return parseFloat(raw.toFixed(decimalsForStep(step)));
1032
- }
1033
- function getEffectiveStep(control, shortcut) {
1034
- const min = control.min ?? 0;
1035
- const max = control.max ?? 1;
1036
- const range = max - min;
1037
- const mode = shortcut.mode ?? "normal";
1038
- return mode === "fine" ? range * 0.01 : mode === "coarse" ? range * 0.1 : control.step ?? 1;
1039
- }
1040
- function applySliderDelta(panelId, path, control, effectiveStep, direction) {
1041
- const currentValue = DialStore.getValue(panelId, path);
1042
- const min = control.min ?? 0;
1043
- const max = control.max ?? 1;
1044
- const newValue = Math.max(min, Math.min(max, currentValue + direction * effectiveStep));
1045
- DialStore.updateValue(panelId, path, roundValue(newValue, effectiveStep));
1046
- }
1047
- function snapToDecile(rawValue, min, max) {
1048
- const normalized = (rawValue - min) / (max - min);
1049
- const nearest = Math.round(normalized * 10) / 10;
1050
- if (Math.abs(normalized - nearest) <= 0.03125) {
1051
- return min + nearest * (max - min);
1052
- }
1053
- return rawValue;
1054
- }
1055
- function isInputFocused() {
1056
- const el = document.activeElement;
1057
- if (!el) return false;
1058
- const tag = el.tagName;
1059
- if (tag === "INPUT" || tag === "TEXTAREA") return true;
1060
- if (el.contentEditable === "true") return true;
1061
- return false;
1062
- }
1063
- function getActiveModifier(e) {
1064
- if (e.altKey) return "alt";
1065
- if (e.shiftKey) return "shift";
1066
- if (e.metaKey) return "meta";
1067
- return void 0;
1068
- }
1069
- function findControl(controls, path) {
1070
- for (const control of controls) {
1071
- if (control.path === path) return control;
1072
- if (control.type === "folder" && control.children) {
1073
- const found = findControl(control.children, path);
1074
- if (found) return found;
1075
- }
1076
- }
1077
- return null;
1078
- }
1079
- var DRAG_SENSITIVITY = 4;
1080
- function formatInteractionLabel(interaction) {
1081
- switch (interaction) {
1082
- case "drag":
1083
- return "Drag";
1084
- case "move":
1085
- return "Move";
1086
- case "scroll-only":
1087
- return "Scroll";
1088
- default:
1089
- return "Scroll";
1090
- }
1091
- }
1092
- function formatSliderShortcut(sc) {
1093
- const interaction = sc.interaction ?? "scroll";
1094
- const actionLabel = formatInteractionLabel(interaction);
1095
- if (!sc.key) return actionLabel;
1096
- const mod = formatModifier(sc.modifier);
1097
- return `${mod}${sc.key.toUpperCase()}+${actionLabel}`;
1098
- }
1099
- function formatToggleShortcut(sc) {
1100
- if (!sc.key) return "Press";
1101
- const mod = formatModifier(sc.modifier);
1102
- return `${mod}${sc.key.toUpperCase()}`;
1103
- }
1104
- function formatModifier(modifier) {
1105
- return modifier === "alt" ? "\u2325" : modifier === "shift" ? "\u21E7" : modifier === "meta" ? "\u2318" : "";
1106
- }
1107
-
1108
- // src/vue/components/Slider.ts
1109
- var CLICK_THRESHOLD = 3;
1110
- var DEAD_ZONE = 32;
1111
- var MAX_CURSOR_RANGE = 200;
1112
- var MAX_STRETCH = 8;
1113
- var Slider = (0, import_vue3.defineComponent)({
1114
- name: "DialKitSlider",
1115
- props: {
1116
- label: { type: String, required: true },
1117
- value: { type: Number, required: true },
1118
- min: { type: Number, required: false },
1119
- max: { type: Number, required: false },
1120
- step: { type: Number, required: false },
1121
- unit: { type: String, required: false },
1122
- shortcut: { type: Object, default: void 0 },
1123
- shortcutActive: { type: Boolean, default: false }
1124
- },
1125
- emits: ["change"],
1126
- setup(props, { emit }) {
1127
- const min = (0, import_vue3.computed)(() => props.min ?? 0);
1128
- const max = (0, import_vue3.computed)(() => props.max ?? 1);
1129
- const step = (0, import_vue3.computed)(() => props.step ?? 0.01);
1130
- const wrapperRef = (0, import_vue3.ref)(null);
1131
- const trackRef = (0, import_vue3.ref)(null);
1132
- const fillRef = (0, import_vue3.ref)(null);
1133
- const handleRef = (0, import_vue3.ref)(null);
1134
- const labelRef = (0, import_vue3.ref)(null);
1135
- const valueSpanRef = (0, import_vue3.ref)(null);
1136
- const inputRef = (0, import_vue3.ref)(null);
1137
- const isInteracting = (0, import_vue3.ref)(false);
1138
- const isDragging = (0, import_vue3.ref)(false);
1139
- const isHovered = (0, import_vue3.ref)(false);
1140
- const isValueHovered = (0, import_vue3.ref)(false);
1141
- const isValueEditable = (0, import_vue3.ref)(false);
1142
- const showInput = (0, import_vue3.ref)(false);
1143
- const inputValue = (0, import_vue3.ref)("");
1144
- const fillPercent = (0, import_motion_v2.motionValue)((props.value - min.value) / (max.value - min.value) * 100);
1145
- const rubberStretchPx = (0, import_motion_v2.motionValue)(0);
1146
- const handleOpacityMv = (0, import_motion_v2.motionValue)(0);
1147
- const handleScaleXMv = (0, import_motion_v2.motionValue)(0.25);
1148
- const handleScaleYMv = (0, import_motion_v2.motionValue)(1);
1149
- const percentage = (0, import_vue3.computed)(() => (props.value - min.value) / (max.value - min.value) * 100);
1150
- const isActive = (0, import_vue3.computed)(() => isInteracting.value || isHovered.value);
1151
- const displayValue = (0, import_vue3.computed)(() => props.value.toFixed(decimalsForStep(step.value)));
1152
- let pointerDownPos = null;
1153
- let isClickFlag = true;
1154
- let wrapperRect = null;
1155
- let scaleVal = 1;
1156
- let hoverTimeout = null;
1157
- let snapAnim = null;
1158
- let rubberAnim = null;
1159
- let handleOpacityAnim = null;
1160
- let handleScaleXAnim = null;
1161
- let handleScaleYAnim = null;
1162
- const applyFillStyles = (pct) => {
1163
- if (fillRef.value) fillRef.value.style.width = `${pct}%`;
1164
- if (handleRef.value) handleRef.value.style.left = `max(5px, calc(${pct}% - 9px))`;
1165
- };
1166
- const applyRubberStyles = (stretch) => {
1167
- if (!trackRef.value) return;
1168
- trackRef.value.style.width = `calc(100% + ${Math.abs(stretch)}px)`;
1169
- trackRef.value.style.transform = `translateX(${stretch < 0 ? stretch : 0}px)`;
1170
- };
1171
- const applyHandleVisualStyles = () => {
1172
- if (!handleRef.value) return;
1173
- handleRef.value.style.opacity = String(handleOpacityMv.get());
1174
- handleRef.value.style.transform = `translateY(-50%) scaleX(${handleScaleXMv.get()}) scaleY(${handleScaleYMv.get()})`;
1175
- };
1176
- const positionToValue = (clientX) => {
1177
- if (!wrapperRect) return props.value;
1178
- const screenX = clientX - wrapperRect.left;
1179
- const sceneX = screenX / scaleVal;
1180
- const nativeWidth = wrapperRef.value ? wrapperRef.value.offsetWidth : wrapperRect.width;
1181
- const pct = Math.max(0, Math.min(1, sceneX / nativeWidth));
1182
- const rawValue = min.value + pct * (max.value - min.value);
1183
- return Math.max(min.value, Math.min(max.value, rawValue));
1184
- };
1185
- const percentFromValue = (value) => (value - min.value) / (max.value - min.value) * 100;
1186
- const computeRubberStretch = (clientX, sign) => {
1187
- if (!wrapperRect) return 0;
1188
- const distancePast = sign < 0 ? wrapperRect.left - clientX : clientX - wrapperRect.right;
1189
- const overflow = Math.max(0, distancePast - DEAD_ZONE);
1190
- return sign * MAX_STRETCH * Math.sqrt(Math.min(overflow / MAX_CURSOR_RANGE, 1));
1191
- };
1192
- const leftThreshold = () => {
1193
- const HANDLE_BUFFER = 8;
1194
- const LABEL_CSS_LEFT = 10;
1195
- const trackWidth = wrapperRef.value?.offsetWidth;
1196
- if (trackWidth && labelRef.value) {
1197
- return (LABEL_CSS_LEFT + labelRef.value.offsetWidth + HANDLE_BUFFER) / trackWidth * 100;
1198
- }
1199
- return 30;
1200
- };
1201
- const rightThreshold = () => {
1202
- const HANDLE_BUFFER = 8;
1203
- const VALUE_CSS_RIGHT = 10;
1204
- const trackWidth = wrapperRef.value?.offsetWidth;
1205
- if (trackWidth && valueSpanRef.value) {
1206
- return (trackWidth - VALUE_CSS_RIGHT - valueSpanRef.value.offsetWidth - HANDLE_BUFFER) / trackWidth * 100;
1207
- }
1208
- return 78;
1209
- };
1210
- const valueDodge = () => percentage.value < leftThreshold() || percentage.value > rightThreshold();
1211
- const handleOpacity = () => {
1212
- if (!isActive.value) return 0;
1213
- if (valueDodge()) return 0.1;
1214
- if (isDragging.value) return 0.9;
1215
- return 0.5;
1216
- };
1217
- const animateHandleState = () => {
1218
- const targetOpacity = handleOpacity();
1219
- const targetScaleX = isActive.value ? 1 : 0.25;
1220
- const targetScaleY = isActive.value && valueDodge() ? 0.75 : 1;
1221
- handleOpacityAnim?.stop();
1222
- handleScaleXAnim?.stop();
1223
- handleScaleYAnim?.stop();
1224
- handleOpacityAnim = (0, import_motion_v2.animate)(handleOpacityMv, targetOpacity, { duration: 0.15 });
1225
- handleScaleXAnim = (0, import_motion_v2.animate)(handleScaleXMv, targetScaleX, {
1226
- type: "spring",
1227
- visualDuration: 0.25,
1228
- bounce: 0.15
1229
- });
1230
- handleScaleYAnim = (0, import_motion_v2.animate)(handleScaleYMv, targetScaleY, {
1231
- type: "spring",
1232
- visualDuration: 0.2,
1233
- bounce: 0.1
1234
- });
1235
- };
1236
- const handlePointerDown = (event) => {
1237
- if (showInput.value) return;
1238
- event.preventDefault();
1239
- event.currentTarget.setPointerCapture(event.pointerId);
1240
- pointerDownPos = { x: event.clientX, y: event.clientY };
1241
- isClickFlag = true;
1242
- isInteracting.value = true;
1243
- if (wrapperRef.value) {
1244
- wrapperRect = wrapperRef.value.getBoundingClientRect();
1245
- scaleVal = wrapperRect.width / wrapperRef.value.offsetWidth;
1246
- }
1247
- };
1248
- const handlePointerMove = (event) => {
1249
- if (!isInteracting.value || !pointerDownPos) return;
1250
- const dx = event.clientX - pointerDownPos.x;
1251
- const dy = event.clientY - pointerDownPos.y;
1252
- const distance = Math.sqrt(dx * dx + dy * dy);
1253
- if (isClickFlag && distance > CLICK_THRESHOLD) {
1254
- isClickFlag = false;
1255
- isDragging.value = true;
1256
- }
1257
- if (!isClickFlag) {
1258
- if (wrapperRect) {
1259
- if (event.clientX < wrapperRect.left) {
1260
- rubberStretchPx.jump(computeRubberStretch(event.clientX, -1));
1261
- } else if (event.clientX > wrapperRect.right) {
1262
- rubberStretchPx.jump(computeRubberStretch(event.clientX, 1));
1263
- } else {
1264
- rubberStretchPx.jump(0);
1265
- }
1266
- }
1267
- const nextValue = positionToValue(event.clientX);
1268
- const nextPct = percentFromValue(nextValue);
1269
- if (snapAnim) {
1270
- snapAnim.stop();
1271
- snapAnim = null;
1272
- }
1273
- fillPercent.jump(nextPct);
1274
- emit("change", roundValue(nextValue, step.value));
1275
- }
1276
- };
1277
- const handlePointerUp = (event) => {
1278
- if (!isInteracting.value) return;
1279
- if (isClickFlag) {
1280
- const rawValue = positionToValue(event.clientX);
1281
- const discreteSteps2 = (max.value - min.value) / step.value;
1282
- const snappedValue = discreteSteps2 <= 10 ? Math.max(min.value, Math.min(max.value, min.value + Math.round((rawValue - min.value) / step.value) * step.value)) : snapToDecile(rawValue, min.value, max.value);
1283
- const nextPct = percentFromValue(snappedValue);
1284
- snapAnim?.stop();
1285
- snapAnim = (0, import_motion_v2.animate)(fillPercent, nextPct, {
1286
- type: "spring",
1287
- stiffness: 300,
1288
- damping: 25,
1289
- mass: 0.8,
1290
- onComplete: () => {
1291
- snapAnim = null;
1292
- }
1293
- });
1294
- emit("change", roundValue(snappedValue, step.value));
1295
- }
1296
- if (rubberStretchPx.get() !== 0) {
1297
- rubberAnim?.stop();
1298
- rubberAnim = (0, import_motion_v2.animate)(rubberStretchPx, 0, {
1299
- type: "spring",
1300
- visualDuration: 0.35,
1301
- bounce: 0.15
1302
- });
1303
- }
1304
- isInteracting.value = false;
1305
- isDragging.value = false;
1306
- pointerDownPos = null;
1307
- };
1308
- const handlePointerCancel = () => {
1309
- if (!isInteracting.value) return;
1310
- isInteracting.value = false;
1311
- isDragging.value = false;
1312
- rubberStretchPx.jump(0);
1313
- pointerDownPos = null;
1314
- };
1315
- const handleInputSubmit = () => {
1316
- const parsed = parseFloat(inputValue.value);
1317
- if (!Number.isNaN(parsed)) {
1318
- const clamped = Math.max(min.value, Math.min(max.value, parsed));
1319
- emit("change", roundValue(clamped, step.value));
1320
- }
1321
- showInput.value = false;
1322
- isValueHovered.value = false;
1323
- isValueEditable.value = false;
1324
- };
1325
- const handleValueClick = (event) => {
1326
- if (!isValueEditable.value) return;
1327
- event.stopPropagation();
1328
- event.preventDefault();
1329
- showInput.value = true;
1330
- inputValue.value = props.value.toFixed(decimalsForStep(step.value));
1331
- };
1332
- const handleInputKeydown = (event) => {
1333
- if (event.key === "Enter") {
1334
- handleInputSubmit();
1335
- } else if (event.key === "Escape") {
1336
- showInput.value = false;
1337
- isValueHovered.value = false;
1338
- }
1339
- };
1340
- (0, import_vue3.watch)(() => props.value, () => {
1341
- if (!isInteracting.value && !snapAnim) {
1342
- fillPercent.jump(percentage.value);
1343
- }
1344
- });
1345
- (0, import_vue3.watch)([isInteracting, isHovered, isDragging, () => props.value], () => {
1346
- animateHandleState();
1347
- });
1348
- (0, import_vue3.watch)([isValueHovered, showInput, isValueEditable], () => {
1349
- if (hoverTimeout) {
1350
- clearTimeout(hoverTimeout);
1351
- hoverTimeout = null;
1352
- }
1353
- if (isValueHovered.value && !showInput.value && !isValueEditable.value) {
1354
- hoverTimeout = setTimeout(() => {
1355
- isValueEditable.value = true;
1356
- animateHandleState();
1357
- }, 800);
1358
- } else if (!isValueHovered.value && !showInput.value) {
1359
- isValueEditable.value = false;
1360
- }
1361
- });
1362
- (0, import_vue3.watch)(showInput, async (visible) => {
1363
- if (!visible) return;
1364
- await (0, import_vue3.nextTick)();
1365
- inputRef.value?.focus();
1366
- inputRef.value?.select();
1367
- });
1368
- const discreteSteps = (0, import_vue3.computed)(() => (max.value - min.value) / step.value);
1369
- const hashMarks = (0, import_vue3.computed)(() => {
1370
- const marks = [];
1371
- if (discreteSteps.value <= 10) {
1372
- const count = Math.max(0, Math.floor(discreteSteps.value) - 1);
1373
- for (let i = 0; i < count; i += 1) {
1374
- const pct = (i + 1) * step.value / (max.value - min.value) * 100;
1375
- marks.push((0, import_vue3.h)("div", { class: "dialkit-slider-hashmark", style: { left: `${pct}%` } }));
1376
- }
1377
- return marks;
1378
- }
1379
- for (let i = 0; i < 9; i += 1) {
1380
- const pct = (i + 1) * 10;
1381
- marks.push((0, import_vue3.h)("div", { class: "dialkit-slider-hashmark", style: { left: `${pct}%` } }));
1382
- }
1383
- return marks;
1384
- });
1385
- let unsubFill = null;
1386
- let unsubRubber = null;
1387
- let unsubHandleOpacity = null;
1388
- let unsubHandleScaleX = null;
1389
- let unsubHandleScaleY = null;
1390
- (0, import_vue3.onMounted)(() => {
1391
- unsubFill = fillPercent.on("change", applyFillStyles);
1392
- unsubRubber = rubberStretchPx.on("change", applyRubberStyles);
1393
- unsubHandleOpacity = handleOpacityMv.on("change", applyHandleVisualStyles);
1394
- unsubHandleScaleX = handleScaleXMv.on("change", applyHandleVisualStyles);
1395
- unsubHandleScaleY = handleScaleYMv.on("change", applyHandleVisualStyles);
1396
- applyFillStyles(fillPercent.get());
1397
- applyRubberStyles(rubberStretchPx.get());
1398
- applyHandleVisualStyles();
1399
- animateHandleState();
1400
- });
1401
- (0, import_vue3.onUnmounted)(() => {
1402
- if (hoverTimeout) clearTimeout(hoverTimeout);
1403
- snapAnim?.stop();
1404
- rubberAnim?.stop();
1405
- handleOpacityAnim?.stop();
1406
- handleScaleXAnim?.stop();
1407
- handleScaleYAnim?.stop();
1408
- unsubFill?.();
1409
- unsubRubber?.();
1410
- unsubHandleOpacity?.();
1411
- unsubHandleScaleX?.();
1412
- unsubHandleScaleY?.();
1413
- });
1414
- return () => (0, import_vue3.h)("div", { ref: wrapperRef, class: "dialkit-slider-wrapper" }, [
1415
- (0, import_vue3.h)("div", {
1416
- ref: trackRef,
1417
- class: `dialkit-slider ${isActive.value ? "dialkit-slider-active" : ""}`,
1418
- onPointerdown: handlePointerDown,
1419
- onPointermove: handlePointerMove,
1420
- onPointerup: handlePointerUp,
1421
- onPointercancel: handlePointerCancel,
1422
- onMouseenter: () => {
1423
- isHovered.value = true;
1424
- animateHandleState();
1425
- },
1426
- onMouseleave: () => {
1427
- isHovered.value = false;
1428
- animateHandleState();
1429
- }
1430
- }, [
1431
- (0, import_vue3.h)("div", { class: "dialkit-slider-hashmarks" }, hashMarks.value),
1432
- (0, import_vue3.h)("div", {
1433
- ref: fillRef,
1434
- class: "dialkit-slider-fill",
1435
- style: {
1436
- width: `${fillPercent.get()}%`
1437
- }
1438
- }),
1439
- (0, import_vue3.h)("div", {
1440
- ref: handleRef,
1441
- class: "dialkit-slider-handle",
1442
- style: {
1443
- left: `max(5px, calc(${fillPercent.get()}% - 9px))`,
1444
- transform: "translateY(-50%) scaleX(0.25) scaleY(1)",
1445
- opacity: 0
1446
- }
1447
- }),
1448
- (0, import_vue3.h)("span", { ref: labelRef, class: "dialkit-slider-label" }, [
1449
- props.label,
1450
- props.shortcut ? (0, import_vue3.h)("span", {
1451
- class: `dialkit-shortcut-pill${props.shortcutActive ? " dialkit-shortcut-pill-active" : ""}`
1452
- }, formatSliderShortcut(props.shortcut)) : null
1453
- ]),
1454
- showInput.value ? (0, import_vue3.h)("input", {
1455
- ref: inputRef,
1456
- type: "text",
1457
- class: "dialkit-slider-input",
1458
- value: inputValue.value,
1459
- onInput: (event) => {
1460
- inputValue.value = event.target.value;
1461
- },
1462
- onKeydown: handleInputKeydown,
1463
- onBlur: handleInputSubmit,
1464
- onClick: (event) => event.stopPropagation(),
1465
- onMousedown: (event) => event.stopPropagation()
1466
- }) : (0, import_vue3.h)("span", {
1467
- ref: valueSpanRef,
1468
- class: `dialkit-slider-value ${isValueEditable.value ? "dialkit-slider-value-editable" : ""}`,
1469
- onMouseenter: () => {
1470
- isValueHovered.value = true;
1471
- },
1472
- onMouseleave: () => {
1473
- isValueHovered.value = false;
1474
- },
1475
- onClick: handleValueClick,
1476
- onMousedown: (event) => {
1477
- if (isValueEditable.value) event.stopPropagation();
1478
- },
1479
- style: { cursor: isValueEditable.value ? "text" : "default" }
1480
- }, displayValue.value)
1481
- ])
1482
- ]);
1483
- }
1484
- });
1485
-
1486
- // src/vue/components/Toggle.ts
1487
- var import_vue5 = require("vue");
1488
-
1489
- // src/vue/components/SegmentedControl.ts
1490
- var import_vue4 = require("vue");
1491
- var import_motion = require("motion");
1492
- var SegmentedControl = (0, import_vue4.defineComponent)({
1493
- name: "DialKitSegmentedControl",
1494
- props: {
1495
- options: {
1496
- type: Array,
1497
- required: true
1498
- },
1499
- value: {
1500
- type: String,
1501
- required: true
1502
- }
1503
- },
1504
- emits: ["change"],
1505
- setup(props, { emit }) {
1506
- const containerRef = (0, import_vue4.ref)(null);
1507
- const pillRef = (0, import_vue4.ref)(null);
1508
- const buttonRefs = /* @__PURE__ */ new Map();
1509
- const pillReady = (0, import_vue4.ref)(false);
1510
- let hasAnimated = false;
1511
- let pillAnim = null;
1512
- const measurePill = () => {
1513
- const button = buttonRefs.get(props.value);
1514
- const container = containerRef.value;
1515
- if (!button || !container) return null;
1516
- const containerRect = container.getBoundingClientRect();
1517
- const buttonRect = button.getBoundingClientRect();
1518
- return {
1519
- left: buttonRect.left - containerRect.left,
1520
- width: buttonRect.width
1521
- };
1522
- };
1523
- const setPillImmediate = (left, width) => {
1524
- if (!pillRef.value) return;
1525
- pillRef.value.style.left = `${left}px`;
1526
- pillRef.value.style.width = `${width}px`;
1527
- pillRef.value.style.visibility = "visible";
1528
- };
1529
- const updatePill = (shouldAnimate) => {
1530
- const next = measurePill();
1531
- if (!next) return;
1532
- if (!pillReady.value) {
1533
- setPillImmediate(next.left, next.width);
1534
- pillReady.value = true;
1535
- return;
1536
- }
1537
- if (!shouldAnimate || !hasAnimated || !pillRef.value) {
1538
- pillAnim?.stop();
1539
- pillAnim = null;
1540
- setPillImmediate(next.left, next.width);
1541
- return;
1542
- }
1543
- pillAnim?.stop();
1544
- pillAnim = (0, import_motion.animate)(
1545
- pillRef.value,
1546
- {
1547
- left: next.left,
1548
- width: next.width
1549
- },
1550
- {
1551
- type: "spring",
1552
- visualDuration: 0.2,
1553
- bounce: 0.15,
1554
- onComplete: () => {
1555
- pillAnim = null;
1556
- }
1557
- }
1558
- );
1559
- };
1560
- let ro;
1561
- (0, import_vue4.onMounted)(() => {
1562
- (0, import_vue4.nextTick)(() => {
1563
- updatePill(false);
1564
- hasAnimated = true;
1565
- });
1566
- if (typeof ResizeObserver !== "undefined" && containerRef.value) {
1567
- ro = new ResizeObserver(() => updatePill(false));
1568
- ro.observe(containerRef.value);
1569
- }
1570
- });
1571
- (0, import_vue4.onUnmounted)(() => {
1572
- pillAnim?.stop();
1573
- ro?.disconnect();
1574
- });
1575
- (0, import_vue4.watch)(
1576
- () => props.value,
1577
- () => {
1578
- updatePill(true);
1579
- },
1580
- { flush: "post" }
1581
- );
1582
- return () => (0, import_vue4.h)("div", { ref: containerRef, class: "dialkit-segmented" }, [
1583
- (0, import_vue4.h)("div", {
1584
- ref: pillRef,
1585
- class: "dialkit-segmented-pill",
1586
- style: {
1587
- left: "0px",
1588
- width: "0px",
1589
- visibility: pillReady.value ? "visible" : "hidden"
1590
- }
1591
- }),
1592
- ...props.options.map((option) => (0, import_vue4.h)("button", {
1593
- ref: ((el) => {
1594
- if (el instanceof HTMLElement) {
1595
- buttonRefs.set(option.value, el);
1596
- return;
1597
- }
1598
- buttonRefs.delete(option.value);
1599
- }),
1600
- class: "dialkit-segmented-button",
1601
- "data-active": String(props.value === option.value),
1602
- onClick: () => emit("change", option.value)
1603
- }, option.label))
1604
- ]);
1605
- }
1606
- });
1607
-
1608
- // src/vue/components/Toggle.ts
1609
- var Toggle = (0, import_vue5.defineComponent)({
1610
- name: "DialKitToggle",
1611
- props: {
1612
- label: { type: String, required: true },
1613
- checked: { type: Boolean, required: true },
1614
- shortcut: { type: Object, default: void 0 },
1615
- shortcutActive: { type: Boolean, default: false }
1616
- },
1617
- emits: ["change"],
1618
- setup(props, { emit }) {
1619
- return () => (0, import_vue5.h)("div", { class: "dialkit-labeled-control" }, [
1620
- (0, import_vue5.h)("span", { class: "dialkit-labeled-control-label" }, [
1621
- props.label,
1622
- props.shortcut ? (0, import_vue5.h)("span", {
1623
- class: `dialkit-shortcut-pill${props.shortcutActive ? " dialkit-shortcut-pill-active" : ""}`
1624
- }, formatToggleShortcut(props.shortcut)) : null
1625
- ]),
1626
- (0, import_vue5.h)(SegmentedControl, {
1627
- options: [
1628
- { value: "off", label: "Off" },
1629
- { value: "on", label: "On" }
1630
- ],
1631
- value: props.checked ? "on" : "off",
1632
- onChange: (value) => emit("change", value === "on")
1633
- })
1634
- ]);
1635
- }
1636
- });
1637
-
1638
- // src/vue/components/SpringControl.ts
1639
- var import_vue7 = require("vue");
1640
-
1641
- // src/vue/components/SpringVisualization.ts
1642
- var import_vue6 = require("vue");
1643
- function generateSpringCurve(stiffness, damping, mass, duration) {
1644
- const points = [];
1645
- const steps = 100;
1646
- const dt = duration / steps;
1647
- let position = 0;
1648
- let velocity = 0;
1649
- const target = 1;
1650
- for (let i = 0; i <= steps; i += 1) {
1651
- const time = i * dt;
1652
- points.push([time, position]);
1653
- const springForce = -stiffness * (position - target);
1654
- const dampingForce = -damping * velocity;
1655
- const acceleration = (springForce + dampingForce) / mass;
1656
- velocity += acceleration * dt;
1657
- position += velocity * dt;
1658
- }
1659
- return points;
1660
- }
1661
- var SpringVisualization = (0, import_vue6.defineComponent)({
1662
- name: "DialKitSpringVisualization",
1663
- props: {
1664
- spring: {
1665
- type: Object,
1666
- required: true
1667
- },
1668
- isSimpleMode: {
1669
- type: Boolean,
1670
- required: true
1671
- }
1672
- },
1673
- setup(props) {
1674
- const width = 256;
1675
- const height = 140;
1676
- const pathData = (0, import_vue6.computed)(() => {
1677
- let stiffness;
1678
- let damping;
1679
- let mass;
1680
- if (props.isSimpleMode) {
1681
- const visualDuration = props.spring.visualDuration ?? 0.3;
1682
- const bounce = props.spring.bounce ?? 0.2;
1683
- mass = 1;
1684
- stiffness = (2 * Math.PI / visualDuration) ** 2;
1685
- const dampingRatio = 1 - bounce;
1686
- damping = 2 * dampingRatio * Math.sqrt(stiffness * mass);
1687
- } else {
1688
- stiffness = props.spring.stiffness ?? 400;
1689
- damping = props.spring.damping ?? 17;
1690
- mass = props.spring.mass ?? 1;
1691
- }
1692
- const duration = 2;
1693
- const points = generateSpringCurve(stiffness, damping, mass, duration);
1694
- const values = points.map(([, value]) => value);
1695
- const minValue = Math.min(...values);
1696
- const maxValue = Math.max(...values);
1697
- const valueRange = maxValue - minValue;
1698
- return points.map(([time, value], index) => {
1699
- const x = time / duration * width;
1700
- const normalizedValue = (value - minValue) / (valueRange || 1);
1701
- const y = height - (normalizedValue * height * 0.6 + height * 0.2);
1702
- return `${index === 0 ? "M" : "L"} ${x} ${y}`;
1703
- }).join(" ");
1704
- });
1705
- return () => (0, import_vue6.h)("svg", { viewBox: `0 0 ${width} ${height}`, class: "dialkit-spring-viz" }, [
1706
- ...Array.from({ length: 3 }).flatMap((_, index) => {
1707
- const lineIndex = index + 1;
1708
- const x = width / 4 * lineIndex;
1709
- const y = height / 4 * lineIndex;
1710
- return [
1711
- (0, import_vue6.h)("line", { x1: x, y1: 0, x2: x, y2: height, stroke: "rgba(255, 255, 255, 0.08)", "stroke-width": 1 }),
1712
- (0, import_vue6.h)("line", { x1: 0, y1: y, x2: width, y2: y, stroke: "rgba(255, 255, 255, 0.08)", "stroke-width": 1 })
1713
- ];
1714
- }),
1715
- (0, import_vue6.h)("line", {
1716
- x1: 0,
1717
- y1: height / 2,
1718
- x2: width,
1719
- y2: height / 2,
1720
- stroke: "rgba(255, 255, 255, 0.15)",
1721
- "stroke-width": 1,
1722
- "stroke-dasharray": "4,4"
1723
- }),
1724
- (0, import_vue6.h)("path", {
1725
- d: pathData.value,
1726
- fill: "none",
1727
- stroke: "rgba(255, 255, 255, 0.6)",
1728
- "stroke-width": 2,
1729
- "stroke-linecap": "round",
1730
- "stroke-linejoin": "round"
1731
- })
1732
- ]);
1733
- }
1734
- });
1735
-
1736
- // src/vue/components/SpringControl.ts
1737
- var SpringControl = (0, import_vue7.defineComponent)({
1738
- name: "DialKitSpringControl",
1739
- props: {
1740
- panelId: { type: String, required: true },
1741
- path: { type: String, required: true },
1742
- label: { type: String, required: true },
1743
- spring: {
1744
- type: Object,
1745
- required: true
1746
- }
1747
- },
1748
- emits: ["change"],
1749
- setup(props, { emit }) {
1750
- const mode = (0, import_vue7.ref)(DialStore.getSpringMode(props.panelId, props.path));
1751
- let unsub;
1752
- (0, import_vue7.onMounted)(() => {
1753
- unsub = DialStore.subscribe(props.panelId, () => {
1754
- mode.value = DialStore.getSpringMode(props.panelId, props.path);
1755
- });
1756
- });
1757
- (0, import_vue7.onUnmounted)(() => {
1758
- unsub?.();
1759
- });
1760
- const isSimpleMode = () => mode.value === "simple";
1761
- const cache = {
1762
- simple: props.spring.visualDuration !== void 0 ? { ...props.spring } : { type: "spring", visualDuration: 0.3, bounce: 0.2 },
1763
- advanced: props.spring.stiffness !== void 0 ? { ...props.spring } : { type: "spring", stiffness: 200, damping: 25, mass: 1 }
1764
- };
1765
- const handleModeChange = (nextMode) => {
1766
- if (isSimpleMode()) {
1767
- cache.simple = { ...props.spring };
1768
- } else {
1769
- cache.advanced = { ...props.spring };
1770
- }
1771
- DialStore.updateSpringMode(props.panelId, props.path, nextMode);
1772
- if (nextMode === "simple") {
1773
- emit("change", cache.simple);
1774
- } else {
1775
- emit("change", cache.advanced);
1776
- }
1777
- };
1778
- const handleUpdate = (key, value) => {
1779
- if (isSimpleMode()) {
1780
- const { stiffness, damping, mass, ...rest } = props.spring;
1781
- emit("change", { ...rest, [key]: value });
1782
- } else {
1783
- const { visualDuration, bounce, ...rest } = props.spring;
1784
- emit("change", { ...rest, [key]: value });
1785
- }
1786
- };
1787
- return () => (0, import_vue7.h)(Folder, { title: props.label, defaultOpen: true }, {
1788
- default: () => [
1789
- (0, import_vue7.h)("div", { style: { display: "flex", flexDirection: "column", gap: "6px" } }, [
1790
- (0, import_vue7.h)(SpringVisualization, { spring: props.spring, isSimpleMode: isSimpleMode() }),
1791
- (0, import_vue7.h)("div", { class: "dialkit-labeled-control" }, [
1792
- (0, import_vue7.h)("span", { class: "dialkit-labeled-control-label" }, "Type"),
1793
- (0, import_vue7.h)(SegmentedControl, {
1794
- options: [
1795
- { value: "simple", label: "Time" },
1796
- { value: "advanced", label: "Physics" }
1797
- ],
1798
- value: mode.value,
1799
- onChange: handleModeChange
1800
- })
1801
- ]),
1802
- ...isSimpleMode() ? [
1803
- (0, import_vue7.h)(Slider, {
1804
- label: "Duration",
1805
- value: props.spring.visualDuration ?? 0.3,
1806
- min: 0.1,
1807
- max: 1,
1808
- step: 0.05,
1809
- unit: "s",
1810
- onChange: (next) => handleUpdate("visualDuration", next)
1811
- }),
1812
- (0, import_vue7.h)(Slider, {
1813
- label: "Bounce",
1814
- value: props.spring.bounce ?? 0.2,
1815
- min: 0,
1816
- max: 1,
1817
- step: 0.05,
1818
- onChange: (next) => handleUpdate("bounce", next)
1819
- })
1820
- ] : [
1821
- (0, import_vue7.h)(Slider, {
1822
- label: "Stiffness",
1823
- value: props.spring.stiffness ?? 400,
1824
- min: 1,
1825
- max: 1e3,
1826
- step: 10,
1827
- onChange: (next) => handleUpdate("stiffness", next)
1828
- }),
1829
- (0, import_vue7.h)(Slider, {
1830
- label: "Damping",
1831
- value: props.spring.damping ?? 17,
1832
- min: 1,
1833
- max: 100,
1834
- step: 1,
1835
- onChange: (next) => handleUpdate("damping", next)
1836
- }),
1837
- (0, import_vue7.h)(Slider, {
1838
- label: "Mass",
1839
- value: props.spring.mass ?? 1,
1840
- min: 0.1,
1841
- max: 10,
1842
- step: 0.1,
1843
- onChange: (next) => handleUpdate("mass", next)
1844
- })
1845
- ]
1846
- ])
1847
- ]
1848
- });
1849
- }
1850
- });
1851
-
1852
- // src/vue/components/TransitionControl.ts
1853
- var import_vue9 = require("vue");
1854
-
1855
- // src/vue/components/EasingVisualization.ts
1856
- var import_vue8 = require("vue");
1857
- var EasingVisualization = (0, import_vue8.defineComponent)({
1858
- name: "DialKitEasingVisualization",
1859
- props: {
1860
- easing: {
1861
- type: Object,
1862
- required: true
1863
- }
1864
- },
1865
- setup(props) {
1866
- const size = 200;
1867
- const pad = 10;
1868
- const inner = size - pad * 2;
1869
- const unit = inner / 2;
1870
- const curve = (0, import_vue8.computed)(() => {
1871
- const [x1, y1, x2, y2] = props.easing.ease;
1872
- const toSvg = (nx, ny) => ({
1873
- x: pad + (nx + 0.5) * unit,
1874
- y: pad + (1.5 - ny) * unit
1875
- });
1876
- const start = toSvg(0, 0);
1877
- const end = toSvg(1, 1);
1878
- const p1 = toSvg(x1, y1);
1879
- const p2 = toSvg(x2, y2);
1880
- return `M ${start.x} ${start.y} C ${p1.x} ${p1.y}, ${p2.x} ${p2.y}, ${end.x} ${end.y}`;
1881
- });
1882
- return () => (0, import_vue8.h)("svg", {
1883
- viewBox: `0 0 ${size} ${size}`,
1884
- preserveAspectRatio: "xMidYMid slice",
1885
- class: "dialkit-spring-viz dialkit-easing-viz"
1886
- }, [
1887
- (0, import_vue8.h)("line", {
1888
- x1: pad + (0 + 0.5) * unit,
1889
- y1: pad + (1.5 - 0) * unit,
1890
- x2: pad + (1 + 0.5) * unit,
1891
- y2: pad + (1.5 - 1) * unit,
1892
- stroke: "rgba(255, 255, 255, 0.15)",
1893
- "stroke-width": 1,
1894
- "stroke-dasharray": "4,4"
1895
- }),
1896
- (0, import_vue8.h)("path", {
1897
- d: curve.value,
1898
- fill: "none",
1899
- stroke: "rgba(255, 255, 255, 0.6)",
1900
- "stroke-width": 2,
1901
- "stroke-linecap": "round"
1902
- })
1903
- ]);
1904
- }
1905
- });
1906
-
1907
- // src/vue/components/TransitionControl.ts
1908
- function formatEase(ease) {
1909
- return ease.map((value) => Number(value.toFixed(2))).join(", ");
1910
- }
1911
- function parseEase(value) {
1912
- const parts = value.split(",").map((part) => Number.parseFloat(part.trim()));
1913
- if (parts.length === 4 && parts.every((part) => Number.isFinite(part))) {
1914
- return parts;
1915
- }
1916
- return null;
1917
- }
1918
- var EaseTextInput = (0, import_vue9.defineComponent)({
1919
- name: "DialKitEaseTextInput",
1920
- props: {
1921
- ease: {
1922
- type: Array,
1923
- required: true
1924
- },
1925
- onChange: {
1926
- type: Function,
1927
- required: true
1928
- }
1929
- },
1930
- setup(props) {
1931
- const editing = (0, import_vue9.ref)(false);
1932
- const draft = (0, import_vue9.ref)("");
1933
- const handleFocus = () => {
1934
- draft.value = formatEase(props.ease);
1935
- editing.value = true;
1936
- };
1937
- const handleBlur = () => {
1938
- const parsed = parseEase(draft.value);
1939
- if (parsed) props.onChange(parsed);
1940
- editing.value = false;
1941
- };
1942
- const handleKeydown = (event) => {
1943
- if (event.key === "Enter") {
1944
- event.target.blur();
1945
- }
1946
- };
1947
- return () => (0, import_vue9.h)("div", { class: "dialkit-labeled-control" }, [
1948
- (0, import_vue9.h)("span", { class: "dialkit-labeled-control-label" }, "Ease"),
1949
- (0, import_vue9.h)("input", {
1950
- type: "text",
1951
- class: "dialkit-text-input",
1952
- value: editing.value ? draft.value : formatEase(props.ease),
1953
- spellcheck: false,
1954
- onInput: (event) => {
1955
- draft.value = event.target.value;
1956
- },
1957
- onFocus: handleFocus,
1958
- onBlur: handleBlur,
1959
- onKeydown: handleKeydown
1960
- })
1961
- ]);
1962
- }
1963
- });
1964
- var TransitionControl = (0, import_vue9.defineComponent)({
1965
- name: "DialKitTransitionControl",
1966
- props: {
1967
- panelId: { type: String, required: true },
1968
- path: { type: String, required: true },
1969
- label: { type: String, required: true },
1970
- value: {
1971
- type: Object,
1972
- required: true
1973
- }
1974
- },
1975
- emits: ["change"],
1976
- setup(props, { emit }) {
1977
- const mode = (0, import_vue9.ref)(DialStore.getTransitionMode(props.panelId, props.path));
1978
- let unsub;
1979
- (0, import_vue9.onMounted)(() => {
1980
- unsub = DialStore.subscribe(props.panelId, () => {
1981
- mode.value = DialStore.getTransitionMode(props.panelId, props.path);
1982
- });
1983
- });
1984
- (0, import_vue9.onUnmounted)(() => unsub?.());
1985
- const cache = {
1986
- easing: props.value.type === "easing" ? { ...props.value } : { type: "easing", duration: 0.3, ease: [1, -0.4, 0.5, 1] },
1987
- simple: props.value.type === "spring" && props.value.visualDuration !== void 0 ? { ...props.value } : { type: "spring", visualDuration: 0.3, bounce: 0.2 },
1988
- advanced: props.value.type === "spring" && props.value.stiffness !== void 0 ? { ...props.value } : { type: "spring", stiffness: 200, damping: 25, mass: 1 }
1989
- };
1990
- const spring = () => {
1991
- if (props.value.type === "spring") {
1992
- if (mode.value === "simple") cache.simple = props.value;
1993
- else if (mode.value === "advanced") cache.advanced = props.value;
1994
- return props.value;
1995
- }
1996
- return cache.simple;
1997
- };
1998
- const easing = () => {
1999
- if (props.value.type === "easing") {
2000
- cache.easing = props.value;
2001
- return props.value;
2002
- }
2003
- return cache.easing;
2004
- };
2005
- const handleModeChange = (nextMode) => {
2006
- DialStore.updateTransitionMode(props.panelId, props.path, nextMode);
2007
- if (nextMode === "easing") {
2008
- emit("change", cache.easing);
2009
- } else if (nextMode === "simple") {
2010
- emit("change", cache.simple);
2011
- } else {
2012
- emit("change", cache.advanced);
2013
- }
2014
- };
2015
- const updateEase = (index, value) => {
2016
- const current = easing();
2017
- const next = [...current.ease];
2018
- next[index] = value;
2019
- emit("change", { ...current, ease: next });
2020
- };
2021
- const handleSpringUpdate = (key, value) => {
2022
- const current = spring();
2023
- if (mode.value === "simple") {
2024
- const { stiffness, damping, mass, ...rest } = current;
2025
- emit("change", { ...rest, [key]: value });
2026
- } else {
2027
- const { visualDuration, bounce, ...rest } = current;
2028
- emit("change", { ...rest, [key]: value });
2029
- }
2030
- };
2031
- return () => {
2032
- const isEasing = mode.value === "easing";
2033
- const isSimpleSpring = mode.value === "simple";
2034
- const currentSpring = spring();
2035
- const currentEasing = easing();
2036
- return (0, import_vue9.h)(Folder, { title: props.label, defaultOpen: true }, {
2037
- default: () => [
2038
- (0, import_vue9.h)("div", { style: { display: "flex", flexDirection: "column", gap: "6px" } }, [
2039
- isEasing ? (0, import_vue9.h)(EasingVisualization, { easing: currentEasing }) : (0, import_vue9.h)(SpringVisualization, { spring: currentSpring, isSimpleMode: isSimpleSpring }),
2040
- (0, import_vue9.h)("div", { class: "dialkit-labeled-control" }, [
2041
- (0, import_vue9.h)("span", { class: "dialkit-labeled-control-label" }, "Type"),
2042
- (0, import_vue9.h)(SegmentedControl, {
2043
- options: [
2044
- { value: "easing", label: "Easing" },
2045
- { value: "simple", label: "Time" },
2046
- { value: "advanced", label: "Physics" }
2047
- ],
2048
- value: mode.value,
2049
- onChange: handleModeChange
2050
- })
2051
- ]),
2052
- ...isEasing ? [
2053
- (0, import_vue9.h)(Slider, { label: "x1", value: currentEasing.ease[0], min: 0, max: 1, step: 0.01, onChange: (next) => updateEase(0, next) }),
2054
- (0, import_vue9.h)(Slider, { label: "y1", value: currentEasing.ease[1], min: -1, max: 2, step: 0.01, onChange: (next) => updateEase(1, next) }),
2055
- (0, import_vue9.h)(Slider, { label: "x2", value: currentEasing.ease[2], min: 0, max: 1, step: 0.01, onChange: (next) => updateEase(2, next) }),
2056
- (0, import_vue9.h)(Slider, { label: "y2", value: currentEasing.ease[3], min: -1, max: 2, step: 0.01, onChange: (next) => updateEase(3, next) }),
2057
- (0, import_vue9.h)(Slider, {
2058
- label: "Duration",
2059
- value: currentEasing.duration,
2060
- min: 0.1,
2061
- max: 2,
2062
- step: 0.05,
2063
- unit: "s",
2064
- onChange: (next) => emit("change", { ...currentEasing, duration: next })
2065
- }),
2066
- (0, import_vue9.h)(EaseTextInput, {
2067
- ease: currentEasing.ease,
2068
- onChange: (next) => emit("change", { ...currentEasing, ease: next })
2069
- })
2070
- ] : isSimpleSpring ? [
2071
- (0, import_vue9.h)(Slider, {
2072
- label: "Duration",
2073
- value: currentSpring.visualDuration ?? 0.3,
2074
- min: 0.1,
2075
- max: 1,
2076
- step: 0.05,
2077
- unit: "s",
2078
- onChange: (next) => handleSpringUpdate("visualDuration", next)
2079
- }),
2080
- (0, import_vue9.h)(Slider, {
2081
- label: "Bounce",
2082
- value: currentSpring.bounce ?? 0.2,
2083
- min: 0,
2084
- max: 1,
2085
- step: 0.05,
2086
- onChange: (next) => handleSpringUpdate("bounce", next)
2087
- })
2088
- ] : [
2089
- (0, import_vue9.h)(Slider, {
2090
- label: "Stiffness",
2091
- value: currentSpring.stiffness ?? 400,
2092
- min: 1,
2093
- max: 1e3,
2094
- step: 10,
2095
- onChange: (next) => handleSpringUpdate("stiffness", next)
2096
- }),
2097
- (0, import_vue9.h)(Slider, {
2098
- label: "Damping",
2099
- value: currentSpring.damping ?? 17,
2100
- min: 1,
2101
- max: 100,
2102
- step: 1,
2103
- onChange: (next) => handleSpringUpdate("damping", next)
2104
- }),
2105
- (0, import_vue9.h)(Slider, {
2106
- label: "Mass",
2107
- value: currentSpring.mass ?? 1,
2108
- min: 0.1,
2109
- max: 10,
2110
- step: 0.1,
2111
- onChange: (next) => handleSpringUpdate("mass", next)
2112
- })
2113
- ]
2114
- ])
2115
- ]
2116
- });
2117
- };
2118
- }
2119
- });
2120
-
2121
- // src/vue/components/TextControl.ts
2122
- var import_vue10 = require("vue");
2123
- var textControlInstance = 0;
2124
- var TextControl = (0, import_vue10.defineComponent)({
2125
- name: "DialKitTextControl",
2126
- props: {
2127
- label: { type: String, required: true },
2128
- value: { type: String, required: true },
2129
- placeholder: { type: String, required: false }
2130
- },
2131
- emits: ["change"],
2132
- setup(props, { emit }) {
2133
- const inputId = (0, import_vue10.ref)(`dialkit-text-${++textControlInstance}`);
2134
- return () => (0, import_vue10.h)("div", { class: "dialkit-text-control" }, [
2135
- (0, import_vue10.h)("label", { class: "dialkit-text-label", for: inputId.value }, props.label),
2136
- (0, import_vue10.h)("input", {
2137
- id: inputId.value,
2138
- type: "text",
2139
- class: "dialkit-text-input",
2140
- value: props.value,
2141
- placeholder: props.placeholder,
2142
- onInput: (event) => emit("change", event.target.value)
2143
- })
2144
- ]);
2145
- }
2146
- });
2147
-
2148
- // src/vue/components/SelectControl.ts
2149
- var import_vue11 = require("vue");
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
2171
- function toTitleCase(value) {
2172
- return value.replace(/\b\w/g, (char) => char.toUpperCase());
2173
- }
2174
- function normalizeOptions(options) {
2175
- return options.map(
2176
- (option) => typeof option === "string" ? { value: option, label: toTitleCase(option) } : option
2177
- );
2178
- }
2179
- var SelectControl = (0, import_vue11.defineComponent)({
2180
- name: "DialKitSelectControl",
2181
- props: {
2182
- label: { type: String, required: true },
2183
- value: { type: String, required: true },
2184
- options: {
2185
- type: Array,
2186
- required: true
2187
- }
2188
- },
2189
- emits: ["change"],
2190
- setup(props, { emit }) {
2191
- const isOpen = (0, import_vue11.ref)(false);
2192
- const pos = (0, import_vue11.ref)(null);
2193
- const portalTarget = (0, import_vue11.ref)(null);
2194
- const triggerRef = (0, import_vue11.ref)(null);
2195
- const dropdownRef = (0, import_vue11.ref)(null);
2196
- const normalizedOptions = () => normalizeOptions(props.options);
2197
- const selectedLabel = () => normalizedOptions().find((option) => option.value === props.value)?.label ?? props.value;
2198
- const updatePos = () => {
2199
- if (!triggerRef.value || !portalTarget.value) return;
2200
- const dropdownHeight = 8 + normalizedOptions().length * 36;
2201
- pos.value = getDropdownPosition(triggerRef.value, portalTarget.value, { dropdownHeight });
2202
- };
2203
- const openDropdown = () => {
2204
- updatePos();
2205
- isOpen.value = true;
2206
- };
2207
- const closeDropdown = () => {
2208
- isOpen.value = false;
2209
- };
2210
- const setDropdownRef = (node) => {
2211
- if (node instanceof HTMLElement) {
2212
- dropdownRef.value = node;
2213
- return;
2214
- }
2215
- if (node && typeof node === "object" && "$el" in node) {
2216
- const el = node.$el;
2217
- dropdownRef.value = el instanceof HTMLElement ? el : null;
2218
- return;
2219
- }
2220
- dropdownRef.value = null;
2221
- };
2222
- const toggleDropdown = () => {
2223
- if (isOpen.value) closeDropdown();
2224
- else openDropdown();
2225
- };
2226
- (0, import_vue11.watch)(isOpen, (open, _, onCleanup) => {
2227
- if (!open) return;
2228
- const handleViewportChange = () => updatePos();
2229
- const handleDocumentClick = (event) => {
2230
- const target = event.target;
2231
- if (triggerRef.value?.contains(target) || dropdownRef.value?.contains(target)) return;
2232
- closeDropdown();
2233
- };
2234
- updatePos();
2235
- document.addEventListener("mousedown", handleDocumentClick);
2236
- window.addEventListener("resize", handleViewportChange);
2237
- window.addEventListener("scroll", handleViewportChange, true);
2238
- onCleanup(() => {
2239
- document.removeEventListener("mousedown", handleDocumentClick);
2240
- window.removeEventListener("resize", handleViewportChange);
2241
- window.removeEventListener("scroll", handleViewportChange, true);
2242
- });
2243
- });
2244
- (0, import_vue11.onMounted)(() => {
2245
- portalTarget.value = getDialKitPortalRoot(triggerRef.value) ?? document.body;
2246
- });
2247
- return () => (0, import_vue11.h)("div", { class: "dialkit-select-row" }, [
2248
- (0, import_vue11.h)("button", {
2249
- ref: triggerRef,
2250
- class: "dialkit-select-trigger",
2251
- "data-open": String(isOpen.value),
2252
- onClick: toggleDropdown
2253
- }, [
2254
- (0, import_vue11.h)("span", { class: "dialkit-select-label" }, props.label),
2255
- (0, import_vue11.h)("div", { class: "dialkit-select-right" }, [
2256
- (0, import_vue11.h)("span", { class: "dialkit-select-value" }, selectedLabel()),
2257
- (0, import_vue11.h)(import_motion_v3.motion.svg, {
2258
- class: "dialkit-select-chevron",
2259
- viewBox: "0 0 24 24",
2260
- fill: "none",
2261
- stroke: "currentColor",
2262
- "stroke-width": "2.5",
2263
- "stroke-linecap": "round",
2264
- "stroke-linejoin": "round",
2265
- animate: { rotate: isOpen.value ? 180 : 0 },
2266
- transition: { type: "spring", visualDuration: 0.2, bounce: 0.15 }
2267
- }, [(0, import_vue11.h)("path", { d: "M6 9.5L12 15.5L18 9.5" })])
2268
- ])
2269
- ]),
2270
- portalTarget.value ? (0, import_vue11.h)(import_vue11.Teleport, { to: portalTarget.value }, [
2271
- (0, import_vue11.h)(import_motion_v3.AnimatePresence, null, {
2272
- default: () => isOpen.value && pos.value ? [(0, import_vue11.h)(import_motion_v3.motion.div, {
2273
- key: "dialkit-select-dropdown",
2274
- ref: setDropdownRef,
2275
- class: "dialkit-select-dropdown",
2276
- initial: { opacity: 0, y: pos.value.above ? 8 : -8, scale: 0.95 },
2277
- animate: { opacity: 1, y: 0, scale: 1 },
2278
- exit: { opacity: 0, y: pos.value.above ? 8 : -8, scale: 0.95 },
2279
- transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
2280
- style: {
2281
- position: "absolute",
2282
- left: `${pos.value.left}px`,
2283
- top: `${pos.value.top}px`,
2284
- width: `${pos.value.width}px`,
2285
- transformOrigin: pos.value.above ? "bottom" : "top"
2286
- }
2287
- }, normalizedOptions().map((option) => (0, import_vue11.h)("button", {
2288
- key: option.value,
2289
- class: "dialkit-select-option",
2290
- "data-selected": String(option.value === props.value),
2291
- onClick: () => {
2292
- emit("change", option.value);
2293
- closeDropdown();
2294
- }
2295
- }, option.label)))] : []
2296
- })
2297
- ]) : null
2298
- ]);
2299
- }
2300
- });
2301
-
2302
- // src/vue/components/ColorControl.ts
2303
- var import_vue12 = require("vue");
2304
- var HEX_COLOR_REGEX = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/;
2305
- function expandShorthandHex(hex) {
2306
- if (hex.length !== 4) return hex;
2307
- return `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;
2308
- }
2309
- var colorControlInstance = 0;
2310
- var ColorControl = (0, import_vue12.defineComponent)({
2311
- name: "DialKitColorControl",
2312
- props: {
2313
- label: { type: String, required: true },
2314
- value: { type: String, required: true }
2315
- },
2316
- emits: ["change"],
2317
- setup(props, { emit }) {
2318
- const textInputId = (0, import_vue12.ref)(`dialkit-color-${++colorControlInstance}`);
2319
- const isEditing = (0, import_vue12.ref)(false);
2320
- const editValue = (0, import_vue12.ref)(props.value);
2321
- const colorInputRef = (0, import_vue12.ref)(null);
2322
- (0, import_vue12.watch)(() => props.value, (value) => {
2323
- if (!isEditing.value) editValue.value = value;
2324
- });
2325
- const submit = () => {
2326
- isEditing.value = false;
2327
- if (HEX_COLOR_REGEX.test(editValue.value)) {
2328
- emit("change", editValue.value);
2329
- } else {
2330
- editValue.value = props.value;
2331
- }
2332
- };
2333
- return () => (0, import_vue12.h)("div", { class: "dialkit-color-control" }, [
2334
- (0, import_vue12.h)("label", { class: "dialkit-color-label", for: textInputId.value }, props.label),
2335
- (0, import_vue12.h)("div", { class: "dialkit-color-inputs" }, [
2336
- isEditing.value ? (0, import_vue12.h)("input", {
2337
- id: textInputId.value,
2338
- type: "text",
2339
- class: "dialkit-color-hex-input",
2340
- value: editValue.value,
2341
- autofocus: true,
2342
- onInput: (event) => {
2343
- editValue.value = event.target.value;
2344
- },
2345
- onBlur: submit,
2346
- onKeydown: (event) => {
2347
- if (event.key === "Enter") submit();
2348
- if (event.key === "Escape") {
2349
- isEditing.value = false;
2350
- editValue.value = props.value;
2351
- }
2352
- }
2353
- }) : (0, import_vue12.h)("span", { class: "dialkit-color-hex", onClick: () => {
2354
- isEditing.value = true;
2355
- } }, (props.value ?? "").toUpperCase()),
2356
- (0, import_vue12.h)("button", {
2357
- class: "dialkit-color-swatch",
2358
- style: { backgroundColor: props.value },
2359
- title: "Pick color",
2360
- "aria-label": `Pick color for ${props.label}`,
2361
- onClick: () => colorInputRef.value?.click()
2362
- }),
2363
- (0, import_vue12.h)("input", {
2364
- ref: colorInputRef,
2365
- type: "color",
2366
- class: "dialkit-color-picker-native",
2367
- "aria-label": `${props.label} color picker`,
2368
- value: props.value.length === 4 ? expandShorthandHex(props.value) : props.value.slice(0, 7),
2369
- onInput: (event) => emit("change", event.target.value)
2370
- })
2371
- ])
2372
- ]);
2373
- }
2374
- });
2375
-
2376
- // src/vue/components/PresetManager.ts
2377
- var import_vue13 = require("vue");
2378
- var import_motion_v4 = require("motion-v");
2379
- var PresetManager = (0, import_vue13.defineComponent)({
2380
- name: "DialKitPresetManager",
2381
- props: {
2382
- panelId: { type: String, required: true },
2383
- presets: {
2384
- type: Array,
2385
- required: true
2386
- },
2387
- activePresetId: {
2388
- type: String,
2389
- required: false,
2390
- default: null
2391
- }
2392
- },
2393
- setup(props) {
2394
- const isOpen = (0, import_vue13.ref)(false);
2395
- const pos = (0, import_vue13.ref)({ top: 0, left: 0, width: 0 });
2396
- const triggerRef = (0, import_vue13.ref)(null);
2397
- const dropdownRef = (0, import_vue13.ref)(null);
2398
- const hasPresets = () => props.presets.length > 0;
2399
- const activePreset = () => props.presets.find((preset) => preset.id === props.activePresetId);
2400
- const open = () => {
2401
- if (!hasPresets()) return;
2402
- const rect = triggerRef.value?.getBoundingClientRect();
2403
- if (rect) {
2404
- pos.value = { top: rect.bottom + 4, left: rect.left, width: rect.width };
2405
- }
2406
- isOpen.value = true;
2407
- };
2408
- const close = () => {
2409
- isOpen.value = false;
2410
- };
2411
- const setDropdownRef = (node) => {
2412
- if (node instanceof HTMLElement) {
2413
- dropdownRef.value = node;
2414
- return;
2415
- }
2416
- if (node && typeof node === "object" && "$el" in node) {
2417
- const el = node.$el;
2418
- dropdownRef.value = el instanceof HTMLElement ? el : null;
2419
- return;
2420
- }
2421
- dropdownRef.value = null;
2422
- };
2423
- const toggle = () => {
2424
- if (isOpen.value) close();
2425
- else open();
2426
- };
2427
- (0, import_vue13.watch)(isOpen, (open2, _, onCleanup) => {
2428
- if (!open2) return;
2429
- const handler = (event) => {
2430
- const target = event.target;
2431
- if (triggerRef.value?.contains(target) || dropdownRef.value?.contains(target)) return;
2432
- close();
2433
- };
2434
- document.addEventListener("mousedown", handler);
2435
- onCleanup(() => {
2436
- document.removeEventListener("mousedown", handler);
2437
- });
2438
- });
2439
- const handleSelect = (presetId) => {
2440
- if (presetId) {
2441
- DialStore.loadPreset(props.panelId, presetId);
2442
- } else {
2443
- DialStore.clearActivePreset(props.panelId);
2444
- }
2445
- close();
2446
- };
2447
- const handleDelete = (event, presetId) => {
2448
- event.stopPropagation();
2449
- DialStore.deletePreset(props.panelId, presetId);
2450
- };
2451
- return () => (0, import_vue13.h)("div", { class: "dialkit-preset-manager" }, [
2452
- (0, import_vue13.h)("button", {
2453
- ref: triggerRef,
2454
- class: "dialkit-preset-trigger",
2455
- onClick: toggle,
2456
- "data-open": String(isOpen.value),
2457
- "data-has-preset": String(!!activePreset()),
2458
- "data-disabled": String(!hasPresets())
2459
- }, [
2460
- (0, import_vue13.h)("span", { class: "dialkit-preset-label" }, activePreset()?.name ?? "Version 1"),
2461
- (0, import_vue13.h)(import_motion_v4.motion.svg, {
2462
- class: "dialkit-select-chevron",
2463
- viewBox: "0 0 24 24",
2464
- fill: "none",
2465
- stroke: "currentColor",
2466
- "stroke-width": "2.5",
2467
- "stroke-linecap": "round",
2468
- "stroke-linejoin": "round",
2469
- animate: { rotate: isOpen.value ? 180 : 0, opacity: hasPresets() ? 0.6 : 0.25 },
2470
- transition: { type: "spring", visualDuration: 0.2, bounce: 0.15 }
2471
- }, [(0, import_vue13.h)("path", { d: ICON_CHEVRON })])
2472
- ]),
2473
- (0, import_vue13.h)(import_vue13.Teleport, { to: "body" }, [
2474
- (0, import_vue13.h)(import_motion_v4.AnimatePresence, null, {
2475
- default: () => isOpen.value ? [(0, import_vue13.h)(import_motion_v4.motion.div, {
2476
- key: "dialkit-preset-dropdown",
2477
- ref: setDropdownRef,
2478
- class: "dialkit-root dialkit-preset-dropdown",
2479
- style: {
2480
- position: "fixed",
2481
- top: `${pos.value.top}px`,
2482
- left: `${pos.value.left}px`,
2483
- minWidth: `${pos.value.width}px`
2484
- },
2485
- initial: { opacity: 0, y: 4, scale: 0.97 },
2486
- animate: { opacity: 1, y: 0, scale: 1 },
2487
- exit: { opacity: 0, y: 4, scale: 0.97, pointerEvents: "none" },
2488
- transition: { type: "spring", visualDuration: 0.15, bounce: 0 }
2489
- }, [
2490
- (0, import_vue13.h)("div", {
2491
- class: "dialkit-preset-item",
2492
- "data-active": String(!props.activePresetId),
2493
- onClick: () => handleSelect(null)
2494
- }, [(0, import_vue13.h)("span", { class: "dialkit-preset-name" }, "Version 1")]),
2495
- ...props.presets.map((preset) => (0, import_vue13.h)("div", {
2496
- key: preset.id,
2497
- class: "dialkit-preset-item",
2498
- "data-active": String(preset.id === props.activePresetId),
2499
- onClick: () => handleSelect(preset.id)
2500
- }, [
2501
- (0, import_vue13.h)("span", { class: "dialkit-preset-name" }, preset.name),
2502
- (0, import_vue13.h)("button", {
2503
- class: "dialkit-preset-delete",
2504
- onClick: (event) => handleDelete(event, preset.id),
2505
- title: "Delete preset"
2506
- }, [
2507
- (0, import_vue13.h)("svg", {
2508
- viewBox: "0 0 24 24",
2509
- fill: "none",
2510
- stroke: "currentColor",
2511
- "stroke-width": "2",
2512
- "stroke-linecap": "round",
2513
- "stroke-linejoin": "round"
2514
- }, ICON_TRASH.map((d) => (0, import_vue13.h)("path", { d })))
2515
- ])
2516
- ]))
2517
- ])] : []
2518
- })
2519
- ])
2520
- ]);
2521
- }
2522
- });
2523
-
2524
- // src/vue/components/ShortcutListener.ts
2525
- var import_vue14 = require("vue");
2526
- var ShortcutKey = /* @__PURE__ */ Symbol("DialKitShortcut");
2527
- function useShortcutContext() {
2528
- return (0, import_vue14.inject)(ShortcutKey, {
2529
- activePanelId: (0, import_vue14.ref)(null),
2530
- activePath: (0, import_vue14.ref)(null)
2531
- });
2532
- }
2533
- var ShortcutListener = (0, import_vue14.defineComponent)({
2534
- name: "DialKitShortcutListener",
2535
- setup(_, { slots }) {
2536
- const activePanelId = (0, import_vue14.ref)(null);
2537
- const activePath = (0, import_vue14.ref)(null);
2538
- const activeKeys = /* @__PURE__ */ new Set();
2539
- let isDragging = false;
2540
- let lastMouseX = null;
2541
- let dragAccumulator = 0;
2542
- (0, import_vue14.provide)(ShortcutKey, { activePanelId, activePath });
2543
- const resolveActiveTarget = (interaction) => {
2544
- for (const key of activeKeys) {
2545
- const panels = DialStore.getPanels();
2546
- for (const panel of panels) {
2547
- for (const [path, shortcut] of Object.entries(panel.shortcuts)) {
2548
- if (!shortcut.key) continue;
2549
- if (shortcut.key.toLowerCase() !== key) continue;
2550
- if ((shortcut.interaction ?? "scroll") !== interaction) continue;
2551
- const control = DialStore.getPanel(panel.id)?.controls ? findControl(panel.controls, path) : null;
2552
- if (control && control.type === "slider") {
2553
- return { panelId: panel.id, path, control, shortcut };
2554
- }
2555
- }
2556
- }
2557
- }
2558
- return null;
2559
- };
2560
- const handleKeyDown = (e) => {
2561
- if (isInputFocused()) return;
2562
- const key = e.key.toLowerCase();
2563
- if (key === "arrowleft" || key === "arrowright" || key === "arrowup" || key === "arrowdown") {
2564
- if (activeKeys.size > 0) {
2565
- const target2 = resolveActiveTarget("scroll") || resolveActiveTarget("drag") || resolveActiveTarget("move");
2566
- if (target2 && target2.control.type === "slider") {
2567
- e.preventDefault();
2568
- const direction = key === "arrowright" || key === "arrowup" ? 1 : -1;
2569
- const effectiveStep = getEffectiveStep(target2.control, target2.shortcut);
2570
- applySliderDelta(target2.panelId, target2.path, target2.control, effectiveStep, direction);
2571
- return;
2572
- }
2573
- }
2574
- }
2575
- const wasAlreadyHeld = activeKeys.has(key);
2576
- activeKeys.add(key);
2577
- const modifier = getActiveModifier(e);
2578
- const target = DialStore.resolveShortcutTarget(key, modifier);
2579
- if (target) {
2580
- activePanelId.value = target.panelId;
2581
- activePath.value = target.path;
2582
- if (!wasAlreadyHeld && target.control.type === "toggle") {
2583
- const currentValue = DialStore.getValue(target.panelId, target.path);
2584
- DialStore.updateValue(target.panelId, target.path, !currentValue);
2585
- }
2586
- }
2587
- if (!wasAlreadyHeld) {
2588
- lastMouseX = null;
2589
- dragAccumulator = 0;
2590
- }
2591
- };
2592
- const handleKeyUp = (e) => {
2593
- const key = e.key.toLowerCase();
2594
- activeKeys.delete(key);
2595
- isDragging = false;
2596
- lastMouseX = null;
2597
- dragAccumulator = 0;
2598
- if (activeKeys.size === 0) {
2599
- activePanelId.value = null;
2600
- activePath.value = null;
2601
- } else {
2602
- let found = false;
2603
- for (const remainingKey of activeKeys) {
2604
- const modifier = getActiveModifier(e);
2605
- const target = DialStore.resolveShortcutTarget(remainingKey, modifier);
2606
- if (target) {
2607
- activePanelId.value = target.panelId;
2608
- activePath.value = target.path;
2609
- found = true;
2610
- break;
2611
- }
2612
- }
2613
- if (!found) {
2614
- activePanelId.value = null;
2615
- activePath.value = null;
2616
- }
2617
- }
2618
- };
2619
- const handleWheel = (e) => {
2620
- if (isInputFocused()) return;
2621
- const modifier = getActiveModifier(e);
2622
- if (activeKeys.size > 0) {
2623
- for (const key of activeKeys) {
2624
- const target = DialStore.resolveShortcutTarget(key, modifier);
2625
- if (!target) continue;
2626
- const { panelId, path, control } = target;
2627
- const interaction = control.shortcut?.interaction ?? "scroll";
2628
- if (interaction !== "scroll" || control.type !== "slider") continue;
2629
- e.preventDefault();
2630
- const effectiveStep = getEffectiveStep(control, control.shortcut);
2631
- const direction = e.deltaY > 0 ? -1 : 1;
2632
- applySliderDelta(panelId, path, control, effectiveStep, direction);
2633
- return;
2634
- }
2635
- }
2636
- const scrollOnlyTargets = DialStore.resolveScrollOnlyTargets();
2637
- for (const { panelId, path, control, shortcut } of scrollOnlyTargets) {
2638
- if (control.type !== "slider") continue;
2639
- e.preventDefault();
2640
- const effectiveStep = getEffectiveStep(control, shortcut);
2641
- const direction = e.deltaY > 0 ? -1 : 1;
2642
- applySliderDelta(panelId, path, control, effectiveStep, direction);
2643
- return;
2644
- }
2645
- };
2646
- const handleMouseDown = (e) => {
2647
- if (isInputFocused()) return;
2648
- if (activeKeys.size === 0) return;
2649
- const target = resolveActiveTarget("drag");
2650
- if (target) {
2651
- isDragging = true;
2652
- lastMouseX = e.clientX;
2653
- dragAccumulator = 0;
2654
- e.preventDefault();
2655
- }
2656
- };
2657
- const handleMouseUp = () => {
2658
- isDragging = false;
2659
- lastMouseX = null;
2660
- dragAccumulator = 0;
2661
- };
2662
- const handleMouseMove = (e) => {
2663
- if (isInputFocused()) return;
2664
- if (activeKeys.size === 0) return;
2665
- if (isDragging) {
2666
- const target = resolveActiveTarget("drag");
2667
- if (target && lastMouseX !== null) {
2668
- const deltaX = e.clientX - lastMouseX;
2669
- lastMouseX = e.clientX;
2670
- dragAccumulator += deltaX;
2671
- const effectiveStep = getEffectiveStep(target.control, target.shortcut);
2672
- const steps = Math.trunc(dragAccumulator / DRAG_SENSITIVITY);
2673
- if (steps !== 0) {
2674
- dragAccumulator -= steps * DRAG_SENSITIVITY;
2675
- applySliderDelta(target.panelId, target.path, target.control, effectiveStep, steps);
2676
- }
2677
- }
2678
- return;
2679
- }
2680
- const moveTarget = resolveActiveTarget("move");
2681
- if (moveTarget) {
2682
- if (lastMouseX === null) {
2683
- lastMouseX = e.clientX;
2684
- return;
2685
- }
2686
- const deltaX = e.clientX - lastMouseX;
2687
- lastMouseX = e.clientX;
2688
- dragAccumulator += deltaX;
2689
- const effectiveStep = getEffectiveStep(moveTarget.control, moveTarget.shortcut);
2690
- const steps = Math.trunc(dragAccumulator / DRAG_SENSITIVITY);
2691
- if (steps !== 0) {
2692
- dragAccumulator -= steps * DRAG_SENSITIVITY;
2693
- applySliderDelta(moveTarget.panelId, moveTarget.path, moveTarget.control, effectiveStep, steps);
2694
- }
2695
- }
2696
- };
2697
- const handleWindowBlur = () => {
2698
- activeKeys.clear();
2699
- isDragging = false;
2700
- lastMouseX = null;
2701
- dragAccumulator = 0;
2702
- activePanelId.value = null;
2703
- activePath.value = null;
2704
- };
2705
- (0, import_vue14.onMounted)(() => {
2706
- window.addEventListener("keydown", handleKeyDown);
2707
- window.addEventListener("keyup", handleKeyUp);
2708
- window.addEventListener("wheel", handleWheel, { passive: false });
2709
- window.addEventListener("mousedown", handleMouseDown);
2710
- window.addEventListener("mouseup", handleMouseUp);
2711
- window.addEventListener("mousemove", handleMouseMove);
2712
- window.addEventListener("blur", handleWindowBlur);
2713
- });
2714
- (0, import_vue14.onUnmounted)(() => {
2715
- window.removeEventListener("keydown", handleKeyDown);
2716
- window.removeEventListener("keyup", handleKeyUp);
2717
- window.removeEventListener("wheel", handleWheel);
2718
- window.removeEventListener("mousedown", handleMouseDown);
2719
- window.removeEventListener("mouseup", handleMouseUp);
2720
- window.removeEventListener("mousemove", handleMouseMove);
2721
- window.removeEventListener("blur", handleWindowBlur);
2722
- });
2723
- return () => slots.default?.();
2724
- }
2725
- });
2726
-
2727
- // src/vue/components/Panel.ts
2728
- var Panel = (0, import_vue15.defineComponent)({
2729
- name: "DialKitPanel",
2730
- props: {
2731
- panel: {
2732
- type: Object,
2733
- required: true
2734
- },
2735
- defaultOpen: {
2736
- type: Boolean,
2737
- default: true
2738
- },
2739
- inline: {
2740
- type: Boolean,
2741
- default: false
2742
- },
2743
- variant: {
2744
- type: String,
2745
- default: "root"
2746
- }
2747
- },
2748
- emits: ["openChange"],
2749
- setup(props, { emit }) {
2750
- const shortcutCtx = useShortcutContext();
2751
- const values = (0, import_vue15.ref)(DialStore.getValues(props.panel.id));
2752
- const presets = (0, import_vue15.ref)(DialStore.getPresets(props.panel.id));
2753
- const activePresetId = (0, import_vue15.ref)(DialStore.getActivePresetId(props.panel.id));
2754
- const copied = (0, import_vue15.ref)(false);
2755
- const hasShortcuts = () => Object.keys(DialStore.getPanel(props.panel.id)?.shortcuts ?? {}).length > 0;
2756
- let unsubscribe;
2757
- let copiedTimeout = null;
2758
- (0, import_vue15.onMounted)(() => {
2759
- unsubscribe = DialStore.subscribe(props.panel.id, () => {
2760
- values.value = DialStore.getValues(props.panel.id);
2761
- presets.value = DialStore.getPresets(props.panel.id);
2762
- activePresetId.value = DialStore.getActivePresetId(props.panel.id);
2763
- });
2764
- });
2765
- (0, import_vue15.onUnmounted)(() => {
2766
- unsubscribe?.();
2767
- if (copiedTimeout) {
2768
- window.clearTimeout(copiedTimeout);
2769
- }
2770
- });
2771
- const handleAddPreset = () => {
2772
- const nextNum = presets.value.length + 2;
2773
- DialStore.savePreset(props.panel.id, `Version ${nextNum}`);
2774
- };
2775
- const handleCopy = () => {
2776
- const json = JSON.stringify(values.value, null, 2);
2777
- const instruction = `Update the useDialKit configuration for "${props.panel.name}" with these values:
2778
-
2779
- \`\`\`json
2780
- ${json}
2781
- \`\`\`
2782
-
2783
- Apply these values as the new defaults in the useDialKit call.`;
2784
- try {
2785
- if (navigator.clipboard?.writeText) {
2786
- void navigator.clipboard.writeText(instruction).catch(() => void 0);
2787
- }
2788
- } catch {
2789
- }
2790
- copied.value = true;
2791
- if (copiedTimeout) {
2792
- window.clearTimeout(copiedTimeout);
2793
- }
2794
- copiedTimeout = window.setTimeout(() => {
2795
- copied.value = false;
2796
- }, 1500);
2797
- };
2798
- const handleOpenChange = (open) => {
2799
- emit("openChange", open);
2800
- };
2801
- const renderControl = (control) => {
2802
- const value = values.value[control.path];
2803
- switch (control.type) {
2804
- case "slider":
2805
- return (0, import_vue15.h)(Slider, {
2806
- key: control.path,
2807
- label: control.label,
2808
- value,
2809
- min: control.min,
2810
- max: control.max,
2811
- step: control.step,
2812
- shortcut: control.shortcut,
2813
- shortcutActive: shortcutCtx.activePanelId.value === props.panel.id && shortcutCtx.activePath.value === control.path,
2814
- onChange: (next) => DialStore.updateValue(props.panel.id, control.path, next)
2815
- });
2816
- case "toggle":
2817
- return (0, import_vue15.h)(Toggle, {
2818
- key: control.path,
2819
- label: control.label,
2820
- checked: value,
2821
- shortcut: control.shortcut,
2822
- shortcutActive: shortcutCtx.activePanelId.value === props.panel.id && shortcutCtx.activePath.value === control.path,
2823
- onChange: (next) => DialStore.updateValue(props.panel.id, control.path, next)
2824
- });
2825
- case "spring":
2826
- return (0, import_vue15.h)(SpringControl, {
2827
- key: control.path,
2828
- panelId: props.panel.id,
2829
- path: control.path,
2830
- label: control.label,
2831
- spring: value,
2832
- onChange: (next) => DialStore.updateValue(props.panel.id, control.path, next)
2833
- });
2834
- case "transition":
2835
- return (0, import_vue15.h)(TransitionControl, {
2836
- key: control.path,
2837
- panelId: props.panel.id,
2838
- path: control.path,
2839
- label: control.label,
2840
- value,
2841
- onChange: (next) => DialStore.updateValue(props.panel.id, control.path, next)
2842
- });
2843
- case "folder":
2844
- return (0, import_vue15.h)(Folder, {
2845
- key: control.path,
2846
- title: control.label,
2847
- defaultOpen: control.defaultOpen ?? true
2848
- }, {
2849
- default: () => (control.children ?? []).map(renderControl)
2850
- });
2851
- case "text":
2852
- return (0, import_vue15.h)(TextControl, {
2853
- key: control.path,
2854
- label: control.label,
2855
- value,
2856
- placeholder: control.placeholder,
2857
- onChange: (next) => DialStore.updateValue(props.panel.id, control.path, next)
2858
- });
2859
- case "select":
2860
- return (0, import_vue15.h)(SelectControl, {
2861
- key: control.path,
2862
- label: control.label,
2863
- value,
2864
- options: control.options ?? [],
2865
- onChange: (next) => DialStore.updateValue(props.panel.id, control.path, next)
2866
- });
2867
- case "color":
2868
- return (0, import_vue15.h)(ColorControl, {
2869
- key: control.path,
2870
- label: control.label,
2871
- value,
2872
- onChange: (next) => DialStore.updateValue(props.panel.id, control.path, next)
2873
- });
2874
- case "action":
2875
- return (0, import_vue15.h)("button", {
2876
- key: control.path,
2877
- class: "dialkit-button",
2878
- onClick: () => DialStore.triggerAction(props.panel.id, control.path)
2879
- }, control.label);
2880
- default:
2881
- return null;
2882
- }
2883
- };
2884
- return () => {
2885
- const toolbarNode = (0, import_vue15.h)(import_vue15.Fragment, null, [
2886
- (0, import_vue15.h)(import_motion_v5.motion.button, {
2887
- class: "dialkit-toolbar-add",
2888
- onClick: handleAddPreset,
2889
- title: "Add preset",
2890
- whilePress: { scale: 0.9 },
2891
- transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 }
2892
- }, [
2893
- (0, import_vue15.h)("svg", {
2894
- viewBox: "0 0 24 24",
2895
- fill: "none",
2896
- stroke: "currentColor",
2897
- "stroke-width": "2.5",
2898
- "stroke-linecap": "round",
2899
- "stroke-linejoin": "round"
2900
- }, ICON_ADD_PRESET.map((d) => (0, import_vue15.h)("path", { d })))
2901
- ]),
2902
- (0, import_vue15.h)(PresetManager, {
2903
- panelId: props.panel.id,
2904
- presets: presets.value,
2905
- activePresetId: activePresetId.value
2906
- }),
2907
- (0, import_vue15.h)(import_motion_v5.motion.button, {
2908
- class: "dialkit-toolbar-copy",
2909
- onClick: handleCopy,
2910
- title: "Copy parameters",
2911
- whilePress: { scale: 0.95 },
2912
- transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 }
2913
- }, [
2914
- (0, import_vue15.h)("span", { class: "dialkit-toolbar-copy-icon-wrap" }, [
2915
- (0, import_vue15.h)("span", {
2916
- class: "dialkit-toolbar-copy-icon",
2917
- style: { opacity: copied.value ? 0 : 1, transition: "opacity 120ms ease" }
2918
- }, [
2919
- (0, import_vue15.h)("svg", {
2920
- viewBox: "0 0 24 24",
2921
- fill: "none",
2922
- width: 16,
2923
- height: 16
2924
- }, [
2925
- (0, import_vue15.h)("path", {
2926
- d: ICON_CLIPBOARD.board,
2927
- stroke: "currentColor",
2928
- "stroke-width": 2,
2929
- "stroke-linejoin": "round"
2930
- }),
2931
- (0, import_vue15.h)("path", {
2932
- d: ICON_CLIPBOARD.sparkle,
2933
- fill: "currentColor"
2934
- }),
2935
- (0, import_vue15.h)("path", {
2936
- d: ICON_CLIPBOARD.body,
2937
- stroke: "currentColor",
2938
- "stroke-width": 2,
2939
- "stroke-linecap": "round",
2940
- "stroke-linejoin": "round"
2941
- })
2942
- ])
2943
- ]),
2944
- (0, import_vue15.h)(import_motion_v5.AnimatePresence, { initial: false, mode: "popLayout" }, {
2945
- default: () => copied.value ? [(0, import_vue15.h)(import_motion_v5.motion.span, {
2946
- key: "check",
2947
- class: "dialkit-toolbar-copy-icon",
2948
- initial: { scale: 0.5, opacity: 0 },
2949
- animate: { scale: 1, opacity: 1 },
2950
- exit: { scale: 0.5, opacity: 0 },
2951
- transition: { type: "spring", visualDuration: 0.3, bounce: 0.2 }
2952
- }, [
2953
- (0, import_vue15.h)("svg", {
2954
- viewBox: "0 0 24 24",
2955
- fill: "none",
2956
- stroke: "currentColor",
2957
- "stroke-width": 2,
2958
- "stroke-linecap": "round",
2959
- "stroke-linejoin": "round",
2960
- width: 16,
2961
- height: 16
2962
- }, [(0, import_vue15.h)("path", { d: ICON_CHECK })])
2963
- ])] : []
2964
- })
2965
- ]),
2966
- "Copy"
2967
- ])
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
- }
2984
- return (0, import_vue15.h)("div", { class: "dialkit-panel-wrapper" }, [
2985
- (0, import_vue15.h)(Folder, {
2986
- title: props.panel.name,
2987
- defaultOpen: props.defaultOpen,
2988
- isRoot: true,
2989
- inline: props.inline,
2990
- toolbar: () => toolbarNode,
2991
- onOpenChange: handleOpenChange
2992
- }, {
2993
- default: () => props.panel.controls.map(renderControl)
2994
- })
2995
- ]);
2996
- };
2997
- }
2998
- });
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
-
3064
- // src/vue/components/DialRoot.ts
3065
- var import_meta = {};
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;
3067
- var DialRoot = (0, import_vue16.defineComponent)({
3068
- name: "DialKitDialRoot",
3069
- props: {
3070
- position: {
3071
- type: String,
3072
- default: "top-right"
3073
- },
3074
- defaultOpen: {
3075
- type: Boolean,
3076
- default: true
3077
- },
3078
- mode: {
3079
- type: String,
3080
- default: "popover"
3081
- },
3082
- theme: {
3083
- type: String,
3084
- default: "system"
3085
- },
3086
- productionEnabled: {
3087
- type: Boolean,
3088
- default: isDevDefault
3089
- }
3090
- },
3091
- emits: ["openChange"],
3092
- setup(props, { emit }) {
3093
- const panels = (0, import_vue16.ref)([]);
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);
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));
3192
- (0, import_vue16.onMounted)(() => {
3193
- mounted.value = true;
3194
- panels.value = DialStore.getPanels();
3195
- syncPanelOpenStates();
3196
- unsubscribe = DialStore.subscribeGlobal(() => {
3197
- panels.value = DialStore.getPanels();
3198
- syncPanelOpenStates();
3199
- });
3200
- (0, import_vue16.nextTick)(connectObserver);
3201
- });
3202
- (0, import_vue16.onUnmounted)(() => {
3203
- unsubscribe?.();
3204
- observer?.disconnect();
3205
- });
3206
- const renderContent = () => (0, import_vue16.h)(ShortcutListener, null, {
3207
- default: () => (0, import_vue16.h)("div", { class: "dialkit-root", "data-mode": props.mode, "data-theme": props.theme }, [
3208
- (0, import_vue16.h)("div", {
3209
- ref: (el) => {
3210
- panelRef.value = el;
3211
- connectObserver();
3212
- },
3213
- class: "dialkit-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, {
3242
- key: panel.id,
3243
- panel,
3244
- defaultOpen: props.mode === "inline" || props.defaultOpen,
3245
- inline: props.mode === "inline",
3246
- onOpenChange: (open) => handlePanelOpenChange(panel.id, open)
3247
- })))
3248
- ])
3249
- });
3250
- return () => {
3251
- if (!props.productionEnabled || !mounted.value || typeof window === "undefined" || panels.value.length === 0) {
3252
- return null;
3253
- }
3254
- if (props.mode === "inline") {
3255
- return renderContent();
3256
- }
3257
- return (0, import_vue16.h)(import_vue16.Teleport, { to: "body" }, renderContent());
3258
- };
3259
- }
3260
- });
3261
-
3262
- // src/vue/directives/dialkit.ts
3263
- var states = /* @__PURE__ */ new WeakMap();
3264
- function normalizeDirectiveValue(value) {
3265
- if (!value) return {};
3266
- if (value === "inline" || value === "popover") {
3267
- return { mode: value };
3268
- }
3269
- return value;
3270
- }
3271
- function mountDialRoot(el, value) {
3272
- if (typeof window === "undefined") return;
3273
- const host = document.createElement("div");
3274
- el.appendChild(host);
3275
- const props = (0, import_vue17.shallowRef)(normalizeDirectiveValue(value));
3276
- const RootHost = (0, import_vue17.defineComponent)({
3277
- name: "DialKitDirectiveHost",
3278
- setup() {
3279
- return () => (0, import_vue17.h)(DialRoot, props.value);
3280
- }
3281
- });
3282
- const app = (0, import_vue17.createApp)(RootHost);
3283
- app.mount(host);
3284
- states.set(el, { app, host, props });
3285
- }
3286
- function unmountDialRoot(el) {
3287
- const state = states.get(el);
3288
- if (!state) return;
3289
- state.app.unmount();
3290
- state.host.remove();
3291
- states.delete(el);
3292
- }
3293
- var vDialKit = {
3294
- mounted(el, binding) {
3295
- mountDialRoot(el, binding.value);
3296
- },
3297
- updated(el, binding) {
3298
- const state = states.get(el);
3299
- if (!state) {
3300
- mountDialRoot(el, binding.value);
3301
- return;
3302
- }
3303
- state.props.value = normalizeDirectiveValue(binding.value);
3304
- },
3305
- beforeUnmount(el) {
3306
- unmountDialRoot(el);
3307
- }
3308
- };
3309
-
3310
- // src/vue/components/ShortcutsMenu.ts
3311
- var import_vue18 = require("vue");
3312
- function formatShortcutKey(sc) {
3313
- if (!sc.key) return "\u2014";
3314
- const mod = sc.modifier === "alt" ? "\u2325" : sc.modifier === "shift" ? "\u21E7" : sc.modifier === "meta" ? "\u2318" : "";
3315
- return `${mod}${sc.key.toUpperCase()}`;
3316
- }
3317
- function formatInteraction(sc) {
3318
- const interaction = sc.interaction ?? "scroll";
3319
- switch (interaction) {
3320
- case "scroll":
3321
- return sc.key ? "key+scroll" : "scroll";
3322
- case "drag":
3323
- return "key+drag";
3324
- case "move":
3325
- return "key+move";
3326
- case "scroll-only":
3327
- return "scroll";
3328
- }
3329
- }
3330
- var ShortcutsMenu = (0, import_vue18.defineComponent)({
3331
- name: "DialKitShortcutsMenu",
3332
- props: {
3333
- panelId: {
3334
- type: String,
3335
- required: true
3336
- }
3337
- },
3338
- setup(props) {
3339
- const isOpen = (0, import_vue18.ref)(false);
3340
- const triggerRef = (0, import_vue18.ref)(null);
3341
- const dropdownRef = (0, import_vue18.ref)(null);
3342
- const pos = (0, import_vue18.ref)({ top: 0, right: 0 });
3343
- const open = () => {
3344
- const rect = triggerRef.value?.getBoundingClientRect();
3345
- if (rect) {
3346
- pos.value = { top: rect.bottom + 4, right: window.innerWidth - rect.right };
3347
- }
3348
- isOpen.value = true;
3349
- };
3350
- const close = () => {
3351
- isOpen.value = false;
3352
- };
3353
- const toggle = () => {
3354
- if (isOpen.value) close();
3355
- else open();
3356
- };
3357
- let mousedownHandler = null;
3358
- const addOutsideClickListener = () => {
3359
- mousedownHandler = (e) => {
3360
- const target = e.target;
3361
- if (triggerRef.value?.contains(target) || dropdownRef.value?.contains(target)) return;
3362
- close();
3363
- };
3364
- document.addEventListener("mousedown", mousedownHandler);
3365
- };
3366
- const removeOutsideClickListener = () => {
3367
- if (mousedownHandler) {
3368
- document.removeEventListener("mousedown", mousedownHandler);
3369
- mousedownHandler = null;
3370
- }
3371
- };
3372
- (0, import_vue18.onUnmounted)(() => {
3373
- removeOutsideClickListener();
3374
- });
3375
- return () => {
3376
- const panel = DialStore.getPanel(props.panelId);
3377
- if (!panel) return null;
3378
- const shortcuts = Object.entries(panel.shortcuts);
3379
- if (shortcuts.length === 0) return null;
3380
- const findLabel = (controls, path) => {
3381
- for (const c of controls) {
3382
- if (c.path === path) return c.label;
3383
- if (c.type === "folder" && c.children) {
3384
- const found = findLabel(c.children, path);
3385
- if (found) return found;
3386
- }
3387
- }
3388
- return path;
3389
- };
3390
- const rows = shortcuts.map(([path, shortcut]) => ({
3391
- path,
3392
- shortcut,
3393
- label: findLabel(panel.controls, path)
3394
- }));
3395
- if (isOpen.value) {
3396
- if (!mousedownHandler) addOutsideClickListener();
3397
- } else {
3398
- removeOutsideClickListener();
3399
- }
3400
- return [
3401
- (0, import_vue18.h)("button", {
3402
- ref: triggerRef,
3403
- class: "dialkit-shortcuts-trigger",
3404
- onClick: toggle,
3405
- title: "Keyboard shortcuts"
3406
- }, [
3407
- (0, import_vue18.h)("svg", {
3408
- viewBox: "0 0 24 24",
3409
- fill: "none",
3410
- stroke: "currentColor",
3411
- "stroke-width": "2",
3412
- "stroke-linecap": "round",
3413
- "stroke-linejoin": "round"
3414
- }, [
3415
- (0, import_vue18.h)("rect", { x: "2", y: "6", width: "20", height: "12", rx: "2" }),
3416
- (0, import_vue18.h)("path", { d: "M6 10H6.01" }),
3417
- (0, import_vue18.h)("path", { d: "M10 10H10.01" }),
3418
- (0, import_vue18.h)("path", { d: "M14 10H14.01" }),
3419
- (0, import_vue18.h)("path", { d: "M18 10H18.01" }),
3420
- (0, import_vue18.h)("path", { d: "M8 14H16" })
3421
- ])
3422
- ]),
3423
- isOpen.value ? (0, import_vue18.h)(import_vue18.Teleport, { to: "body" }, [
3424
- (0, import_vue18.h)("div", {
3425
- ref: dropdownRef,
3426
- class: "dialkit-root dialkit-shortcuts-dropdown",
3427
- style: {
3428
- position: "fixed",
3429
- top: `${pos.value.top}px`,
3430
- right: `${pos.value.right}px`
3431
- }
3432
- }, [
3433
- (0, import_vue18.h)("div", { class: "dialkit-shortcuts-title" }, "Keyboard Shortcuts"),
3434
- (0, import_vue18.h)(
3435
- "div",
3436
- { class: "dialkit-shortcuts-list" },
3437
- rows.map(
3438
- (row) => (0, import_vue18.h)("div", { key: row.path, class: "dialkit-shortcuts-row" }, [
3439
- (0, import_vue18.h)("span", { class: "dialkit-shortcuts-row-key" }, formatShortcutKey(row.shortcut)),
3440
- (0, import_vue18.h)("span", { class: "dialkit-shortcuts-row-label" }, row.label),
3441
- (0, import_vue18.h)("span", { class: "dialkit-shortcuts-row-mode" }, formatInteraction(row.shortcut))
3442
- ])
3443
- )
3444
- ),
3445
- (0, import_vue18.h)("div", { class: "dialkit-shortcuts-hint" }, "See pill badges on controls for keys")
3446
- ])
3447
- ]) : null
3448
- ];
3449
- };
3450
- }
3451
- });
3452
-
3453
- // src/vue/components/ButtonGroup.ts
3454
- var import_vue19 = require("vue");
3455
- var ButtonGroup = (0, import_vue19.defineComponent)({
3456
- name: "DialKitButtonGroup",
3457
- props: {
3458
- buttons: {
3459
- type: Array,
3460
- required: true
3461
- }
3462
- },
3463
- setup(props) {
3464
- return () => (0, import_vue19.h)(
3465
- "div",
3466
- { class: "dialkit-button-group" },
3467
- props.buttons.map(
3468
- (button) => (0, import_vue19.h)("button", { class: "dialkit-button", onClick: button.onClick }, button.label)
3469
- )
3470
- );
3471
- }
3472
- });
3473
- // Annotate the CommonJS export names for ESM import in node:
3474
- 0 && (module.exports = {
3475
- ButtonGroup,
3476
- ColorControl,
3477
- DialRoot,
3478
- DialStore,
3479
- EasingVisualization,
3480
- Folder,
3481
- PresetManager,
3482
- SelectControl,
3483
- ShortcutKey,
3484
- ShortcutListener,
3485
- ShortcutsMenu,
3486
- Slider,
3487
- SpringControl,
3488
- SpringVisualization,
3489
- TextControl,
3490
- Toggle,
3491
- TransitionControl,
3492
- useDialKit,
3493
- useDialKitController,
3494
- useShortcutContext,
3495
- vDialKit
3496
- });
3497
- //# sourceMappingURL=index.cjs.map