dialkit 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +433 -3
  2. package/dist/icons.d.ts +4 -1
  3. package/dist/icons.js +13 -0
  4. package/dist/icons.js.map +1 -1
  5. package/dist/index.cjs +3118 -480
  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 +3050 -417
  10. package/dist/index.js.map +1 -1
  11. package/dist/solid/index.d.ts +225 -3
  12. package/dist/solid/index.js +5697 -2508
  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 +704 -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 +76 -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 +756 -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 +704 -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 +2867 -361
  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
package/dist/index.cjs CHANGED
@@ -23,8 +23,10 @@ var index_exports = {};
23
23
  __export(index_exports, {
24
24
  ButtonGroup: () => ButtonGroup,
25
25
  ColorControl: () => ColorControl,
26
+ ControlRenderer: () => ControlRenderer,
26
27
  DialRoot: () => DialRoot,
27
28
  DialStore: () => DialStore,
29
+ DialTimeline: () => DialTimeline,
28
30
  EasingVisualization: () => EasingVisualization,
29
31
  Folder: () => Folder,
30
32
  PresetManager: () => PresetManager,
@@ -34,15 +36,18 @@ __export(index_exports, {
34
36
  SpringControl: () => SpringControl,
35
37
  SpringVisualization: () => SpringVisualization,
36
38
  TextControl: () => TextControl,
39
+ TimelineStore: () => TimelineStore,
37
40
  Toggle: () => Toggle,
38
41
  TransitionControl: () => TransitionControl,
42
+ formatClock: () => formatClock,
39
43
  useDialKit: () => useDialKit,
40
- useDialKitController: () => useDialKitController
44
+ useDialKitController: () => useDialKitController,
45
+ useDialTimeline: () => useDialTimeline
41
46
  });
42
47
  module.exports = __toCommonJS(index_exports);
43
48
 
44
49
  // src/hooks/useDialKit.ts
45
- var import_react = require("react");
50
+ var import_react2 = require("react");
46
51
 
47
52
  // src/store/DialStore.ts
48
53
  var EMPTY_VALUES = Object.freeze({});
@@ -112,6 +117,19 @@ function isSpringConfigValue(value) {
112
117
  function isEasingConfigValue(value) {
113
118
  return hasType(value, "easing");
114
119
  }
120
+ function isHexColor(value) {
121
+ return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(value);
122
+ }
123
+ function formatLabel(key) {
124
+ return key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
125
+ }
126
+ function inferStep(min, max) {
127
+ const range = max - min;
128
+ if (range <= 1) return 0.01;
129
+ if (range <= 10) return 0.1;
130
+ if (range <= 100) return 1;
131
+ return 10;
132
+ }
115
133
  function isActionConfigValue(value) {
116
134
  return hasType(value, "action");
117
135
  }
@@ -132,6 +150,9 @@ function getFirstOptionValue(options) {
132
150
  var DialStoreClass = class {
133
151
  constructor() {
134
152
  this.panels = /* @__PURE__ */ new Map();
153
+ this.panelsSnapshot = [];
154
+ this.standardPanelsSnapshot = [];
155
+ this.timelinePanelsSnapshot = [];
135
156
  this.listeners = /* @__PURE__ */ new Map();
136
157
  this.globalListeners = /* @__PURE__ */ new Set();
137
158
  this.snapshots = /* @__PURE__ */ new Map();
@@ -145,6 +166,12 @@ var DialStoreClass = class {
145
166
  this.persistConfigs = /* @__PURE__ */ new Map();
146
167
  }
147
168
  registerPanel(id, name, config, shortcuts, options = {}) {
169
+ const existingPanel = this.panels.get(id);
170
+ if (existingPanel && existingPanel.kind !== options.kind) {
171
+ console.warn(
172
+ `[dialkit] Panel id "${id}" cannot be shared by a timeline and a standard panel; the most recent registration controls where it renders.`
173
+ );
174
+ }
148
175
  this.configurePanelRetention(id, options);
149
176
  this.registrationCounts.set(id, (this.registrationCounts.get(id) ?? 0) + 1);
150
177
  const controls = this.parseConfig(config, "", shortcuts);
@@ -156,7 +183,7 @@ var DialStoreClass = class {
156
183
  const values = this.reconcileValues(defaultValues, previousValues, controlsByPath);
157
184
  const previousBaseValues = this.baseValues.get(id) ?? persisted?.baseValues ?? persisted?.values ?? {};
158
185
  const baseValues = this.reconcileValues(defaultValues, previousBaseValues, controlsByPath);
159
- this.panels.set(id, { id, name, controls, values, shortcuts: shortcuts ?? {} });
186
+ this.panels.set(id, { id, name, controls, values, shortcuts: shortcuts ?? {}, kind: options.kind });
160
187
  this.snapshots.set(id, { ...values });
161
188
  this.baseValues.set(id, baseValues);
162
189
  this.defaultValues.set(id, { ...defaultValues });
@@ -183,7 +210,7 @@ var DialStoreClass = class {
183
210
  const defaultValues = this.flattenValues(config, "");
184
211
  this.initTransitionModes(config, "", defaultValues);
185
212
  const nextValues = this.reconcileValues(defaultValues, existing.values, controlsByPath);
186
- const nextPanel = { id, name, controls, values: nextValues, shortcuts: shortcuts ?? existing.shortcuts };
213
+ const nextPanel = { id, name, controls, values: nextValues, shortcuts: shortcuts ?? existing.shortcuts, kind: options.kind ?? existing.kind };
187
214
  this.panels.set(id, nextPanel);
188
215
  this.snapshots.set(id, { ...nextValues });
189
216
  const previousBaseValues = this.baseValues.get(id) ?? {};
@@ -208,8 +235,8 @@ var DialStoreClass = class {
208
235
  }
209
236
  this.registrationCounts.delete(id);
210
237
  this.panels.delete(id);
211
- this.listeners.delete(id);
212
- this.actionListeners.delete(id);
238
+ if (this.listeners.get(id)?.size === 0) this.listeners.delete(id);
239
+ if (this.actionListeners.get(id)?.size === 0) this.actionListeners.delete(id);
213
240
  if (!this.retainedPanels.has(id)) {
214
241
  this.snapshots.delete(id);
215
242
  this.baseValues.delete(id);
@@ -301,8 +328,10 @@ var DialStoreClass = class {
301
328
  getValues(panelId) {
302
329
  return this.snapshots.get(panelId) ?? EMPTY_VALUES;
303
330
  }
304
- getPanels() {
305
- return Array.from(this.panels.values());
331
+ getPanels(kind) {
332
+ if (kind === "panel") return this.standardPanelsSnapshot;
333
+ if (kind === "timeline") return this.timelinePanelsSnapshot;
334
+ return this.panelsSnapshot;
306
335
  }
307
336
  getPanel(id) {
308
337
  return this.panels.get(id);
@@ -313,7 +342,11 @@ var DialStoreClass = class {
313
342
  }
314
343
  this.listeners.get(panelId).add(listener);
315
344
  return () => {
316
- this.listeners.get(panelId)?.delete(listener);
345
+ const listeners = this.listeners.get(panelId);
346
+ listeners?.delete(listener);
347
+ if (listeners?.size === 0 && !this.panels.has(panelId)) {
348
+ this.listeners.delete(panelId);
349
+ }
317
350
  };
318
351
  }
319
352
  subscribeGlobal(listener) {
@@ -326,7 +359,11 @@ var DialStoreClass = class {
326
359
  }
327
360
  this.actionListeners.get(panelId).add(listener);
328
361
  return () => {
329
- this.actionListeners.get(panelId)?.delete(listener);
362
+ const listeners = this.actionListeners.get(panelId);
363
+ listeners?.delete(listener);
364
+ if (listeners?.size === 0 && !this.panels.has(panelId)) {
365
+ this.actionListeners.delete(panelId);
366
+ }
330
367
  };
331
368
  }
332
369
  triggerAction(panelId, path) {
@@ -521,6 +558,9 @@ var DialStoreClass = class {
521
558
  this.listeners.get(panelId)?.forEach((fn) => fn());
522
559
  }
523
560
  notifyGlobal() {
561
+ this.panelsSnapshot = Array.from(this.panels.values());
562
+ this.standardPanelsSnapshot = this.panelsSnapshot.filter((panel) => panel.kind !== "timeline");
563
+ this.timelinePanelsSnapshot = this.panelsSnapshot.filter((panel) => panel.kind === "timeline");
524
564
  this.globalListeners.forEach((fn) => fn());
525
565
  }
526
566
  initTransitionModes(config, prefix, values) {
@@ -636,10 +676,10 @@ var DialStoreClass = class {
636
676
  return typeof value === "object" && value !== null && "type" in value && value.type === "text";
637
677
  }
638
678
  isHexColor(value) {
639
- return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(value);
679
+ return isHexColor(value);
640
680
  }
641
681
  formatLabel(key) {
642
- return key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
682
+ return formatLabel(key);
643
683
  }
644
684
  inferRange(value) {
645
685
  if (value >= 0 && value <= 1) {
@@ -655,11 +695,7 @@ var DialStoreClass = class {
655
695
  }
656
696
  }
657
697
  inferStep(min, max) {
658
- const range = max - min;
659
- if (range <= 1) return 0.01;
660
- if (range <= 10) return 0.1;
661
- if (range <= 100) return 1;
662
- return 10;
698
+ return inferStep(min, max);
663
699
  }
664
700
  normalizePreservedValue(existingValue, defaultValue, control) {
665
701
  if (existingValue === void 0 || !control) {
@@ -731,31 +767,33 @@ var DialStoreClass = class {
731
767
  return map;
732
768
  }
733
769
  };
734
- var DialStore = new DialStoreClass();
770
+ var DialStore = /* @__PURE__ */ new DialStoreClass();
735
771
 
736
- // src/hooks/useDialKit.ts
737
- function useDialKit(name, config, options) {
738
- return useDialKitController(name, config, options).values;
772
+ // src/hooks/useDialStorePanel.ts
773
+ var import_react = require("react");
774
+ function useSerialized(value) {
775
+ const ref = (0, import_react.useRef)();
776
+ if (!ref.current || !Object.is(ref.current.value, value)) {
777
+ ref.current = { value, text: JSON.stringify(value) };
778
+ }
779
+ return ref.current.text;
739
780
  }
740
- function useDialKitController(name, config, options) {
781
+ function useDialStorePanel(name, config, options = {}) {
741
782
  const instanceId = (0, import_react.useId)();
742
- const hasStableId = options?.id !== void 0;
743
- const panelId = options?.id ?? `${name}-${instanceId}`;
783
+ const hasStableId = options.id !== void 0;
784
+ const panelId = options.id ?? `${name}-${instanceId}`;
744
785
  const configRef = (0, import_react.useRef)(config);
745
- const serializedConfig = JSON.stringify(config);
746
786
  configRef.current = config;
747
- const onActionRef = (0, import_react.useRef)(options?.onAction);
748
- onActionRef.current = options?.onAction;
749
- const shortcutsRef = (0, import_react.useRef)(options?.shortcuts);
750
- shortcutsRef.current = options?.shortcuts;
751
- const persistRef = (0, import_react.useRef)(options?.persist);
752
- persistRef.current = options?.persist;
753
- const serializedShortcuts = JSON.stringify(options?.shortcuts);
754
- const serializedPersist = JSON.stringify(options?.persist);
787
+ const optionsRef = (0, import_react.useRef)(options);
788
+ optionsRef.current = options;
789
+ const serializedConfig = useSerialized(config);
790
+ const serializedShortcuts = useSerialized(options.shortcuts);
791
+ const serializedPersist = useSerialized(options.persist);
755
792
  (0, import_react.useEffect)(() => {
756
- DialStore.registerPanel(panelId, name, configRef.current, shortcutsRef.current, {
793
+ DialStore.registerPanel(panelId, name, configRef.current, optionsRef.current.shortcuts, {
757
794
  retainOnUnmount: hasStableId,
758
- persist: persistRef.current
795
+ persist: optionsRef.current.persist,
796
+ kind: optionsRef.current.kind
759
797
  });
760
798
  return () => DialStore.unregisterPanel(panelId);
761
799
  }, [hasStableId, panelId, name]);
@@ -765,49 +803,65 @@ function useDialKitController(name, config, options) {
765
803
  mountedRef.current = true;
766
804
  return;
767
805
  }
768
- DialStore.updatePanel(panelId, name, configRef.current, shortcutsRef.current, {
806
+ DialStore.updatePanel(panelId, name, configRef.current, optionsRef.current.shortcuts, {
769
807
  retainOnUnmount: hasStableId,
770
- persist: persistRef.current
808
+ persist: optionsRef.current.persist,
809
+ kind: optionsRef.current.kind
771
810
  });
772
811
  }, [hasStableId, panelId, name, serializedConfig, serializedShortcuts, serializedPersist]);
773
- (0, import_react.useEffect)(() => {
774
- return DialStore.subscribeActions(panelId, (action) => {
775
- onActionRef.current?.(action);
776
- });
777
- }, [panelId]);
778
812
  const subscribe = (0, import_react.useCallback)(
779
813
  (callback) => DialStore.subscribe(panelId, callback),
780
814
  [panelId]
781
815
  );
782
- const getSnapshot = (0, import_react.useCallback)(
783
- () => DialStore.getValues(panelId),
784
- [panelId]
785
- );
816
+ const getSnapshot = (0, import_react.useCallback)(() => DialStore.getValues(panelId), [panelId]);
786
817
  const flatValues = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
787
- const values = (0, import_react.useMemo)(
818
+ return { panelId, flatValues, serializedConfig };
819
+ }
820
+
821
+ // src/hooks/useDialKit.ts
822
+ function useDialKit(name, config, options) {
823
+ return useDialKitController(name, config, options).values;
824
+ }
825
+ function useDialKitController(name, config, options) {
826
+ const { panelId, flatValues, serializedConfig } = useDialStorePanel(name, config, {
827
+ id: options?.id,
828
+ persist: options?.persist,
829
+ shortcuts: options?.shortcuts
830
+ });
831
+ const configRef = (0, import_react2.useRef)(config);
832
+ configRef.current = config;
833
+ const onActionRef = (0, import_react2.useRef)(options?.onAction);
834
+ onActionRef.current = options?.onAction;
835
+ (0, import_react2.useEffect)(() => {
836
+ return DialStore.subscribeActions(panelId, (action) => {
837
+ onActionRef.current?.(action);
838
+ });
839
+ }, [panelId]);
840
+ const values = (0, import_react2.useMemo)(
788
841
  () => resolveDialValues(configRef.current, flatValues),
842
+ // eslint-disable-next-line react-hooks/exhaustive-deps
789
843
  [flatValues, serializedConfig]
790
844
  );
791
- const setValue = (0, import_react.useCallback)(
845
+ const setValue = (0, import_react2.useCallback)(
792
846
  (path, value) => {
793
847
  DialStore.updateValue(panelId, path, value);
794
848
  },
795
849
  [panelId]
796
850
  );
797
- const setValues = (0, import_react.useCallback)(
851
+ const setValues = (0, import_react2.useCallback)(
798
852
  (nextValues) => {
799
853
  DialStore.updateValues(panelId, flattenDialValueUpdates(configRef.current, nextValues));
800
854
  },
801
855
  [panelId]
802
856
  );
803
- const resetValues = (0, import_react.useCallback)(() => {
857
+ const resetValues = (0, import_react2.useCallback)(() => {
804
858
  DialStore.resetValues(panelId);
805
859
  }, [panelId]);
806
- const getValues = (0, import_react.useCallback)(
860
+ const getValues = (0, import_react2.useCallback)(
807
861
  () => resolveDialValues(configRef.current, DialStore.getValues(panelId)),
808
862
  [panelId]
809
863
  );
810
- return (0, import_react.useMemo)(
864
+ return (0, import_react2.useMemo)(
811
865
  () => ({
812
866
  values,
813
867
  setValue,
@@ -820,16 +874,222 @@ function useDialKitController(name, config, options) {
820
874
  }
821
875
 
822
876
  // src/components/DialRoot.tsx
823
- var import_react17 = require("react");
877
+ var import_react21 = require("react");
824
878
  var import_react_dom3 = require("react-dom");
825
879
 
880
+ // src/store/TimelineStore.ts
881
+ function loopSpan(duration, loopStart) {
882
+ if (!Number.isFinite(duration) || duration <= 0) return 0;
883
+ if (!Number.isFinite(loopStart)) loopStart = 0;
884
+ const start = Math.min(Math.max(0, loopStart), duration);
885
+ return duration - start > 0 ? duration - start : duration;
886
+ }
887
+ function foldLoopTime(time, duration, loopStart = 0) {
888
+ if (!Number.isFinite(time) || !Number.isFinite(duration) || duration <= 0) {
889
+ return { time: 0, wraps: 0 };
890
+ }
891
+ if (time < duration) return { time, wraps: 0 };
892
+ const span = loopSpan(duration, loopStart);
893
+ const base = duration - span;
894
+ const over = time - base;
895
+ return { time: base + over % span, wraps: Math.floor(over / span) };
896
+ }
897
+ var TIMELINE_CLIP_COLORS = [
898
+ "#E8E8E8"
899
+ // neutral white — slightly off-white so the pure-white selection ring still reads
900
+ ];
901
+ var EMPTY_TRANSPORT = Object.freeze({ time: 0, playing: false, duration: 0, wraps: 0 });
902
+ var TimelineStoreClass = class {
903
+ constructor() {
904
+ this.timelines = /* @__PURE__ */ new Map();
905
+ this.transports = /* @__PURE__ */ new Map();
906
+ this.listeners = /* @__PURE__ */ new Map();
907
+ this.globalListeners = /* @__PURE__ */ new Set();
908
+ this.registrationCounts = /* @__PURE__ */ new Map();
909
+ this.listCache = null;
910
+ this.rafId = null;
911
+ this.lastTick = 0;
912
+ this.tick = (now) => {
913
+ const dt = Math.max(0, (now - this.lastTick) / 1e3);
914
+ this.lastTick = now;
915
+ let anyPlaying = false;
916
+ for (const [id, transport] of this.transports) {
917
+ if (!transport.playing) continue;
918
+ const meta = this.timelines.get(id);
919
+ const duration = meta?.duration ?? transport.duration;
920
+ if (!Number.isFinite(duration) || duration <= 0) {
921
+ this.transports.set(id, { time: 0, playing: false, duration: 0, wraps: 0 });
922
+ this.notify(id);
923
+ continue;
924
+ }
925
+ let time = transport.time + dt;
926
+ let playing = true;
927
+ let wraps = transport.wraps;
928
+ if (time >= duration) {
929
+ if (meta?.loop) {
930
+ const folded = foldLoopTime(time, duration, meta.loopStart);
931
+ time = folded.time;
932
+ wraps += folded.wraps;
933
+ } else {
934
+ time = duration;
935
+ playing = false;
936
+ }
937
+ }
938
+ this.transports.set(id, { time, playing, duration, wraps });
939
+ if (playing) anyPlaying = true;
940
+ this.notify(id);
941
+ }
942
+ this.rafId = anyPlaying ? window.requestAnimationFrame(this.tick) : null;
943
+ };
944
+ }
945
+ register(meta, options) {
946
+ const existing = this.timelines.get(meta.id);
947
+ if (existing && existing.name !== meta.name) {
948
+ console.warn(
949
+ `[dialkit] Timeline id "${meta.id}" is already registered by "${existing.name}"; "${meta.name}" will share and overwrite that transport.`
950
+ );
951
+ }
952
+ this.registrationCounts.set(meta.id, (this.registrationCounts.get(meta.id) ?? 0) + 1);
953
+ this.applyMeta(meta, options.autoplay);
954
+ }
955
+ update(meta) {
956
+ if (!this.timelines.has(meta.id)) return;
957
+ this.applyMeta(meta, false);
958
+ }
959
+ unregister(id) {
960
+ const nextCount = (this.registrationCounts.get(id) ?? 1) - 1;
961
+ if (nextCount > 0) {
962
+ this.registrationCounts.set(id, nextCount);
963
+ return;
964
+ }
965
+ this.registrationCounts.delete(id);
966
+ this.timelines.delete(id);
967
+ this.transports.delete(id);
968
+ if (this.listeners.get(id)?.size === 0) this.listeners.delete(id);
969
+ this.listCache = null;
970
+ this.notifyGlobal();
971
+ }
972
+ play(id) {
973
+ const transport = this.transports.get(id);
974
+ if (!transport || transport.duration <= 0 || transport.playing) return;
975
+ const restart = transport.time >= transport.duration;
976
+ this.transports.set(id, {
977
+ ...transport,
978
+ time: restart ? 0 : transport.time,
979
+ wraps: restart ? 0 : transport.wraps,
980
+ playing: true
981
+ });
982
+ this.notify(id);
983
+ this.ensureLoop();
984
+ }
985
+ pause(id) {
986
+ const transport = this.transports.get(id);
987
+ if (!transport || !transport.playing) return;
988
+ this.transports.set(id, { ...transport, playing: false });
989
+ this.notify(id);
990
+ }
991
+ replay(id) {
992
+ const transport = this.transports.get(id);
993
+ if (!transport || transport.duration <= 0) return;
994
+ this.transports.set(id, { ...transport, time: 0, wraps: 0, playing: true });
995
+ this.notify(id);
996
+ this.ensureLoop();
997
+ }
998
+ seek(id, time) {
999
+ const transport = this.transports.get(id);
1000
+ if (!transport || !Number.isFinite(time)) return;
1001
+ const clamped = Math.min(transport.duration, Math.max(0, time));
1002
+ this.transports.set(id, { ...transport, time: clamped, wraps: 0 });
1003
+ this.notify(id);
1004
+ }
1005
+ getTransport(id) {
1006
+ return this.transports.get(id) ?? EMPTY_TRANSPORT;
1007
+ }
1008
+ getTimeline(id) {
1009
+ return this.timelines.get(id);
1010
+ }
1011
+ getTimelines() {
1012
+ if (!this.listCache) {
1013
+ this.listCache = Array.from(this.timelines.values());
1014
+ }
1015
+ return this.listCache;
1016
+ }
1017
+ subscribe(id, listener) {
1018
+ if (!this.listeners.has(id)) {
1019
+ this.listeners.set(id, /* @__PURE__ */ new Set());
1020
+ }
1021
+ this.listeners.get(id).add(listener);
1022
+ return () => {
1023
+ const listeners = this.listeners.get(id);
1024
+ listeners?.delete(listener);
1025
+ if (listeners?.size === 0 && !this.timelines.has(id)) {
1026
+ this.listeners.delete(id);
1027
+ }
1028
+ };
1029
+ }
1030
+ subscribeGlobal(listener) {
1031
+ this.globalListeners.add(listener);
1032
+ return () => {
1033
+ this.globalListeners.delete(listener);
1034
+ };
1035
+ }
1036
+ applyMeta(meta, autoplay) {
1037
+ const duration = Number.isFinite(meta.duration) ? Math.max(0, meta.duration) : 0;
1038
+ const loopStart = Number.isFinite(meta.loopStart) ? Math.min(duration, Math.max(0, meta.loopStart)) : 0;
1039
+ const safeMeta = { ...meta, duration, loopStart };
1040
+ this.timelines.set(meta.id, safeMeta);
1041
+ const existing = this.transports.get(meta.id);
1042
+ if (existing) {
1043
+ this.transports.set(meta.id, {
1044
+ time: Math.min(existing.time, duration),
1045
+ playing: duration > 0 && existing.playing,
1046
+ duration,
1047
+ wraps: existing.wraps
1048
+ });
1049
+ } else {
1050
+ const playing = duration > 0 && autoplay;
1051
+ this.transports.set(meta.id, { time: 0, playing, duration, wraps: 0 });
1052
+ if (playing) this.ensureLoop();
1053
+ }
1054
+ this.listCache = null;
1055
+ this.notify(meta.id);
1056
+ this.notifyGlobal();
1057
+ }
1058
+ ensureLoop() {
1059
+ if (this.rafId !== null || typeof window === "undefined") return;
1060
+ this.lastTick = performance.now();
1061
+ this.rafId = window.requestAnimationFrame(this.tick);
1062
+ }
1063
+ notify(id) {
1064
+ this.listeners.get(id)?.forEach((fn) => fn());
1065
+ }
1066
+ notifyGlobal() {
1067
+ this.globalListeners.forEach((fn) => fn());
1068
+ }
1069
+ };
1070
+ var TimelineStore = /* @__PURE__ */ new TimelineStoreClass();
1071
+
1072
+ // src/env.ts
1073
+ var import_meta = {};
1074
+ 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;
1075
+
826
1076
  // src/components/Folder.tsx
827
- var import_react2 = require("react");
828
- var import_react3 = require("motion/react");
1077
+ var import_react3 = require("react");
1078
+ var import_react4 = require("motion/react");
829
1079
 
830
1080
  // src/icons.ts
831
1081
  var ICON_CHEVRON = "M6 9.5L12 15.5L18 9.5";
832
1082
  var ICON_CHECK = "M5 12.75L10 19L19 5";
1083
+ var ICON_PAUSE = [
1084
+ "M6.75 3C5.23122 3 4 4.23122 4 5.75V18.25C4 19.7688 5.23122 21 6.75 21H7.25C8.76878 21 10 19.7688 10 18.25V5.75C10 4.23122 8.76878 3 7.25 3H6.75Z",
1085
+ "M16.75 3C15.2312 3 14 4.23122 14 5.75V18.25C14 19.7688 15.2312 21 16.75 21H17.25C18.7688 21 20 19.7688 20 18.25V5.75C20 4.23122 18.7688 3 17.25 3H16.75Z"
1086
+ ];
1087
+ var ICON_PLAY = "M9.24394 2.36758C7.41419 1.18362 5 2.49701 5 4.67639V19.3238C5 21.5032 7.41419 22.8166 9.24394 21.6326L20.5624 14.3089C22.2371 13.2253 22.2372 10.775 20.5624 9.69129L9.24394 2.36758Z";
1088
+ var ICON_TIMELINE = [
1089
+ "M18.868 10C20.8517 10.0003 22.2886 11.8914 21.7577 13.8027L20.369 18.8027C20.0083 20.1012 18.826 20.9999 17.4784 21H6.51941C5.17179 21 3.98948 20.1012 3.62878 18.8027L2.24011 13.8027C1.7092 11.8913 3.14603 10.0003 5.12976 10H18.868Z",
1090
+ "M18.9989 6.5C19.5511 6.50007 19.9989 6.94776 19.9989 7.5C19.9989 8.05224 19.5511 8.49993 18.9989 8.5H4.9989C4.44661 8.5 3.9989 8.05228 3.9989 7.5C3.9989 6.94772 4.44661 6.5 4.9989 6.5H18.9989Z",
1091
+ "M16.9989 3C17.5511 3.00007 17.9989 3.44776 17.9989 4C17.9989 4.55224 17.5511 4.99993 16.9989 5H6.9989C6.44661 5 5.9989 4.55228 5.9989 4C5.9989 3.44772 6.44661 3 6.9989 3H16.9989Z"
1092
+ ];
833
1093
  var ICON_CLIPBOARD = {
834
1094
  board: "M8 6C8 4.34315 9.34315 3 11 3H13C14.6569 3 16 4.34315 16 6V7H8V6Z",
835
1095
  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",
@@ -861,18 +1121,18 @@ var ICON_PANEL = {
861
1121
  // src/components/Folder.tsx
862
1122
  var import_jsx_runtime = require("react/jsx-runtime");
863
1123
  function Folder({ title, children, defaultOpen = true, isRoot = false, inline = false, onOpenChange, toolbar, panelHeightOffset = 10 }) {
864
- const [isOpen, setIsOpen] = (0, import_react2.useState)(defaultOpen);
865
- const [isCollapsed, setIsCollapsed] = (0, import_react2.useState)(!defaultOpen);
866
- const contentRef = (0, import_react2.useRef)(null);
867
- const [contentHeight, setContentHeight] = (0, import_react2.useState)(void 0);
868
- const [windowHeight, setWindowHeight] = (0, import_react2.useState)(typeof window !== "undefined" ? window.innerHeight : 800);
869
- (0, import_react2.useEffect)(() => {
1124
+ const [isOpen, setIsOpen] = (0, import_react3.useState)(defaultOpen);
1125
+ const [isCollapsed, setIsCollapsed] = (0, import_react3.useState)(!defaultOpen);
1126
+ const contentRef = (0, import_react3.useRef)(null);
1127
+ const [contentHeight, setContentHeight] = (0, import_react3.useState)(void 0);
1128
+ const [windowHeight, setWindowHeight] = (0, import_react3.useState)(typeof window !== "undefined" ? window.innerHeight : 800);
1129
+ (0, import_react3.useEffect)(() => {
870
1130
  if (!isRoot) return;
871
1131
  const onResize = () => setWindowHeight(window.innerHeight);
872
1132
  window.addEventListener("resize", onResize);
873
1133
  return () => window.removeEventListener("resize", onResize);
874
1134
  }, [isRoot]);
875
- (0, import_react2.useEffect)(() => {
1135
+ (0, import_react3.useEffect)(() => {
876
1136
  const el = contentRef.current;
877
1137
  if (!el) return;
878
1138
  const ro = new ResizeObserver(() => {
@@ -918,7 +1178,7 @@ function Folder({ title, children, defaultOpen = true, isRoot = false, inline =
918
1178
  }
919
1179
  ),
920
1180
  !isRoot && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
921
- import_react3.motion.svg,
1181
+ import_react4.motion.svg,
922
1182
  {
923
1183
  className: "dialkit-folder-icon",
924
1184
  viewBox: "0 0 24 24",
@@ -936,8 +1196,8 @@ function Folder({ title, children, defaultOpen = true, isRoot = false, inline =
936
1196
  ] }),
937
1197
  isRoot && toolbar && isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dialkit-panel-toolbar", onClick: (e) => e.stopPropagation(), children: toolbar })
938
1198
  ] }),
939
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react3.AnimatePresence, { initial: false, children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
940
- import_react3.motion.div,
1199
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.AnimatePresence, { initial: false, children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1200
+ import_react4.motion.div,
941
1201
  {
942
1202
  className: "dialkit-folder-content",
943
1203
  initial: isRoot ? void 0 : { height: 0, opacity: 0 },
@@ -957,7 +1217,7 @@ function Folder({ title, children, defaultOpen = true, isRoot = false, inline =
957
1217
  }
958
1218
  const panelStyle = isOpen ? { width: 280, height: contentHeight !== void 0 ? Math.min(contentHeight + panelHeightOffset, windowHeight - 32) : "auto", borderRadius: 14, boxShadow: "var(--dial-shadow)", cursor: void 0, overflowY: "auto" } : { width: 42, height: 42, borderRadius: "50%", boxSizing: "border-box", boxShadow: "var(--dial-shadow-collapsed)", overflow: "hidden", cursor: "pointer" };
959
1219
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
960
- import_react3.motion.div,
1220
+ import_react4.motion.div,
961
1221
  {
962
1222
  className: "dialkit-panel-inner",
963
1223
  style: panelStyle,
@@ -973,11 +1233,43 @@ function Folder({ title, children, defaultOpen = true, isRoot = false, inline =
973
1233
  }
974
1234
 
975
1235
  // src/components/Panel.tsx
976
- var import_react15 = require("react");
977
- var import_react16 = require("motion/react");
1236
+ var import_react17 = require("react");
1237
+ var import_react18 = require("motion/react");
1238
+
1239
+ // src/copy-instruction.ts
1240
+ function buildCopyInstruction(hookName, panelName, values) {
1241
+ const jsonStr = JSON.stringify(values, null, 2);
1242
+ if (hookName === "useDialTimeline" || hookName === "createDialTimeline") {
1243
+ return `Update the ${hookName} configuration for "${panelName}" with these values:
1244
+
1245
+ \`\`\`json
1246
+ ${jsonStr}
1247
+ \`\`\`
1248
+
1249
+ Apply these values as the new defaults in the ${hookName} call. Keep the existing \`clip.current\` bindings while this timeline is being authored; do not convert the animation or remove DialKit yet.
1250
+
1251
+ Add this comment immediately above the ${hookName} call as a production handoff note:
1252
+
1253
+ \`\`\`tsx
1254
+ // TODO(production): DialKit's clip.current values are the scrubbable authoring preview.
1255
+ // Replace them with equivalent real Motion animations using the tuned timeline
1256
+ // timings and transitions, then remove ${hookName} and <DialTimeline />.
1257
+ \`\`\``;
1258
+ }
1259
+ return `Update the ${hookName} configuration for "${panelName}" with these values:
1260
+
1261
+ \`\`\`json
1262
+ ${jsonStr}
1263
+ \`\`\`
1264
+
1265
+ Apply these values as the new defaults in the ${hookName} call.`;
1266
+ }
1267
+
1268
+ // src/components/ControlRenderer.tsx
1269
+ var import_react14 = require("react");
978
1270
 
979
1271
  // src/components/ShortcutListener.tsx
980
- var import_react4 = require("react");
1272
+ var import_react5 = require("react");
981
1273
 
982
1274
  // src/shortcut-utils.ts
983
1275
  function decimalsForStep(step) {
@@ -1066,14 +1358,14 @@ function formatModifier(modifier) {
1066
1358
 
1067
1359
  // src/components/ShortcutListener.tsx
1068
1360
  var import_jsx_runtime2 = require("react/jsx-runtime");
1069
- var ShortcutContext = (0, import_react4.createContext)({ activePanelId: null, activePath: null });
1361
+ var ShortcutContext = (0, import_react5.createContext)({ activePanelId: null, activePath: null });
1070
1362
  function ShortcutListener({ children }) {
1071
- const [activeShortcut, setActiveShortcut] = (0, import_react4.useState)({ activePanelId: null, activePath: null });
1072
- const activeKeysRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
1073
- const isDraggingRef = (0, import_react4.useRef)(false);
1074
- const lastMouseXRef = (0, import_react4.useRef)(null);
1075
- const dragAccumulatorRef = (0, import_react4.useRef)(0);
1076
- const resolveActiveTarget = (0, import_react4.useCallback)((interaction) => {
1363
+ const [activeShortcut, setActiveShortcut] = (0, import_react5.useState)({ activePanelId: null, activePath: null });
1364
+ const activeKeysRef = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
1365
+ const isDraggingRef = (0, import_react5.useRef)(false);
1366
+ const lastMouseXRef = (0, import_react5.useRef)(null);
1367
+ const dragAccumulatorRef = (0, import_react5.useRef)(0);
1368
+ const resolveActiveTarget = (0, import_react5.useCallback)((interaction) => {
1077
1369
  for (const key of activeKeysRef.current) {
1078
1370
  const panels = DialStore.getPanels();
1079
1371
  for (const panel of panels) {
@@ -1090,7 +1382,7 @@ function ShortcutListener({ children }) {
1090
1382
  }
1091
1383
  return null;
1092
1384
  }, []);
1093
- (0, import_react4.useEffect)(() => {
1385
+ (0, import_react5.useEffect)(() => {
1094
1386
  const handleKeyDown = (e) => {
1095
1387
  if (isInputFocused()) return;
1096
1388
  const key = e.key.toLowerCase();
@@ -1252,8 +1544,8 @@ function ShortcutListener({ children }) {
1252
1544
  }
1253
1545
 
1254
1546
  // src/components/Slider.tsx
1255
- var import_react5 = require("react");
1256
- var import_react6 = require("motion/react");
1547
+ var import_react6 = require("react");
1548
+ var import_react7 = require("motion/react");
1257
1549
  var import_jsx_runtime3 = require("react/jsx-runtime");
1258
1550
  var CLICK_THRESHOLD = 3;
1259
1551
  var DEAD_ZONE = 32;
@@ -1270,47 +1562,47 @@ function Slider({
1270
1562
  shortcut,
1271
1563
  shortcutActive
1272
1564
  }) {
1273
- const wrapperRef = (0, import_react5.useRef)(null);
1274
- const trackRef = (0, import_react5.useRef)(null);
1275
- const inputRef = (0, import_react5.useRef)(null);
1276
- const labelRef = (0, import_react5.useRef)(null);
1277
- const valueSpanRef = (0, import_react5.useRef)(null);
1278
- const [isInteracting, setIsInteracting] = (0, import_react5.useState)(false);
1279
- const [isDragging, setIsDragging] = (0, import_react5.useState)(false);
1280
- const [isHovered, setIsHovered] = (0, import_react5.useState)(false);
1281
- const [isValueHovered, setIsValueHovered] = (0, import_react5.useState)(false);
1282
- const [isValueEditable, setIsValueEditable] = (0, import_react5.useState)(false);
1283
- const [showInput, setShowInput] = (0, import_react5.useState)(false);
1284
- const [inputValue, setInputValue] = (0, import_react5.useState)("");
1285
- const hoverTimeoutRef = (0, import_react5.useRef)(null);
1286
- const pointerDownPos = (0, import_react5.useRef)(null);
1287
- const isClickRef = (0, import_react5.useRef)(true);
1288
- const animRef = (0, import_react5.useRef)(null);
1289
- const wrapperRectRef = (0, import_react5.useRef)(null);
1290
- const scaleRef = (0, import_react5.useRef)(1);
1565
+ const wrapperRef = (0, import_react6.useRef)(null);
1566
+ const trackRef = (0, import_react6.useRef)(null);
1567
+ const inputRef = (0, import_react6.useRef)(null);
1568
+ const labelRef = (0, import_react6.useRef)(null);
1569
+ const valueSpanRef = (0, import_react6.useRef)(null);
1570
+ const [isInteracting, setIsInteracting] = (0, import_react6.useState)(false);
1571
+ const [isDragging, setIsDragging] = (0, import_react6.useState)(false);
1572
+ const [isHovered, setIsHovered] = (0, import_react6.useState)(false);
1573
+ const [isValueHovered, setIsValueHovered] = (0, import_react6.useState)(false);
1574
+ const [isValueEditable, setIsValueEditable] = (0, import_react6.useState)(false);
1575
+ const [showInput, setShowInput] = (0, import_react6.useState)(false);
1576
+ const [inputValue, setInputValue] = (0, import_react6.useState)("");
1577
+ const hoverTimeoutRef = (0, import_react6.useRef)(null);
1578
+ const pointerDownPos = (0, import_react6.useRef)(null);
1579
+ const isClickRef = (0, import_react6.useRef)(true);
1580
+ const animRef = (0, import_react6.useRef)(null);
1581
+ const wrapperRectRef = (0, import_react6.useRef)(null);
1582
+ const scaleRef = (0, import_react6.useRef)(1);
1291
1583
  const percentage = (value - min) / (max - min) * 100;
1292
1584
  const isActive = isInteracting || isHovered;
1293
- const fillPercent = (0, import_react6.useMotionValue)(percentage);
1294
- const fillWidth = (0, import_react6.useTransform)(fillPercent, (pct) => `${pct}%`);
1295
- const handleLeft = (0, import_react6.useTransform)(
1585
+ const fillPercent = (0, import_react7.useMotionValue)(percentage);
1586
+ const fillWidth = (0, import_react7.useTransform)(fillPercent, (pct) => `${pct}%`);
1587
+ const handleLeft = (0, import_react7.useTransform)(
1296
1588
  fillPercent,
1297
1589
  (pct) => `max(5px, calc(${pct}% - 9px))`
1298
1590
  );
1299
- const rubberStretchPx = (0, import_react6.useMotionValue)(0);
1300
- const rubberBandWidth = (0, import_react6.useTransform)(
1591
+ const rubberStretchPx = (0, import_react7.useMotionValue)(0);
1592
+ const rubberBandWidth = (0, import_react7.useTransform)(
1301
1593
  rubberStretchPx,
1302
1594
  (stretch) => `calc(100% + ${Math.abs(stretch)}px)`
1303
1595
  );
1304
- const rubberBandX = (0, import_react6.useTransform)(
1596
+ const rubberBandX = (0, import_react7.useTransform)(
1305
1597
  rubberStretchPx,
1306
1598
  (stretch) => stretch < 0 ? stretch : 0
1307
1599
  );
1308
- (0, import_react5.useEffect)(() => {
1600
+ (0, import_react6.useEffect)(() => {
1309
1601
  if (!isInteracting && !animRef.current) {
1310
1602
  fillPercent.jump(percentage);
1311
1603
  }
1312
1604
  }, [percentage, isInteracting, fillPercent]);
1313
- const positionToValue = (0, import_react5.useCallback)(
1605
+ const positionToValue = (0, import_react6.useCallback)(
1314
1606
  (clientX) => {
1315
1607
  const rect = wrapperRectRef.current;
1316
1608
  if (!rect) return value;
@@ -1323,11 +1615,11 @@ function Slider({
1323
1615
  },
1324
1616
  [min, max, value]
1325
1617
  );
1326
- const percentFromValue = (0, import_react5.useCallback)(
1618
+ const percentFromValue = (0, import_react6.useCallback)(
1327
1619
  (v) => (v - min) / (max - min) * 100,
1328
1620
  [min, max]
1329
1621
  );
1330
- const computeRubberStretch = (0, import_react5.useCallback)(
1622
+ const computeRubberStretch = (0, import_react6.useCallback)(
1331
1623
  (clientX, sign) => {
1332
1624
  const rect = wrapperRectRef.current;
1333
1625
  if (!rect) return 0;
@@ -1337,7 +1629,7 @@ function Slider({
1337
1629
  },
1338
1630
  []
1339
1631
  );
1340
- const handlePointerDown = (0, import_react5.useCallback)(
1632
+ const handlePointerDown = (0, import_react6.useCallback)(
1341
1633
  (e) => {
1342
1634
  if (showInput) return;
1343
1635
  e.preventDefault();
@@ -1353,7 +1645,7 @@ function Slider({
1353
1645
  },
1354
1646
  [showInput]
1355
1647
  );
1356
- const handlePointerMove = (0, import_react5.useCallback)(
1648
+ const handlePointerMove = (0, import_react6.useCallback)(
1357
1649
  (e) => {
1358
1650
  if (!isInteracting || !pointerDownPos.current) return;
1359
1651
  const dx = e.clientX - pointerDownPos.current.x;
@@ -1394,7 +1686,7 @@ function Slider({
1394
1686
  computeRubberStretch
1395
1687
  ]
1396
1688
  );
1397
- const handlePointerUp = (0, import_react5.useCallback)(
1689
+ const handlePointerUp = (0, import_react6.useCallback)(
1398
1690
  (e) => {
1399
1691
  if (!isInteracting) return;
1400
1692
  if (isClickRef.current) {
@@ -1405,7 +1697,7 @@ function Slider({
1405
1697
  if (animRef.current) {
1406
1698
  animRef.current.stop();
1407
1699
  }
1408
- animRef.current = (0, import_react6.animate)(fillPercent, newPct, {
1700
+ animRef.current = (0, import_react7.animate)(fillPercent, newPct, {
1409
1701
  type: "spring",
1410
1702
  stiffness: 300,
1411
1703
  damping: 25,
@@ -1417,7 +1709,7 @@ function Slider({
1417
1709
  onChange(roundValue(snappedValue, step));
1418
1710
  }
1419
1711
  if (rubberStretchPx.get() !== 0) {
1420
- (0, import_react6.animate)(rubberStretchPx, 0, {
1712
+ (0, import_react7.animate)(rubberStretchPx, 0, {
1421
1713
  type: "spring",
1422
1714
  visualDuration: 0.35,
1423
1715
  bounce: 0.15
@@ -1438,7 +1730,7 @@ function Slider({
1438
1730
  rubberStretchPx
1439
1731
  ]
1440
1732
  );
1441
- (0, import_react5.useEffect)(() => {
1733
+ (0, import_react6.useEffect)(() => {
1442
1734
  if (isValueHovered && !showInput && !isValueEditable) {
1443
1735
  hoverTimeoutRef.current = setTimeout(() => {
1444
1736
  setIsValueEditable(true);
@@ -1456,7 +1748,7 @@ function Slider({
1456
1748
  }
1457
1749
  };
1458
1750
  }, [isValueHovered, showInput, isValueEditable]);
1459
- (0, import_react5.useEffect)(() => {
1751
+ (0, import_react6.useEffect)(() => {
1460
1752
  if (showInput && inputRef.current) {
1461
1753
  inputRef.current.focus();
1462
1754
  inputRef.current.select();
@@ -1534,7 +1826,7 @@ function Slider({
1534
1826
  );
1535
1827
  });
1536
1828
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref: wrapperRef, className: "dialkit-slider-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1537
- import_react6.motion.div,
1829
+ import_react7.motion.div,
1538
1830
  {
1539
1831
  ref: trackRef,
1540
1832
  className: `dialkit-slider ${isActive ? "dialkit-slider-active" : ""}`,
@@ -1547,7 +1839,7 @@ function Slider({
1547
1839
  children: [
1548
1840
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dialkit-slider-hashmarks", children: hashMarks }),
1549
1841
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1550
- import_react6.motion.div,
1842
+ import_react7.motion.div,
1551
1843
  {
1552
1844
  className: "dialkit-slider-fill",
1553
1845
  style: {
@@ -1556,7 +1848,7 @@ function Slider({
1556
1848
  }
1557
1849
  ),
1558
1850
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1559
- import_react6.motion.div,
1851
+ import_react7.motion.div,
1560
1852
  {
1561
1853
  className: "dialkit-slider-handle",
1562
1854
  style: {
@@ -1611,17 +1903,17 @@ function Slider({
1611
1903
  }
1612
1904
 
1613
1905
  // src/components/SegmentedControl.tsx
1614
- var import_react7 = require("react");
1906
+ var import_react8 = require("react");
1615
1907
  var import_jsx_runtime4 = require("react/jsx-runtime");
1616
1908
  function SegmentedControl({
1617
1909
  options,
1618
1910
  value,
1619
1911
  onChange
1620
1912
  }) {
1621
- const containerRef = (0, import_react7.useRef)(null);
1622
- const hasAnimated = (0, import_react7.useRef)(false);
1623
- const [pillStyle, setPillStyle] = (0, import_react7.useState)(null);
1624
- const measure = (0, import_react7.useCallback)(() => {
1913
+ const containerRef = (0, import_react8.useRef)(null);
1914
+ const hasAnimated = (0, import_react8.useRef)(false);
1915
+ const [pillStyle, setPillStyle] = (0, import_react8.useState)(null);
1916
+ const measure = (0, import_react8.useCallback)(() => {
1625
1917
  const container = containerRef.current;
1626
1918
  if (!container) return;
1627
1919
  const activeButton = container.querySelector('[data-active="true"]');
@@ -1631,7 +1923,7 @@ function SegmentedControl({
1631
1923
  width: activeButton.offsetWidth
1632
1924
  });
1633
1925
  }, []);
1634
- (0, import_react7.useLayoutEffect)(() => {
1926
+ (0, import_react8.useLayoutEffect)(() => {
1635
1927
  measure();
1636
1928
  }, [value, options.length, measure]);
1637
1929
  const shouldAnimate = hasAnimated.current;
@@ -1686,47 +1978,118 @@ function Toggle({ label, checked, onChange, shortcut, shortcutActive }) {
1686
1978
  ] });
1687
1979
  }
1688
1980
 
1689
- // src/components/SpringVisualization.tsx
1690
- var import_jsx_runtime6 = require("react/jsx-runtime");
1691
- function generateSpringCurve(stiffness, damping, mass, duration) {
1692
- const points = [];
1693
- const steps = 100;
1694
- const dt = duration / steps;
1695
- let position = 0;
1696
- let velocity = 0;
1697
- const target = 1;
1698
- for (let i = 0; i <= steps; i++) {
1699
- const time = i * dt;
1700
- points.push([time, position]);
1701
- const springForce = -stiffness * (position - target);
1702
- const dampingForce = -damping * velocity;
1703
- const acceleration = (springForce + dampingForce) / mass;
1704
- velocity += acceleration * dt;
1705
- position += velocity * dt;
1981
+ // src/transition-math.ts
1982
+ function round2(value) {
1983
+ return Math.round(value * 100) / 100;
1984
+ }
1985
+ function clamp(value, min, max) {
1986
+ return Math.min(max, Math.max(min, value));
1987
+ }
1988
+ function isTransitionConfig(value) {
1989
+ return isSpringConfigValue(value) || isEasingConfigValue(value);
1990
+ }
1991
+ function isPhysicsSpring(transition) {
1992
+ return transition.type === "spring" && (transition.stiffness !== void 0 || transition.damping !== void 0 || transition.mass !== void 0);
1993
+ }
1994
+ function springParams(spring) {
1995
+ if (isPhysicsSpring(spring)) {
1996
+ return { stiffness: spring.stiffness ?? 200, damping: spring.damping ?? 25, mass: spring.mass ?? 1 };
1997
+ }
1998
+ const visualDuration = Math.max(0.05, spring.visualDuration ?? 0.3);
1999
+ const bounce = spring.bounce ?? 0.3;
2000
+ const root = 2 * Math.PI / (visualDuration * 1.2);
2001
+ const stiffness = root * root;
2002
+ const damping = 2 * Math.min(1, Math.max(0.05, 1 - bounce)) * Math.sqrt(stiffness);
2003
+ return { stiffness, damping, mass: 1 };
2004
+ }
2005
+ function springProgress(t, { stiffness, damping, mass }) {
2006
+ if (t <= 0) return 0;
2007
+ const w0 = Math.sqrt(stiffness / mass);
2008
+ const zeta = damping / (2 * Math.sqrt(stiffness * mass));
2009
+ if (zeta < 0.9999) {
2010
+ const wd2 = w0 * Math.sqrt(1 - zeta * zeta);
2011
+ return 1 - Math.exp(-zeta * w0 * t) * (Math.cos(wd2 * t) + zeta * w0 / wd2 * Math.sin(wd2 * t));
2012
+ }
2013
+ if (zeta < 1.0001) {
2014
+ return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
2015
+ }
2016
+ const wd = w0 * Math.sqrt(zeta * zeta - 1);
2017
+ const r1 = -zeta * w0 + wd;
2018
+ const r2 = -zeta * w0 - wd;
2019
+ return 1 + (r2 * Math.exp(r1 * t) - r1 * Math.exp(r2 * t)) / (r1 - r2);
2020
+ }
2021
+ function springSettleDuration(params) {
2022
+ const w0 = Math.sqrt(params.stiffness / params.mass);
2023
+ const zeta = params.damping / (2 * Math.sqrt(params.stiffness * params.mass));
2024
+ const decay = zeta >= 1 ? zeta * w0 - w0 * Math.sqrt(Math.max(0, zeta * zeta - 1)) : zeta * w0;
2025
+ const duration = Math.log(200) / Math.max(decay, 1e-6);
2026
+ return round2(clamp(duration, 0.05, 10));
2027
+ }
2028
+ function cubicBezierProgress(p, [x1, y1, x2, y2]) {
2029
+ if (p <= 0) return 0;
2030
+ if (p >= 1) return 1;
2031
+ const sampleX = (t2) => bezierAxis(t2, x1, x2);
2032
+ const sampleY = (t2) => bezierAxis(t2, y1, y2);
2033
+ let t = p;
2034
+ for (let i = 0; i < 8; i++) {
2035
+ const x = sampleX(t) - p;
2036
+ if (Math.abs(x) < 1e-5) return sampleY(t);
2037
+ const dx = bezierAxisDerivative(t, x1, x2);
2038
+ if (Math.abs(dx) < 1e-6) break;
2039
+ t -= x / dx;
2040
+ }
2041
+ let lo = 0;
2042
+ let hi = 1;
2043
+ t = p;
2044
+ while (hi - lo > 1e-5) {
2045
+ if (sampleX(t) < p) lo = t;
2046
+ else hi = t;
2047
+ t = (lo + hi) / 2;
2048
+ }
2049
+ return sampleY(t);
2050
+ }
2051
+ function bezierAxis(t, a1, a2) {
2052
+ return (1 - 3 * a2 + 3 * a1) * t * t * t + (3 * a2 - 6 * a1) * t * t + 3 * a1 * t;
2053
+ }
2054
+ function bezierAxisDerivative(t, a1, a2) {
2055
+ return 3 * (1 - 3 * a2 + 3 * a1) * t * t + 2 * (3 * a2 - 6 * a1) * t + 3 * a1;
2056
+ }
2057
+ function resolveClipTransition(raw, clipDuration) {
2058
+ const safeDuration = Math.max(0.05, clipDuration);
2059
+ if (raw.type === "easing") {
2060
+ return {
2061
+ transition: { ...raw, duration: safeDuration },
2062
+ duration: safeDuration,
2063
+ isPhysics: false
2064
+ };
2065
+ }
2066
+ if (isPhysicsSpring(raw)) {
2067
+ return {
2068
+ transition: raw,
2069
+ duration: springSettleDuration(springParams(raw)),
2070
+ isPhysics: true
2071
+ };
1706
2072
  }
1707
- return points;
2073
+ return {
2074
+ transition: { type: "spring", bounce: raw.bounce ?? 0.2, visualDuration: safeDuration },
2075
+ duration: safeDuration,
2076
+ isPhysics: false
2077
+ };
1708
2078
  }
2079
+
2080
+ // src/components/SpringVisualization.tsx
2081
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1709
2082
  function SpringVisualization({ spring, isSimpleMode }) {
1710
2083
  const width = 256;
1711
2084
  const height = 140;
1712
- let stiffness;
1713
- let damping;
1714
- let mass;
1715
- if (isSimpleMode) {
1716
- const visualDuration = spring.visualDuration ?? 0.3;
1717
- const bounce = spring.bounce ?? 0.2;
1718
- mass = 1;
1719
- stiffness = 2 * Math.PI / visualDuration;
1720
- stiffness = Math.pow(stiffness, 2);
1721
- const dampingRatio = 1 - bounce;
1722
- damping = 2 * dampingRatio * Math.sqrt(stiffness * mass);
1723
- } else {
1724
- stiffness = spring.stiffness ?? 400;
1725
- damping = spring.damping ?? 17;
1726
- mass = spring.mass ?? 1;
1727
- }
2085
+ const params = isSimpleMode ? springParams({ type: "spring", visualDuration: spring.visualDuration ?? 0.3, bounce: spring.bounce ?? 0.2 }) : springParams({ type: "spring", stiffness: spring.stiffness ?? 400, damping: spring.damping ?? 17, mass: spring.mass ?? 1 });
1728
2086
  const duration = 2;
1729
- const points = generateSpringCurve(stiffness, damping, mass, duration);
2087
+ const steps = 100;
2088
+ const points = [];
2089
+ for (let i = 0; i <= steps; i++) {
2090
+ const time = i / steps * duration;
2091
+ points.push([time, springProgress(time, params)]);
2092
+ }
1730
2093
  const values = points.map(([, value]) => value);
1731
2094
  const minValue = Math.min(...values);
1732
2095
  const maxValue = Math.max(...values);
@@ -1775,20 +2138,20 @@ function SpringVisualization({ spring, isSimpleMode }) {
1775
2138
  }
1776
2139
 
1777
2140
  // src/components/SpringControl.tsx
1778
- var import_react8 = require("react");
2141
+ var import_react9 = require("react");
1779
2142
  var import_jsx_runtime7 = require("react/jsx-runtime");
1780
2143
  function SpringControl({ panelId, path, label, spring, onChange }) {
1781
- const subscribe = (0, import_react8.useCallback)(
2144
+ const subscribe = (0, import_react9.useCallback)(
1782
2145
  (callback) => DialStore.subscribe(panelId, callback),
1783
2146
  [panelId]
1784
2147
  );
1785
- const getSnapshot = (0, import_react8.useCallback)(
2148
+ const getSnapshot = (0, import_react9.useCallback)(
1786
2149
  () => DialStore.getSpringMode(panelId, path),
1787
2150
  [panelId, path]
1788
2151
  );
1789
- const mode = (0, import_react8.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
2152
+ const mode = (0, import_react9.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1790
2153
  const isSimpleMode = mode === "simple";
1791
- const cache = (0, import_react8.useRef)({
2154
+ const cache = (0, import_react9.useRef)({
1792
2155
  simple: spring.visualDuration !== void 0 ? spring : { type: "spring", visualDuration: 0.3, bounce: 0.2 },
1793
2156
  advanced: spring.stiffness !== void 0 ? spring : { type: "spring", stiffness: 200, damping: 25, mass: 1 }
1794
2157
  });
@@ -1935,21 +2298,29 @@ function EasingVisualization({ easing }) {
1935
2298
  }
1936
2299
 
1937
2300
  // src/components/TransitionControl.tsx
1938
- var import_react9 = require("react");
2301
+ var import_react10 = require("react");
1939
2302
  var import_jsx_runtime9 = require("react/jsx-runtime");
1940
- function TransitionControl({ panelId, path, label, value, onChange }) {
1941
- const subscribe = (0, import_react9.useCallback)(
2303
+ function TransitionControl({
2304
+ panelId,
2305
+ path,
2306
+ label,
2307
+ value,
2308
+ onChange,
2309
+ hideDuration = false,
2310
+ durationControl
2311
+ }) {
2312
+ const subscribe = (0, import_react10.useCallback)(
1942
2313
  (callback) => DialStore.subscribe(panelId, callback),
1943
2314
  [panelId]
1944
2315
  );
1945
- const getSnapshot = (0, import_react9.useCallback)(
2316
+ const getSnapshot = (0, import_react10.useCallback)(
1946
2317
  () => DialStore.getTransitionMode(panelId, path),
1947
2318
  [panelId, path]
1948
2319
  );
1949
- const mode = (0, import_react9.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
2320
+ const mode = (0, import_react10.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1950
2321
  const isEasing = mode === "easing";
1951
2322
  const isSimpleSpring = mode === "simple";
1952
- const cache = (0, import_react9.useRef)({
2323
+ const cache = (0, import_react10.useRef)({
1953
2324
  easing: value.type === "easing" ? value : { type: "easing", duration: 0.3, ease: [1, -0.4, 0.5, 1] },
1954
2325
  simple: value.type === "spring" && value.visualDuration !== void 0 ? value : { type: "spring", visualDuration: 0.3, bounce: 0.2 },
1955
2326
  advanced: value.type === "spring" && value.stiffness !== void 0 ? value : { type: "spring", stiffness: 200, damping: 25, mass: 1 }
@@ -1987,6 +2358,21 @@ function TransitionControl({ panelId, path, label, value, onChange }) {
1987
2358
  newEase[index] = val;
1988
2359
  onChange({ ...easing, ease: newEase });
1989
2360
  };
2361
+ const durationSlider = !hideDuration && (isEasing || isSimpleSpring) ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2362
+ Slider,
2363
+ {
2364
+ label: "Duration",
2365
+ value: durationControl?.value ?? (isEasing ? easing.duration : spring.visualDuration ?? 0.3),
2366
+ onChange: durationControl?.onChange ?? ((next) => {
2367
+ if (isEasing) onChange({ ...easing, duration: next });
2368
+ else handleSpringUpdate("visualDuration", next);
2369
+ }),
2370
+ min: durationControl?.min ?? 0.1,
2371
+ max: durationControl?.max ?? (isEasing ? 2 : 1),
2372
+ step: durationControl?.step ?? 0.05,
2373
+ unit: "s"
2374
+ }
2375
+ ) : null;
1990
2376
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Folder, { title: label, defaultOpen: true, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
1991
2377
  isEasing ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(EasingVisualization, { easing }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SpringVisualization, { spring, isSimpleMode: isSimpleSpring }),
1992
2378
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dialkit-labeled-control", children: [
@@ -2009,16 +2395,13 @@ function TransitionControl({ panelId, path, label, value, onChange }) {
2009
2395
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Slider, { label: "y1", value: easing.ease[1], onChange: (v) => updateEase(1, v), min: -1, max: 2, step: 0.01 }),
2010
2396
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Slider, { label: "x2", value: easing.ease[2], onChange: (v) => updateEase(2, v), min: 0, max: 1, step: 0.01 }),
2011
2397
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Slider, { label: "y2", value: easing.ease[3], onChange: (v) => updateEase(3, v), min: -1, max: 2, step: 0.01 }),
2012
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Slider, { label: "Duration", value: easing.duration, onChange: (v) => onChange({ ...easing, duration: v }), min: 0.1, max: 2, step: 0.05, unit: "s" }),
2013
2398
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(EaseTextInput, { ease: easing.ease, onChange: (newEase) => onChange({ ...easing, ease: newEase }) })
2014
- ] }) : isSimpleSpring ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2015
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Slider, { label: "Duration", value: spring.visualDuration ?? 0.3, onChange: (v) => handleSpringUpdate("visualDuration", v), min: 0.1, max: 1, step: 0.05, unit: "s" }),
2016
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Slider, { label: "Bounce", value: spring.bounce ?? 0.2, onChange: (v) => handleSpringUpdate("bounce", v), min: 0, max: 1, step: 0.05 })
2017
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2399
+ ] }) : isSimpleSpring ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Slider, { label: "Bounce", value: spring.bounce ?? 0.2, onChange: (v) => handleSpringUpdate("bounce", v), min: 0, max: 1, step: 0.05 }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2018
2400
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Slider, { label: "Stiffness", value: spring.stiffness ?? 400, onChange: (v) => handleSpringUpdate("stiffness", v), min: 1, max: 1e3, step: 10 }),
2019
2401
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Slider, { label: "Damping", value: spring.damping ?? 17, onChange: (v) => handleSpringUpdate("damping", v), min: 1, max: 100, step: 1 }),
2020
2402
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Slider, { label: "Mass", value: spring.mass ?? 1, onChange: (v) => handleSpringUpdate("mass", v), min: 0.1, max: 10, step: 0.1 })
2021
- ] })
2403
+ ] }),
2404
+ durationSlider
2022
2405
  ] }) });
2023
2406
  }
2024
2407
  function formatEase(ease) {
@@ -2032,8 +2415,8 @@ function parseEase(str) {
2032
2415
  return null;
2033
2416
  }
2034
2417
  function EaseTextInput({ ease, onChange }) {
2035
- const [editing, setEditing] = (0, import_react9.useState)(false);
2036
- const [draft, setDraft] = (0, import_react9.useState)("");
2418
+ const [editing, setEditing] = (0, import_react10.useState)(false);
2419
+ const [draft, setDraft] = (0, import_react10.useState)("");
2037
2420
  const handleFocus = () => {
2038
2421
  setDraft(formatEase(ease));
2039
2422
  setEditing(true);
@@ -2085,9 +2468,9 @@ function TextControl({ label, value, onChange, placeholder }) {
2085
2468
  }
2086
2469
 
2087
2470
  // src/components/SelectControl.tsx
2088
- var import_react10 = require("react");
2471
+ var import_react11 = require("react");
2089
2472
  var import_react_dom = require("react-dom");
2090
- var import_react11 = require("motion/react");
2473
+ var import_react12 = require("motion/react");
2091
2474
 
2092
2475
  // src/dropdown-position.ts
2093
2476
  function getDropdownPosition(trigger, portalRoot, options = {}) {
@@ -2118,27 +2501,27 @@ function normalizeOptions(options) {
2118
2501
  );
2119
2502
  }
2120
2503
  function SelectControl({ label, value, options, onChange }) {
2121
- const [isOpen, setIsOpen] = (0, import_react10.useState)(false);
2122
- const triggerRef = (0, import_react10.useRef)(null);
2123
- const dropdownRef = (0, import_react10.useRef)(null);
2124
- const [portalTarget, setPortalTarget] = (0, import_react10.useState)(null);
2125
- const [pos, setPos] = (0, import_react10.useState)(null);
2504
+ const [isOpen, setIsOpen] = (0, import_react11.useState)(false);
2505
+ const triggerRef = (0, import_react11.useRef)(null);
2506
+ const dropdownRef = (0, import_react11.useRef)(null);
2507
+ const [portalTarget, setPortalTarget] = (0, import_react11.useState)(null);
2508
+ const [pos, setPos] = (0, import_react11.useState)(null);
2126
2509
  const normalized = normalizeOptions(options);
2127
2510
  const selectedOption = normalized.find((o) => o.value === value);
2128
- const updatePos = (0, import_react10.useCallback)(() => {
2511
+ const updatePos = (0, import_react11.useCallback)(() => {
2129
2512
  const el = triggerRef.current;
2130
2513
  if (!el || !portalTarget) return;
2131
2514
  const dropdownHeight = 8 + normalized.length * 36;
2132
2515
  setPos(getDropdownPosition(el, portalTarget, { dropdownHeight }));
2133
2516
  }, [normalized.length, portalTarget]);
2134
- (0, import_react10.useEffect)(() => {
2517
+ (0, import_react11.useEffect)(() => {
2135
2518
  setPortalTarget(getDialKitPortalRoot(triggerRef.current) ?? document.body);
2136
2519
  }, []);
2137
- (0, import_react10.useEffect)(() => {
2520
+ (0, import_react11.useEffect)(() => {
2138
2521
  if (!isOpen) return;
2139
2522
  updatePos();
2140
2523
  }, [isOpen, updatePos]);
2141
- (0, import_react10.useEffect)(() => {
2524
+ (0, import_react11.useEffect)(() => {
2142
2525
  if (!isOpen) return;
2143
2526
  const handleClick = (e) => {
2144
2527
  const target = e.target;
@@ -2162,7 +2545,7 @@ function SelectControl({ label, value, options, onChange }) {
2162
2545
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dialkit-select-right", children: [
2163
2546
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "dialkit-select-value", children: selectedOption?.label ?? value }),
2164
2547
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2165
- import_react11.motion.svg,
2548
+ import_react12.motion.svg,
2166
2549
  {
2167
2550
  className: "dialkit-select-chevron",
2168
2551
  viewBox: "0 0 24 24",
@@ -2181,8 +2564,8 @@ function SelectControl({ label, value, options, onChange }) {
2181
2564
  }
2182
2565
  ),
2183
2566
  portalTarget && (0, import_react_dom.createPortal)(
2184
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react11.AnimatePresence, { children: isOpen && pos && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2185
- import_react11.motion.div,
2567
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react12.AnimatePresence, { children: isOpen && pos && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2568
+ import_react12.motion.div,
2186
2569
  {
2187
2570
  ref: dropdownRef,
2188
2571
  className: "dialkit-select-dropdown",
@@ -2218,14 +2601,14 @@ function SelectControl({ label, value, options, onChange }) {
2218
2601
  }
2219
2602
 
2220
2603
  // src/components/ColorControl.tsx
2221
- var import_react12 = require("react");
2604
+ var import_react13 = require("react");
2222
2605
  var import_jsx_runtime12 = require("react/jsx-runtime");
2223
2606
  var HEX_COLOR_REGEX = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/;
2224
2607
  function ColorControl({ label, value, onChange }) {
2225
- const [isEditing, setIsEditing] = (0, import_react12.useState)(false);
2226
- const [editValue, setEditValue] = (0, import_react12.useState)(value);
2227
- const colorInputRef = (0, import_react12.useRef)(null);
2228
- (0, import_react12.useEffect)(() => {
2608
+ const [isEditing, setIsEditing] = (0, import_react13.useState)(false);
2609
+ const [editValue, setEditValue] = (0, import_react13.useState)(value);
2610
+ const colorInputRef = (0, import_react13.useRef)(null);
2611
+ (0, import_react13.useEffect)(() => {
2229
2612
  if (!isEditing) {
2230
2613
  setEditValue(value);
2231
2614
  }
@@ -2295,266 +2678,105 @@ function expandShorthandHex(hex) {
2295
2678
  return `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;
2296
2679
  }
2297
2680
 
2298
- // src/components/PresetManager.tsx
2299
- var import_react13 = require("react");
2300
- var import_react_dom2 = require("react-dom");
2301
- var import_react14 = require("motion/react");
2681
+ // src/components/ControlRenderer.tsx
2302
2682
  var import_jsx_runtime13 = require("react/jsx-runtime");
2303
- function PresetManager({ panelId, presets, activePresetId, onAdd }) {
2304
- const [isOpen, setIsOpen] = (0, import_react13.useState)(false);
2305
- const triggerRef = (0, import_react13.useRef)(null);
2306
- const dropdownRef = (0, import_react13.useRef)(null);
2307
- const [pos, setPos] = (0, import_react13.useState)({ top: 0, left: 0, width: 0 });
2308
- const hasPresets = presets.length > 0;
2309
- const activePreset = presets.find((p) => p.id === activePresetId);
2310
- const open = (0, import_react13.useCallback)(() => {
2311
- if (!hasPresets) return;
2312
- const rect = triggerRef.current?.getBoundingClientRect();
2313
- if (rect) {
2314
- setPos({ top: rect.bottom + 4, left: rect.left, width: rect.width });
2315
- }
2316
- setIsOpen(true);
2317
- }, [hasPresets]);
2318
- const close = (0, import_react13.useCallback)(() => setIsOpen(false), []);
2319
- const toggle = (0, import_react13.useCallback)(() => {
2320
- if (isOpen) close();
2321
- else open();
2322
- }, [isOpen, open, close]);
2323
- (0, import_react13.useEffect)(() => {
2324
- if (!isOpen) return;
2325
- const handler = (e) => {
2326
- const target = e.target;
2327
- if (triggerRef.current?.contains(target) || dropdownRef.current?.contains(target)) return;
2328
- close();
2329
- };
2330
- document.addEventListener("mousedown", handler);
2331
- return () => document.removeEventListener("mousedown", handler);
2332
- }, [isOpen, close]);
2333
- const handleSelect = (presetId) => {
2334
- if (presetId) {
2335
- DialStore.loadPreset(panelId, presetId);
2336
- } else {
2337
- DialStore.clearActivePreset(panelId);
2338
- }
2339
- close();
2340
- };
2341
- const handleDelete = (e, presetId) => {
2342
- e.stopPropagation();
2343
- DialStore.deletePreset(panelId, presetId);
2344
- };
2345
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "dialkit-preset-manager", children: [
2346
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2347
- "button",
2348
- {
2349
- ref: triggerRef,
2350
- className: "dialkit-preset-trigger",
2351
- onClick: toggle,
2352
- "data-open": String(isOpen),
2353
- "data-has-preset": String(!!activePreset),
2354
- "data-disabled": String(!hasPresets),
2355
- children: [
2356
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "dialkit-preset-label", children: activePreset ? activePreset.name : "Version 1" }),
2357
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2358
- import_react14.motion.svg,
2359
- {
2360
- className: "dialkit-select-chevron",
2361
- viewBox: "0 0 24 24",
2362
- fill: "none",
2363
- stroke: "currentColor",
2364
- strokeWidth: "2.5",
2365
- strokeLinecap: "round",
2366
- strokeLinejoin: "round",
2367
- animate: { rotate: isOpen ? 180 : 0, opacity: hasPresets ? 0.6 : 0.25 },
2368
- transition: { type: "spring", visualDuration: 0.2, bounce: 0.15 },
2369
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: ICON_CHEVRON })
2370
- }
2371
- )
2372
- ]
2373
- }
2374
- ),
2375
- (0, import_react_dom2.createPortal)(
2376
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react14.AnimatePresence, { children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2377
- import_react14.motion.div,
2378
- {
2379
- ref: dropdownRef,
2380
- className: "dialkit-root dialkit-preset-dropdown",
2381
- style: { position: "fixed", top: pos.top, left: pos.left, minWidth: pos.width },
2382
- initial: { opacity: 0, y: 4, scale: 0.97 },
2383
- animate: { opacity: 1, y: 0, scale: 1 },
2384
- exit: { opacity: 0, y: 4, scale: 0.97, pointerEvents: "none" },
2385
- transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
2386
- children: [
2387
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2388
- "div",
2389
- {
2390
- className: "dialkit-preset-item",
2391
- "data-active": String(!activePresetId),
2392
- onClick: () => handleSelect(null),
2393
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "dialkit-preset-name", children: "Version 1" })
2394
- }
2395
- ),
2396
- presets.map((preset) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2397
- "div",
2398
- {
2399
- className: "dialkit-preset-item",
2400
- "data-active": String(preset.id === activePresetId),
2401
- onClick: () => handleSelect(preset.id),
2402
- children: [
2403
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "dialkit-preset-name", children: preset.name }),
2404
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2405
- "button",
2406
- {
2407
- className: "dialkit-preset-delete",
2408
- onClick: (e) => handleDelete(e, preset.id),
2409
- title: "Delete preset",
2410
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: ICON_TRASH.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d }, i)) })
2411
- }
2412
- )
2413
- ]
2414
- },
2415
- preset.id
2416
- ))
2417
- ]
2418
- }
2419
- ) }),
2420
- document.body
2421
- )
2422
- ] });
2423
- }
2424
-
2425
- // src/components/Panel.tsx
2426
- var import_jsx_runtime14 = require("react/jsx-runtime");
2427
- function Panel({ panel, defaultOpen = true, inline = false, onOpenChange, variant = "root" }) {
2428
- const [copied, setCopied] = (0, import_react15.useState)(false);
2429
- const [isPanelOpen, setIsPanelOpen] = (0, import_react15.useState)(defaultOpen);
2430
- const shortcutCtx = (0, import_react15.useContext)(ShortcutContext);
2431
- const hasShortcuts = Object.keys(panel.shortcuts).length > 0;
2432
- const subscribe = (0, import_react15.useCallback)(
2433
- (callback) => DialStore.subscribe(panel.id, callback),
2434
- [panel.id]
2435
- );
2436
- const getSnapshot = (0, import_react15.useCallback)(
2437
- () => DialStore.getValues(panel.id),
2438
- [panel.id]
2439
- );
2440
- const values = (0, import_react15.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
2441
- const presets = DialStore.getPresets(panel.id);
2442
- const activePresetId = DialStore.getActivePresetId(panel.id);
2443
- const handleAddPreset = () => {
2444
- const nextNum = presets.length + 2;
2445
- DialStore.savePreset(panel.id, `Version ${nextNum}`);
2446
- };
2447
- const handleCopy = () => {
2448
- const jsonStr = JSON.stringify(values, null, 2);
2449
- const instruction = `Update the useDialKit configuration for "${panel.name}" with these values:
2450
-
2451
- \`\`\`json
2452
- ${jsonStr}
2453
- \`\`\`
2454
-
2455
- Apply these values as the new defaults in the useDialKit call.`;
2456
- navigator.clipboard.writeText(instruction);
2457
- setCopied(true);
2458
- setTimeout(() => setCopied(false), 1500);
2459
- };
2460
- const handleOpenChange = (0, import_react15.useCallback)((open) => {
2461
- setIsPanelOpen(open);
2462
- onOpenChange?.(open);
2463
- }, [onOpenChange]);
2683
+ function ControlRenderer({ panelId, controls, values, transitionDuration }) {
2684
+ const shortcutCtx = (0, import_react14.useContext)(ShortcutContext);
2464
2685
  const renderControl = (control) => {
2465
2686
  const value = values[control.path];
2466
2687
  switch (control.type) {
2467
2688
  case "slider":
2468
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2689
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2469
2690
  Slider,
2470
2691
  {
2471
2692
  label: control.label,
2472
2693
  value,
2473
- onChange: (v) => DialStore.updateValue(panel.id, control.path, v),
2694
+ onChange: (v) => DialStore.updateValue(panelId, control.path, v),
2474
2695
  min: control.min,
2475
2696
  max: control.max,
2476
2697
  step: control.step,
2477
2698
  shortcut: control.shortcut,
2478
- shortcutActive: shortcutCtx.activePanelId === panel.id && shortcutCtx.activePath === control.path
2699
+ shortcutActive: shortcutCtx.activePanelId === panelId && shortcutCtx.activePath === control.path
2479
2700
  },
2480
2701
  control.path
2481
2702
  );
2482
2703
  case "toggle":
2483
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2704
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2484
2705
  Toggle,
2485
2706
  {
2486
2707
  label: control.label,
2487
2708
  checked: value,
2488
- onChange: (v) => DialStore.updateValue(panel.id, control.path, v),
2709
+ onChange: (v) => DialStore.updateValue(panelId, control.path, v),
2489
2710
  shortcut: control.shortcut,
2490
- shortcutActive: shortcutCtx.activePanelId === panel.id && shortcutCtx.activePath === control.path
2711
+ shortcutActive: shortcutCtx.activePanelId === panelId && shortcutCtx.activePath === control.path
2491
2712
  },
2492
2713
  control.path
2493
2714
  );
2494
2715
  case "spring":
2495
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2716
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2496
2717
  SpringControl,
2497
2718
  {
2498
- panelId: panel.id,
2719
+ panelId,
2499
2720
  path: control.path,
2500
2721
  label: control.label,
2501
2722
  spring: value,
2502
- onChange: (v) => DialStore.updateValue(panel.id, control.path, v)
2723
+ onChange: (v) => DialStore.updateValue(panelId, control.path, v)
2503
2724
  },
2504
2725
  control.path
2505
2726
  );
2506
2727
  case "transition":
2507
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2728
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2508
2729
  TransitionControl,
2509
2730
  {
2510
- panelId: panel.id,
2731
+ panelId,
2511
2732
  path: control.path,
2512
2733
  label: control.label,
2513
2734
  value,
2514
- onChange: (v) => DialStore.updateValue(panel.id, control.path, v)
2735
+ onChange: (v) => DialStore.updateValue(panelId, control.path, v),
2736
+ durationControl: transitionDuration
2515
2737
  },
2516
2738
  control.path
2517
2739
  );
2518
2740
  case "folder":
2519
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Folder, { title: control.label, defaultOpen: control.defaultOpen ?? true, children: control.children?.map(renderControl) }, control.path);
2741
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Folder, { title: control.label, defaultOpen: control.defaultOpen ?? true, children: control.children?.map(renderControl) }, control.path);
2520
2742
  case "text":
2521
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2743
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2522
2744
  TextControl,
2523
2745
  {
2524
2746
  label: control.label,
2525
2747
  value,
2526
- onChange: (v) => DialStore.updateValue(panel.id, control.path, v),
2748
+ onChange: (v) => DialStore.updateValue(panelId, control.path, v),
2527
2749
  placeholder: control.placeholder
2528
2750
  },
2529
2751
  control.path
2530
2752
  );
2531
2753
  case "select":
2532
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2754
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2533
2755
  SelectControl,
2534
2756
  {
2535
2757
  label: control.label,
2536
2758
  value,
2537
2759
  options: control.options ?? [],
2538
- onChange: (v) => DialStore.updateValue(panel.id, control.path, v)
2760
+ onChange: (v) => DialStore.updateValue(panelId, control.path, v)
2539
2761
  },
2540
2762
  control.path
2541
2763
  );
2542
2764
  case "color":
2543
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2765
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2544
2766
  ColorControl,
2545
2767
  {
2546
2768
  label: control.label,
2547
2769
  value,
2548
- onChange: (v) => DialStore.updateValue(panel.id, control.path, v)
2770
+ onChange: (v) => DialStore.updateValue(panelId, control.path, v)
2549
2771
  },
2550
2772
  control.path
2551
2773
  );
2552
2774
  case "action":
2553
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2775
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2554
2776
  "button",
2555
2777
  {
2556
2778
  className: "dialkit-button",
2557
- onClick: () => DialStore.triggerAction(panel.id, control.path),
2779
+ onClick: () => DialStore.triggerAction(panelId, control.path),
2558
2780
  children: control.label
2559
2781
  },
2560
2782
  control.path
@@ -2563,23 +2785,181 @@ Apply these values as the new defaults in the useDialKit call.`;
2563
2785
  return null;
2564
2786
  }
2565
2787
  };
2566
- const renderControls = () => {
2567
- return panel.controls.map(renderControl);
2788
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_jsx_runtime13.Fragment, { children: controls.map(renderControl) });
2789
+ }
2790
+
2791
+ // src/components/PresetManager.tsx
2792
+ var import_react15 = require("react");
2793
+ var import_react_dom2 = require("react-dom");
2794
+ var import_react16 = require("motion/react");
2795
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2796
+ function PresetManager({ panelId, presets, activePresetId, onAdd }) {
2797
+ const [isOpen, setIsOpen] = (0, import_react15.useState)(false);
2798
+ const triggerRef = (0, import_react15.useRef)(null);
2799
+ const dropdownRef = (0, import_react15.useRef)(null);
2800
+ const [pos, setPos] = (0, import_react15.useState)({ top: 0, left: 0, width: 0 });
2801
+ const hasPresets = presets.length > 0;
2802
+ const activePreset = presets.find((p) => p.id === activePresetId);
2803
+ const open = (0, import_react15.useCallback)(() => {
2804
+ if (!hasPresets) return;
2805
+ const rect = triggerRef.current?.getBoundingClientRect();
2806
+ if (rect) {
2807
+ setPos({ top: rect.bottom + 4, left: rect.left, width: rect.width });
2808
+ }
2809
+ setIsOpen(true);
2810
+ }, [hasPresets]);
2811
+ const close = (0, import_react15.useCallback)(() => setIsOpen(false), []);
2812
+ const toggle = (0, import_react15.useCallback)(() => {
2813
+ if (isOpen) close();
2814
+ else open();
2815
+ }, [isOpen, open, close]);
2816
+ (0, import_react15.useEffect)(() => {
2817
+ if (!isOpen) return;
2818
+ const handler = (e) => {
2819
+ const target = e.target;
2820
+ if (triggerRef.current?.contains(target) || dropdownRef.current?.contains(target)) return;
2821
+ close();
2822
+ };
2823
+ document.addEventListener("mousedown", handler);
2824
+ return () => document.removeEventListener("mousedown", handler);
2825
+ }, [isOpen, close]);
2826
+ const handleSelect = (presetId) => {
2827
+ if (presetId) {
2828
+ DialStore.loadPreset(panelId, presetId);
2829
+ } else {
2830
+ DialStore.clearActivePreset(panelId);
2831
+ }
2832
+ close();
2833
+ };
2834
+ const handleDelete = (e, presetId) => {
2835
+ e.stopPropagation();
2836
+ DialStore.deletePreset(panelId, presetId);
2837
+ };
2838
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "dialkit-preset-manager", children: [
2839
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2840
+ "button",
2841
+ {
2842
+ ref: triggerRef,
2843
+ className: "dialkit-preset-trigger",
2844
+ onClick: toggle,
2845
+ "data-open": String(isOpen),
2846
+ "data-has-preset": String(!!activePreset),
2847
+ "data-disabled": String(!hasPresets),
2848
+ children: [
2849
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "dialkit-preset-label", children: activePreset ? activePreset.name : "Version 1" }),
2850
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2851
+ import_react16.motion.svg,
2852
+ {
2853
+ className: "dialkit-select-chevron",
2854
+ viewBox: "0 0 24 24",
2855
+ fill: "none",
2856
+ stroke: "currentColor",
2857
+ strokeWidth: "2.5",
2858
+ strokeLinecap: "round",
2859
+ strokeLinejoin: "round",
2860
+ animate: { rotate: isOpen ? 180 : 0, opacity: hasPresets ? 0.6 : 0.25 },
2861
+ transition: { type: "spring", visualDuration: 0.2, bounce: 0.15 },
2862
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: ICON_CHEVRON })
2863
+ }
2864
+ )
2865
+ ]
2866
+ }
2867
+ ),
2868
+ (0, import_react_dom2.createPortal)(
2869
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react16.AnimatePresence, { children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2870
+ import_react16.motion.div,
2871
+ {
2872
+ ref: dropdownRef,
2873
+ className: "dialkit-root dialkit-preset-dropdown",
2874
+ style: { position: "fixed", top: pos.top, left: pos.left, minWidth: pos.width },
2875
+ initial: { opacity: 0, y: 4, scale: 0.97 },
2876
+ animate: { opacity: 1, y: 0, scale: 1 },
2877
+ exit: { opacity: 0, y: 4, scale: 0.97, pointerEvents: "none" },
2878
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
2879
+ children: [
2880
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2881
+ "div",
2882
+ {
2883
+ className: "dialkit-preset-item",
2884
+ "data-active": String(!activePresetId),
2885
+ onClick: () => handleSelect(null),
2886
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "dialkit-preset-name", children: "Version 1" })
2887
+ }
2888
+ ),
2889
+ presets.map((preset) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2890
+ "div",
2891
+ {
2892
+ className: "dialkit-preset-item",
2893
+ "data-active": String(preset.id === activePresetId),
2894
+ onClick: () => handleSelect(preset.id),
2895
+ children: [
2896
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "dialkit-preset-name", children: preset.name }),
2897
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2898
+ "button",
2899
+ {
2900
+ className: "dialkit-preset-delete",
2901
+ onClick: (e) => handleDelete(e, preset.id),
2902
+ title: "Delete preset",
2903
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: ICON_TRASH.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d }, i)) })
2904
+ }
2905
+ )
2906
+ ]
2907
+ },
2908
+ preset.id
2909
+ ))
2910
+ ]
2911
+ }
2912
+ ) }),
2913
+ document.body
2914
+ )
2915
+ ] });
2916
+ }
2917
+
2918
+ // src/components/Panel.tsx
2919
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2920
+ function Panel({ panel, defaultOpen = true, inline = false, onOpenChange, variant = "root", toolbarExtra }) {
2921
+ const [copied, setCopied] = (0, import_react17.useState)(false);
2922
+ const [isPanelOpen, setIsPanelOpen] = (0, import_react17.useState)(defaultOpen);
2923
+ const hasShortcuts = Object.keys(panel.shortcuts).length > 0;
2924
+ const subscribe = (0, import_react17.useCallback)(
2925
+ (callback) => DialStore.subscribe(panel.id, callback),
2926
+ [panel.id]
2927
+ );
2928
+ const getSnapshot = (0, import_react17.useCallback)(
2929
+ () => DialStore.getValues(panel.id),
2930
+ [panel.id]
2931
+ );
2932
+ const values = (0, import_react17.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
2933
+ const presets = DialStore.getPresets(panel.id);
2934
+ const activePresetId = DialStore.getActivePresetId(panel.id);
2935
+ const handleAddPreset = () => {
2936
+ const nextNum = presets.length + 2;
2937
+ DialStore.savePreset(panel.id, `Version ${nextNum}`);
2938
+ };
2939
+ const handleCopy = () => {
2940
+ navigator.clipboard.writeText(buildCopyInstruction("useDialKit", panel.name, values));
2941
+ setCopied(true);
2942
+ setTimeout(() => setCopied(false), 1500);
2568
2943
  };
2944
+ const handleOpenChange = (0, import_react17.useCallback)((open) => {
2945
+ setIsPanelOpen(open);
2946
+ onOpenChange?.(open);
2947
+ }, [onOpenChange]);
2948
+ const renderControls = () => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ControlRenderer, { panelId: panel.id, controls: panel.controls, values });
2569
2949
  const iconTransition = { type: "spring", visualDuration: 0.4, bounce: 0.1 };
2570
- const toolbar = /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2571
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2572
- import_react16.motion.button,
2950
+ const toolbar = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2951
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2952
+ import_react18.motion.button,
2573
2953
  {
2574
2954
  className: "dialkit-toolbar-add",
2575
2955
  onClick: handleAddPreset,
2576
2956
  title: "Add preset",
2577
2957
  whileTap: { scale: 0.9 },
2578
2958
  transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
2579
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: ICON_ADD_PRESET.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d }, i)) })
2959
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: ICON_ADD_PRESET.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d }, i)) })
2580
2960
  }
2581
2961
  ),
2582
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2962
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2583
2963
  PresetManager,
2584
2964
  {
2585
2965
  panelId: panel.id,
@@ -2588,16 +2968,16 @@ Apply these values as the new defaults in the useDialKit call.`;
2588
2968
  onAdd: handleAddPreset
2589
2969
  }
2590
2970
  ),
2591
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2592
- import_react16.motion.button,
2971
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2972
+ import_react18.motion.button,
2593
2973
  {
2594
2974
  className: "dialkit-toolbar-add",
2595
2975
  onClick: handleCopy,
2596
2976
  title: "Copy parameters",
2597
2977
  whileTap: { scale: 0.9 },
2598
2978
  transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
2599
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: { position: "relative", width: 16, height: 16 }, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react16.AnimatePresence, { initial: false, mode: "wait", children: copied ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2600
- import_react16.motion.svg,
2979
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: { position: "relative", width: 16, height: 16 }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react18.AnimatePresence, { initial: false, mode: "wait", children: copied ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2980
+ import_react18.motion.svg,
2601
2981
  {
2602
2982
  viewBox: "0 0 24 24",
2603
2983
  fill: "none",
@@ -2610,11 +2990,11 @@ Apply these values as the new defaults in the useDialKit call.`;
2610
2990
  animate: { scale: 1, opacity: 1 },
2611
2991
  exit: { scale: 0.8, opacity: 0 },
2612
2992
  transition: { duration: 0.08 },
2613
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: ICON_CHECK })
2993
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: ICON_CHECK })
2614
2994
  },
2615
2995
  "check"
2616
- ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2617
- import_react16.motion.svg,
2996
+ ) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2997
+ import_react18.motion.svg,
2618
2998
  {
2619
2999
  viewBox: "0 0 24 24",
2620
3000
  fill: "none",
@@ -2624,23 +3004,116 @@ Apply these values as the new defaults in the useDialKit call.`;
2624
3004
  exit: { scale: 0.8, opacity: 0 },
2625
3005
  transition: { duration: 0.08 },
2626
3006
  children: [
2627
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: ICON_CLIPBOARD.board, stroke: "currentColor", strokeWidth: "2", strokeLinejoin: "round" }),
2628
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: ICON_CLIPBOARD.sparkle, fill: "currentColor" }),
2629
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: ICON_CLIPBOARD.body, stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
3007
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: ICON_CLIPBOARD.board, stroke: "currentColor", strokeWidth: "2", strokeLinejoin: "round" }),
3008
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: ICON_CLIPBOARD.sparkle, fill: "currentColor" }),
3009
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: ICON_CLIPBOARD.body, stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
2630
3010
  ]
2631
3011
  },
2632
3012
  "clipboard"
2633
3013
  ) }) })
2634
3014
  }
2635
- )
3015
+ ),
3016
+ toolbarExtra
2636
3017
  ] });
2637
3018
  if (variant === "section") {
2638
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Folder, { title: panel.name, defaultOpen, onOpenChange: handleOpenChange, children: [
2639
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "dialkit-panel-section-toolbar", onClick: (e) => e.stopPropagation(), children: toolbar }),
3019
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Folder, { title: panel.name, defaultOpen, onOpenChange: handleOpenChange, children: [
3020
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dialkit-panel-section-toolbar", onClick: (e) => e.stopPropagation(), children: toolbar }),
2640
3021
  renderControls()
2641
3022
  ] });
2642
3023
  }
2643
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "dialkit-panel-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Folder, { title: panel.name, defaultOpen, isRoot: true, inline, onOpenChange: handleOpenChange, toolbar, children: renderControls() }) });
3024
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dialkit-panel-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Folder, { title: panel.name, defaultOpen, isRoot: true, inline, onOpenChange: handleOpenChange, toolbar, children: renderControls() }) });
3025
+ }
3026
+
3027
+ // src/components/Timeline/TimelineToggleButton.tsx
3028
+ var import_react19 = require("react");
3029
+ var import_react20 = require("motion/react");
3030
+
3031
+ // src/store/TimelineUiStore.ts
3032
+ var TimelineUiStoreClass = class {
3033
+ constructor() {
3034
+ this.visible = true;
3035
+ this.initialized = false;
3036
+ this.controllers = /* @__PURE__ */ new Map();
3037
+ this.listeners = /* @__PURE__ */ new Set();
3038
+ }
3039
+ getVisible() {
3040
+ for (const controller of this.controllers.values()) {
3041
+ if (controller.visible !== void 0) return controller.visible;
3042
+ }
3043
+ return this.visible;
3044
+ }
3045
+ registerController(id, controller) {
3046
+ const previous = this.getVisible();
3047
+ if (!this.initialized) {
3048
+ this.visible = controller.defaultVisible;
3049
+ this.initialized = true;
3050
+ }
3051
+ this.controllers.set(id, controller);
3052
+ if (previous !== this.getVisible()) this.notify();
3053
+ return () => {
3054
+ const before = this.getVisible();
3055
+ this.controllers.delete(id);
3056
+ if (this.controllers.size === 0) this.initialized = false;
3057
+ if (before !== this.getVisible()) this.notify();
3058
+ };
3059
+ }
3060
+ updateController(id, controller) {
3061
+ if (!this.controllers.has(id)) return;
3062
+ const previous = this.getVisible();
3063
+ this.controllers.set(id, controller);
3064
+ if (previous !== this.getVisible()) this.notify();
3065
+ }
3066
+ requestVisible(visible) {
3067
+ const current = this.getVisible();
3068
+ if (current === visible) return;
3069
+ const controlled = Array.from(this.controllers.values()).filter(
3070
+ (controller) => controller.visible !== void 0
3071
+ );
3072
+ if (controlled.length > 0) {
3073
+ controlled.forEach((controller) => controller.onVisibilityChange?.(visible));
3074
+ return;
3075
+ }
3076
+ this.visible = visible;
3077
+ this.controllers.forEach((controller) => controller.onVisibilityChange?.(visible));
3078
+ this.notify();
3079
+ }
3080
+ toggle() {
3081
+ this.requestVisible(!this.getVisible());
3082
+ }
3083
+ subscribe(listener) {
3084
+ this.listeners.add(listener);
3085
+ return () => this.listeners.delete(listener);
3086
+ }
3087
+ notify() {
3088
+ this.listeners.forEach((listener) => listener());
3089
+ }
3090
+ };
3091
+ var TimelineUiStore = /* @__PURE__ */ new TimelineUiStoreClass();
3092
+
3093
+ // src/components/Timeline/TimelineToggleButton.tsx
3094
+ var import_jsx_runtime16 = require("react/jsx-runtime");
3095
+ function TimelineToggleButton() {
3096
+ const subscribe = (0, import_react19.useCallback)(
3097
+ (listener) => TimelineUiStore.subscribe(listener),
3098
+ []
3099
+ );
3100
+ const getVisible = (0, import_react19.useCallback)(() => TimelineUiStore.getVisible(), []);
3101
+ const visible = (0, import_react19.useSyncExternalStore)(subscribe, getVisible, getVisible);
3102
+ const label = visible ? "Hide timeline" : "Show timeline";
3103
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3104
+ import_react20.motion.button,
3105
+ {
3106
+ className: "dialkit-toolbar-add dialkit-timeline-toolbar-toggle",
3107
+ "data-active": visible || void 0,
3108
+ "aria-pressed": visible,
3109
+ "aria-label": label,
3110
+ title: label,
3111
+ onClick: () => TimelineUiStore.toggle(),
3112
+ whileTap: { scale: 0.9 },
3113
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
3114
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: ICON_TIMELINE.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d, fill: "currentColor" }, i)) })
3115
+ }
3116
+ );
2644
3117
  }
2645
3118
 
2646
3119
  // src/panel-drag.ts
@@ -2708,33 +3181,39 @@ function blockPanelDragClick(handle) {
2708
3181
  }
2709
3182
 
2710
3183
  // src/components/DialRoot.tsx
2711
- var import_jsx_runtime15 = require("react/jsx-runtime");
2712
- var import_meta = {};
2713
- var isDevDefault = typeof process !== "undefined" && process?.env?.NODE_ENV ? process.env.NODE_ENV !== "production" : typeof import_meta !== "undefined" && import_meta.env?.MODE ? import_meta.env.MODE !== "production" : true;
3184
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2714
3185
  function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover", theme = "system", productionEnabled = isDevDefault, onOpenChange }) {
2715
3186
  if (!productionEnabled) return null;
2716
- const [panels, setPanels] = (0, import_react17.useState)([]);
2717
- const [mounted, setMounted] = (0, import_react17.useState)(false);
3187
+ const [panels, setPanels] = (0, import_react21.useState)([]);
3188
+ const [timelineCount, setTimelineCount] = (0, import_react21.useState)(0);
3189
+ const [mounted, setMounted] = (0, import_react21.useState)(false);
2718
3190
  const inline = mode === "inline";
2719
- const panelRef = (0, import_react17.useRef)(null);
2720
- const [dragOffset, setDragOffset] = (0, import_react17.useState)(null);
2721
- const [activePosition, setActivePosition] = (0, import_react17.useState)(position);
2722
- const lastDragOffset = (0, import_react17.useRef)(null);
2723
- const draggingRef = (0, import_react17.useRef)(false);
2724
- const dragStartRef = (0, import_react17.useRef)(null);
2725
- const didDragRef = (0, import_react17.useRef)(false);
2726
- const dragTargetRef = (0, import_react17.useRef)(null);
2727
- const panelOpenStatesRef = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
2728
- const rootOpenRef = (0, import_react17.useRef)(null);
2729
- (0, import_react17.useEffect)(() => {
3191
+ const panelRef = (0, import_react21.useRef)(null);
3192
+ const [dragOffset, setDragOffset] = (0, import_react21.useState)(null);
3193
+ const [activePosition, setActivePosition] = (0, import_react21.useState)(position);
3194
+ const lastDragOffset = (0, import_react21.useRef)(null);
3195
+ const draggingRef = (0, import_react21.useRef)(false);
3196
+ const dragStartRef = (0, import_react21.useRef)(null);
3197
+ const didDragRef = (0, import_react21.useRef)(false);
3198
+ const dragTargetRef = (0, import_react21.useRef)(null);
3199
+ const panelOpenStatesRef = (0, import_react21.useRef)(/* @__PURE__ */ new Map());
3200
+ const rootOpenRef = (0, import_react21.useRef)(null);
3201
+ (0, import_react21.useEffect)(() => {
2730
3202
  setMounted(true);
2731
- setPanels(DialStore.getPanels());
2732
- const unsubscribe = DialStore.subscribeGlobal(() => {
2733
- setPanels(DialStore.getPanels());
3203
+ setPanels(DialStore.getPanels("panel"));
3204
+ setTimelineCount(TimelineStore.getTimelines().length);
3205
+ const unsubscribePanels = DialStore.subscribeGlobal(() => {
3206
+ setPanels(DialStore.getPanels("panel"));
2734
3207
  });
2735
- return unsubscribe;
3208
+ const unsubscribeTimelines = TimelineStore.subscribeGlobal(() => {
3209
+ setTimelineCount(TimelineStore.getTimelines().length);
3210
+ });
3211
+ return () => {
3212
+ unsubscribePanels();
3213
+ unsubscribeTimelines();
3214
+ };
2736
3215
  }, []);
2737
- (0, import_react17.useEffect)(() => {
3216
+ (0, import_react21.useEffect)(() => {
2738
3217
  const fallbackOpen = inline || defaultOpen;
2739
3218
  const nextStates = /* @__PURE__ */ new Map();
2740
3219
  for (const panel of panels) {
@@ -2743,7 +3222,7 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2743
3222
  panelOpenStatesRef.current = nextStates;
2744
3223
  rootOpenRef.current = Array.from(nextStates.values()).some(Boolean);
2745
3224
  }, [defaultOpen, inline, panels]);
2746
- (0, import_react17.useEffect)(() => {
3225
+ (0, import_react21.useEffect)(() => {
2747
3226
  if (!panelRef.current || inline) return;
2748
3227
  const observer = new MutationObserver(() => {
2749
3228
  const inners = panelRef.current?.querySelectorAll(".dialkit-panel-inner");
@@ -2771,7 +3250,7 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2771
3250
  observer.observe(panelRef.current, { subtree: true, attributes: true, attributeFilter: ["data-collapsed"] });
2772
3251
  return () => observer.disconnect();
2773
3252
  }, [inline, dragOffset, position]);
2774
- const handlePointerDown = (0, import_react17.useCallback)((e) => {
3253
+ const handlePointerDown = (0, import_react21.useCallback)((e) => {
2775
3254
  const panel = panelRef.current;
2776
3255
  const handle = getPanelDragHandle(e.target, panel);
2777
3256
  if (!panel || !handle) return;
@@ -2781,13 +3260,13 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2781
3260
  draggingRef.current = true;
2782
3261
  handle.setPointerCapture(e.pointerId);
2783
3262
  }, []);
2784
- const handlePointerMove = (0, import_react17.useCallback)((e) => {
3263
+ const handlePointerMove = (0, import_react21.useCallback)((e) => {
2785
3264
  if (!draggingRef.current || !dragStartRef.current) return;
2786
3265
  if (!didDragRef.current && !hasPanelDragMoved(dragStartRef.current, e.clientX, e.clientY)) return;
2787
3266
  didDragRef.current = true;
2788
3267
  setDragOffset(getPanelDragOffset(dragStartRef.current, e.clientX, e.clientY));
2789
3268
  }, []);
2790
- const handlePointerUp = (0, import_react17.useCallback)((e) => {
3269
+ const handlePointerUp = (0, import_react21.useCallback)((e) => {
2791
3270
  if (!draggingRef.current) return;
2792
3271
  draggingRef.current = false;
2793
3272
  dragStartRef.current = null;
@@ -2803,7 +3282,7 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2803
3282
  }
2804
3283
  dragTargetRef.current = null;
2805
3284
  }, []);
2806
- const handlePanelOpenChange = (0, import_react17.useCallback)((panelId, open) => {
3285
+ const handlePanelOpenChange = (0, import_react21.useCallback)((panelId, open) => {
2807
3286
  panelOpenStatesRef.current.set(panelId, open);
2808
3287
  const fallbackOpen = inline || defaultOpen;
2809
3288
  const nextRootOpen = panels.some((panel) => panelOpenStatesRef.current.get(panel.id) ?? fallbackOpen);
@@ -2811,7 +3290,7 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2811
3290
  rootOpenRef.current = nextRootOpen;
2812
3291
  onOpenChange?.(nextRootOpen);
2813
3292
  }, [defaultOpen, inline, onOpenChange, panels]);
2814
- const handleRootOpenChange = (0, import_react17.useCallback)((open) => {
3293
+ const handleRootOpenChange = (0, import_react21.useCallback)((open) => {
2815
3294
  if (rootOpenRef.current === open) return;
2816
3295
  rootOpenRef.current = open;
2817
3296
  onOpenChange?.(open);
@@ -2819,7 +3298,7 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2819
3298
  if (!mounted || typeof window === "undefined") {
2820
3299
  return null;
2821
3300
  }
2822
- if (panels.length === 0) {
3301
+ if (panels.length === 0 && timelineCount === 0) {
2823
3302
  return null;
2824
3303
  }
2825
3304
  const dragStyle = dragOffset ? {
@@ -2830,7 +3309,8 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2830
3309
  } : void 0;
2831
3310
  const originX = getPanelOriginX(activePosition, dragOffset);
2832
3311
  const hasMultiplePanels = panels.length > 1;
2833
- const content = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ShortcutListener, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dialkit-root", "data-mode": mode, "data-theme": theme, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3312
+ const timelineToggle = timelineCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TimelineToggleButton, {}) : null;
3313
+ const content = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ShortcutListener, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "dialkit-root", "data-mode": mode, "data-theme": theme, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2834
3314
  "div",
2835
3315
  {
2836
3316
  ref: panelRef,
@@ -2844,7 +3324,19 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2844
3324
  onPointerMove: !inline ? handlePointerMove : void 0,
2845
3325
  onPointerUp: !inline ? handlePointerUp : void 0,
2846
3326
  onPointerCancel: !inline ? handlePointerUp : void 0,
2847
- children: hasMultiplePanels ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dialkit-panel-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3327
+ children: panels.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "dialkit-panel-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3328
+ Folder,
3329
+ {
3330
+ title: "DialKit",
3331
+ defaultOpen: inline || defaultOpen,
3332
+ isRoot: true,
3333
+ inline,
3334
+ onOpenChange: handleRootOpenChange,
3335
+ toolbar: timelineToggle,
3336
+ panelHeightOffset: 2,
3337
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "dialkit-timeline-toolkit-only", children: "Timeline" })
3338
+ }
3339
+ ) }) : hasMultiplePanels ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "dialkit-panel-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2848
3340
  Folder,
2849
3341
  {
2850
3342
  title: "DialKit",
@@ -2852,8 +3344,9 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2852
3344
  isRoot: true,
2853
3345
  inline,
2854
3346
  onOpenChange: handleRootOpenChange,
3347
+ toolbar: timelineToggle,
2855
3348
  panelHeightOffset: 2,
2856
- children: panels.map((panel) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3349
+ children: panels.map((panel) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2857
3350
  Panel,
2858
3351
  {
2859
3352
  panel,
@@ -2863,12 +3356,13 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2863
3356
  panel.id
2864
3357
  ))
2865
3358
  }
2866
- ) }) : panels.map((panel) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3359
+ ) }) : panels.map((panel) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2867
3360
  Panel,
2868
3361
  {
2869
3362
  panel,
2870
3363
  defaultOpen: inline || defaultOpen,
2871
3364
  inline,
3365
+ toolbarExtra: timelineToggle,
2872
3366
  onOpenChange: (open) => handlePanelOpenChange(panel.id, open)
2873
3367
  },
2874
3368
  panel.id
@@ -2881,25 +3375,2164 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
2881
3375
  return (0, import_react_dom3.createPortal)(content, document.body);
2882
3376
  }
2883
3377
 
2884
- // src/components/ButtonGroup.tsx
2885
- var import_jsx_runtime16 = require("react/jsx-runtime");
2886
- function ButtonGroup({ buttons }) {
2887
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "dialkit-button-group", children: buttons.map((button, index) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2888
- "button",
2889
- {
2890
- className: "dialkit-button",
2891
- onClick: button.onClick,
2892
- children: button.label
2893
- },
2894
- index
2895
- )) });
2896
- }
3378
+ // src/hooks/useDialTimeline.ts
3379
+ var import_react22 = require("react");
2897
3380
 
2898
- // src/components/ShortcutsMenu.tsx
2899
- var import_react18 = require("react");
3381
+ // src/timeline-core.ts
3382
+ var CLIP_VALUE_STEP = 0.01;
3383
+ var TIMELINE_MIN_CLIP_DURATION = 0.05;
3384
+ var DEFAULT_STEP_DURATION = 0.3;
3385
+ var DEFAULT_CLIP_TRANSITION = { type: "spring", bounce: 0.2 };
3386
+ var RESERVED_KEYS = /* @__PURE__ */ new Set(["time", "playing", "duration", "play", "pause", "replay", "seek"]);
3387
+ function isClipConfig(value) {
3388
+ return isPlainObject(value) && Number.isFinite(value.at);
3389
+ }
3390
+ function isGroupConfig(value) {
3391
+ if (!isPlainObject(value) || "at" in value) return false;
3392
+ const entries = Object.values(value);
3393
+ return entries.length > 0 && entries.some(isClipConfig);
3394
+ }
3395
+ function isPlainObject(value) {
3396
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3397
+ }
3398
+ function nonNegativeFinite(value, fallback = 0) {
3399
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : fallback;
3400
+ }
3401
+ function animatedDuration(value, fallback = DEFAULT_STEP_DURATION) {
3402
+ return Math.max(TIMELINE_MIN_CLIP_DURATION, nonNegativeFinite(value, fallback));
3403
+ }
3404
+ function transitionDefaultDuration(transition) {
3405
+ if (transition.type === "easing") return animatedDuration(transition.duration);
3406
+ if (isPhysicsSpring(transition)) {
3407
+ return animatedDuration(springSettleDuration(springParams(transition)));
3408
+ }
3409
+ if (transition.visualDuration !== void 0) return animatedDuration(transition.visualDuration);
3410
+ return animatedDuration(springSettleDuration(springParams(transition)));
3411
+ }
3412
+ function defaultStepDuration(step, inheritedTransition) {
3413
+ const curve = step.transition ?? inheritedTransition;
3414
+ if (curve && isPhysicsSpring(curve)) return transitionDefaultDuration(curve);
3415
+ if (step.duration !== void 0) return animatedDuration(step.duration);
3416
+ if (step.transition) return transitionDefaultDuration(step.transition);
3417
+ return DEFAULT_STEP_DURATION;
3418
+ }
3419
+ function defaultTrackDuration(track, inheritedTransition) {
3420
+ const curve = track.transition ?? inheritedTransition;
3421
+ if (track.steps?.length) {
3422
+ return track.steps.reduce((sum, step) => sum + defaultStepDuration(step, curve), 0);
3423
+ }
3424
+ if (curve && isPhysicsSpring(curve)) return transitionDefaultDuration(curve);
3425
+ if (track.duration !== void 0) return animatedDuration(track.duration);
3426
+ if (track.transition) return transitionDefaultDuration(track.transition);
3427
+ return DEFAULT_STEP_DURATION;
3428
+ }
3429
+ function defaultClipDuration(clip) {
3430
+ const defaultCurve = isTransitionConfig(clip.transition) ? clip.transition : DEFAULT_CLIP_TRANSITION;
3431
+ if (clip.props) {
3432
+ return Object.values(clip.props).reduce(
3433
+ (max, track) => Math.max(
3434
+ max,
3435
+ nonNegativeFinite(track.delay) + defaultTrackDuration(track, defaultCurve)
3436
+ ),
3437
+ 0
3438
+ );
3439
+ }
3440
+ if (clip.steps?.length) {
3441
+ return clip.steps.reduce((sum, step) => sum + defaultStepDuration(step, defaultCurve), 0);
3442
+ }
3443
+ const animating = Boolean(clip.transition || clip.from || clip.to);
3444
+ if (!animating) return nonNegativeFinite(clip.duration);
3445
+ if (isPhysicsSpring(defaultCurve)) return transitionDefaultDuration(defaultCurve);
3446
+ if (clip.duration !== void 0) return animatedDuration(clip.duration);
3447
+ if (isTransitionConfig(clip.transition)) return transitionDefaultDuration(clip.transition);
3448
+ return clip.from || clip.to ? transitionDefaultDuration(DEFAULT_CLIP_TRANSITION) : 0;
3449
+ }
3450
+ function normalizeLoopMode(value) {
3451
+ if (value === true || value === "mirror" || value === "repeat") return "repeat";
3452
+ return "off";
3453
+ }
3454
+ function normalizeStoredTransition(transition, clipDuration) {
3455
+ if (transition.type === "easing") {
3456
+ return { ...transition, duration: clipDuration };
3457
+ }
3458
+ if (isPhysicsSpring(transition)) {
3459
+ return transition;
3460
+ }
3461
+ return { type: "spring", bounce: transition.bounce ?? 0.2 };
3462
+ }
3463
+ function collectClipEntries(config) {
3464
+ const entries = [];
3465
+ for (const [key, value] of Object.entries(config)) {
3466
+ if (key === "duration") continue;
3467
+ if (RESERVED_KEYS.has(key)) {
3468
+ console.warn(`[dialkit] Timeline key "${key}" collides with a reserved key and was skipped.`);
3469
+ continue;
3470
+ }
3471
+ if (isClipConfig(value)) {
3472
+ entries.push({ path: key, childKey: key, clip: value });
3473
+ } else if (isGroupConfig(value)) {
3474
+ for (const [childKey, childClip] of Object.entries(value)) {
3475
+ if (isClipConfig(childClip)) {
3476
+ entries.push({ path: `${key}.${childKey}`, childKey, group: key, clip: childClip });
3477
+ } else {
3478
+ console.warn(
3479
+ `[dialkit] Timeline clip "${key}.${childKey}" is missing a numeric "at" and was skipped.`
3480
+ );
3481
+ }
3482
+ }
3483
+ } else {
3484
+ console.warn(
3485
+ `[dialkit] Timeline entry "${key}" is neither a clip (needs a numeric "at") nor a group of clips and was skipped.`
3486
+ );
3487
+ }
3488
+ }
3489
+ return entries;
3490
+ }
3491
+ function definedValues(values) {
3492
+ if (!values) return void 0;
3493
+ const result = {};
3494
+ for (const [key, value] of Object.entries(values)) {
3495
+ if (value !== void 0) result[key] = value;
3496
+ }
3497
+ return result;
3498
+ }
3499
+ function setDialPath(dialConfig, path, value) {
3500
+ const segments = path.split(".");
3501
+ let node = dialConfig;
3502
+ for (const segment of segments.slice(0, -1)) {
3503
+ node = node[segment] ?? (node[segment] = {});
3504
+ }
3505
+ node[segments[segments.length - 1]] = value;
3506
+ }
3507
+ function parseTimelineConfig(config) {
3508
+ const entries = collectClipEntries(config);
3509
+ let maxEnd = 0;
3510
+ for (const { clip } of entries) {
3511
+ maxEnd = Math.max(maxEnd, nonNegativeFinite(clip.at) + defaultClipDuration(clip));
3512
+ }
3513
+ const duration = typeof config.duration === "number" && Number.isFinite(config.duration) && config.duration > 0 ? config.duration : maxEnd > 0 ? Math.ceil(maxEnd * 100 - 1e-4) / 100 : 1;
3514
+ const dialConfig = {};
3515
+ const clips = [];
3516
+ entries.forEach(({ path, childKey, group, clip }, index) => {
3517
+ const raw = clip;
3518
+ if (raw.props && (raw.steps?.length || raw.from || raw.to)) {
3519
+ console.warn(
3520
+ `[dialkit] Timeline clip "${path}": "props" is mutually exclusive with from/to/steps \u2014 using "props".`
3521
+ );
3522
+ } else if (raw.steps?.length && raw.to) {
3523
+ console.warn(
3524
+ `[dialkit] Timeline clip "${path}": "to" is ignored when "steps" is present \u2014 each leg's "to" defines its targets.`
3525
+ );
3526
+ }
3527
+ const hasSteps = Boolean(clip.steps?.length) && !clip.props;
3528
+ const hasProps = Boolean(clip.props);
3529
+ const single = isTransitionConfig(clip.transition) ? clip.transition : void 0;
3530
+ const total = defaultClipDuration(clip);
3531
+ const defaultCurve = single ?? DEFAULT_CLIP_TRANSITION;
3532
+ const clipAt = nonNegativeFinite(clip.at);
3533
+ const clipDial = {
3534
+ at: [clipAt, 0, duration, CLIP_VALUE_STEP]
3535
+ };
3536
+ if (!hasSteps && !hasProps) {
3537
+ clipDial.duration = [total, 0, duration, CLIP_VALUE_STEP];
3538
+ }
3539
+ if (!hasSteps && !hasProps && (clip.transition || clip.from || clip.to)) {
3540
+ clipDial.transition = normalizeStoredTransition(defaultCurve, total);
3541
+ }
3542
+ let tracks;
3543
+ if (clip.props) {
3544
+ tracks = [];
3545
+ for (const [prop, track] of Object.entries(clip.props)) {
3546
+ if (TRACK_RESERVED.has(prop) || /^step\d+$/.test(prop)) {
3547
+ console.warn(`[dialkit] Timeline property "${prop}" collides with a clip field and was skipped.`);
3548
+ continue;
3549
+ }
3550
+ const trackDuration = defaultTrackDuration(track, defaultCurve);
3551
+ const trackCurve = track.transition ?? defaultCurve;
3552
+ const hasTrackSteps = Boolean(track.steps?.length);
3553
+ const trackDial = {
3554
+ delay: [nonNegativeFinite(track.delay), 0, duration, CLIP_VALUE_STEP]
3555
+ };
3556
+ if (!hasTrackSteps) {
3557
+ trackDial.duration = [trackDuration, 0, duration, CLIP_VALUE_STEP];
3558
+ trackDial.transition = normalizeStoredTransition(trackCurve, trackDuration);
3559
+ }
3560
+ const fromValue = track.from ?? (hasTrackSteps ? void 0 : track.to);
3561
+ if (hasTrackSteps && fromValue === void 0) {
3562
+ console.warn(
3563
+ `[dialkit] Timeline clip "${path}": track "${prop}" has steps but no "from" \u2014 declare its starting value.`
3564
+ );
3565
+ }
3566
+ if (fromValue !== void 0) {
3567
+ trackDial.from = scalarDial(prop, fromValue, hasTrackSteps ? track.steps[0]?.to : track.to);
3568
+ }
3569
+ if (!hasTrackSteps && track.to !== void 0) {
3570
+ trackDial.to = scalarDial(prop, track.to, fromValue);
3571
+ }
3572
+ let trackStepKeys;
3573
+ if (hasTrackSteps) {
3574
+ trackStepKeys = [];
3575
+ let previous = fromValue;
3576
+ track.steps.forEach((step, stepIndex) => {
3577
+ const stepKey = `step${stepIndex + 1}`;
3578
+ trackStepKeys.push(stepKey);
3579
+ const stepDuration = defaultStepDuration(step, trackCurve);
3580
+ const stepDial = {
3581
+ duration: [stepDuration, 0, duration, CLIP_VALUE_STEP],
3582
+ transition: normalizeStoredTransition(step.transition ?? trackCurve, stepDuration)
3583
+ };
3584
+ if (step.to !== void 0) {
3585
+ stepDial.to = scalarDial(prop, step.to, previous);
3586
+ previous = step.to;
3587
+ }
3588
+ trackDial[stepKey] = stepDial;
3589
+ });
3590
+ }
3591
+ clipDial[prop] = trackDial;
3592
+ tracks.push({ prop, stepKeys: trackStepKeys });
3593
+ }
3594
+ }
3595
+ if (clip.from && !hasProps) {
3596
+ clipDial.from = withFromToRanges(
3597
+ clip.from,
3598
+ hasSteps ? definedValues(clip.steps[0]?.to) : clip.to
3599
+ );
3600
+ }
3601
+ if (!hasSteps && !hasProps && clip.to) {
3602
+ clipDial.to = withFromToRanges(clip.to, clip.from);
3603
+ }
3604
+ let stepKeys;
3605
+ if (hasSteps) {
3606
+ stepKeys = [];
3607
+ let running = clip.from;
3608
+ clip.steps.forEach((step, stepIndex) => {
3609
+ const stepKey = `step${stepIndex + 1}`;
3610
+ stepKeys.push(stepKey);
3611
+ const stepDuration = defaultStepDuration(step, defaultCurve);
3612
+ const stepDial = {
3613
+ duration: [stepDuration, 0, duration, CLIP_VALUE_STEP],
3614
+ transition: normalizeStoredTransition(step.transition ?? defaultCurve, stepDuration)
3615
+ };
3616
+ const stepTo = definedValues(step.to);
3617
+ if (stepTo) {
3618
+ for (const prop of Object.keys(stepTo)) {
3619
+ if (!running || !(prop in running)) {
3620
+ console.warn(
3621
+ `[dialkit] Timeline clip "${path}": property "${prop}" first animates in step ${stepIndex + 1} with no starting value \u2014 declare it in "from".`
3622
+ );
3623
+ }
3624
+ }
3625
+ stepDial.to = withFromToRanges(stepTo, running);
3626
+ }
3627
+ clipDial[stepKey] = stepDial;
3628
+ running = { ...running ?? {}, ...stepTo ?? {} };
3629
+ });
3630
+ }
3631
+ setDialPath(dialConfig, path, clipDial);
3632
+ clips.push({
3633
+ key: path,
3634
+ label: formatLabel(childKey),
3635
+ color: TIMELINE_CLIP_COLORS[index % TIMELINE_CLIP_COLORS.length],
3636
+ loop: normalizeLoopMode(clip.loop),
3637
+ group,
3638
+ stepKeys,
3639
+ tracks
3640
+ });
3641
+ });
3642
+ return { duration, dialConfig, clips };
3643
+ }
3644
+ var TRACK_RESERVED = /* @__PURE__ */ new Set(["at", "duration", "loop", "from", "to", "transition", "delay"]);
3645
+ function scalarDial(prop, value, counterpart) {
3646
+ const record = withFromToRanges(
3647
+ { [prop]: value },
3648
+ counterpart === void 0 ? void 0 : { [prop]: counterpart }
3649
+ );
3650
+ return record[prop];
3651
+ }
3652
+ var FROM_TO_RANGE_PRESETS = [
3653
+ [/^(x|y|z|tx|ty|offsetx|offsety|translatex|translatey)$/i, { min: -100, max: 100, step: 1 }],
3654
+ [/rotat|angle|skew/i, { min: -180, max: 180, step: 1 }],
3655
+ [/^scale/i, { min: 0, max: 2, step: 0.01 }],
3656
+ [/opacity|alpha/i, { min: 0, max: 1, step: 0.01 }],
3657
+ [/blur|radius|spread/i, { min: 0, max: 100, step: 1 }]
3658
+ ];
3659
+ function inferFromToRange(key, value, counterpart) {
3660
+ const lo = Math.min(value, counterpart ?? value);
3661
+ const hi = Math.max(value, counterpart ?? value);
3662
+ const preset = FROM_TO_RANGE_PRESETS.find(([pattern]) => pattern.test(key))?.[1];
3663
+ if (preset) {
3664
+ return [value, Math.min(preset.min, lo), Math.max(preset.max, hi), preset.step];
3665
+ }
3666
+ if (lo >= 0 && hi <= 1) {
3667
+ return [value, 0, 1, 0.01];
3668
+ }
3669
+ const extent = Math.max(Math.abs(lo), Math.abs(hi), 1);
3670
+ const min = lo < 0 ? -extent * 2 : 0;
3671
+ const max = Math.max(extent * 2, hi);
3672
+ return [value, min, max, inferStep(min, max)];
3673
+ }
3674
+ function withFromToRanges(config, counterpart) {
3675
+ const result = {};
3676
+ for (const [key, value] of Object.entries(config)) {
3677
+ const other = counterpart?.[key];
3678
+ if (typeof value === "number") {
3679
+ result[key] = inferFromToRange(key, value, typeof other === "number" ? other : void 0);
3680
+ } else if (isPlainObject(value) && !("type" in value)) {
3681
+ result[key] = withFromToRanges(
3682
+ value,
3683
+ isPlainObject(other) && !("type" in other) ? other : void 0
3684
+ );
3685
+ } else {
3686
+ result[key] = value;
3687
+ }
3688
+ }
3689
+ return result;
3690
+ }
3691
+ function curveStatic(transition, duration) {
3692
+ if (!transition) return { duration };
3693
+ if (transition.type === "easing") return { duration, ease: transition.ease };
3694
+ const spring = springParams(transition);
3695
+ return { duration, spring, settle: springSettleDuration(spring) };
3696
+ }
3697
+ function sampleCurve(curve, elapsed) {
3698
+ if (elapsed <= 0) return 0;
3699
+ if (curve.spring) {
3700
+ if (curve.settle !== void 0 && elapsed >= curve.settle) return 1;
3701
+ return springProgress(elapsed, curve.spring);
3702
+ }
3703
+ if (curve.ease) {
3704
+ return cubicBezierProgress(clamp(curve.duration > 0 ? elapsed / curve.duration : 1, 0, 1), curve.ease);
3705
+ }
3706
+ return curve.duration > 0 ? Math.min(1, elapsed / curve.duration) : 1;
3707
+ }
3708
+ function resolvedAtPath(resolved, path) {
3709
+ let node = resolved;
3710
+ for (const segment of path.split(".")) {
3711
+ node = isPlainObject(node) ? node[segment] : void 0;
3712
+ }
3713
+ return isPlainObject(node) ? node : {};
3714
+ }
3715
+ function computeStaticClips(parsed, flatValues) {
3716
+ const resolved = resolveDialValues(parsed.dialConfig, flatValues);
3717
+ return parsed.clips.map(
3718
+ (clip) => buildClipStatic(resolvedAtPath(resolved, clip.key), clip, parsed.duration)
3719
+ );
3720
+ }
3721
+ function computeStaticTimeline(parsed, flatValues) {
3722
+ let clips = computeStaticClips(parsed, flatValues);
3723
+ const maxEnd = clips.reduce(
3724
+ (end, clip) => Math.max(end, clip.at + clip.duration),
3725
+ parsed.duration
3726
+ );
3727
+ const duration = maxEnd > parsed.duration ? Math.ceil(maxEnd * 100 - 1e-4) / 100 : parsed.duration;
3728
+ if (duration !== parsed.duration) {
3729
+ clips = clips.map(
3730
+ (clip) => clip.loop === "repeat" ? { ...clip, end: duration } : clip
3731
+ );
3732
+ }
3733
+ return { duration, clips };
3734
+ }
3735
+ function computeClipStaticFromValues(values, clip, timelineDuration) {
3736
+ return buildClipStatic(unflattenClipValues(values, clip.key), clip, timelineDuration);
3737
+ }
3738
+ function buildClipStatic(clipResolved, clip, timelineDuration) {
3739
+ {
3740
+ const at = typeof clipResolved.at === "number" ? clipResolved.at : 0;
3741
+ const from = isPlainObject(clipResolved.from) ? clipResolved.from : void 0;
3742
+ const single = isTransitionConfig(clipResolved.transition) ? clipResolved.transition : void 0;
3743
+ const staticClip = {
3744
+ key: clip.key,
3745
+ childKey: clip.group ? clip.key.slice(clip.group.length + 1) : clip.key,
3746
+ group: clip.group,
3747
+ at,
3748
+ duration: 0,
3749
+ loop: "off",
3750
+ end: 0,
3751
+ isPhysics: false,
3752
+ from,
3753
+ tracks: [],
3754
+ explicitSteps: Boolean(clip.stepKeys?.length)
3755
+ };
3756
+ if (clip.tracks?.length) {
3757
+ const tracks = clip.tracks.map(({ prop, stepKeys }) => {
3758
+ const trackResolved = isPlainObject(clipResolved[prop]) ? clipResolved[prop] : {};
3759
+ const delay = typeof trackResolved.delay === "number" ? trackResolved.delay : 0;
3760
+ const fromValue = trackResolved.from;
3761
+ let steps;
3762
+ let trackDuration = 0;
3763
+ if (stepKeys?.length) {
3764
+ let running = fromValue;
3765
+ steps = stepKeys.map((stepKey) => {
3766
+ const stepResolved = isPlainObject(trackResolved[stepKey]) ? trackResolved[stepKey] : {};
3767
+ const storedDuration = typeof stepResolved.duration === "number" ? stepResolved.duration : 0;
3768
+ const raw = isTransitionConfig(stepResolved.transition) ? stepResolved.transition : void 0;
3769
+ const effective = raw ? resolveClipTransition(raw, storedDuration) : { transition: void 0, duration: storedDuration, isPhysics: false };
3770
+ const toValue = stepResolved.to;
3771
+ const step = {
3772
+ key: stepKey,
3773
+ offset: trackDuration,
3774
+ duration: effective.duration,
3775
+ isPhysics: effective.isPhysics,
3776
+ start: running === void 0 ? {} : { [prop]: running },
3777
+ to: toValue === void 0 ? {} : { [prop]: toValue },
3778
+ curve: curveStatic(effective.transition, effective.duration)
3779
+ };
3780
+ if (toValue !== void 0) running = toValue;
3781
+ trackDuration += effective.duration;
3782
+ return step;
3783
+ });
3784
+ } else {
3785
+ const storedDuration = typeof trackResolved.duration === "number" ? trackResolved.duration : 0;
3786
+ const raw = isTransitionConfig(trackResolved.transition) ? trackResolved.transition : void 0;
3787
+ const effective = raw ? resolveClipTransition(raw, storedDuration) : { transition: void 0, duration: storedDuration, isPhysics: false };
3788
+ const toValue = trackResolved.to;
3789
+ trackDuration = effective.duration;
3790
+ steps = [
3791
+ {
3792
+ key: null,
3793
+ offset: 0,
3794
+ duration: effective.duration,
3795
+ isPhysics: effective.isPhysics,
3796
+ start: fromValue === void 0 ? {} : { [prop]: fromValue },
3797
+ to: toValue === void 0 ? {} : { [prop]: toValue },
3798
+ curve: curveStatic(effective.transition, effective.duration)
3799
+ }
3800
+ ];
3801
+ }
3802
+ return { prop, delay, duration: trackDuration, steps };
3803
+ });
3804
+ staticClip.tracks = tracks;
3805
+ staticClip.props = tracks.map((track) => track.prop);
3806
+ staticClip.duration = tracks.reduce((max, track) => Math.max(max, track.delay + track.duration), 0);
3807
+ staticClip.from = Object.fromEntries(
3808
+ tracks.map((track) => [track.prop, track.steps[0].start[track.prop]])
3809
+ );
3810
+ staticClip.to = Object.fromEntries(
3811
+ tracks.map((track) => {
3812
+ const last = track.steps[track.steps.length - 1];
3813
+ return [track.prop, last.to[track.prop] ?? last.start[track.prop]];
3814
+ })
3815
+ );
3816
+ staticClip.loop = staticClip.duration > 0 ? clip.loop : "off";
3817
+ staticClip.end = staticClip.loop === "off" ? staticClip.at + staticClip.duration : timelineDuration;
3818
+ return staticClip;
3819
+ }
3820
+ if (clip.stepKeys?.length) {
3821
+ let running = { ...from ?? {} };
3822
+ let offset = 0;
3823
+ const steps = clip.stepKeys.map((stepKey) => {
3824
+ const stepResolved = isPlainObject(clipResolved[stepKey]) ? clipResolved[stepKey] : {};
3825
+ const storedDuration = typeof stepResolved.duration === "number" ? stepResolved.duration : 0;
3826
+ const raw = isTransitionConfig(stepResolved.transition) ? stepResolved.transition : void 0;
3827
+ const effective = raw ? resolveClipTransition(raw, storedDuration) : { transition: void 0, duration: storedDuration, isPhysics: false };
3828
+ const to = isPlainObject(stepResolved.to) ? stepResolved.to : {};
3829
+ const step = {
3830
+ key: stepKey,
3831
+ offset,
3832
+ duration: effective.duration,
3833
+ isPhysics: effective.isPhysics,
3834
+ start: running,
3835
+ to,
3836
+ curve: curveStatic(effective.transition, effective.duration)
3837
+ };
3838
+ running = { ...running, ...to };
3839
+ offset += effective.duration;
3840
+ return step;
3841
+ });
3842
+ staticClip.tracks = [{ delay: 0, duration: offset, steps }];
3843
+ staticClip.duration = offset;
3844
+ staticClip.to = running;
3845
+ } else {
3846
+ const storedDuration = typeof clipResolved.duration === "number" ? clipResolved.duration : 0;
3847
+ const to = isPlainObject(clipResolved.to) ? clipResolved.to : void 0;
3848
+ if (single) {
3849
+ const effective = resolveClipTransition(single, storedDuration);
3850
+ staticClip.duration = effective.duration;
3851
+ staticClip.isPhysics = effective.isPhysics;
3852
+ staticClip.transition = effective.transition;
3853
+ staticClip.css = transitionToCss(effective.transition);
3854
+ staticClip.to = to;
3855
+ if (from && to) {
3856
+ staticClip.tracks = [
3857
+ {
3858
+ delay: 0,
3859
+ duration: effective.duration,
3860
+ steps: [
3861
+ {
3862
+ key: null,
3863
+ offset: 0,
3864
+ duration: effective.duration,
3865
+ isPhysics: effective.isPhysics,
3866
+ start: from,
3867
+ to,
3868
+ curve: curveStatic(effective.transition, effective.duration)
3869
+ }
3870
+ ]
3871
+ }
3872
+ ];
3873
+ }
3874
+ } else {
3875
+ staticClip.duration = storedDuration;
3876
+ staticClip.to = to;
3877
+ if (from && to) {
3878
+ const base = resolveClipTransition(DEFAULT_CLIP_TRANSITION, storedDuration);
3879
+ staticClip.duration = base.duration;
3880
+ staticClip.tracks = [
3881
+ {
3882
+ delay: 0,
3883
+ duration: base.duration,
3884
+ steps: [
3885
+ {
3886
+ key: null,
3887
+ offset: 0,
3888
+ duration: base.duration,
3889
+ isPhysics: false,
3890
+ start: from,
3891
+ to,
3892
+ curve: curveStatic(base.transition, base.duration)
3893
+ }
3894
+ ]
3895
+ }
3896
+ ];
3897
+ }
3898
+ }
3899
+ }
3900
+ if (staticClip.tracks.length) {
3901
+ const props = new Set(Object.keys(from ?? {}));
3902
+ for (const track of staticClip.tracks) {
3903
+ for (const step of track.steps) {
3904
+ for (const prop of Object.keys(step.to)) props.add(prop);
3905
+ }
3906
+ }
3907
+ staticClip.props = Array.from(props);
3908
+ }
3909
+ staticClip.loop = staticClip.duration > 0 ? clip.loop : "off";
3910
+ staticClip.end = staticClip.loop === "off" ? staticClip.at + staticClip.duration : timelineDuration;
3911
+ return staticClip;
3912
+ }
3913
+ }
3914
+ function stepAtPosition(steps, pos) {
3915
+ for (const step of steps) {
3916
+ if (pos < step.offset + step.duration) return step;
3917
+ }
3918
+ return steps[steps.length - 1];
3919
+ }
3920
+ function evalPropAtPos(steps, prop, pos) {
3921
+ const step = stepAtPosition(steps, pos);
3922
+ const within = Math.max(0, pos - step.offset);
3923
+ if (prop in step.to) {
3924
+ const eased = sampleCurve(step.curve, within);
3925
+ return interpolateResolved(step.start[prop], step.to[prop], eased);
3926
+ }
3927
+ return step.start[prop];
3928
+ }
3929
+ function computeClipState(clip, time, cycleTime = time) {
3930
+ const total = clip.duration;
3931
+ const looping = clip.loop === "repeat" && total > 0;
3932
+ const started = time >= clip.at || looping && cycleTime > time;
3933
+ const done = time >= clip.end;
3934
+ const elapsed = time - clip.at;
3935
+ const phaseElapsed = looping ? cycleTime - clip.at : elapsed;
3936
+ const fold = (e) => looping ? e % total : e;
3937
+ const basePos = started ? fold(Math.max(0, phaseElapsed)) : 0;
3938
+ const progress = total > 0 ? clamp(basePos / total, 0, 1) : started ? 1 : 0;
3939
+ let current;
3940
+ let stepIndex = 0;
3941
+ if (clip.tracks.length && clip.props?.length) {
3942
+ current = {};
3943
+ for (const track of clip.tracks) {
3944
+ const props = track.prop !== void 0 ? [track.prop] : clip.props;
3945
+ for (const prop of props) {
3946
+ const startValue = track.steps[0]?.start[prop];
3947
+ if (!started) {
3948
+ if (startValue !== void 0) current[prop] = startValue;
3949
+ continue;
3950
+ }
3951
+ const phase = phaseElapsed - track.delay;
3952
+ if (phase <= 0) {
3953
+ if (startValue !== void 0) current[prop] = startValue;
3954
+ continue;
3955
+ }
3956
+ const pos = looping && track.duration > 0 ? phase % track.duration : phase;
3957
+ const value = evalPropAtPos(track.steps, prop, pos);
3958
+ if (value !== void 0) current[prop] = value;
3959
+ }
3960
+ }
3961
+ const shared = clip.tracks[0];
3962
+ if (started && clip.explicitSteps && shared.prop === void 0) {
3963
+ stepIndex = shared.steps.indexOf(stepAtPosition(shared.steps, basePos));
3964
+ }
3965
+ }
3966
+ return {
3967
+ at: clip.at,
3968
+ duration: clip.duration,
3969
+ loop: clip.loop,
3970
+ started,
3971
+ active: started && !done,
3972
+ done,
3973
+ progress,
3974
+ step: clip.explicitSteps ? stepIndex : void 0,
3975
+ from: clip.from,
3976
+ to: clip.to,
3977
+ animate: started ? clip.to : clip.from,
3978
+ transition: clip.transition,
3979
+ css: clip.css,
3980
+ current
3981
+ };
3982
+ }
3983
+ function interpolateResolved(from, to, p) {
3984
+ if (typeof from === "number" && typeof to === "number") {
3985
+ return from + (to - from) * p;
3986
+ }
3987
+ if (typeof from === "string" && typeof to === "string") {
3988
+ const mixed = mixHexColors(from, to, p);
3989
+ if (mixed) return mixed;
3990
+ }
3991
+ if (isPlainObject(from) && isPlainObject(to)) {
3992
+ const result = {};
3993
+ for (const key of Object.keys(from)) {
3994
+ result[key] = key in to ? interpolateResolved(from[key], to[key], p) : from[key];
3995
+ }
3996
+ for (const key of Object.keys(to)) {
3997
+ if (!(key in from)) result[key] = to[key];
3998
+ }
3999
+ return result;
4000
+ }
4001
+ return p < 0.5 ? from : to;
4002
+ }
4003
+ function parseHex(hex) {
4004
+ if (!isHexColor(hex)) return null;
4005
+ let h = hex.slice(1);
4006
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
4007
+ return [
4008
+ parseInt(h.slice(0, 2), 16),
4009
+ parseInt(h.slice(2, 4), 16),
4010
+ parseInt(h.slice(4, 6), 16),
4011
+ h.length === 8 ? parseInt(h.slice(6, 8), 16) : 255
4012
+ ];
4013
+ }
4014
+ function mixHexColors(a, b, p) {
4015
+ const ca = parseHex(a);
4016
+ const cb = parseHex(b);
4017
+ if (!ca || !cb) return null;
4018
+ const t = clamp(p, 0, 1);
4019
+ const mixed = ca.map((v, i) => Math.round(v + (cb[i] - v) * t));
4020
+ const hex = (n) => n.toString(16).padStart(2, "0");
4021
+ const rgb = `#${hex(mixed[0])}${hex(mixed[1])}${hex(mixed[2])}`;
4022
+ return mixed[3] === 255 ? rgb : `${rgb}${hex(mixed[3])}`;
4023
+ }
4024
+ function transitionToCss(transition) {
4025
+ if (!transition) return void 0;
4026
+ if (transition.type === "easing") {
4027
+ return {
4028
+ transitionDuration: `${round2(transition.duration)}s`,
4029
+ transitionTimingFunction: `cubic-bezier(${transition.ease.map((v) => round2(v)).join(", ")})`
4030
+ };
4031
+ }
4032
+ const params = springParams(transition);
4033
+ const dampingRatio = params.damping / (2 * Math.sqrt(params.stiffness * params.mass));
4034
+ const duration = transition.visualDuration ?? springSettleDuration(params);
4035
+ const bounce = transition.bounce ?? Math.max(0, round2(1 - dampingRatio));
4036
+ return {
4037
+ transitionDuration: `${round2(duration)}s`,
4038
+ transitionTimingFunction: bounce > 0.05 ? `cubic-bezier(0.34, ${round2(1.2 + bounce)}, 0.64, 1)` : "cubic-bezier(0.25, 0.6, 0.35, 1)"
4039
+ };
4040
+ }
4041
+ function timelinePopoverDisplayValues(values, clipKey, stepKeys, stepKey) {
4042
+ const display = { ...values };
4043
+ const swap = (path, duration) => {
4044
+ const raw = display[path];
4045
+ if (isTransitionConfig(raw)) display[path] = resolveClipTransition(raw, duration).transition;
4046
+ };
4047
+ if (stepKey) {
4048
+ swap(`${clipKey}.${stepKey}.transition`, numberValue(values[`${clipKey}.${stepKey}.duration`]));
4049
+ return display;
4050
+ }
4051
+ const cycle = stepKeys?.length ? stepKeys.reduce((sum, sk) => sum + numberValue(values[`${clipKey}.${sk}.duration`]), 0) : numberValue(values[`${clipKey}.duration`]);
4052
+ swap(`${clipKey}.transition`, cycle);
4053
+ return display;
4054
+ }
4055
+ function numberValue(value) {
4056
+ return typeof value === "number" ? value : 0;
4057
+ }
4058
+ function unflattenClipValues(values, clipKey) {
4059
+ const prefix = `${clipKey}.`;
4060
+ const result = {};
4061
+ const entries = Object.entries(values).filter(([path]) => path.startsWith(prefix)).map(([path, value]) => ({ segments: path.slice(prefix.length).split("."), value })).sort((a, b) => a.segments.length - b.segments.length);
4062
+ for (const { segments, value } of entries) {
4063
+ let node = result;
4064
+ for (let i = 0; i < segments.length - 1; i++) {
4065
+ const existing = node[segments[i]];
4066
+ node = isPlainObject(existing) ? existing : node[segments[i]] = {};
4067
+ }
4068
+ node[segments[segments.length - 1]] = cloneTimelineValue(value);
4069
+ }
4070
+ return result;
4071
+ }
4072
+ function cloneTimelineValue(value) {
4073
+ if (Array.isArray(value)) return value.map(cloneTimelineValue);
4074
+ if (!isPlainObject(value)) return value;
4075
+ return Object.fromEntries(
4076
+ Object.entries(value).map(([key, nested]) => [key, cloneTimelineValue(nested)])
4077
+ );
4078
+ }
4079
+ function clampTrackDelay(delay, at, trackDuration, timelineDuration) {
4080
+ return clamp(round2(delay), 0, Math.max(0, round2(timelineDuration - at - trackDuration)));
4081
+ }
4082
+ function clampClipMove(at, duration, timelineDuration) {
4083
+ return clamp(round2(at), 0, Math.max(0, timelineDuration - duration));
4084
+ }
4085
+ function clampClipResizeEnd(duration, at, timelineDuration) {
4086
+ return clamp(round2(duration), TIMELINE_MIN_CLIP_DURATION, timelineDuration - at);
4087
+ }
4088
+ function clampClipResizeStart(newAt, at, duration) {
4089
+ const clampedAt = clamp(round2(newAt), 0, at + duration - TIMELINE_MIN_CLIP_DURATION);
4090
+ return { at: clampedAt, duration: round2(at + duration - clampedAt) };
4091
+ }
4092
+ function clampStepResize(duration, at, otherStepsTotal, timelineDuration) {
4093
+ const max = Math.max(TIMELINE_MIN_CLIP_DURATION, timelineDuration - at - otherStepsTotal);
4094
+ return clamp(round2(duration), TIMELINE_MIN_CLIP_DURATION, max);
4095
+ }
4096
+ function normalizeTimelineValuesForCopy(values, clips) {
4097
+ const normalized = { ...values };
4098
+ for (const path of Object.keys(normalized)) {
4099
+ if (path.endsWith(".__mode")) delete normalized[path];
4100
+ }
4101
+ const normalizeTransitionAt = (transitionPath, durationPath) => {
4102
+ const raw = normalized[transitionPath];
4103
+ if (!isTransitionConfig(raw)) return;
4104
+ if (isPhysicsSpring(raw)) {
4105
+ normalized[durationPath] = transitionDefaultDuration(raw);
4106
+ }
4107
+ normalized[transitionPath] = normalizeStoredTransition(
4108
+ raw,
4109
+ numberValue(normalized[durationPath])
4110
+ );
4111
+ };
4112
+ for (const clip of clips) {
4113
+ for (const stepKey of clip.stepKeys ?? []) {
4114
+ normalizeTransitionAt(
4115
+ `${clip.key}.${stepKey}.transition`,
4116
+ `${clip.key}.${stepKey}.duration`
4117
+ );
4118
+ }
4119
+ normalizeTransitionAt(`${clip.key}.transition`, `${clip.key}.duration`);
4120
+ for (const track of clip.tracks ?? []) {
4121
+ const trackKey = `${clip.key}.${track.prop}`;
4122
+ for (const stepKey of track.stepKeys ?? []) {
4123
+ normalizeTransitionAt(
4124
+ `${trackKey}.${stepKey}.transition`,
4125
+ `${trackKey}.${stepKey}.duration`
4126
+ );
4127
+ }
4128
+ normalizeTransitionAt(`${trackKey}.transition`, `${trackKey}.duration`);
4129
+ if (normalized[`${trackKey}.delay`] === 0) {
4130
+ delete normalized[`${trackKey}.delay`];
4131
+ }
4132
+ }
4133
+ delete normalized[`${clip.key}.loop`];
4134
+ }
4135
+ return normalized;
4136
+ }
4137
+ function formatClock(time, tenths = false) {
4138
+ const safe = Math.max(0, time);
4139
+ const minutes = Math.floor(safe / 60);
4140
+ const seconds = safe - minutes * 60;
4141
+ const secondsText = tenths ? seconds.toFixed(1).padStart(4, "0") : String(Math.floor(seconds)).padStart(2, "0");
4142
+ return `${String(minutes).padStart(2, "0")}:${secondsText}`;
4143
+ }
4144
+ function formatSeconds(value) {
4145
+ return `${round2(value)}s`;
4146
+ }
4147
+ function formatStepLabel(stepKey) {
4148
+ const match = /^step(\d+)$/.exec(stepKey);
4149
+ return match ? `Step ${match[1]}` : formatLabel(stepKey);
4150
+ }
4151
+
4152
+ // src/timeline/adapter.ts
4153
+ function resolveTimelineLoop(loop) {
4154
+ if (typeof loop === "object" && loop !== null) {
4155
+ return {
4156
+ enabled: true,
4157
+ start: Number.isFinite(loop.from) ? Math.max(0, loop.from) : 0
4158
+ };
4159
+ }
4160
+ return { enabled: Boolean(loop), start: 0 };
4161
+ }
4162
+ function buildTimelineMeta(id, name, duration, parsed, loop) {
4163
+ const resolvedLoop = resolveTimelineLoop(loop);
4164
+ return {
4165
+ id,
4166
+ name,
4167
+ duration,
4168
+ loop: resolvedLoop.enabled,
4169
+ loopStart: resolvedLoop.start,
4170
+ clips: parsed.clips
4171
+ };
4172
+ }
4173
+ function buildTimelineValues(staticClips, transport, timelineDuration, loopStart, actions) {
4174
+ var _a;
4175
+ const result = {
4176
+ time: transport.time,
4177
+ playing: transport.playing,
4178
+ duration: timelineDuration,
4179
+ ...actions
4180
+ };
4181
+ const span = loopSpan(transport.duration, loopStart);
4182
+ const cycleTime = (span > 0 ? transport.wraps * span : 0) + transport.time;
4183
+ for (const clip of staticClips) {
4184
+ const state = computeClipState(clip, transport.time, cycleTime);
4185
+ if (clip.group) {
4186
+ const bucket = result[_a = clip.group] ?? (result[_a] = {});
4187
+ bucket[clip.childKey] = state;
4188
+ } else {
4189
+ result[clip.key] = state;
4190
+ }
4191
+ }
4192
+ return result;
4193
+ }
4194
+
4195
+ // src/hooks/useDialTimeline.ts
4196
+ function useDialTimeline(name, config, options) {
4197
+ const serializedConfig = useSerialized(config);
4198
+ const parsed = (0, import_react22.useMemo)(() => parseTimelineConfig(config), [serializedConfig]);
4199
+ const { panelId, flatValues } = useDialStorePanel(name, parsed.dialConfig, {
4200
+ id: options?.id,
4201
+ persist: options?.persist,
4202
+ kind: "timeline"
4203
+ });
4204
+ const staticTimeline = (0, import_react22.useMemo)(
4205
+ () => computeStaticTimeline(parsed, flatValues),
4206
+ [parsed, flatValues]
4207
+ );
4208
+ const timelineDuration = staticTimeline.duration;
4209
+ const staticClips = staticTimeline.clips;
4210
+ const parsedRef = (0, import_react22.useRef)(parsed);
4211
+ parsedRef.current = parsed;
4212
+ const optionsRef = (0, import_react22.useRef)(options);
4213
+ optionsRef.current = options;
4214
+ const { start: loopStart } = resolveTimelineLoop(options?.loop);
4215
+ const buildMeta = (0, import_react22.useCallback)(
4216
+ () => buildTimelineMeta(panelId, name, timelineDuration, parsedRef.current, options?.loop),
4217
+ [panelId, name, timelineDuration, options?.loop]
4218
+ );
4219
+ const buildMetaRef = (0, import_react22.useRef)(buildMeta);
4220
+ buildMetaRef.current = buildMeta;
4221
+ (0, import_react22.useEffect)(() => {
4222
+ TimelineStore.register(buildMetaRef.current(), { autoplay: optionsRef.current?.autoplay ?? true });
4223
+ return () => TimelineStore.unregister(panelId);
4224
+ }, [panelId, name]);
4225
+ const mountedRef = (0, import_react22.useRef)(false);
4226
+ (0, import_react22.useEffect)(() => {
4227
+ if (!mountedRef.current) {
4228
+ mountedRef.current = true;
4229
+ return;
4230
+ }
4231
+ TimelineStore.update(buildMeta());
4232
+ }, [buildMeta, parsed]);
4233
+ const subscribeTransport = (0, import_react22.useCallback)(
4234
+ (callback) => TimelineStore.subscribe(panelId, callback),
4235
+ [panelId]
4236
+ );
4237
+ const getTransport = (0, import_react22.useCallback)(() => TimelineStore.getTransport(panelId), [panelId]);
4238
+ const transport = (0, import_react22.useSyncExternalStore)(subscribeTransport, getTransport, getTransport);
4239
+ const play = (0, import_react22.useCallback)(() => TimelineStore.play(panelId), [panelId]);
4240
+ const pause = (0, import_react22.useCallback)(() => TimelineStore.pause(panelId), [panelId]);
4241
+ const replay = (0, import_react22.useCallback)(() => TimelineStore.replay(panelId), [panelId]);
4242
+ const seek = (0, import_react22.useCallback)((time) => TimelineStore.seek(panelId, time), [panelId]);
4243
+ return (0, import_react22.useMemo)(
4244
+ () => buildTimelineValues(staticClips, transport, timelineDuration, loopStart, {
4245
+ play,
4246
+ pause,
4247
+ replay,
4248
+ seek
4249
+ }),
4250
+ [staticClips, transport, timelineDuration, loopStart, play, pause, replay, seek]
4251
+ );
4252
+ }
4253
+
4254
+ // src/components/Timeline/DialTimeline.tsx
4255
+ var import_react23 = require("react");
2900
4256
  var import_react_dom4 = require("react-dom");
2901
- var import_react19 = require("motion/react");
2902
- var import_jsx_runtime17 = require("react/jsx-runtime");
4257
+ var import_react24 = require("motion/react");
4258
+ var import_jsx_runtime18 = require("react/jsx-runtime");
4259
+ var DRAG_THRESHOLD_PX = 3;
4260
+ var MAJOR_TICK_TARGET_PX = 140;
4261
+ var MILLISECOND_STEP = 1e-3;
4262
+ var SECOND_TICK_STEPS = [
4263
+ 1e-3,
4264
+ 2e-3,
4265
+ 5e-3,
4266
+ 0.01,
4267
+ 0.02,
4268
+ 0.05,
4269
+ 0.1,
4270
+ 0.2,
4271
+ 0.5,
4272
+ 1,
4273
+ 2,
4274
+ 5,
4275
+ 10,
4276
+ 15,
4277
+ 30,
4278
+ 60,
4279
+ 120,
4280
+ 300,
4281
+ 600
4282
+ ];
4283
+ var MIN_TIMELINE_MAX_ZOOM = 8;
4284
+ var PLAYHEAD_FLAG_WIDTH = 52;
4285
+ var POPOVER_WIDTH = 280;
4286
+ var ZOOM_DRAG_DISTANCE = 180;
4287
+ var subscribeGlobalTimelines = (callback) => TimelineStore.subscribeGlobal(callback);
4288
+ var getTimelines = () => TimelineStore.getTimelines();
4289
+ var subscribeTimelineVisibility = (callback) => TimelineUiStore.subscribe(callback);
4290
+ var getTimelineVisibility = () => TimelineUiStore.getVisible();
4291
+ var DialTimeline = (0, import_react23.memo)(function DialTimeline2({
4292
+ theme = "system",
4293
+ defaultVisible = true,
4294
+ visible,
4295
+ onVisibilityChange,
4296
+ defaultOpen = true,
4297
+ productionEnabled = isDevDefault
4298
+ }) {
4299
+ if (!productionEnabled) return null;
4300
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4301
+ DialTimelineDock,
4302
+ {
4303
+ theme,
4304
+ defaultVisible,
4305
+ visible,
4306
+ onVisibilityChange,
4307
+ defaultOpen
4308
+ }
4309
+ );
4310
+ });
4311
+ function DialTimelineDock({
4312
+ theme,
4313
+ defaultVisible,
4314
+ visible,
4315
+ onVisibilityChange,
4316
+ defaultOpen
4317
+ }) {
4318
+ const [mounted, setMounted] = (0, import_react23.useState)(false);
4319
+ const visibilityControllerId = (0, import_react23.useRef)(/* @__PURE__ */ Symbol("dialkit-timeline-visibility"));
4320
+ (0, import_react23.useEffect)(() => TimelineUiStore.registerController(visibilityControllerId.current, {
4321
+ visible,
4322
+ defaultVisible,
4323
+ onVisibilityChange
4324
+ }), []);
4325
+ (0, import_react23.useEffect)(() => {
4326
+ TimelineUiStore.updateController(visibilityControllerId.current, {
4327
+ visible,
4328
+ defaultVisible,
4329
+ onVisibilityChange
4330
+ });
4331
+ }, [defaultVisible, onVisibilityChange, visible]);
4332
+ (0, import_react23.useEffect)(() => {
4333
+ setMounted(true);
4334
+ }, []);
4335
+ const timelines = (0, import_react23.useSyncExternalStore)(subscribeGlobalTimelines, getTimelines, getTimelines);
4336
+ const dockVisible = (0, import_react23.useSyncExternalStore)(
4337
+ subscribeTimelineVisibility,
4338
+ getTimelineVisibility,
4339
+ getTimelineVisibility
4340
+ );
4341
+ if (!mounted || typeof window === "undefined" || timelines.length === 0) {
4342
+ return null;
4343
+ }
4344
+ return (0, import_react_dom4.createPortal)(
4345
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-root dialkit-timeline", "data-theme": theme, hidden: !dockVisible, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-dock", children: timelines.map((timeline) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4346
+ TimelineSection,
4347
+ {
4348
+ meta: timeline,
4349
+ defaultOpen,
4350
+ theme,
4351
+ dockVisible
4352
+ },
4353
+ timeline.id
4354
+ )) }) }),
4355
+ document.body
4356
+ );
4357
+ }
4358
+ function useTransportSubscribe(id) {
4359
+ return (0, import_react23.useCallback)((callback) => TimelineStore.subscribe(id, callback), [id]);
4360
+ }
4361
+ function PlayPauseButton({ id }) {
4362
+ const subscribe = useTransportSubscribe(id);
4363
+ const getPlaying = (0, import_react23.useCallback)(() => TimelineStore.getTransport(id).playing, [id]);
4364
+ const playing = (0, import_react23.useSyncExternalStore)(subscribe, getPlaying, getPlaying);
4365
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4366
+ import_react24.motion.button,
4367
+ {
4368
+ className: "dialkit-toolbar-add",
4369
+ onClick: () => playing ? TimelineStore.pause(id) : TimelineStore.play(id),
4370
+ title: playing ? "Pause" : "Play",
4371
+ "aria-label": playing ? "Pause" : "Play",
4372
+ whileTap: { scale: 0.9 },
4373
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
4374
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: { position: "relative", width: 16, height: 16 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react24.AnimatePresence, { initial: false, mode: "wait", children: playing ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4375
+ import_react24.motion.svg,
4376
+ {
4377
+ viewBox: "0 0 24 24",
4378
+ fill: "none",
4379
+ "aria-hidden": "true",
4380
+ style: { position: "absolute", inset: 0, width: 16, height: 16, color: "var(--dial-text-label)" },
4381
+ initial: { scale: 0.8, opacity: 0 },
4382
+ animate: { scale: 1, opacity: 1 },
4383
+ exit: { scale: 0.8, opacity: 0 },
4384
+ transition: { duration: 0.08 },
4385
+ children: ICON_PAUSE.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("path", { d, fill: "currentColor" }, i))
4386
+ },
4387
+ "pause"
4388
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4389
+ import_react24.motion.svg,
4390
+ {
4391
+ viewBox: "0 0 24 24",
4392
+ fill: "none",
4393
+ "aria-hidden": "true",
4394
+ style: { position: "absolute", inset: 0, width: 16, height: 16, color: "var(--dial-text-label)" },
4395
+ initial: { scale: 0.8, opacity: 0 },
4396
+ animate: { scale: 1, opacity: 1 },
4397
+ exit: { scale: 0.8, opacity: 0 },
4398
+ transition: { duration: 0.08 },
4399
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("path", { d: ICON_PLAY, fill: "currentColor" })
4400
+ },
4401
+ "play"
4402
+ ) }) })
4403
+ }
4404
+ );
4405
+ }
4406
+ function TimelinePlayheadFlag({
4407
+ id,
4408
+ duration,
4409
+ pxPerSecond,
4410
+ viewStart,
4411
+ viewEnd,
4412
+ laneWidth,
4413
+ rulerRef,
4414
+ headerClearStart,
4415
+ headerClearEnd,
4416
+ onResetView
4417
+ }) {
4418
+ const subscribe = useTransportSubscribe(id);
4419
+ const getTime = (0, import_react23.useCallback)(() => TimelineStore.getTransport(id).time, [id]);
4420
+ const time = (0, import_react23.useSyncExternalStore)(subscribe, getTime, getTime);
4421
+ const scrubRef = (0, import_react23.useRef)(null);
4422
+ const cleanupScrubRef = (0, import_react23.useRef)(null);
4423
+ const seekFromClientX = (0, import_react23.useCallback)((clientX) => {
4424
+ const rect = scrubRef.current?.rect;
4425
+ const scrub = scrubRef.current;
4426
+ const contentWidth = rect?.width ?? 0;
4427
+ if (!rect || !scrub || contentWidth <= 0) return;
4428
+ const nextTime = clamp(
4429
+ scrub.viewStart + (clientX - rect.left) / contentWidth * (scrub.viewEnd - scrub.viewStart),
4430
+ scrub.viewStart,
4431
+ scrub.viewEnd
4432
+ );
4433
+ TimelineStore.seek(id, nextTime);
4434
+ }, [id]);
4435
+ const handlePointerDown = (0, import_react23.useCallback)((e) => {
4436
+ const rect = rulerRef.current?.getBoundingClientRect();
4437
+ if (!rect) return;
4438
+ e.preventDefault();
4439
+ e.stopPropagation();
4440
+ cleanupScrubRef.current?.();
4441
+ const resetView = e.shiftKey;
4442
+ scrubRef.current = {
4443
+ wasPlaying: TimelineStore.getTransport(id).playing,
4444
+ rect,
4445
+ viewStart: resetView ? 0 : viewStart,
4446
+ viewEnd: resetView ? duration : viewEnd
4447
+ };
4448
+ if (resetView) onResetView();
4449
+ TimelineStore.pause(id);
4450
+ seekFromClientX(e.clientX);
4451
+ const handleWindowPointerMove = (event) => {
4452
+ event.preventDefault();
4453
+ seekFromClientX(event.clientX);
4454
+ };
4455
+ const finishWindowScrub = () => {
4456
+ window.removeEventListener("pointermove", handleWindowPointerMove);
4457
+ window.removeEventListener("pointerup", finishWindowScrub);
4458
+ window.removeEventListener("pointercancel", finishWindowScrub);
4459
+ if (scrubRef.current?.wasPlaying) TimelineStore.play(id);
4460
+ scrubRef.current = null;
4461
+ cleanupScrubRef.current = null;
4462
+ };
4463
+ window.addEventListener("pointermove", handleWindowPointerMove, { passive: false });
4464
+ window.addEventListener("pointerup", finishWindowScrub);
4465
+ window.addEventListener("pointercancel", finishWindowScrub);
4466
+ cleanupScrubRef.current = finishWindowScrub;
4467
+ }, [duration, id, onResetView, rulerRef, seekFromClientX, viewEnd, viewStart]);
4468
+ (0, import_react23.useEffect)(() => () => cleanupScrubRef.current?.(), []);
4469
+ if (time < viewStart || time > viewEnd || laneWidth <= 0) return null;
4470
+ const x = clamp(
4471
+ (time - viewStart) * pxPerSecond,
4472
+ 0,
4473
+ laneWidth
4474
+ );
4475
+ const flagLeft = x - PLAYHEAD_FLAG_WIDTH / 2;
4476
+ const flagRight = flagLeft + PLAYHEAD_FLAG_WIDTH;
4477
+ const placement = flagLeft >= headerClearStart && flagRight <= headerClearEnd ? "raised" : "lowered";
4478
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4479
+ "div",
4480
+ {
4481
+ className: "dialkit-timeline-playhead-control",
4482
+ "data-edge": "center",
4483
+ "data-placement": placement,
4484
+ style: { left: `calc(var(--dial-timeline-label-w) + ${x}px)` },
4485
+ onPointerDown: handlePointerDown,
4486
+ role: "slider",
4487
+ "aria-label": "Timeline current time",
4488
+ "aria-valuemin": 0,
4489
+ "aria-valuemax": duration,
4490
+ "aria-valuenow": time,
4491
+ title: "Drag to scrub the timeline",
4492
+ children: [
4493
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-playhead-flag", children: time.toFixed(2) }),
4494
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-playhead-stem" })
4495
+ ]
4496
+ }
4497
+ );
4498
+ }
4499
+ function TimelineOverview({
4500
+ id,
4501
+ duration,
4502
+ viewStart,
4503
+ viewEnd,
4504
+ onNavigate
4505
+ }) {
4506
+ const subscribe = useTransportSubscribe(id);
4507
+ const getTime = (0, import_react23.useCallback)(() => TimelineStore.getTransport(id).time, [id]);
4508
+ const time = (0, import_react23.useSyncExternalStore)(subscribe, getTime, getTime);
4509
+ const scrubRef = (0, import_react23.useRef)(null);
4510
+ const seekFromClientX = (0, import_react23.useCallback)((clientX) => {
4511
+ const rect = scrubRef.current?.rect;
4512
+ if (!rect || rect.width <= 0 || duration <= 0) return;
4513
+ const nextTime = clamp((clientX - rect.left) / rect.width * duration, 0, duration);
4514
+ TimelineStore.seek(id, nextTime);
4515
+ onNavigate(nextTime);
4516
+ }, [duration, id, onNavigate]);
4517
+ const handlePointerDown = (0, import_react23.useCallback)((e) => {
4518
+ e.preventDefault();
4519
+ e.currentTarget.setPointerCapture(e.pointerId);
4520
+ scrubRef.current = {
4521
+ wasPlaying: TimelineStore.getTransport(id).playing,
4522
+ rect: e.currentTarget.getBoundingClientRect()
4523
+ };
4524
+ TimelineStore.pause(id);
4525
+ seekFromClientX(e.clientX);
4526
+ }, [id, seekFromClientX]);
4527
+ const handlePointerMove = (0, import_react23.useCallback)((e) => {
4528
+ if (scrubRef.current) seekFromClientX(e.clientX);
4529
+ }, [seekFromClientX]);
4530
+ const finishScrub = (0, import_react23.useCallback)(() => {
4531
+ if (scrubRef.current?.wasPlaying) TimelineStore.play(id);
4532
+ scrubRef.current = null;
4533
+ }, [id]);
4534
+ const viewportLeft = duration > 0 ? viewStart / duration * 100 : 0;
4535
+ const viewportWidth = duration > 0 ? (viewEnd - viewStart) / duration * 100 : 100;
4536
+ const playheadLeft = duration > 0 ? time / duration * 100 : 0;
4537
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4538
+ "div",
4539
+ {
4540
+ className: "dialkit-timeline-overview",
4541
+ onPointerDown: handlePointerDown,
4542
+ onPointerMove: handlePointerMove,
4543
+ onPointerUp: finishScrub,
4544
+ onPointerCancel: finishScrub,
4545
+ onLostPointerCapture: finishScrub,
4546
+ title: "Drag to scrub the full timeline",
4547
+ children: [
4548
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4549
+ "div",
4550
+ {
4551
+ className: "dialkit-timeline-overview-viewport",
4552
+ "data-zoomed": viewportWidth < 99.999 || void 0,
4553
+ style: { left: `${viewportLeft}%`, width: `${viewportWidth}%` }
4554
+ }
4555
+ ),
4556
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-overview-progress", style: { width: `${playheadLeft}%` } }),
4557
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-overview-playhead", style: { left: `${playheadLeft}%` } })
4558
+ ]
4559
+ }
4560
+ );
4561
+ }
4562
+ function clampViewStart(start, duration, visibleDuration) {
4563
+ return clamp(start, 0, Math.max(0, duration - visibleDuration));
4564
+ }
4565
+ function formatRulerSeconds(time, step) {
4566
+ if (step >= 1 && Number.isInteger(time)) return formatClock(time);
4567
+ const decimals = Math.min(3, Math.max(1, Math.ceil(-Math.log10(step))));
4568
+ return `${time.toFixed(decimals)}s`;
4569
+ }
4570
+ var TimelineSection = (0, import_react23.memo)(function TimelineSection2({
4571
+ meta,
4572
+ defaultOpen,
4573
+ theme,
4574
+ dockVisible
4575
+ }) {
4576
+ const [open, setOpen] = (0, import_react23.useState)(defaultOpen);
4577
+ const [copied, setCopied] = (0, import_react23.useState)(false);
4578
+ const [popover, setPopover] = (0, import_react23.useState)(null);
4579
+ const [collapsedGroups, setCollapsedGroups] = (0, import_react23.useState)(() => /* @__PURE__ */ new Set());
4580
+ const [expandedTracks, setExpandedTracks] = (0, import_react23.useState)(() => /* @__PURE__ */ new Set());
4581
+ const [zoom, setZoom] = (0, import_react23.useState)(1);
4582
+ const [viewStart, setViewStart] = (0, import_react23.useState)(0);
4583
+ const subscribeValues = (0, import_react23.useCallback)(
4584
+ (callback) => DialStore.subscribe(meta.id, callback),
4585
+ [meta.id]
4586
+ );
4587
+ const getValues = (0, import_react23.useCallback)(() => DialStore.getValues(meta.id), [meta.id]);
4588
+ const values = (0, import_react23.useSyncExternalStore)(subscribeValues, getValues, getValues);
4589
+ const presets = DialStore.getPresets(meta.id);
4590
+ const activePresetId = DialStore.getActivePresetId(meta.id);
4591
+ const laneAreaRef = (0, import_react23.useRef)(null);
4592
+ const titleRef = (0, import_react23.useRef)(null);
4593
+ const actionsRef = (0, import_react23.useRef)(null);
4594
+ const [laneWidth, setLaneWidth] = (0, import_react23.useState)(0);
4595
+ const [flagClearRange, setFlagClearRange] = (0, import_react23.useState)({ start: 0, end: 0 });
4596
+ (0, import_react23.useLayoutEffect)(() => {
4597
+ if (!open) return;
4598
+ const ruler = laneAreaRef.current;
4599
+ const title = titleRef.current;
4600
+ const actions = actionsRef.current;
4601
+ if (!ruler || !title || !actions) return;
4602
+ const measure = () => {
4603
+ const rulerRect = ruler.getBoundingClientRect();
4604
+ const titleRect = title.getBoundingClientRect();
4605
+ const actionsRect = actions.getBoundingClientRect();
4606
+ setLaneWidth(rulerRect.width);
4607
+ const start = Math.round(titleRect.right + 10 - rulerRect.left);
4608
+ const end = Math.round(actionsRect.left - 10 - rulerRect.left);
4609
+ setFlagClearRange((current) => current.start === start && current.end === end ? current : { start, end });
4610
+ };
4611
+ measure();
4612
+ const observer = new ResizeObserver(measure);
4613
+ observer.observe(ruler);
4614
+ observer.observe(title);
4615
+ observer.observe(actions);
4616
+ return () => observer.disconnect();
4617
+ }, [open]);
4618
+ const visibleDuration = meta.duration > 0 ? meta.duration / zoom : meta.duration;
4619
+ const safeViewStart = clampViewStart(viewStart, meta.duration, visibleDuration);
4620
+ const viewEnd = safeViewStart + visibleDuration;
4621
+ const pxPerSecond = visibleDuration > 0 && laneWidth > 0 ? laneWidth / visibleDuration : 0;
4622
+ const millisecondReadableZoom = laneWidth > 0 && meta.duration > 0 ? MAJOR_TICK_TARGET_PX * meta.duration / (MILLISECOND_STEP * 10 * laneWidth) : MIN_TIMELINE_MAX_ZOOM;
4623
+ const maxZoom = Math.max(MIN_TIMELINE_MAX_ZOOM, millisecondReadableZoom);
4624
+ (0, import_react23.useEffect)(() => {
4625
+ setZoom((current) => clamp(current, 1, maxZoom));
4626
+ }, [maxZoom]);
4627
+ (0, import_react23.useEffect)(() => {
4628
+ setViewStart((current) => clampViewStart(current, meta.duration, meta.duration / zoom));
4629
+ }, [meta.duration, zoom]);
4630
+ (0, import_react23.useEffect)(() => {
4631
+ if (!dockVisible) setPopover(null);
4632
+ }, [dockVisible]);
4633
+ const centerViewAt = (0, import_react23.useCallback)((time) => {
4634
+ if (zoom <= 1 || meta.duration <= 0) return;
4635
+ const windowDuration = meta.duration / zoom;
4636
+ setViewStart(clampViewStart(time - windowDuration / 2, meta.duration, windowDuration));
4637
+ }, [meta.duration, zoom]);
4638
+ const resetView = (0, import_react23.useCallback)(() => {
4639
+ setZoom(1);
4640
+ setViewStart(0);
4641
+ }, []);
4642
+ const zoomDragRef = (0, import_react23.useRef)(null);
4643
+ const rulerScrubRef = (0, import_react23.useRef)(null);
4644
+ const seekRulerFromClientX = (0, import_react23.useCallback)((clientX) => {
4645
+ const scrub = rulerScrubRef.current;
4646
+ const contentWidth = scrub?.rect.width ?? 0;
4647
+ if (!scrub || contentWidth <= 0) return;
4648
+ TimelineStore.seek(
4649
+ meta.id,
4650
+ clamp(
4651
+ scrub.viewStart + (clientX - scrub.rect.left) / contentWidth * scrub.visibleDuration,
4652
+ scrub.viewStart,
4653
+ scrub.viewStart + scrub.visibleDuration
4654
+ )
4655
+ );
4656
+ }, [meta.id]);
4657
+ const handleRulerPointerDown = (0, import_react23.useCallback)((e) => {
4658
+ e.preventDefault();
4659
+ e.stopPropagation();
4660
+ const rect = e.currentTarget.getBoundingClientRect();
4661
+ const contentWidth = rect.width;
4662
+ if (contentWidth <= 0) return;
4663
+ e.currentTarget.setPointerCapture(e.pointerId);
4664
+ if (!e.altKey) {
4665
+ const resetView2 = e.shiftKey;
4666
+ rulerScrubRef.current = {
4667
+ wasPlaying: TimelineStore.getTransport(meta.id).playing,
4668
+ rect,
4669
+ viewStart: resetView2 ? 0 : safeViewStart,
4670
+ visibleDuration: resetView2 ? meta.duration : visibleDuration
4671
+ };
4672
+ if (resetView2) {
4673
+ setZoom(1);
4674
+ setViewStart(0);
4675
+ }
4676
+ TimelineStore.pause(meta.id);
4677
+ seekRulerFromClientX(e.clientX);
4678
+ return;
4679
+ }
4680
+ const anchorRatio = clamp((e.clientX - rect.left) / contentWidth, 0, 1);
4681
+ zoomDragRef.current = {
4682
+ pointerX: e.clientX,
4683
+ rect,
4684
+ zoom,
4685
+ viewStart: safeViewStart,
4686
+ anchorRatio,
4687
+ anchorTime: safeViewStart + anchorRatio * visibleDuration,
4688
+ moved: false
4689
+ };
4690
+ }, [meta.duration, meta.id, safeViewStart, seekRulerFromClientX, visibleDuration, zoom]);
4691
+ const handleRulerPointerMove = (0, import_react23.useCallback)((e) => {
4692
+ if (rulerScrubRef.current) {
4693
+ seekRulerFromClientX(e.clientX);
4694
+ return;
4695
+ }
4696
+ const drag = zoomDragRef.current;
4697
+ if (!drag || meta.duration <= 0) return;
4698
+ const dx = e.clientX - drag.pointerX;
4699
+ if (!drag.moved && Math.abs(dx) <= DRAG_THRESHOLD_PX) return;
4700
+ drag.moved = true;
4701
+ const nextZoom = clamp(drag.zoom * Math.exp(dx / ZOOM_DRAG_DISTANCE), 1, maxZoom);
4702
+ const nextVisibleDuration = meta.duration / nextZoom;
4703
+ const nextStart = clampViewStart(
4704
+ drag.anchorTime - drag.anchorRatio * nextVisibleDuration,
4705
+ meta.duration,
4706
+ nextVisibleDuration
4707
+ );
4708
+ setZoom(nextZoom);
4709
+ setViewStart(nextStart);
4710
+ }, [maxZoom, meta.duration, seekRulerFromClientX]);
4711
+ const handleRulerPointerUp = (0, import_react23.useCallback)(() => {
4712
+ if (rulerScrubRef.current?.wasPlaying) TimelineStore.play(meta.id);
4713
+ rulerScrubRef.current = null;
4714
+ zoomDragRef.current = null;
4715
+ }, [meta.id]);
4716
+ const handleRulerPointerCancel = (0, import_react23.useCallback)(() => {
4717
+ if (rulerScrubRef.current?.wasPlaying) TimelineStore.play(meta.id);
4718
+ rulerScrubRef.current = null;
4719
+ zoomDragRef.current = null;
4720
+ }, [meta.id]);
4721
+ const trackScrubRef = (0, import_react23.useRef)(null);
4722
+ const seekTrackFromClientX = (0, import_react23.useCallback)((clientX) => {
4723
+ const scrub = trackScrubRef.current;
4724
+ const contentWidth = scrub?.rect.width ?? 0;
4725
+ if (!scrub || contentWidth <= 0) return;
4726
+ const nextTime = clamp(
4727
+ scrub.viewStart + (clientX - scrub.rect.left) / contentWidth * scrub.visibleDuration,
4728
+ scrub.viewStart,
4729
+ scrub.viewStart + scrub.visibleDuration
4730
+ );
4731
+ TimelineStore.seek(meta.id, nextTime);
4732
+ }, [meta.id]);
4733
+ const handleTrackPointerDown = (0, import_react23.useCallback)((e) => {
4734
+ const target = e.target;
4735
+ if (target.closest(".dialkit-timeline-label, button")) return;
4736
+ if (!e.shiftKey && target.closest(".dialkit-timeline-clip")) return;
4737
+ const rect = laneAreaRef.current?.getBoundingClientRect();
4738
+ if (!rect) return;
4739
+ e.preventDefault();
4740
+ e.currentTarget.setPointerCapture(e.pointerId);
4741
+ const resetView2 = e.shiftKey;
4742
+ trackScrubRef.current = {
4743
+ wasPlaying: TimelineStore.getTransport(meta.id).playing,
4744
+ rect,
4745
+ viewStart: resetView2 ? 0 : safeViewStart,
4746
+ visibleDuration: resetView2 ? meta.duration : visibleDuration
4747
+ };
4748
+ if (resetView2) {
4749
+ setZoom(1);
4750
+ setViewStart(0);
4751
+ }
4752
+ setPopover(null);
4753
+ TimelineStore.pause(meta.id);
4754
+ seekTrackFromClientX(e.clientX);
4755
+ }, [meta.duration, meta.id, safeViewStart, seekTrackFromClientX, visibleDuration]);
4756
+ const handleTrackPointerMove = (0, import_react23.useCallback)((e) => {
4757
+ if (trackScrubRef.current) seekTrackFromClientX(e.clientX);
4758
+ }, [seekTrackFromClientX]);
4759
+ const finishTrackScrub = (0, import_react23.useCallback)(() => {
4760
+ if (trackScrubRef.current?.wasPlaying) TimelineStore.play(meta.id);
4761
+ trackScrubRef.current = null;
4762
+ }, [meta.id]);
4763
+ const handleCopy = (0, import_react23.useCallback)(() => {
4764
+ const normalized = normalizeTimelineValuesForCopy(DialStore.getValues(meta.id), meta.clips);
4765
+ navigator.clipboard.writeText(buildCopyInstruction("useDialTimeline", meta.name, normalized));
4766
+ setCopied(true);
4767
+ setTimeout(() => setCopied(false), 1500);
4768
+ }, [meta.clips, meta.id, meta.name]);
4769
+ const handleAddPreset = (0, import_react23.useCallback)(() => {
4770
+ DialStore.savePreset(meta.id, `Version ${presets.length + 2}`);
4771
+ }, [meta.id, presets.length]);
4772
+ const closePopover = (0, import_react23.useCallback)(() => setPopover(null), []);
4773
+ const openClipPopover = (0, import_react23.useCallback)(
4774
+ (clip, rect, stepKey) => {
4775
+ const targetPath = stepKey ? `${clip.key}.${stepKey}` : clip.key;
4776
+ const exclude = stepKey ? void 0 : clipPopoverExclusions(clip);
4777
+ if (getClipControls(meta.id, targetPath, exclude).length === 0) return;
4778
+ setPopover(
4779
+ (prev) => prev?.clip.key === clip.key && prev?.stepKey === stepKey ? null : {
4780
+ clip,
4781
+ stepKey,
4782
+ anchor: {
4783
+ left: rect.left,
4784
+ top: rect.top,
4785
+ right: rect.right,
4786
+ bottom: rect.bottom,
4787
+ width: rect.width,
4788
+ height: rect.height
4789
+ }
4790
+ }
4791
+ );
4792
+ },
4793
+ [meta.id]
4794
+ );
4795
+ const toggleTracks = (0, import_react23.useCallback)((clipKey) => {
4796
+ setExpandedTracks((prev) => {
4797
+ const next = new Set(prev);
4798
+ if (next.has(clipKey)) next.delete(clipKey);
4799
+ else next.add(clipKey);
4800
+ return next;
4801
+ });
4802
+ }, []);
4803
+ const handleBarClick = (0, import_react23.useCallback)(
4804
+ (clip, rect, stepKey) => {
4805
+ if (!stepKey && clip.tracks?.length) {
4806
+ toggleTracks(clip.key);
4807
+ return;
4808
+ }
4809
+ openClipPopover(clip, rect, stepKey);
4810
+ },
4811
+ [openClipPopover, toggleTracks]
4812
+ );
4813
+ const toggleGroup = (0, import_react23.useCallback)((group) => {
4814
+ setCollapsedGroups((prev) => {
4815
+ const next = new Set(prev);
4816
+ if (next.has(group)) next.delete(group);
4817
+ else next.add(group);
4818
+ return next;
4819
+ });
4820
+ }, []);
4821
+ const rawStep = pxPerSecond > 0 ? MAJOR_TICK_TARGET_PX / pxPerSecond : 1;
4822
+ const adaptiveMajorStep = SECOND_TICK_STEPS.find((step) => step >= rawStep) ?? SECOND_TICK_STEPS[SECOND_TICK_STEPS.length - 1];
4823
+ const majorStep = zoom < 1.5 && meta.duration >= 1 ? Math.max(1, adaptiveMajorStep) : adaptiveMajorStep;
4824
+ const fineTickStep = majorStep / 10;
4825
+ const majorTicks = [];
4826
+ const mediumTicks = [];
4827
+ const fineTicks = [];
4828
+ const firstMajorTick = Math.ceil((safeViewStart - 1e-6) / majorStep) * majorStep;
4829
+ for (let t = firstMajorTick; t <= viewEnd + 1e-6; t += majorStep) {
4830
+ majorTicks.push(Number(t.toFixed(4)));
4831
+ }
4832
+ const firstFineIndex = Math.ceil((safeViewStart - 1e-6) / fineTickStep);
4833
+ const lastFineIndex = Math.floor((viewEnd + 1e-6) / fineTickStep);
4834
+ for (let index = firstFineIndex; index <= lastFineIndex; index++) {
4835
+ if (index % 10 === 0) continue;
4836
+ const tick = Number((index * fineTickStep).toFixed(6));
4837
+ if (index % 5 === 0) mediumTicks.push(tick);
4838
+ else fineTicks.push(tick);
4839
+ }
4840
+ const rows = [];
4841
+ let lastGroup;
4842
+ for (const clip of meta.clips) {
4843
+ if (clip.group !== lastGroup) {
4844
+ lastGroup = clip.group;
4845
+ if (clip.group) {
4846
+ const group = clip.group;
4847
+ const isCollapsed = collapsedGroups.has(group);
4848
+ rows.push(
4849
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dialkit-timeline-row dialkit-timeline-group-row", children: [
4850
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dialkit-timeline-label", children: [
4851
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4852
+ "button",
4853
+ {
4854
+ className: "dialkit-timeline-group-toggle",
4855
+ "data-open": !isCollapsed,
4856
+ onClick: () => toggleGroup(group),
4857
+ title: isCollapsed ? "Expand layer" : "Collapse layer",
4858
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("path", { d: ICON_CHEVRON }) })
4859
+ }
4860
+ ),
4861
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: formatLabel(group) })
4862
+ ] }),
4863
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-lane" })
4864
+ ] }, `group:${group}`)
4865
+ );
4866
+ }
4867
+ }
4868
+ if (clip.group && collapsedGroups.has(clip.group)) continue;
4869
+ const isProps = Boolean(clip.tracks?.length);
4870
+ const tracksOpen = isProps && expandedTracks.has(clip.key);
4871
+ const stat = computeClipStaticFromValues(values, clip, meta.duration);
4872
+ rows.push(
4873
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dialkit-timeline-row", "data-grouped": clip.group ? "" : void 0, children: [
4874
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dialkit-timeline-label", children: [
4875
+ isProps ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4876
+ "button",
4877
+ {
4878
+ className: "dialkit-timeline-group-toggle",
4879
+ "data-open": tracksOpen,
4880
+ onClick: (e) => {
4881
+ e.stopPropagation();
4882
+ toggleTracks(clip.key);
4883
+ },
4884
+ title: tracksOpen ? "Collapse properties" : "Expand properties",
4885
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("path", { d: ICON_CHEVRON }) })
4886
+ }
4887
+ ) : null,
4888
+ clip.label
4889
+ ] }),
4890
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-lane", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4891
+ TimelineClip,
4892
+ {
4893
+ timelineId: meta.id,
4894
+ clip,
4895
+ at: stat.at,
4896
+ duration: stat.duration,
4897
+ loop: stat.loop,
4898
+ steps: clip.stepKeys?.length ? stat.tracks[0]?.steps : void 0,
4899
+ fixedDuration: isProps ? true : stat.isPhysics,
4900
+ composite: isProps,
4901
+ pxPerSecond,
4902
+ viewStart: safeViewStart,
4903
+ timelineDuration: meta.duration,
4904
+ selected: popover?.clip.key === clip.key,
4905
+ selectedStepKey: popover?.clip.key === clip.key ? popover.stepKey : void 0,
4906
+ onClick: handleBarClick,
4907
+ onDrag: closePopover
4908
+ }
4909
+ ) })
4910
+ ] }, clip.key)
4911
+ );
4912
+ if (tracksOpen) {
4913
+ for (const trackRef of clip.tracks ?? []) {
4914
+ const track = stat.tracks.find((candidate) => candidate.prop === trackRef.prop);
4915
+ if (!track) continue;
4916
+ const trackKey = `${clip.key}.${trackRef.prop}`;
4917
+ const trackMeta = {
4918
+ key: trackKey,
4919
+ label: `${clip.label} \xB7 ${formatLabel(trackRef.prop)}`,
4920
+ color: clip.color,
4921
+ loop: clip.loop,
4922
+ group: clip.group,
4923
+ stepKeys: trackRef.stepKeys
4924
+ };
4925
+ rows.push(
4926
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4927
+ "div",
4928
+ {
4929
+ className: "dialkit-timeline-row dialkit-timeline-track-row",
4930
+ "data-grouped": clip.group ? "" : void 0,
4931
+ children: [
4932
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-label", children: formatLabel(trackRef.prop) }),
4933
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-lane", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4934
+ TimelineClip,
4935
+ {
4936
+ timelineId: meta.id,
4937
+ clip: trackMeta,
4938
+ at: stat.at + track.delay,
4939
+ duration: track.duration,
4940
+ loop: stat.loop,
4941
+ steps: trackRef.stepKeys?.length ? track.steps : void 0,
4942
+ fixedDuration: !trackRef.stepKeys?.length && track.steps[0]?.isPhysics === true,
4943
+ baseAt: stat.at,
4944
+ delayMode: true,
4945
+ pxPerSecond,
4946
+ viewStart: safeViewStart,
4947
+ timelineDuration: meta.duration,
4948
+ selected: popover?.clip.key === trackKey,
4949
+ selectedStepKey: popover?.clip.key === trackKey ? popover.stepKey : void 0,
4950
+ onClick: openClipPopover,
4951
+ onDrag: closePopover
4952
+ }
4953
+ ) })
4954
+ ]
4955
+ },
4956
+ trackKey
4957
+ )
4958
+ );
4959
+ }
4960
+ }
4961
+ }
4962
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dialkit-timeline-section", children: [
4963
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dialkit-timeline-header", "data-open": open || void 0, children: [
4964
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-identity", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { ref: titleRef, className: "dialkit-timeline-title", children: meta.name }) }),
4965
+ !open && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4966
+ TimelineOverview,
4967
+ {
4968
+ id: meta.id,
4969
+ duration: meta.duration,
4970
+ viewStart: safeViewStart,
4971
+ viewEnd,
4972
+ onNavigate: centerViewAt
4973
+ }
4974
+ ),
4975
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { ref: actionsRef, className: "dialkit-timeline-actions", children: [
4976
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PlayPauseButton, { id: meta.id }),
4977
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4978
+ import_react24.motion.button,
4979
+ {
4980
+ className: "dialkit-toolbar-add",
4981
+ onClick: handleAddPreset,
4982
+ title: "Add timeline version",
4983
+ "aria-label": "Add timeline version",
4984
+ whileTap: { scale: 0.9 },
4985
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
4986
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: ICON_ADD_PRESET.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("path", { d }, i)) })
4987
+ }
4988
+ ),
4989
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4990
+ PresetManager,
4991
+ {
4992
+ panelId: meta.id,
4993
+ presets,
4994
+ activePresetId,
4995
+ onAdd: handleAddPreset
4996
+ }
4997
+ ),
4998
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4999
+ import_react24.motion.button,
5000
+ {
5001
+ className: "dialkit-toolbar-add",
5002
+ onClick: handleCopy,
5003
+ title: "Copy parameters",
5004
+ "aria-label": copied ? "Copied parameters" : "Copy parameters",
5005
+ whileTap: { scale: 0.9 },
5006
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
5007
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: { position: "relative", width: 16, height: 16 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react24.AnimatePresence, { initial: false, mode: "wait", children: copied ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5008
+ import_react24.motion.svg,
5009
+ {
5010
+ viewBox: "0 0 24 24",
5011
+ fill: "none",
5012
+ stroke: "currentColor",
5013
+ strokeWidth: "2",
5014
+ strokeLinecap: "round",
5015
+ strokeLinejoin: "round",
5016
+ "aria-hidden": "true",
5017
+ style: { position: "absolute", inset: 0, width: 16, height: 16, color: "var(--dial-text-label)" },
5018
+ initial: { scale: 0.8, opacity: 0 },
5019
+ animate: { scale: 1, opacity: 1 },
5020
+ exit: { scale: 0.8, opacity: 0 },
5021
+ transition: { duration: 0.08 },
5022
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("path", { d: ICON_CHECK })
5023
+ },
5024
+ "check"
5025
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
5026
+ import_react24.motion.svg,
5027
+ {
5028
+ viewBox: "0 0 24 24",
5029
+ fill: "none",
5030
+ "aria-hidden": "true",
5031
+ style: { position: "absolute", inset: 0, width: 16, height: 16, color: "var(--dial-text-label)" },
5032
+ initial: { scale: 0.8, opacity: 0 },
5033
+ animate: { scale: 1, opacity: 1 },
5034
+ exit: { scale: 0.8, opacity: 0 },
5035
+ transition: { duration: 0.08 },
5036
+ children: [
5037
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("path", { d: ICON_CLIPBOARD.board, stroke: "currentColor", strokeWidth: "2", strokeLinejoin: "round" }),
5038
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("path", { d: ICON_CLIPBOARD.sparkle, fill: "currentColor" }),
5039
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("path", { d: ICON_CLIPBOARD.body, stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
5040
+ ]
5041
+ },
5042
+ "clipboard"
5043
+ ) }) })
5044
+ }
5045
+ ),
5046
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5047
+ "button",
5048
+ {
5049
+ className: "dialkit-timeline-chevron",
5050
+ "data-open": open,
5051
+ "aria-expanded": open,
5052
+ onClick: () => setOpen(!open),
5053
+ title: open ? "Collapse timeline" : "Expand timeline",
5054
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("path", { d: ICON_CHEVRON }) })
5055
+ }
5056
+ )
5057
+ ] })
5058
+ ] }),
5059
+ open && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5060
+ "div",
5061
+ {
5062
+ className: "dialkit-timeline-body",
5063
+ onPointerDown: handleTrackPointerDown,
5064
+ onPointerMove: handleTrackPointerMove,
5065
+ onPointerUp: finishTrackScrub,
5066
+ onPointerCancel: finishTrackScrub,
5067
+ onLostPointerCapture: finishTrackScrub,
5068
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dialkit-timeline-grid", children: [
5069
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dialkit-timeline-row dialkit-timeline-ruler-row", children: [
5070
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-label" }),
5071
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
5072
+ "div",
5073
+ {
5074
+ ref: laneAreaRef,
5075
+ className: "dialkit-timeline-ruler",
5076
+ onPointerDown: handleRulerPointerDown,
5077
+ onPointerMove: handleRulerPointerMove,
5078
+ onPointerUp: handleRulerPointerUp,
5079
+ onPointerCancel: handleRulerPointerCancel,
5080
+ onLostPointerCapture: handleRulerPointerCancel,
5081
+ title: "Drag to seek \xB7 Option-drag to zoom \xB7 Shift-drag to reset zoom",
5082
+ children: [
5083
+ fineTicks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-tick dialkit-timeline-tick-fine", style: { left: (t - safeViewStart) * pxPerSecond } }, `fine:${t}`)),
5084
+ mediumTicks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-tick dialkit-timeline-tick-medium", style: { left: (t - safeViewStart) * pxPerSecond } }, `medium:${t}`)),
5085
+ majorTicks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-tick", style: { left: (t - safeViewStart) * pxPerSecond }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "dialkit-timeline-tick-label", children: formatRulerSeconds(t, majorStep) }) }, t))
5086
+ ]
5087
+ }
5088
+ )
5089
+ ] }),
5090
+ rows,
5091
+ pxPerSecond > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5092
+ TimelinePlayheadFlag,
5093
+ {
5094
+ id: meta.id,
5095
+ duration: meta.duration,
5096
+ pxPerSecond,
5097
+ viewStart: safeViewStart,
5098
+ viewEnd,
5099
+ laneWidth,
5100
+ rulerRef: laneAreaRef,
5101
+ headerClearStart: flagClearRange.start,
5102
+ headerClearEnd: flagClearRange.end,
5103
+ onResetView: resetView
5104
+ }
5105
+ )
5106
+ ] })
5107
+ }
5108
+ ),
5109
+ popover && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5110
+ ClipPopover,
5111
+ {
5112
+ panelId: meta.id,
5113
+ popover,
5114
+ values,
5115
+ theme,
5116
+ onClose: closePopover
5117
+ }
5118
+ )
5119
+ ] });
5120
+ });
5121
+ function ClipPopover({
5122
+ panelId,
5123
+ popover,
5124
+ values,
5125
+ theme,
5126
+ onClose
5127
+ }) {
5128
+ const ref = (0, import_react23.useRef)(null);
5129
+ const [naturalHeight, setNaturalHeight] = (0, import_react23.useState)(0);
5130
+ const [viewport, setViewport] = (0, import_react23.useState)(() => ({
5131
+ width: window.visualViewport?.width ?? window.innerWidth,
5132
+ height: window.visualViewport?.height ?? window.innerHeight,
5133
+ offsetLeft: window.visualViewport?.offsetLeft ?? 0,
5134
+ offsetTop: window.visualViewport?.offsetTop ?? 0
5135
+ }));
5136
+ (0, import_react23.useLayoutEffect)(() => {
5137
+ const element = ref.current;
5138
+ if (!element) return;
5139
+ const measure = () => setNaturalHeight(element.scrollHeight + 2);
5140
+ measure();
5141
+ const observer = new ResizeObserver(measure);
5142
+ const body = element.querySelector(".dialkit-timeline-popover-body");
5143
+ observer.observe(body ?? element);
5144
+ return () => observer.disconnect();
5145
+ }, [popover.clip.key, popover.stepKey]);
5146
+ (0, import_react23.useEffect)(() => {
5147
+ const updateViewport = () => setViewport({
5148
+ width: window.visualViewport?.width ?? window.innerWidth,
5149
+ height: window.visualViewport?.height ?? window.innerHeight,
5150
+ offsetLeft: window.visualViewport?.offsetLeft ?? 0,
5151
+ offsetTop: window.visualViewport?.offsetTop ?? 0
5152
+ });
5153
+ window.addEventListener("resize", updateViewport);
5154
+ window.visualViewport?.addEventListener("resize", updateViewport);
5155
+ window.visualViewport?.addEventListener("scroll", updateViewport);
5156
+ return () => {
5157
+ window.removeEventListener("resize", updateViewport);
5158
+ window.visualViewport?.removeEventListener("resize", updateViewport);
5159
+ window.visualViewport?.removeEventListener("scroll", updateViewport);
5160
+ };
5161
+ }, []);
5162
+ (0, import_react23.useEffect)(() => {
5163
+ const handlePointerDown = (e) => {
5164
+ const target = e.target;
5165
+ if (ref.current?.contains(target)) return;
5166
+ if (target.closest?.(".dialkit-timeline-clip")) return;
5167
+ if (target.closest?.(".dialkit-timeline-label")) return;
5168
+ onClose();
5169
+ };
5170
+ const handleKeyDown = (e) => {
5171
+ if (e.key === "Escape") onClose();
5172
+ };
5173
+ document.addEventListener("pointerdown", handlePointerDown, true);
5174
+ document.addEventListener("keydown", handleKeyDown);
5175
+ return () => {
5176
+ document.removeEventListener("pointerdown", handlePointerDown, true);
5177
+ document.removeEventListener("keydown", handleKeyDown);
5178
+ };
5179
+ }, [onClose]);
5180
+ const { clip, stepKey } = popover;
5181
+ let controls;
5182
+ let title;
5183
+ if (stepKey) {
5184
+ controls = getClipControls(panelId, `${clip.key}.${stepKey}`);
5185
+ if (stepKey === clip.stepKeys?.[0]) {
5186
+ const from = getControlAt(panelId, `${clip.key}.from`);
5187
+ if (from) {
5188
+ const toIndex = controls.findIndex((control) => control.path === `${clip.key}.${stepKey}.to`);
5189
+ controls = toIndex >= 0 ? [...controls.slice(0, toIndex), from, ...controls.slice(toIndex)] : [...controls, from];
5190
+ }
5191
+ }
5192
+ title = `${clip.label} \xB7 ${formatStepLabel(stepKey)}`;
5193
+ } else {
5194
+ controls = getClipControls(panelId, clip.key, clipPopoverExclusions(clip));
5195
+ title = clip.label;
5196
+ }
5197
+ if (controls.length === 0) return null;
5198
+ const targetPath = stepKey ? `${clip.key}.${stepKey}` : clip.key;
5199
+ const durationMeta = getControlAt(panelId, `${targetPath}.duration`);
5200
+ const durationValue = durationMeta ? values[durationMeta.path] : void 0;
5201
+ const transitionDuration = durationMeta?.type === "slider" && typeof durationValue === "number" ? {
5202
+ value: durationValue,
5203
+ onChange: (next) => DialStore.updateValue(panelId, durationMeta.path, next),
5204
+ min: Math.max(TIMELINE_MIN_CLIP_DURATION, durationMeta.min ?? 0),
5205
+ max: durationMeta.max,
5206
+ step: durationMeta.step
5207
+ } : void 0;
5208
+ const displayValues = timelinePopoverDisplayValues(values, clip.key, clip.stepKeys, stepKey);
5209
+ const viewportRight = viewport.offsetLeft + viewport.width;
5210
+ const viewportBottom = viewport.offsetTop + viewport.height;
5211
+ const popoverWidth = Math.min(POPOVER_WIDTH, Math.max(220, viewport.width - 24));
5212
+ const left = clamp(
5213
+ popover.anchor.left + popover.anchor.width / 2 - popoverWidth / 2,
5214
+ viewport.offsetLeft + 12,
5215
+ Math.max(viewport.offsetLeft + 12, viewportRight - popoverWidth - 12)
5216
+ );
5217
+ const spaceAbove = Math.max(0, popover.anchor.top - viewport.offsetTop - 22);
5218
+ const spaceBelow = Math.max(0, viewportBottom - popover.anchor.bottom - 22);
5219
+ const placeAbove = naturalHeight === 0 ? spaceAbove >= spaceBelow : naturalHeight <= spaceAbove || naturalHeight > spaceBelow && spaceAbove >= spaceBelow;
5220
+ const availableHeight = placeAbove ? spaceAbove : spaceBelow;
5221
+ const renderedHeight = Math.min(naturalHeight || availableHeight, availableHeight);
5222
+ const unclampedTop = placeAbove ? popover.anchor.top - 10 - renderedHeight : popover.anchor.bottom + 10;
5223
+ const top = clamp(
5224
+ unclampedTop,
5225
+ viewport.offsetTop + 12,
5226
+ Math.max(viewport.offsetTop + 12, viewportBottom - renderedHeight - 12)
5227
+ );
5228
+ return (0, import_react_dom4.createPortal)(
5229
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-root", "data-theme": theme, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
5230
+ "div",
5231
+ {
5232
+ ref,
5233
+ className: "dialkit-timeline-popover",
5234
+ "data-placement": placeAbove ? "above" : "below",
5235
+ style: {
5236
+ left,
5237
+ top,
5238
+ width: popoverWidth,
5239
+ maxHeight: availableHeight,
5240
+ visibility: naturalHeight > 0 ? "visible" : "hidden"
5241
+ },
5242
+ role: "dialog",
5243
+ "aria-label": `Edit ${title}`,
5244
+ children: [
5245
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dialkit-timeline-popover-header", children: [
5246
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "dialkit-timeline-popover-title", children: title }),
5247
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { className: "dialkit-timeline-popover-close", onClick: onClose, title: "Close editor", "aria-label": "Close editor", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("path", { d: "M6 6L18 18M18 6L6 18" }) }) })
5248
+ ] }),
5249
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-popover-body", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5250
+ ControlRenderer,
5251
+ {
5252
+ panelId,
5253
+ controls,
5254
+ values: displayValues,
5255
+ transitionDuration
5256
+ }
5257
+ ) })
5258
+ ]
5259
+ }
5260
+ ) }),
5261
+ document.body
5262
+ );
5263
+ }
5264
+ function clipPopoverExclusions(clip) {
5265
+ return /* @__PURE__ */ new Set([
5266
+ ...clip.stepKeys ?? [],
5267
+ ...clip.tracks?.map((track) => track.prop) ?? []
5268
+ ]);
5269
+ }
5270
+ function getClipControls(panelId, controlPath, excludeChildren) {
5271
+ const panel = DialStore.getPanel(panelId);
5272
+ const folder = panel ? findControl(panel.controls, controlPath) : null;
5273
+ if (!folder?.children) return [];
5274
+ return folder.children.filter((control) => {
5275
+ const childKey = control.path.slice(controlPath.length + 1);
5276
+ if (childKey === "at" || childKey === "duration") return false;
5277
+ return !excludeChildren?.has(childKey);
5278
+ });
5279
+ }
5280
+ function getControlAt(panelId, path) {
5281
+ const panel = DialStore.getPanel(panelId);
5282
+ return panel ? findControl(panel.controls, path) : null;
5283
+ }
5284
+ function TimelineClip({
5285
+ timelineId,
5286
+ clip,
5287
+ at,
5288
+ duration,
5289
+ loop,
5290
+ steps,
5291
+ fixedDuration,
5292
+ composite = false,
5293
+ baseAt = 0,
5294
+ delayMode = false,
5295
+ pxPerSecond,
5296
+ viewStart,
5297
+ timelineDuration,
5298
+ selected,
5299
+ selectedStepKey,
5300
+ onClick,
5301
+ onDrag
5302
+ }) {
5303
+ const dragRef = (0, import_react23.useRef)(null);
5304
+ const [dragging, setDragging] = (0, import_react23.useState)(false);
5305
+ const isSteps = Boolean(steps?.length);
5306
+ const handlePointerDown = (0, import_react23.useCallback)(
5307
+ (e) => {
5308
+ if (e.shiftKey) return;
5309
+ e.stopPropagation();
5310
+ const target = e.target;
5311
+ let mode = "move";
5312
+ let boundaryIndex;
5313
+ const boundary = target.dataset?.boundary;
5314
+ if (boundary !== void 0) {
5315
+ mode = "boundary";
5316
+ boundaryIndex = Number(boundary);
5317
+ } else if (!fixedDuration) {
5318
+ const edge = target.dataset?.edge;
5319
+ if (edge) mode = edge;
5320
+ }
5321
+ dragRef.current = {
5322
+ mode,
5323
+ boundaryIndex,
5324
+ pointerX: e.clientX,
5325
+ at,
5326
+ duration,
5327
+ stepDurations: steps?.map((step) => step.duration),
5328
+ clickEl: target.closest?.("[data-step]") ?? null,
5329
+ moved: false
5330
+ };
5331
+ e.currentTarget.setPointerCapture(e.pointerId);
5332
+ },
5333
+ [at, duration, fixedDuration, steps]
5334
+ );
5335
+ const handlePointerMove = (0, import_react23.useCallback)(
5336
+ (e) => {
5337
+ const drag = dragRef.current;
5338
+ if (!drag || pxPerSecond <= 0) return;
5339
+ const dx = e.clientX - drag.pointerX;
5340
+ if (!drag.moved) {
5341
+ if (Math.abs(dx) <= DRAG_THRESHOLD_PX) return;
5342
+ drag.moved = true;
5343
+ setDragging(true);
5344
+ onDrag();
5345
+ }
5346
+ const dt = dx / pxPerSecond;
5347
+ if (drag.mode === "boundary" && steps && drag.stepDurations) {
5348
+ const index = drag.boundaryIndex ?? 0;
5349
+ const others = drag.stepDurations.reduce((sum, d, j) => j === index ? sum : sum + d, 0);
5350
+ DialStore.updateValue(
5351
+ timelineId,
5352
+ `${clip.key}.${steps[index].key ?? ""}.duration`,
5353
+ clampStepResize(drag.stepDurations[index] + dt, drag.at, others, timelineDuration)
5354
+ );
5355
+ } else if (drag.mode === "move") {
5356
+ if (delayMode) {
5357
+ DialStore.updateValue(
5358
+ timelineId,
5359
+ `${clip.key}.delay`,
5360
+ clampTrackDelay(drag.at + dt - baseAt, baseAt, drag.duration, timelineDuration)
5361
+ );
5362
+ } else {
5363
+ DialStore.updateValue(timelineId, `${clip.key}.at`, clampClipMove(drag.at + dt, drag.duration, timelineDuration));
5364
+ }
5365
+ } else if (drag.mode === "end") {
5366
+ DialStore.updateValue(
5367
+ timelineId,
5368
+ `${clip.key}.duration`,
5369
+ clampClipResizeEnd(drag.duration + dt, drag.at, timelineDuration)
5370
+ );
5371
+ } else if (steps && drag.stepDurations) {
5372
+ const limit = Math.max(baseAt, 0);
5373
+ const next = clampClipResizeStart(Math.max(drag.at + dt, limit), drag.at, drag.stepDurations[0]);
5374
+ DialStore.updateValues(timelineId, {
5375
+ [delayMode ? `${clip.key}.delay` : `${clip.key}.at`]: delayMode ? Math.max(0, next.at - baseAt) : next.at,
5376
+ [`${clip.key}.${steps[0].key ?? ""}.duration`]: next.duration
5377
+ });
5378
+ } else {
5379
+ const limit = Math.max(baseAt, 0);
5380
+ const next = clampClipResizeStart(Math.max(drag.at + dt, limit), drag.at, drag.duration);
5381
+ DialStore.updateValues(timelineId, {
5382
+ [delayMode ? `${clip.key}.delay` : `${clip.key}.at`]: delayMode ? Math.max(0, next.at - baseAt) : next.at,
5383
+ [`${clip.key}.duration`]: next.duration
5384
+ });
5385
+ }
5386
+ },
5387
+ [baseAt, clip.key, delayMode, onDrag, pxPerSecond, steps, timelineId, timelineDuration]
5388
+ );
5389
+ const handlePointerUp = (0, import_react23.useCallback)(
5390
+ (e) => {
5391
+ const drag = dragRef.current;
5392
+ dragRef.current = null;
5393
+ setDragging(false);
5394
+ if (drag && !drag.moved) {
5395
+ const stepKey = drag.clickEl?.dataset?.step;
5396
+ const anchorEl = drag.clickEl ?? e.currentTarget;
5397
+ onClick(clip, anchorEl.getBoundingClientRect(), stepKey);
5398
+ }
5399
+ },
5400
+ [clip, onClick]
5401
+ );
5402
+ const handlePointerCancel = (0, import_react23.useCallback)(() => {
5403
+ dragRef.current = null;
5404
+ setDragging(false);
5405
+ }, []);
5406
+ const width = Math.max(duration * pxPerSecond, 14);
5407
+ const resizable = duration > 0 && !fixedDuration && !composite;
5408
+ const durationText = `${fixedDuration && !composite ? "~" : ""}${formatSeconds(duration)}`;
5409
+ const looping = loop === "repeat" && duration > 0;
5410
+ const ghostCycles = [];
5411
+ if (looping) {
5412
+ const maxGhostCycles = 256;
5413
+ const firstGhostIndex = Math.max(1, Math.floor((viewStart - at) / duration));
5414
+ for (let offset = 0; offset < maxGhostCycles; offset++) {
5415
+ const index = firstGhostIndex + offset;
5416
+ const start = at + duration * index;
5417
+ if (start >= timelineDuration - 1e-6) break;
5418
+ ghostCycles.push({
5419
+ start,
5420
+ duration: Math.min(duration, timelineDuration - start),
5421
+ index
5422
+ });
5423
+ }
5424
+ }
5425
+ const boundaryOffsets = [];
5426
+ if (steps) {
5427
+ let cumulative = 0;
5428
+ for (const step of steps) {
5429
+ cumulative += step.duration;
5430
+ boundaryOffsets.push(cumulative);
5431
+ }
5432
+ }
5433
+ const barTitle = composite ? `${clip.label} \u2014 composite of its property tracks${looping ? " \xB7 repeats through timeline" : ""} \xB7 click to expand` : `${clip.label} \u2014 ${formatSeconds(at)} for ${durationText}${fixedDuration ? " (duration set by spring physics)" : ""}${looping ? " \xB7 repeats through timeline" : ""}${delayMode ? " \xB7 drag to phase-shift" : ""}`;
5434
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
5435
+ ghostCycles.map((cycle) => {
5436
+ const ghostWidth = Math.max(1, cycle.duration * pxPerSecond - 2);
5437
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5438
+ "div",
5439
+ {
5440
+ className: "dialkit-timeline-clip-ghost",
5441
+ "data-steps": isSteps || void 0,
5442
+ "aria-hidden": "true",
5443
+ style: {
5444
+ left: (cycle.start - viewStart) * pxPerSecond + 1,
5445
+ width: ghostWidth,
5446
+ background: clip.color
5447
+ },
5448
+ children: steps?.map((step, stepIndex) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5449
+ "span",
5450
+ {
5451
+ className: "dialkit-timeline-clip-ghost-segment",
5452
+ style: { width: step.duration * pxPerSecond }
5453
+ },
5454
+ step.key ?? `step:${stepIndex}`
5455
+ ))
5456
+ },
5457
+ `ghost:${cycle.index}`
5458
+ );
5459
+ }),
5460
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5461
+ "div",
5462
+ {
5463
+ className: "dialkit-timeline-clip",
5464
+ "data-steps": isSteps || void 0,
5465
+ "data-composite": composite || void 0,
5466
+ "data-selected": selected || void 0,
5467
+ "data-dragging": dragging || void 0,
5468
+ style: {
5469
+ left: (at - viewStart) * pxPerSecond,
5470
+ width,
5471
+ background: composite ? `${clip.color}80` : clip.color
5472
+ },
5473
+ onPointerDown: handlePointerDown,
5474
+ onPointerMove: handlePointerMove,
5475
+ onPointerUp: handlePointerUp,
5476
+ onPointerCancel: handlePointerCancel,
5477
+ onLostPointerCapture: handlePointerCancel,
5478
+ title: barTitle,
5479
+ children: composite ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_jsx_runtime18.Fragment, { children: width > 56 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "dialkit-timeline-clip-duration", children: durationText }) }) : isSteps ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
5480
+ steps.map((step) => {
5481
+ const segmentWidth = step.duration * pxPerSecond;
5482
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5483
+ "div",
5484
+ {
5485
+ className: "dialkit-timeline-clip-segment",
5486
+ "data-step": step.key ?? void 0,
5487
+ "data-selected": selectedStepKey === step.key || void 0,
5488
+ style: { width: segmentWidth },
5489
+ children: segmentWidth > 52 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "dialkit-timeline-clip-duration", children: formatSeconds(step.duration) })
5490
+ },
5491
+ step.key ?? "step"
5492
+ );
5493
+ }),
5494
+ steps.map(
5495
+ (step, index) => step.isPhysics ? null : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5496
+ "div",
5497
+ {
5498
+ className: "dialkit-timeline-clip-handle",
5499
+ "data-boundary": index,
5500
+ style: { left: boundaryOffsets[index] * pxPerSecond - 4 }
5501
+ },
5502
+ `boundary:${step.key}`
5503
+ )
5504
+ ),
5505
+ !steps[0].isPhysics && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-clip-handle", "data-edge": "start" })
5506
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
5507
+ resizable && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-clip-handle", "data-edge": "start" }),
5508
+ width > 56 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "dialkit-timeline-clip-duration", children: durationText }),
5509
+ resizable && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dialkit-timeline-clip-handle", "data-edge": "end" })
5510
+ ] })
5511
+ }
5512
+ ),
5513
+ looping && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "dialkit-timeline-loop-infinity", "aria-hidden": "true", title: "Repeats indefinitely", children: "\u221E" })
5514
+ ] });
5515
+ }
5516
+
5517
+ // src/components/ButtonGroup.tsx
5518
+ var import_jsx_runtime19 = require("react/jsx-runtime");
5519
+ function ButtonGroup({ buttons }) {
5520
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "dialkit-button-group", children: buttons.map((button, index) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5521
+ "button",
5522
+ {
5523
+ className: "dialkit-button",
5524
+ onClick: button.onClick,
5525
+ children: button.label
5526
+ },
5527
+ index
5528
+ )) });
5529
+ }
5530
+
5531
+ // src/components/ShortcutsMenu.tsx
5532
+ var import_react25 = require("react");
5533
+ var import_react_dom5 = require("react-dom");
5534
+ var import_react26 = require("motion/react");
5535
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2903
5536
  function formatShortcutKey(sc) {
2904
5537
  if (!sc.key) return "\u2014";
2905
5538
  const mod = sc.modifier === "alt" ? "\u2325" : sc.modifier === "shift" ? "\u21E7" : sc.modifier === "meta" ? "\u2318" : "";
@@ -2919,23 +5552,23 @@ function formatInteraction(sc) {
2919
5552
  }
2920
5553
  }
2921
5554
  function ShortcutsMenu({ panelId }) {
2922
- const [isOpen, setIsOpen] = (0, import_react18.useState)(false);
2923
- const triggerRef = (0, import_react18.useRef)(null);
2924
- const dropdownRef = (0, import_react18.useRef)(null);
2925
- const [pos, setPos] = (0, import_react18.useState)({ top: 0, right: 0 });
2926
- const open = (0, import_react18.useCallback)(() => {
5555
+ const [isOpen, setIsOpen] = (0, import_react25.useState)(false);
5556
+ const triggerRef = (0, import_react25.useRef)(null);
5557
+ const dropdownRef = (0, import_react25.useRef)(null);
5558
+ const [pos, setPos] = (0, import_react25.useState)({ top: 0, right: 0 });
5559
+ const open = (0, import_react25.useCallback)(() => {
2927
5560
  const rect = triggerRef.current?.getBoundingClientRect();
2928
5561
  if (rect) {
2929
5562
  setPos({ top: rect.bottom + 4, right: window.innerWidth - rect.right });
2930
5563
  }
2931
5564
  setIsOpen(true);
2932
5565
  }, []);
2933
- const close = (0, import_react18.useCallback)(() => setIsOpen(false), []);
2934
- const toggle = (0, import_react18.useCallback)(() => {
5566
+ const close = (0, import_react25.useCallback)(() => setIsOpen(false), []);
5567
+ const toggle = (0, import_react25.useCallback)(() => {
2935
5568
  if (isOpen) close();
2936
5569
  else open();
2937
5570
  }, [isOpen, open, close]);
2938
- (0, import_react18.useEffect)(() => {
5571
+ (0, import_react25.useEffect)(() => {
2939
5572
  if (!isOpen) return;
2940
5573
  const handler = (e) => {
2941
5574
  const target = e.target;
@@ -2966,9 +5599,9 @@ function ShortcutsMenu({ panelId }) {
2966
5599
  label: findLabel(panel.controls)
2967
5600
  };
2968
5601
  });
2969
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
2970
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2971
- import_react19.motion.button,
5602
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
5603
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5604
+ import_react26.motion.button,
2972
5605
  {
2973
5606
  ref: triggerRef,
2974
5607
  className: "dialkit-shortcuts-trigger",
@@ -2976,19 +5609,19 @@ function ShortcutsMenu({ panelId }) {
2976
5609
  title: "Keyboard shortcuts",
2977
5610
  whileTap: { scale: 0.9 },
2978
5611
  transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
2979
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2980
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("rect", { x: "2", y: "6", width: "20", height: "12", rx: "2" }),
2981
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M6 10H6.01" }),
2982
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M10 10H10.01" }),
2983
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M14 10H14.01" }),
2984
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M18 10H18.01" }),
2985
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M8 14H16" })
5612
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
5613
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("rect", { x: "2", y: "6", width: "20", height: "12", rx: "2" }),
5614
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M6 10H6.01" }),
5615
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M10 10H10.01" }),
5616
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M14 10H14.01" }),
5617
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M18 10H18.01" }),
5618
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M8 14H16" })
2986
5619
  ] })
2987
5620
  }
2988
5621
  ),
2989
- (0, import_react_dom4.createPortal)(
2990
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react19.AnimatePresence, { children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
2991
- import_react19.motion.div,
5622
+ (0, import_react_dom5.createPortal)(
5623
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react26.AnimatePresence, { children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
5624
+ import_react26.motion.div,
2992
5625
  {
2993
5626
  ref: dropdownRef,
2994
5627
  className: "dialkit-root dialkit-shortcuts-dropdown",
@@ -2998,13 +5631,13 @@ function ShortcutsMenu({ panelId }) {
2998
5631
  exit: { opacity: 0, y: 4, scale: 0.97, pointerEvents: "none" },
2999
5632
  transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
3000
5633
  children: [
3001
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "dialkit-shortcuts-title", children: "Keyboard Shortcuts" }),
3002
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "dialkit-shortcuts-list", children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "dialkit-shortcuts-row", children: [
3003
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "dialkit-shortcuts-row-key", children: formatShortcutKey(row.shortcut) }),
3004
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "dialkit-shortcuts-row-label", children: row.label }),
3005
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "dialkit-shortcuts-row-mode", children: formatInteraction(row.shortcut) })
5634
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dialkit-shortcuts-title", children: "Keyboard Shortcuts" }),
5635
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dialkit-shortcuts-list", children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "dialkit-shortcuts-row", children: [
5636
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "dialkit-shortcuts-row-key", children: formatShortcutKey(row.shortcut) }),
5637
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "dialkit-shortcuts-row-label", children: row.label }),
5638
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "dialkit-shortcuts-row-mode", children: formatInteraction(row.shortcut) })
3006
5639
  ] }, row.path)) }),
3007
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "dialkit-shortcuts-hint", children: "See pill badges on controls for keys" })
5640
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dialkit-shortcuts-hint", children: "See pill badges on controls for keys" })
3008
5641
  ]
3009
5642
  }
3010
5643
  ) }),
@@ -3016,8 +5649,10 @@ function ShortcutsMenu({ panelId }) {
3016
5649
  0 && (module.exports = {
3017
5650
  ButtonGroup,
3018
5651
  ColorControl,
5652
+ ControlRenderer,
3019
5653
  DialRoot,
3020
5654
  DialStore,
5655
+ DialTimeline,
3021
5656
  EasingVisualization,
3022
5657
  Folder,
3023
5658
  PresetManager,
@@ -3027,9 +5662,12 @@ function ShortcutsMenu({ panelId }) {
3027
5662
  SpringControl,
3028
5663
  SpringVisualization,
3029
5664
  TextControl,
5665
+ TimelineStore,
3030
5666
  Toggle,
3031
5667
  TransitionControl,
5668
+ formatClock,
3032
5669
  useDialKit,
3033
- useDialKitController
5670
+ useDialKitController,
5671
+ useDialTimeline
3034
5672
  });
3035
5673
  //# sourceMappingURL=index.cjs.map