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/vue/index.js CHANGED
@@ -69,6 +69,19 @@ function isSpringConfigValue(value) {
69
69
  function isEasingConfigValue(value) {
70
70
  return hasType(value, "easing");
71
71
  }
72
+ function isHexColor(value) {
73
+ return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(value);
74
+ }
75
+ function formatLabel(key) {
76
+ return key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
77
+ }
78
+ function inferStep(min, max) {
79
+ const range = max - min;
80
+ if (range <= 1) return 0.01;
81
+ if (range <= 10) return 0.1;
82
+ if (range <= 100) return 1;
83
+ return 10;
84
+ }
72
85
  function isActionConfigValue(value) {
73
86
  return hasType(value, "action");
74
87
  }
@@ -89,6 +102,9 @@ function getFirstOptionValue(options) {
89
102
  var DialStoreClass = class {
90
103
  constructor() {
91
104
  this.panels = /* @__PURE__ */ new Map();
105
+ this.panelsSnapshot = [];
106
+ this.standardPanelsSnapshot = [];
107
+ this.timelinePanelsSnapshot = [];
92
108
  this.listeners = /* @__PURE__ */ new Map();
93
109
  this.globalListeners = /* @__PURE__ */ new Set();
94
110
  this.snapshots = /* @__PURE__ */ new Map();
@@ -102,6 +118,12 @@ var DialStoreClass = class {
102
118
  this.persistConfigs = /* @__PURE__ */ new Map();
103
119
  }
104
120
  registerPanel(id, name, config, shortcuts, options = {}) {
121
+ const existingPanel = this.panels.get(id);
122
+ if (existingPanel && existingPanel.kind !== options.kind) {
123
+ console.warn(
124
+ `[dialkit] Panel id "${id}" cannot be shared by a timeline and a standard panel; the most recent registration controls where it renders.`
125
+ );
126
+ }
105
127
  this.configurePanelRetention(id, options);
106
128
  this.registrationCounts.set(id, (this.registrationCounts.get(id) ?? 0) + 1);
107
129
  const controls = this.parseConfig(config, "", shortcuts);
@@ -113,7 +135,7 @@ var DialStoreClass = class {
113
135
  const values = this.reconcileValues(defaultValues, previousValues, controlsByPath);
114
136
  const previousBaseValues = this.baseValues.get(id) ?? persisted?.baseValues ?? persisted?.values ?? {};
115
137
  const baseValues = this.reconcileValues(defaultValues, previousBaseValues, controlsByPath);
116
- this.panels.set(id, { id, name, controls, values, shortcuts: shortcuts ?? {} });
138
+ this.panels.set(id, { id, name, controls, values, shortcuts: shortcuts ?? {}, kind: options.kind });
117
139
  this.snapshots.set(id, { ...values });
118
140
  this.baseValues.set(id, baseValues);
119
141
  this.defaultValues.set(id, { ...defaultValues });
@@ -140,7 +162,7 @@ var DialStoreClass = class {
140
162
  const defaultValues = this.flattenValues(config, "");
141
163
  this.initTransitionModes(config, "", defaultValues);
142
164
  const nextValues = this.reconcileValues(defaultValues, existing.values, controlsByPath);
143
- const nextPanel = { id, name, controls, values: nextValues, shortcuts: shortcuts ?? existing.shortcuts };
165
+ const nextPanel = { id, name, controls, values: nextValues, shortcuts: shortcuts ?? existing.shortcuts, kind: options.kind ?? existing.kind };
144
166
  this.panels.set(id, nextPanel);
145
167
  this.snapshots.set(id, { ...nextValues });
146
168
  const previousBaseValues = this.baseValues.get(id) ?? {};
@@ -165,8 +187,8 @@ var DialStoreClass = class {
165
187
  }
166
188
  this.registrationCounts.delete(id);
167
189
  this.panels.delete(id);
168
- this.listeners.delete(id);
169
- this.actionListeners.delete(id);
190
+ if (this.listeners.get(id)?.size === 0) this.listeners.delete(id);
191
+ if (this.actionListeners.get(id)?.size === 0) this.actionListeners.delete(id);
170
192
  if (!this.retainedPanels.has(id)) {
171
193
  this.snapshots.delete(id);
172
194
  this.baseValues.delete(id);
@@ -258,8 +280,10 @@ var DialStoreClass = class {
258
280
  getValues(panelId) {
259
281
  return this.snapshots.get(panelId) ?? EMPTY_VALUES;
260
282
  }
261
- getPanels() {
262
- return Array.from(this.panels.values());
283
+ getPanels(kind) {
284
+ if (kind === "panel") return this.standardPanelsSnapshot;
285
+ if (kind === "timeline") return this.timelinePanelsSnapshot;
286
+ return this.panelsSnapshot;
263
287
  }
264
288
  getPanel(id) {
265
289
  return this.panels.get(id);
@@ -270,7 +294,11 @@ var DialStoreClass = class {
270
294
  }
271
295
  this.listeners.get(panelId).add(listener);
272
296
  return () => {
273
- this.listeners.get(panelId)?.delete(listener);
297
+ const listeners = this.listeners.get(panelId);
298
+ listeners?.delete(listener);
299
+ if (listeners?.size === 0 && !this.panels.has(panelId)) {
300
+ this.listeners.delete(panelId);
301
+ }
274
302
  };
275
303
  }
276
304
  subscribeGlobal(listener) {
@@ -283,7 +311,11 @@ var DialStoreClass = class {
283
311
  }
284
312
  this.actionListeners.get(panelId).add(listener);
285
313
  return () => {
286
- this.actionListeners.get(panelId)?.delete(listener);
314
+ const listeners = this.actionListeners.get(panelId);
315
+ listeners?.delete(listener);
316
+ if (listeners?.size === 0 && !this.panels.has(panelId)) {
317
+ this.actionListeners.delete(panelId);
318
+ }
287
319
  };
288
320
  }
289
321
  triggerAction(panelId, path) {
@@ -478,6 +510,9 @@ var DialStoreClass = class {
478
510
  this.listeners.get(panelId)?.forEach((fn) => fn());
479
511
  }
480
512
  notifyGlobal() {
513
+ this.panelsSnapshot = Array.from(this.panels.values());
514
+ this.standardPanelsSnapshot = this.panelsSnapshot.filter((panel) => panel.kind !== "timeline");
515
+ this.timelinePanelsSnapshot = this.panelsSnapshot.filter((panel) => panel.kind === "timeline");
481
516
  this.globalListeners.forEach((fn) => fn());
482
517
  }
483
518
  initTransitionModes(config, prefix, values) {
@@ -593,10 +628,10 @@ var DialStoreClass = class {
593
628
  return typeof value === "object" && value !== null && "type" in value && value.type === "text";
594
629
  }
595
630
  isHexColor(value) {
596
- return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(value);
631
+ return isHexColor(value);
597
632
  }
598
633
  formatLabel(key) {
599
- return key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
634
+ return formatLabel(key);
600
635
  }
601
636
  inferRange(value) {
602
637
  if (value >= 0 && value <= 1) {
@@ -612,11 +647,7 @@ var DialStoreClass = class {
612
647
  }
613
648
  }
614
649
  inferStep(min, max) {
615
- const range = max - min;
616
- if (range <= 1) return 0.01;
617
- if (range <= 10) return 0.1;
618
- if (range <= 100) return 1;
619
- return 10;
650
+ return inferStep(min, max);
620
651
  }
621
652
  normalizePreservedValue(existingValue, defaultValue, control) {
622
653
  if (existingValue === void 0 || !control) {
@@ -688,7 +719,7 @@ var DialStoreClass = class {
688
719
  return map;
689
720
  }
690
721
  };
691
- var DialStore = new DialStoreClass();
722
+ var DialStore = /* @__PURE__ */ new DialStoreClass();
692
723
 
693
724
  // src/vue/useDialKit.ts
694
725
  var dialKitInstance = 0;
@@ -770,226 +801,1518 @@ function useDialKitController(name, config, options) {
770
801
  };
771
802
  }
772
803
 
773
- // src/vue/directives/dialkit.ts
774
- import {
775
- createApp,
776
- defineComponent as defineComponent16,
777
- h as h16,
778
- shallowRef as shallowRef2
779
- } from "vue";
780
-
781
- // src/vue/components/DialRoot.ts
782
- import { computed as computed5, defineComponent as defineComponent15, h as h15, nextTick as nextTick3, onMounted as onMounted10, onUnmounted as onUnmounted9, ref as ref13, Teleport as Teleport3 } from "vue";
783
-
784
- // src/vue/components/Folder.ts
785
- import { defineComponent, h, onMounted as onMounted2, onUnmounted as onUnmounted2, ref as ref2 } from "vue";
786
- import { AnimatePresence, motion } from "motion-v";
804
+ // src/vue/useDialTimeline.ts
805
+ import { computed as computed2, onMounted as onMounted2, onUnmounted as onUnmounted2, shallowRef as shallowRef2, watch as watch2 } from "vue";
787
806
 
788
- // src/icons.ts
789
- var ICON_CHEVRON = "M6 9.5L12 15.5L18 9.5";
790
- var ICON_CHECK = "M5 12.75L10 19L19 5";
791
- var ICON_CLIPBOARD = {
792
- board: "M8 6C8 4.34315 9.34315 3 11 3H13C14.6569 3 16 4.34315 16 6V7H8V6Z",
793
- 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",
794
- body: "M16 5H17C18.6569 5 20 6.34315 20 8V11M8 5H7C5.34315 5 4 6.34315 4 8V18C4 19.6569 5.34315 21 7 21H12"
795
- };
796
- var ICON_ADD_PRESET = [
797
- "M4 6H20",
798
- "M4 12H10",
799
- "M15 15L21 15",
800
- "M18 12V18",
801
- "M4 18H10"
802
- ];
803
- var ICON_TRASH = [
804
- "M5 6.5L5.80734 18.2064C5.91582 19.7794 7.22348 21 8.80023 21H15.1998C16.7765 21 18.0842 19.7794 18.1927 18.2064L19 6.5",
805
- "M10 11V16",
806
- "M14 11V16",
807
- "M3.5 6H20.5",
808
- "M8.07092 5.74621C8.42348 3.89745 10.0485 2.5 12 2.5C13.9515 2.5 15.5765 3.89745 15.9291 5.74621"
807
+ // src/store/TimelineStore.ts
808
+ function loopSpan(duration, loopStart) {
809
+ if (!Number.isFinite(duration) || duration <= 0) return 0;
810
+ if (!Number.isFinite(loopStart)) loopStart = 0;
811
+ const start = Math.min(Math.max(0, loopStart), duration);
812
+ return duration - start > 0 ? duration - start : duration;
813
+ }
814
+ function foldLoopTime(time, duration, loopStart = 0) {
815
+ if (!Number.isFinite(time) || !Number.isFinite(duration) || duration <= 0) {
816
+ return { time: 0, wraps: 0 };
817
+ }
818
+ if (time < duration) return { time, wraps: 0 };
819
+ const span = loopSpan(duration, loopStart);
820
+ const base = duration - span;
821
+ const over = time - base;
822
+ return { time: base + over % span, wraps: Math.floor(over / span) };
823
+ }
824
+ var TIMELINE_CLIP_COLORS = [
825
+ "#E8E8E8"
826
+ // neutral white — slightly off-white so the pure-white selection ring still reads
809
827
  ];
810
- var ICON_PANEL = {
811
- path: "M6.84766 11.75C6.78583 11.9899 6.75 12.2408 6.75 12.5C6.75 12.7592 6.78583 13.0101 6.84766 13.25H2C1.58579 13.25 1.25 12.9142 1.25 12.5C1.25 12.0858 1.58579 11.75 2 11.75H6.84766ZM14 11.75C14.4142 11.75 14.75 12.0858 14.75 12.5C14.75 12.9142 14.4142 13.25 14 13.25H12.6523C12.7142 13.0101 12.75 12.7592 12.75 12.5C12.75 12.2408 12.7142 11.9899 12.6523 11.75H14ZM3.09766 7.25C3.03583 7.48994 3 7.74075 3 8C3 8.25925 3.03583 8.51006 3.09766 8.75H2C1.58579 8.75 1.25 8.41421 1.25 8C1.25 7.58579 1.58579 7.25 2 7.25H3.09766ZM14 7.25C14.4142 7.25 14.75 7.58579 14.75 8C14.75 8.41421 14.4142 8.75 14 8.75H8.90234C8.96417 8.51006 9 8.25925 9 8C9 7.74075 8.96417 7.48994 8.90234 7.25H14ZM7.59766 2.75C7.53583 2.98994 7.5 3.24075 7.5 3.5C7.5 3.75925 7.53583 4.01006 7.59766 4.25H2C1.58579 4.25 1.25 3.91421 1.25 3.5C1.25 3.08579 1.58579 2.75 2 2.75H7.59766ZM14 2.75C14.4142 2.75 14.75 3.08579 14.75 3.5C14.75 3.91421 14.4142 4.25 14 4.25H13.4023C13.4642 4.01006 13.5 3.75925 13.5 3.5C13.5 3.24075 13.4642 2.98994 13.4023 2.75H14Z",
812
- circles: [
813
- { cx: "6", cy: "8", r: "0.998596" },
814
- { cx: "10.4999", cy: "3.5", r: "0.998657" },
815
- { cx: "9.75015", cy: "12.5", r: "0.997986" }
816
- ]
817
- };
818
-
819
- // src/vue/components/Folder.ts
820
- var Folder = defineComponent({
821
- name: "DialKitFolder",
822
- props: {
823
- title: { type: String, required: true },
824
- defaultOpen: { type: Boolean, default: true },
825
- isRoot: { type: Boolean, default: false },
826
- inline: { type: Boolean, default: false },
827
- toolbar: {
828
- type: null,
829
- required: false,
830
- default: null
831
- },
832
- panelHeightOffset: {
833
- type: Number,
834
- default: 10
835
- }
836
- },
837
- emits: ["openChange"],
838
- setup(props, { emit, slots }) {
839
- const isOpen = ref2(props.defaultOpen);
840
- const isCollapsed = ref2(!props.defaultOpen);
841
- const contentRef = ref2(null);
842
- const contentHeight = ref2(void 0);
843
- const windowHeight = ref2(typeof window !== "undefined" ? window.innerHeight : 800);
844
- let resizeHandler = null;
845
- if (props.isRoot) {
846
- resizeHandler = () => {
847
- windowHeight.value = window.innerHeight;
848
- };
849
- window.addEventListener("resize", resizeHandler);
850
- }
851
- onUnmounted2(() => {
852
- if (resizeHandler) window.removeEventListener("resize", resizeHandler);
853
- });
854
- const handleToggle = () => {
855
- if (props.inline && props.isRoot) return;
856
- const next = !isOpen.value;
857
- isOpen.value = next;
858
- isCollapsed.value = !next;
859
- emit("openChange", next);
860
- };
861
- let ro = null;
862
- onMounted2(() => {
863
- if (!props.isRoot || typeof ResizeObserver === "undefined") return;
864
- const el = contentRef.value;
865
- if (!el) return;
866
- ro = new ResizeObserver(() => {
867
- if (isOpen.value) {
868
- const next = el.offsetHeight;
869
- if (contentHeight.value !== next) {
870
- contentHeight.value = next;
828
+ var EMPTY_TRANSPORT = Object.freeze({ time: 0, playing: false, duration: 0, wraps: 0 });
829
+ var TimelineStoreClass = class {
830
+ constructor() {
831
+ this.timelines = /* @__PURE__ */ new Map();
832
+ this.transports = /* @__PURE__ */ new Map();
833
+ this.listeners = /* @__PURE__ */ new Map();
834
+ this.globalListeners = /* @__PURE__ */ new Set();
835
+ this.registrationCounts = /* @__PURE__ */ new Map();
836
+ this.listCache = null;
837
+ this.rafId = null;
838
+ this.lastTick = 0;
839
+ this.tick = (now) => {
840
+ const dt = Math.max(0, (now - this.lastTick) / 1e3);
841
+ this.lastTick = now;
842
+ let anyPlaying = false;
843
+ for (const [id, transport] of this.transports) {
844
+ if (!transport.playing) continue;
845
+ const meta = this.timelines.get(id);
846
+ const duration = meta?.duration ?? transport.duration;
847
+ if (!Number.isFinite(duration) || duration <= 0) {
848
+ this.transports.set(id, { time: 0, playing: false, duration: 0, wraps: 0 });
849
+ this.notify(id);
850
+ continue;
851
+ }
852
+ let time = transport.time + dt;
853
+ let playing = true;
854
+ let wraps = transport.wraps;
855
+ if (time >= duration) {
856
+ if (meta?.loop) {
857
+ const folded = foldLoopTime(time, duration, meta.loopStart);
858
+ time = folded.time;
859
+ wraps += folded.wraps;
860
+ } else {
861
+ time = duration;
862
+ playing = false;
871
863
  }
872
864
  }
873
- });
874
- ro.observe(el);
875
- if (isOpen.value) {
876
- contentHeight.value = el.offsetHeight;
865
+ this.transports.set(id, { time, playing, duration, wraps });
866
+ if (playing) anyPlaying = true;
867
+ this.notify(id);
877
868
  }
869
+ this.rafId = anyPlaying ? window.requestAnimationFrame(this.tick) : null;
870
+ };
871
+ }
872
+ register(meta, options) {
873
+ const existing = this.timelines.get(meta.id);
874
+ if (existing && existing.name !== meta.name) {
875
+ console.warn(
876
+ `[dialkit] Timeline id "${meta.id}" is already registered by "${existing.name}"; "${meta.name}" will share and overwrite that transport.`
877
+ );
878
+ }
879
+ this.registrationCounts.set(meta.id, (this.registrationCounts.get(meta.id) ?? 0) + 1);
880
+ this.applyMeta(meta, options.autoplay);
881
+ }
882
+ update(meta) {
883
+ if (!this.timelines.has(meta.id)) return;
884
+ this.applyMeta(meta, false);
885
+ }
886
+ unregister(id) {
887
+ const nextCount = (this.registrationCounts.get(id) ?? 1) - 1;
888
+ if (nextCount > 0) {
889
+ this.registrationCounts.set(id, nextCount);
890
+ return;
891
+ }
892
+ this.registrationCounts.delete(id);
893
+ this.timelines.delete(id);
894
+ this.transports.delete(id);
895
+ if (this.listeners.get(id)?.size === 0) this.listeners.delete(id);
896
+ this.listCache = null;
897
+ this.notifyGlobal();
898
+ }
899
+ play(id) {
900
+ const transport = this.transports.get(id);
901
+ if (!transport || transport.duration <= 0 || transport.playing) return;
902
+ const restart = transport.time >= transport.duration;
903
+ this.transports.set(id, {
904
+ ...transport,
905
+ time: restart ? 0 : transport.time,
906
+ wraps: restart ? 0 : transport.wraps,
907
+ playing: true
878
908
  });
879
- onUnmounted2(() => {
880
- ro?.disconnect();
881
- });
882
- const renderHeader = () => h("div", {
883
- class: `dialkit-folder-header ${props.isRoot ? "dialkit-panel-header" : ""}`,
884
- onClick: handleToggle
885
- }, [
886
- h("div", { class: "dialkit-folder-header-top" }, [
887
- props.isRoot ? isOpen.value ? h("div", { class: "dialkit-folder-title-row" }, [
888
- h("span", { class: "dialkit-folder-title dialkit-folder-title-root" }, props.title)
889
- ]) : null : h("div", { class: "dialkit-folder-title-row" }, [
890
- h("span", { class: "dialkit-folder-title" }, props.title)
891
- ]),
892
- props.isRoot && !props.inline ? h("svg", { class: "dialkit-panel-icon", viewBox: "0 0 16 16", fill: "none" }, [
893
- h("path", {
894
- opacity: "0.5",
895
- d: ICON_PANEL.path,
896
- fill: "currentColor"
897
- }),
898
- ...ICON_PANEL.circles.map((c) => h("circle", { cx: c.cx, cy: c.cy, r: c.r, fill: "currentColor", stroke: "currentColor", "stroke-width": "1.25" }))
899
- ]) : null,
900
- !props.isRoot ? h(motion.svg, {
901
- class: "dialkit-folder-icon",
902
- viewBox: "0 0 24 24",
903
- fill: "none",
904
- stroke: "currentColor",
905
- "stroke-width": "2.5",
906
- "stroke-linecap": "round",
907
- "stroke-linejoin": "round",
908
- initial: false,
909
- animate: { rotate: isOpen.value ? 0 : 180 },
910
- transition: { type: "spring", visualDuration: 0.35, bounce: 0.15 }
911
- }, [h("path", { d: ICON_CHEVRON })]) : null
912
- ]),
913
- props.isRoot && props.toolbar && isOpen.value ? h("div", { class: "dialkit-panel-toolbar", onClick: (event) => event.stopPropagation() }, [props.toolbar()]) : null
914
- ]);
915
- const renderChildren = () => h("div", { class: "dialkit-folder-inner" }, slots.default ? slots.default() : []);
916
- const renderContent = () => {
917
- if (props.isRoot) {
918
- return isOpen.value ? h("div", { class: "dialkit-folder-content" }, [renderChildren()]) : null;
909
+ this.notify(id);
910
+ this.ensureLoop();
911
+ }
912
+ pause(id) {
913
+ const transport = this.transports.get(id);
914
+ if (!transport || !transport.playing) return;
915
+ this.transports.set(id, { ...transport, playing: false });
916
+ this.notify(id);
917
+ }
918
+ replay(id) {
919
+ const transport = this.transports.get(id);
920
+ if (!transport || transport.duration <= 0) return;
921
+ this.transports.set(id, { ...transport, time: 0, wraps: 0, playing: true });
922
+ this.notify(id);
923
+ this.ensureLoop();
924
+ }
925
+ seek(id, time) {
926
+ const transport = this.transports.get(id);
927
+ if (!transport || !Number.isFinite(time)) return;
928
+ const clamped = Math.min(transport.duration, Math.max(0, time));
929
+ this.transports.set(id, { ...transport, time: clamped, wraps: 0 });
930
+ this.notify(id);
931
+ }
932
+ getTransport(id) {
933
+ return this.transports.get(id) ?? EMPTY_TRANSPORT;
934
+ }
935
+ getTimeline(id) {
936
+ return this.timelines.get(id);
937
+ }
938
+ getTimelines() {
939
+ if (!this.listCache) {
940
+ this.listCache = Array.from(this.timelines.values());
941
+ }
942
+ return this.listCache;
943
+ }
944
+ subscribe(id, listener) {
945
+ if (!this.listeners.has(id)) {
946
+ this.listeners.set(id, /* @__PURE__ */ new Set());
947
+ }
948
+ this.listeners.get(id).add(listener);
949
+ return () => {
950
+ const listeners = this.listeners.get(id);
951
+ listeners?.delete(listener);
952
+ if (listeners?.size === 0 && !this.timelines.has(id)) {
953
+ this.listeners.delete(id);
919
954
  }
920
- return h(AnimatePresence, { initial: false }, {
921
- default: () => isOpen.value ? [h(motion.div, {
922
- key: "dialkit-folder-content",
923
- class: "dialkit-folder-content",
924
- initial: { height: 0, opacity: 0 },
925
- animate: { height: "auto", opacity: 1 },
926
- exit: { height: 0, opacity: 0 },
927
- transition: { type: "spring", visualDuration: 0.35, bounce: 0.1 },
928
- style: { clipPath: "inset(0 -20px)" }
929
- }, [renderChildren()])] : []
930
- });
931
955
  };
932
- const folderContent = () => h("div", {
933
- ref: props.isRoot ? contentRef : void 0,
934
- class: `dialkit-folder ${props.isRoot ? "dialkit-folder-root" : ""}`,
935
- "data-open": String(isOpen.value)
936
- }, [
937
- renderHeader(),
938
- renderContent()
939
- ]);
956
+ }
957
+ subscribeGlobal(listener) {
958
+ this.globalListeners.add(listener);
940
959
  return () => {
941
- if (props.isRoot) {
942
- if (props.inline) {
943
- return h("div", { class: "dialkit-panel-inner dialkit-panel-inline" }, [folderContent()]);
944
- }
945
- const panelStyle = isOpen.value ? {
946
- width: 280,
947
- height: contentHeight.value !== void 0 ? Math.min(contentHeight.value + props.panelHeightOffset, windowHeight.value - 32) : "auto",
948
- borderRadius: 14,
949
- boxShadow: "var(--dial-shadow)",
950
- cursor: void 0,
951
- overflowY: "auto"
952
- } : {
953
- width: 42,
954
- height: 42,
955
- borderRadius: 21,
956
- boxShadow: "var(--dial-shadow-collapsed)",
957
- overflow: "hidden",
958
- cursor: "pointer"
959
- };
960
- return h(motion.div, {
961
- class: "dialkit-panel-inner",
962
- style: panelStyle,
963
- onClick: !isOpen.value ? handleToggle : void 0,
964
- "data-collapsed": String(isCollapsed.value),
965
- whilePress: !isOpen.value ? { scale: 0.9 } : void 0,
966
- transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 }
967
- }, [folderContent()]);
968
- }
969
- return folderContent();
960
+ this.globalListeners.delete(listener);
970
961
  };
971
962
  }
972
- });
973
-
974
- // src/vue/components/Panel.ts
975
- import { Fragment, defineComponent as defineComponent14, h as h14, onMounted as onMounted9, onUnmounted as onUnmounted8, ref as ref12 } from "vue";
976
- import { AnimatePresence as AnimatePresence4, motion as motion4 } from "motion-v";
977
-
978
- // src/vue/components/Slider.ts
979
- import { defineComponent as defineComponent2, h as h2, computed as computed2, nextTick, onMounted as onMounted3, onUnmounted as onUnmounted3, ref as ref3, watch as watch2 } from "vue";
980
- import { animate, motionValue } from "motion-v";
963
+ applyMeta(meta, autoplay) {
964
+ const duration = Number.isFinite(meta.duration) ? Math.max(0, meta.duration) : 0;
965
+ const loopStart = Number.isFinite(meta.loopStart) ? Math.min(duration, Math.max(0, meta.loopStart)) : 0;
966
+ const safeMeta = { ...meta, duration, loopStart };
967
+ this.timelines.set(meta.id, safeMeta);
968
+ const existing = this.transports.get(meta.id);
969
+ if (existing) {
970
+ this.transports.set(meta.id, {
971
+ time: Math.min(existing.time, duration),
972
+ playing: duration > 0 && existing.playing,
973
+ duration,
974
+ wraps: existing.wraps
975
+ });
976
+ } else {
977
+ const playing = duration > 0 && autoplay;
978
+ this.transports.set(meta.id, { time: 0, playing, duration, wraps: 0 });
979
+ if (playing) this.ensureLoop();
980
+ }
981
+ this.listCache = null;
982
+ this.notify(meta.id);
983
+ this.notifyGlobal();
984
+ }
985
+ ensureLoop() {
986
+ if (this.rafId !== null || typeof window === "undefined") return;
987
+ this.lastTick = performance.now();
988
+ this.rafId = window.requestAnimationFrame(this.tick);
989
+ }
990
+ notify(id) {
991
+ this.listeners.get(id)?.forEach((fn) => fn());
992
+ }
993
+ notifyGlobal() {
994
+ this.globalListeners.forEach((fn) => fn());
995
+ }
996
+ };
997
+ var TimelineStore = /* @__PURE__ */ new TimelineStoreClass();
981
998
 
982
- // src/shortcut-utils.ts
983
- function decimalsForStep(step) {
984
- const s = step.toString();
985
- const dot = s.indexOf(".");
986
- return dot === -1 ? 0 : s.length - dot - 1;
999
+ // src/transition-math.ts
1000
+ function round2(value) {
1001
+ return Math.round(value * 100) / 100;
987
1002
  }
988
- function roundValue(val, step) {
989
- const raw = Math.round(val / step) * step;
990
- return parseFloat(raw.toFixed(decimalsForStep(step)));
1003
+ function clamp(value, min, max) {
1004
+ return Math.min(max, Math.max(min, value));
991
1005
  }
992
- function getEffectiveStep(control, shortcut) {
1006
+ function isTransitionConfig(value) {
1007
+ return isSpringConfigValue(value) || isEasingConfigValue(value);
1008
+ }
1009
+ function isPhysicsSpring(transition) {
1010
+ return transition.type === "spring" && (transition.stiffness !== void 0 || transition.damping !== void 0 || transition.mass !== void 0);
1011
+ }
1012
+ function springParams(spring) {
1013
+ if (isPhysicsSpring(spring)) {
1014
+ return { stiffness: spring.stiffness ?? 200, damping: spring.damping ?? 25, mass: spring.mass ?? 1 };
1015
+ }
1016
+ const visualDuration = Math.max(0.05, spring.visualDuration ?? 0.3);
1017
+ const bounce = spring.bounce ?? 0.3;
1018
+ const root = 2 * Math.PI / (visualDuration * 1.2);
1019
+ const stiffness = root * root;
1020
+ const damping = 2 * Math.min(1, Math.max(0.05, 1 - bounce)) * Math.sqrt(stiffness);
1021
+ return { stiffness, damping, mass: 1 };
1022
+ }
1023
+ function springProgress(t, { stiffness, damping, mass }) {
1024
+ if (t <= 0) return 0;
1025
+ const w0 = Math.sqrt(stiffness / mass);
1026
+ const zeta = damping / (2 * Math.sqrt(stiffness * mass));
1027
+ if (zeta < 0.9999) {
1028
+ const wd2 = w0 * Math.sqrt(1 - zeta * zeta);
1029
+ return 1 - Math.exp(-zeta * w0 * t) * (Math.cos(wd2 * t) + zeta * w0 / wd2 * Math.sin(wd2 * t));
1030
+ }
1031
+ if (zeta < 1.0001) {
1032
+ return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
1033
+ }
1034
+ const wd = w0 * Math.sqrt(zeta * zeta - 1);
1035
+ const r1 = -zeta * w0 + wd;
1036
+ const r2 = -zeta * w0 - wd;
1037
+ return 1 + (r2 * Math.exp(r1 * t) - r1 * Math.exp(r2 * t)) / (r1 - r2);
1038
+ }
1039
+ function springSettleDuration(params) {
1040
+ const w0 = Math.sqrt(params.stiffness / params.mass);
1041
+ const zeta = params.damping / (2 * Math.sqrt(params.stiffness * params.mass));
1042
+ const decay = zeta >= 1 ? zeta * w0 - w0 * Math.sqrt(Math.max(0, zeta * zeta - 1)) : zeta * w0;
1043
+ const duration = Math.log(200) / Math.max(decay, 1e-6);
1044
+ return round2(clamp(duration, 0.05, 10));
1045
+ }
1046
+ function cubicBezierProgress(p, [x1, y1, x2, y2]) {
1047
+ if (p <= 0) return 0;
1048
+ if (p >= 1) return 1;
1049
+ const sampleX = (t2) => bezierAxis(t2, x1, x2);
1050
+ const sampleY = (t2) => bezierAxis(t2, y1, y2);
1051
+ let t = p;
1052
+ for (let i = 0; i < 8; i++) {
1053
+ const x = sampleX(t) - p;
1054
+ if (Math.abs(x) < 1e-5) return sampleY(t);
1055
+ const dx = bezierAxisDerivative(t, x1, x2);
1056
+ if (Math.abs(dx) < 1e-6) break;
1057
+ t -= x / dx;
1058
+ }
1059
+ let lo = 0;
1060
+ let hi = 1;
1061
+ t = p;
1062
+ while (hi - lo > 1e-5) {
1063
+ if (sampleX(t) < p) lo = t;
1064
+ else hi = t;
1065
+ t = (lo + hi) / 2;
1066
+ }
1067
+ return sampleY(t);
1068
+ }
1069
+ function bezierAxis(t, a1, a2) {
1070
+ return (1 - 3 * a2 + 3 * a1) * t * t * t + (3 * a2 - 6 * a1) * t * t + 3 * a1 * t;
1071
+ }
1072
+ function bezierAxisDerivative(t, a1, a2) {
1073
+ return 3 * (1 - 3 * a2 + 3 * a1) * t * t + 2 * (3 * a2 - 6 * a1) * t + 3 * a1;
1074
+ }
1075
+ function resolveClipTransition(raw, clipDuration) {
1076
+ const safeDuration = Math.max(0.05, clipDuration);
1077
+ if (raw.type === "easing") {
1078
+ return {
1079
+ transition: { ...raw, duration: safeDuration },
1080
+ duration: safeDuration,
1081
+ isPhysics: false
1082
+ };
1083
+ }
1084
+ if (isPhysicsSpring(raw)) {
1085
+ return {
1086
+ transition: raw,
1087
+ duration: springSettleDuration(springParams(raw)),
1088
+ isPhysics: true
1089
+ };
1090
+ }
1091
+ return {
1092
+ transition: { type: "spring", bounce: raw.bounce ?? 0.2, visualDuration: safeDuration },
1093
+ duration: safeDuration,
1094
+ isPhysics: false
1095
+ };
1096
+ }
1097
+
1098
+ // src/timeline-core.ts
1099
+ var CLIP_VALUE_STEP = 0.01;
1100
+ var TIMELINE_MIN_CLIP_DURATION = 0.05;
1101
+ var DEFAULT_STEP_DURATION = 0.3;
1102
+ var DEFAULT_CLIP_TRANSITION = { type: "spring", bounce: 0.2 };
1103
+ var RESERVED_KEYS = /* @__PURE__ */ new Set(["time", "playing", "duration", "play", "pause", "replay", "seek"]);
1104
+ function isClipConfig(value) {
1105
+ return isPlainObject(value) && Number.isFinite(value.at);
1106
+ }
1107
+ function isGroupConfig(value) {
1108
+ if (!isPlainObject(value) || "at" in value) return false;
1109
+ const entries = Object.values(value);
1110
+ return entries.length > 0 && entries.some(isClipConfig);
1111
+ }
1112
+ function isPlainObject(value) {
1113
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1114
+ }
1115
+ function nonNegativeFinite(value, fallback = 0) {
1116
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : fallback;
1117
+ }
1118
+ function animatedDuration(value, fallback = DEFAULT_STEP_DURATION) {
1119
+ return Math.max(TIMELINE_MIN_CLIP_DURATION, nonNegativeFinite(value, fallback));
1120
+ }
1121
+ function transitionDefaultDuration(transition) {
1122
+ if (transition.type === "easing") return animatedDuration(transition.duration);
1123
+ if (isPhysicsSpring(transition)) {
1124
+ return animatedDuration(springSettleDuration(springParams(transition)));
1125
+ }
1126
+ if (transition.visualDuration !== void 0) return animatedDuration(transition.visualDuration);
1127
+ return animatedDuration(springSettleDuration(springParams(transition)));
1128
+ }
1129
+ function defaultStepDuration(step, inheritedTransition) {
1130
+ const curve = step.transition ?? inheritedTransition;
1131
+ if (curve && isPhysicsSpring(curve)) return transitionDefaultDuration(curve);
1132
+ if (step.duration !== void 0) return animatedDuration(step.duration);
1133
+ if (step.transition) return transitionDefaultDuration(step.transition);
1134
+ return DEFAULT_STEP_DURATION;
1135
+ }
1136
+ function defaultTrackDuration(track, inheritedTransition) {
1137
+ const curve = track.transition ?? inheritedTransition;
1138
+ if (track.steps?.length) {
1139
+ return track.steps.reduce((sum, step) => sum + defaultStepDuration(step, curve), 0);
1140
+ }
1141
+ if (curve && isPhysicsSpring(curve)) return transitionDefaultDuration(curve);
1142
+ if (track.duration !== void 0) return animatedDuration(track.duration);
1143
+ if (track.transition) return transitionDefaultDuration(track.transition);
1144
+ return DEFAULT_STEP_DURATION;
1145
+ }
1146
+ function defaultClipDuration(clip) {
1147
+ const defaultCurve = isTransitionConfig(clip.transition) ? clip.transition : DEFAULT_CLIP_TRANSITION;
1148
+ if (clip.props) {
1149
+ return Object.values(clip.props).reduce(
1150
+ (max, track) => Math.max(
1151
+ max,
1152
+ nonNegativeFinite(track.delay) + defaultTrackDuration(track, defaultCurve)
1153
+ ),
1154
+ 0
1155
+ );
1156
+ }
1157
+ if (clip.steps?.length) {
1158
+ return clip.steps.reduce((sum, step) => sum + defaultStepDuration(step, defaultCurve), 0);
1159
+ }
1160
+ const animating = Boolean(clip.transition || clip.from || clip.to);
1161
+ if (!animating) return nonNegativeFinite(clip.duration);
1162
+ if (isPhysicsSpring(defaultCurve)) return transitionDefaultDuration(defaultCurve);
1163
+ if (clip.duration !== void 0) return animatedDuration(clip.duration);
1164
+ if (isTransitionConfig(clip.transition)) return transitionDefaultDuration(clip.transition);
1165
+ return clip.from || clip.to ? transitionDefaultDuration(DEFAULT_CLIP_TRANSITION) : 0;
1166
+ }
1167
+ function normalizeLoopMode(value) {
1168
+ if (value === true || value === "mirror" || value === "repeat") return "repeat";
1169
+ return "off";
1170
+ }
1171
+ function normalizeStoredTransition(transition, clipDuration) {
1172
+ if (transition.type === "easing") {
1173
+ return { ...transition, duration: clipDuration };
1174
+ }
1175
+ if (isPhysicsSpring(transition)) {
1176
+ return transition;
1177
+ }
1178
+ return { type: "spring", bounce: transition.bounce ?? 0.2 };
1179
+ }
1180
+ function collectClipEntries(config) {
1181
+ const entries = [];
1182
+ for (const [key, value] of Object.entries(config)) {
1183
+ if (key === "duration") continue;
1184
+ if (RESERVED_KEYS.has(key)) {
1185
+ console.warn(`[dialkit] Timeline key "${key}" collides with a reserved key and was skipped.`);
1186
+ continue;
1187
+ }
1188
+ if (isClipConfig(value)) {
1189
+ entries.push({ path: key, childKey: key, clip: value });
1190
+ } else if (isGroupConfig(value)) {
1191
+ for (const [childKey, childClip] of Object.entries(value)) {
1192
+ if (isClipConfig(childClip)) {
1193
+ entries.push({ path: `${key}.${childKey}`, childKey, group: key, clip: childClip });
1194
+ } else {
1195
+ console.warn(
1196
+ `[dialkit] Timeline clip "${key}.${childKey}" is missing a numeric "at" and was skipped.`
1197
+ );
1198
+ }
1199
+ }
1200
+ } else {
1201
+ console.warn(
1202
+ `[dialkit] Timeline entry "${key}" is neither a clip (needs a numeric "at") nor a group of clips and was skipped.`
1203
+ );
1204
+ }
1205
+ }
1206
+ return entries;
1207
+ }
1208
+ function definedValues(values) {
1209
+ if (!values) return void 0;
1210
+ const result = {};
1211
+ for (const [key, value] of Object.entries(values)) {
1212
+ if (value !== void 0) result[key] = value;
1213
+ }
1214
+ return result;
1215
+ }
1216
+ function setDialPath(dialConfig, path, value) {
1217
+ const segments = path.split(".");
1218
+ let node = dialConfig;
1219
+ for (const segment of segments.slice(0, -1)) {
1220
+ node = node[segment] ?? (node[segment] = {});
1221
+ }
1222
+ node[segments[segments.length - 1]] = value;
1223
+ }
1224
+ function parseTimelineConfig(config) {
1225
+ const entries = collectClipEntries(config);
1226
+ let maxEnd = 0;
1227
+ for (const { clip } of entries) {
1228
+ maxEnd = Math.max(maxEnd, nonNegativeFinite(clip.at) + defaultClipDuration(clip));
1229
+ }
1230
+ 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;
1231
+ const dialConfig = {};
1232
+ const clips = [];
1233
+ entries.forEach(({ path, childKey, group, clip }, index) => {
1234
+ const raw = clip;
1235
+ if (raw.props && (raw.steps?.length || raw.from || raw.to)) {
1236
+ console.warn(
1237
+ `[dialkit] Timeline clip "${path}": "props" is mutually exclusive with from/to/steps \u2014 using "props".`
1238
+ );
1239
+ } else if (raw.steps?.length && raw.to) {
1240
+ console.warn(
1241
+ `[dialkit] Timeline clip "${path}": "to" is ignored when "steps" is present \u2014 each leg's "to" defines its targets.`
1242
+ );
1243
+ }
1244
+ const hasSteps = Boolean(clip.steps?.length) && !clip.props;
1245
+ const hasProps = Boolean(clip.props);
1246
+ const single = isTransitionConfig(clip.transition) ? clip.transition : void 0;
1247
+ const total = defaultClipDuration(clip);
1248
+ const defaultCurve = single ?? DEFAULT_CLIP_TRANSITION;
1249
+ const clipAt = nonNegativeFinite(clip.at);
1250
+ const clipDial = {
1251
+ at: [clipAt, 0, duration, CLIP_VALUE_STEP]
1252
+ };
1253
+ if (!hasSteps && !hasProps) {
1254
+ clipDial.duration = [total, 0, duration, CLIP_VALUE_STEP];
1255
+ }
1256
+ if (!hasSteps && !hasProps && (clip.transition || clip.from || clip.to)) {
1257
+ clipDial.transition = normalizeStoredTransition(defaultCurve, total);
1258
+ }
1259
+ let tracks;
1260
+ if (clip.props) {
1261
+ tracks = [];
1262
+ for (const [prop, track] of Object.entries(clip.props)) {
1263
+ if (TRACK_RESERVED.has(prop) || /^step\d+$/.test(prop)) {
1264
+ console.warn(`[dialkit] Timeline property "${prop}" collides with a clip field and was skipped.`);
1265
+ continue;
1266
+ }
1267
+ const trackDuration = defaultTrackDuration(track, defaultCurve);
1268
+ const trackCurve = track.transition ?? defaultCurve;
1269
+ const hasTrackSteps = Boolean(track.steps?.length);
1270
+ const trackDial = {
1271
+ delay: [nonNegativeFinite(track.delay), 0, duration, CLIP_VALUE_STEP]
1272
+ };
1273
+ if (!hasTrackSteps) {
1274
+ trackDial.duration = [trackDuration, 0, duration, CLIP_VALUE_STEP];
1275
+ trackDial.transition = normalizeStoredTransition(trackCurve, trackDuration);
1276
+ }
1277
+ const fromValue = track.from ?? (hasTrackSteps ? void 0 : track.to);
1278
+ if (hasTrackSteps && fromValue === void 0) {
1279
+ console.warn(
1280
+ `[dialkit] Timeline clip "${path}": track "${prop}" has steps but no "from" \u2014 declare its starting value.`
1281
+ );
1282
+ }
1283
+ if (fromValue !== void 0) {
1284
+ trackDial.from = scalarDial(prop, fromValue, hasTrackSteps ? track.steps[0]?.to : track.to);
1285
+ }
1286
+ if (!hasTrackSteps && track.to !== void 0) {
1287
+ trackDial.to = scalarDial(prop, track.to, fromValue);
1288
+ }
1289
+ let trackStepKeys;
1290
+ if (hasTrackSteps) {
1291
+ trackStepKeys = [];
1292
+ let previous = fromValue;
1293
+ track.steps.forEach((step, stepIndex) => {
1294
+ const stepKey = `step${stepIndex + 1}`;
1295
+ trackStepKeys.push(stepKey);
1296
+ const stepDuration = defaultStepDuration(step, trackCurve);
1297
+ const stepDial = {
1298
+ duration: [stepDuration, 0, duration, CLIP_VALUE_STEP],
1299
+ transition: normalizeStoredTransition(step.transition ?? trackCurve, stepDuration)
1300
+ };
1301
+ if (step.to !== void 0) {
1302
+ stepDial.to = scalarDial(prop, step.to, previous);
1303
+ previous = step.to;
1304
+ }
1305
+ trackDial[stepKey] = stepDial;
1306
+ });
1307
+ }
1308
+ clipDial[prop] = trackDial;
1309
+ tracks.push({ prop, stepKeys: trackStepKeys });
1310
+ }
1311
+ }
1312
+ if (clip.from && !hasProps) {
1313
+ clipDial.from = withFromToRanges(
1314
+ clip.from,
1315
+ hasSteps ? definedValues(clip.steps[0]?.to) : clip.to
1316
+ );
1317
+ }
1318
+ if (!hasSteps && !hasProps && clip.to) {
1319
+ clipDial.to = withFromToRanges(clip.to, clip.from);
1320
+ }
1321
+ let stepKeys;
1322
+ if (hasSteps) {
1323
+ stepKeys = [];
1324
+ let running = clip.from;
1325
+ clip.steps.forEach((step, stepIndex) => {
1326
+ const stepKey = `step${stepIndex + 1}`;
1327
+ stepKeys.push(stepKey);
1328
+ const stepDuration = defaultStepDuration(step, defaultCurve);
1329
+ const stepDial = {
1330
+ duration: [stepDuration, 0, duration, CLIP_VALUE_STEP],
1331
+ transition: normalizeStoredTransition(step.transition ?? defaultCurve, stepDuration)
1332
+ };
1333
+ const stepTo = definedValues(step.to);
1334
+ if (stepTo) {
1335
+ for (const prop of Object.keys(stepTo)) {
1336
+ if (!running || !(prop in running)) {
1337
+ console.warn(
1338
+ `[dialkit] Timeline clip "${path}": property "${prop}" first animates in step ${stepIndex + 1} with no starting value \u2014 declare it in "from".`
1339
+ );
1340
+ }
1341
+ }
1342
+ stepDial.to = withFromToRanges(stepTo, running);
1343
+ }
1344
+ clipDial[stepKey] = stepDial;
1345
+ running = { ...running ?? {}, ...stepTo ?? {} };
1346
+ });
1347
+ }
1348
+ setDialPath(dialConfig, path, clipDial);
1349
+ clips.push({
1350
+ key: path,
1351
+ label: formatLabel(childKey),
1352
+ color: TIMELINE_CLIP_COLORS[index % TIMELINE_CLIP_COLORS.length],
1353
+ loop: normalizeLoopMode(clip.loop),
1354
+ group,
1355
+ stepKeys,
1356
+ tracks
1357
+ });
1358
+ });
1359
+ return { duration, dialConfig, clips };
1360
+ }
1361
+ var TRACK_RESERVED = /* @__PURE__ */ new Set(["at", "duration", "loop", "from", "to", "transition", "delay"]);
1362
+ function scalarDial(prop, value, counterpart) {
1363
+ const record = withFromToRanges(
1364
+ { [prop]: value },
1365
+ counterpart === void 0 ? void 0 : { [prop]: counterpart }
1366
+ );
1367
+ return record[prop];
1368
+ }
1369
+ var FROM_TO_RANGE_PRESETS = [
1370
+ [/^(x|y|z|tx|ty|offsetx|offsety|translatex|translatey)$/i, { min: -100, max: 100, step: 1 }],
1371
+ [/rotat|angle|skew/i, { min: -180, max: 180, step: 1 }],
1372
+ [/^scale/i, { min: 0, max: 2, step: 0.01 }],
1373
+ [/opacity|alpha/i, { min: 0, max: 1, step: 0.01 }],
1374
+ [/blur|radius|spread/i, { min: 0, max: 100, step: 1 }]
1375
+ ];
1376
+ function inferFromToRange(key, value, counterpart) {
1377
+ const lo = Math.min(value, counterpart ?? value);
1378
+ const hi = Math.max(value, counterpart ?? value);
1379
+ const preset = FROM_TO_RANGE_PRESETS.find(([pattern]) => pattern.test(key))?.[1];
1380
+ if (preset) {
1381
+ return [value, Math.min(preset.min, lo), Math.max(preset.max, hi), preset.step];
1382
+ }
1383
+ if (lo >= 0 && hi <= 1) {
1384
+ return [value, 0, 1, 0.01];
1385
+ }
1386
+ const extent = Math.max(Math.abs(lo), Math.abs(hi), 1);
1387
+ const min = lo < 0 ? -extent * 2 : 0;
1388
+ const max = Math.max(extent * 2, hi);
1389
+ return [value, min, max, inferStep(min, max)];
1390
+ }
1391
+ function withFromToRanges(config, counterpart) {
1392
+ const result = {};
1393
+ for (const [key, value] of Object.entries(config)) {
1394
+ const other = counterpart?.[key];
1395
+ if (typeof value === "number") {
1396
+ result[key] = inferFromToRange(key, value, typeof other === "number" ? other : void 0);
1397
+ } else if (isPlainObject(value) && !("type" in value)) {
1398
+ result[key] = withFromToRanges(
1399
+ value,
1400
+ isPlainObject(other) && !("type" in other) ? other : void 0
1401
+ );
1402
+ } else {
1403
+ result[key] = value;
1404
+ }
1405
+ }
1406
+ return result;
1407
+ }
1408
+ function curveStatic(transition, duration) {
1409
+ if (!transition) return { duration };
1410
+ if (transition.type === "easing") return { duration, ease: transition.ease };
1411
+ const spring = springParams(transition);
1412
+ return { duration, spring, settle: springSettleDuration(spring) };
1413
+ }
1414
+ function sampleCurve(curve, elapsed) {
1415
+ if (elapsed <= 0) return 0;
1416
+ if (curve.spring) {
1417
+ if (curve.settle !== void 0 && elapsed >= curve.settle) return 1;
1418
+ return springProgress(elapsed, curve.spring);
1419
+ }
1420
+ if (curve.ease) {
1421
+ return cubicBezierProgress(clamp(curve.duration > 0 ? elapsed / curve.duration : 1, 0, 1), curve.ease);
1422
+ }
1423
+ return curve.duration > 0 ? Math.min(1, elapsed / curve.duration) : 1;
1424
+ }
1425
+ function resolvedAtPath(resolved, path) {
1426
+ let node = resolved;
1427
+ for (const segment of path.split(".")) {
1428
+ node = isPlainObject(node) ? node[segment] : void 0;
1429
+ }
1430
+ return isPlainObject(node) ? node : {};
1431
+ }
1432
+ function computeStaticClips(parsed, flatValues) {
1433
+ const resolved = resolveDialValues(parsed.dialConfig, flatValues);
1434
+ return parsed.clips.map(
1435
+ (clip) => buildClipStatic(resolvedAtPath(resolved, clip.key), clip, parsed.duration)
1436
+ );
1437
+ }
1438
+ function computeStaticTimeline(parsed, flatValues) {
1439
+ let clips = computeStaticClips(parsed, flatValues);
1440
+ const maxEnd = clips.reduce(
1441
+ (end, clip) => Math.max(end, clip.at + clip.duration),
1442
+ parsed.duration
1443
+ );
1444
+ const duration = maxEnd > parsed.duration ? Math.ceil(maxEnd * 100 - 1e-4) / 100 : parsed.duration;
1445
+ if (duration !== parsed.duration) {
1446
+ clips = clips.map(
1447
+ (clip) => clip.loop === "repeat" ? { ...clip, end: duration } : clip
1448
+ );
1449
+ }
1450
+ return { duration, clips };
1451
+ }
1452
+ function computeClipStaticFromValues(values, clip, timelineDuration) {
1453
+ return buildClipStatic(unflattenClipValues(values, clip.key), clip, timelineDuration);
1454
+ }
1455
+ function buildClipStatic(clipResolved, clip, timelineDuration) {
1456
+ {
1457
+ const at = typeof clipResolved.at === "number" ? clipResolved.at : 0;
1458
+ const from = isPlainObject(clipResolved.from) ? clipResolved.from : void 0;
1459
+ const single = isTransitionConfig(clipResolved.transition) ? clipResolved.transition : void 0;
1460
+ const staticClip = {
1461
+ key: clip.key,
1462
+ childKey: clip.group ? clip.key.slice(clip.group.length + 1) : clip.key,
1463
+ group: clip.group,
1464
+ at,
1465
+ duration: 0,
1466
+ loop: "off",
1467
+ end: 0,
1468
+ isPhysics: false,
1469
+ from,
1470
+ tracks: [],
1471
+ explicitSteps: Boolean(clip.stepKeys?.length)
1472
+ };
1473
+ if (clip.tracks?.length) {
1474
+ const tracks = clip.tracks.map(({ prop, stepKeys }) => {
1475
+ const trackResolved = isPlainObject(clipResolved[prop]) ? clipResolved[prop] : {};
1476
+ const delay = typeof trackResolved.delay === "number" ? trackResolved.delay : 0;
1477
+ const fromValue = trackResolved.from;
1478
+ let steps;
1479
+ let trackDuration = 0;
1480
+ if (stepKeys?.length) {
1481
+ let running = fromValue;
1482
+ steps = stepKeys.map((stepKey) => {
1483
+ const stepResolved = isPlainObject(trackResolved[stepKey]) ? trackResolved[stepKey] : {};
1484
+ const storedDuration = typeof stepResolved.duration === "number" ? stepResolved.duration : 0;
1485
+ const raw = isTransitionConfig(stepResolved.transition) ? stepResolved.transition : void 0;
1486
+ const effective = raw ? resolveClipTransition(raw, storedDuration) : { transition: void 0, duration: storedDuration, isPhysics: false };
1487
+ const toValue = stepResolved.to;
1488
+ const step = {
1489
+ key: stepKey,
1490
+ offset: trackDuration,
1491
+ duration: effective.duration,
1492
+ isPhysics: effective.isPhysics,
1493
+ start: running === void 0 ? {} : { [prop]: running },
1494
+ to: toValue === void 0 ? {} : { [prop]: toValue },
1495
+ curve: curveStatic(effective.transition, effective.duration)
1496
+ };
1497
+ if (toValue !== void 0) running = toValue;
1498
+ trackDuration += effective.duration;
1499
+ return step;
1500
+ });
1501
+ } else {
1502
+ const storedDuration = typeof trackResolved.duration === "number" ? trackResolved.duration : 0;
1503
+ const raw = isTransitionConfig(trackResolved.transition) ? trackResolved.transition : void 0;
1504
+ const effective = raw ? resolveClipTransition(raw, storedDuration) : { transition: void 0, duration: storedDuration, isPhysics: false };
1505
+ const toValue = trackResolved.to;
1506
+ trackDuration = effective.duration;
1507
+ steps = [
1508
+ {
1509
+ key: null,
1510
+ offset: 0,
1511
+ duration: effective.duration,
1512
+ isPhysics: effective.isPhysics,
1513
+ start: fromValue === void 0 ? {} : { [prop]: fromValue },
1514
+ to: toValue === void 0 ? {} : { [prop]: toValue },
1515
+ curve: curveStatic(effective.transition, effective.duration)
1516
+ }
1517
+ ];
1518
+ }
1519
+ return { prop, delay, duration: trackDuration, steps };
1520
+ });
1521
+ staticClip.tracks = tracks;
1522
+ staticClip.props = tracks.map((track) => track.prop);
1523
+ staticClip.duration = tracks.reduce((max, track) => Math.max(max, track.delay + track.duration), 0);
1524
+ staticClip.from = Object.fromEntries(
1525
+ tracks.map((track) => [track.prop, track.steps[0].start[track.prop]])
1526
+ );
1527
+ staticClip.to = Object.fromEntries(
1528
+ tracks.map((track) => {
1529
+ const last = track.steps[track.steps.length - 1];
1530
+ return [track.prop, last.to[track.prop] ?? last.start[track.prop]];
1531
+ })
1532
+ );
1533
+ staticClip.loop = staticClip.duration > 0 ? clip.loop : "off";
1534
+ staticClip.end = staticClip.loop === "off" ? staticClip.at + staticClip.duration : timelineDuration;
1535
+ return staticClip;
1536
+ }
1537
+ if (clip.stepKeys?.length) {
1538
+ let running = { ...from ?? {} };
1539
+ let offset = 0;
1540
+ const steps = clip.stepKeys.map((stepKey) => {
1541
+ const stepResolved = isPlainObject(clipResolved[stepKey]) ? clipResolved[stepKey] : {};
1542
+ const storedDuration = typeof stepResolved.duration === "number" ? stepResolved.duration : 0;
1543
+ const raw = isTransitionConfig(stepResolved.transition) ? stepResolved.transition : void 0;
1544
+ const effective = raw ? resolveClipTransition(raw, storedDuration) : { transition: void 0, duration: storedDuration, isPhysics: false };
1545
+ const to = isPlainObject(stepResolved.to) ? stepResolved.to : {};
1546
+ const step = {
1547
+ key: stepKey,
1548
+ offset,
1549
+ duration: effective.duration,
1550
+ isPhysics: effective.isPhysics,
1551
+ start: running,
1552
+ to,
1553
+ curve: curveStatic(effective.transition, effective.duration)
1554
+ };
1555
+ running = { ...running, ...to };
1556
+ offset += effective.duration;
1557
+ return step;
1558
+ });
1559
+ staticClip.tracks = [{ delay: 0, duration: offset, steps }];
1560
+ staticClip.duration = offset;
1561
+ staticClip.to = running;
1562
+ } else {
1563
+ const storedDuration = typeof clipResolved.duration === "number" ? clipResolved.duration : 0;
1564
+ const to = isPlainObject(clipResolved.to) ? clipResolved.to : void 0;
1565
+ if (single) {
1566
+ const effective = resolveClipTransition(single, storedDuration);
1567
+ staticClip.duration = effective.duration;
1568
+ staticClip.isPhysics = effective.isPhysics;
1569
+ staticClip.transition = effective.transition;
1570
+ staticClip.css = transitionToCss(effective.transition);
1571
+ staticClip.to = to;
1572
+ if (from && to) {
1573
+ staticClip.tracks = [
1574
+ {
1575
+ delay: 0,
1576
+ duration: effective.duration,
1577
+ steps: [
1578
+ {
1579
+ key: null,
1580
+ offset: 0,
1581
+ duration: effective.duration,
1582
+ isPhysics: effective.isPhysics,
1583
+ start: from,
1584
+ to,
1585
+ curve: curveStatic(effective.transition, effective.duration)
1586
+ }
1587
+ ]
1588
+ }
1589
+ ];
1590
+ }
1591
+ } else {
1592
+ staticClip.duration = storedDuration;
1593
+ staticClip.to = to;
1594
+ if (from && to) {
1595
+ const base = resolveClipTransition(DEFAULT_CLIP_TRANSITION, storedDuration);
1596
+ staticClip.duration = base.duration;
1597
+ staticClip.tracks = [
1598
+ {
1599
+ delay: 0,
1600
+ duration: base.duration,
1601
+ steps: [
1602
+ {
1603
+ key: null,
1604
+ offset: 0,
1605
+ duration: base.duration,
1606
+ isPhysics: false,
1607
+ start: from,
1608
+ to,
1609
+ curve: curveStatic(base.transition, base.duration)
1610
+ }
1611
+ ]
1612
+ }
1613
+ ];
1614
+ }
1615
+ }
1616
+ }
1617
+ if (staticClip.tracks.length) {
1618
+ const props = new Set(Object.keys(from ?? {}));
1619
+ for (const track of staticClip.tracks) {
1620
+ for (const step of track.steps) {
1621
+ for (const prop of Object.keys(step.to)) props.add(prop);
1622
+ }
1623
+ }
1624
+ staticClip.props = Array.from(props);
1625
+ }
1626
+ staticClip.loop = staticClip.duration > 0 ? clip.loop : "off";
1627
+ staticClip.end = staticClip.loop === "off" ? staticClip.at + staticClip.duration : timelineDuration;
1628
+ return staticClip;
1629
+ }
1630
+ }
1631
+ function stepAtPosition(steps, pos) {
1632
+ for (const step of steps) {
1633
+ if (pos < step.offset + step.duration) return step;
1634
+ }
1635
+ return steps[steps.length - 1];
1636
+ }
1637
+ function evalPropAtPos(steps, prop, pos) {
1638
+ const step = stepAtPosition(steps, pos);
1639
+ const within = Math.max(0, pos - step.offset);
1640
+ if (prop in step.to) {
1641
+ const eased = sampleCurve(step.curve, within);
1642
+ return interpolateResolved(step.start[prop], step.to[prop], eased);
1643
+ }
1644
+ return step.start[prop];
1645
+ }
1646
+ function computeClipState(clip, time, cycleTime = time) {
1647
+ const total = clip.duration;
1648
+ const looping = clip.loop === "repeat" && total > 0;
1649
+ const started = time >= clip.at || looping && cycleTime > time;
1650
+ const done = time >= clip.end;
1651
+ const elapsed = time - clip.at;
1652
+ const phaseElapsed = looping ? cycleTime - clip.at : elapsed;
1653
+ const fold = (e) => looping ? e % total : e;
1654
+ const basePos = started ? fold(Math.max(0, phaseElapsed)) : 0;
1655
+ const progress = total > 0 ? clamp(basePos / total, 0, 1) : started ? 1 : 0;
1656
+ let current;
1657
+ let stepIndex = 0;
1658
+ if (clip.tracks.length && clip.props?.length) {
1659
+ current = {};
1660
+ for (const track of clip.tracks) {
1661
+ const props = track.prop !== void 0 ? [track.prop] : clip.props;
1662
+ for (const prop of props) {
1663
+ const startValue = track.steps[0]?.start[prop];
1664
+ if (!started) {
1665
+ if (startValue !== void 0) current[prop] = startValue;
1666
+ continue;
1667
+ }
1668
+ const phase = phaseElapsed - track.delay;
1669
+ if (phase <= 0) {
1670
+ if (startValue !== void 0) current[prop] = startValue;
1671
+ continue;
1672
+ }
1673
+ const pos = looping && track.duration > 0 ? phase % track.duration : phase;
1674
+ const value = evalPropAtPos(track.steps, prop, pos);
1675
+ if (value !== void 0) current[prop] = value;
1676
+ }
1677
+ }
1678
+ const shared = clip.tracks[0];
1679
+ if (started && clip.explicitSteps && shared.prop === void 0) {
1680
+ stepIndex = shared.steps.indexOf(stepAtPosition(shared.steps, basePos));
1681
+ }
1682
+ }
1683
+ return {
1684
+ at: clip.at,
1685
+ duration: clip.duration,
1686
+ loop: clip.loop,
1687
+ started,
1688
+ active: started && !done,
1689
+ done,
1690
+ progress,
1691
+ step: clip.explicitSteps ? stepIndex : void 0,
1692
+ from: clip.from,
1693
+ to: clip.to,
1694
+ animate: started ? clip.to : clip.from,
1695
+ transition: clip.transition,
1696
+ css: clip.css,
1697
+ current
1698
+ };
1699
+ }
1700
+ function interpolateResolved(from, to, p) {
1701
+ if (typeof from === "number" && typeof to === "number") {
1702
+ return from + (to - from) * p;
1703
+ }
1704
+ if (typeof from === "string" && typeof to === "string") {
1705
+ const mixed = mixHexColors(from, to, p);
1706
+ if (mixed) return mixed;
1707
+ }
1708
+ if (isPlainObject(from) && isPlainObject(to)) {
1709
+ const result = {};
1710
+ for (const key of Object.keys(from)) {
1711
+ result[key] = key in to ? interpolateResolved(from[key], to[key], p) : from[key];
1712
+ }
1713
+ for (const key of Object.keys(to)) {
1714
+ if (!(key in from)) result[key] = to[key];
1715
+ }
1716
+ return result;
1717
+ }
1718
+ return p < 0.5 ? from : to;
1719
+ }
1720
+ function parseHex(hex) {
1721
+ if (!isHexColor(hex)) return null;
1722
+ let h22 = hex.slice(1);
1723
+ if (h22.length === 3) h22 = h22.split("").map((c) => c + c).join("");
1724
+ return [
1725
+ parseInt(h22.slice(0, 2), 16),
1726
+ parseInt(h22.slice(2, 4), 16),
1727
+ parseInt(h22.slice(4, 6), 16),
1728
+ h22.length === 8 ? parseInt(h22.slice(6, 8), 16) : 255
1729
+ ];
1730
+ }
1731
+ function mixHexColors(a, b, p) {
1732
+ const ca = parseHex(a);
1733
+ const cb = parseHex(b);
1734
+ if (!ca || !cb) return null;
1735
+ const t = clamp(p, 0, 1);
1736
+ const mixed = ca.map((v, i) => Math.round(v + (cb[i] - v) * t));
1737
+ const hex = (n) => n.toString(16).padStart(2, "0");
1738
+ const rgb = `#${hex(mixed[0])}${hex(mixed[1])}${hex(mixed[2])}`;
1739
+ return mixed[3] === 255 ? rgb : `${rgb}${hex(mixed[3])}`;
1740
+ }
1741
+ function transitionToCss(transition) {
1742
+ if (!transition) return void 0;
1743
+ if (transition.type === "easing") {
1744
+ return {
1745
+ transitionDuration: `${round2(transition.duration)}s`,
1746
+ transitionTimingFunction: `cubic-bezier(${transition.ease.map((v) => round2(v)).join(", ")})`
1747
+ };
1748
+ }
1749
+ const params = springParams(transition);
1750
+ const dampingRatio = params.damping / (2 * Math.sqrt(params.stiffness * params.mass));
1751
+ const duration = transition.visualDuration ?? springSettleDuration(params);
1752
+ const bounce = transition.bounce ?? Math.max(0, round2(1 - dampingRatio));
1753
+ return {
1754
+ transitionDuration: `${round2(duration)}s`,
1755
+ transitionTimingFunction: bounce > 0.05 ? `cubic-bezier(0.34, ${round2(1.2 + bounce)}, 0.64, 1)` : "cubic-bezier(0.25, 0.6, 0.35, 1)"
1756
+ };
1757
+ }
1758
+ function timelinePopoverDisplayValues(values, clipKey, stepKeys, stepKey) {
1759
+ const display = { ...values };
1760
+ const swap = (path, duration) => {
1761
+ const raw = display[path];
1762
+ if (isTransitionConfig(raw)) display[path] = resolveClipTransition(raw, duration).transition;
1763
+ };
1764
+ if (stepKey) {
1765
+ swap(`${clipKey}.${stepKey}.transition`, numberValue(values[`${clipKey}.${stepKey}.duration`]));
1766
+ return display;
1767
+ }
1768
+ const cycle = stepKeys?.length ? stepKeys.reduce((sum, sk) => sum + numberValue(values[`${clipKey}.${sk}.duration`]), 0) : numberValue(values[`${clipKey}.duration`]);
1769
+ swap(`${clipKey}.transition`, cycle);
1770
+ return display;
1771
+ }
1772
+ function numberValue(value) {
1773
+ return typeof value === "number" ? value : 0;
1774
+ }
1775
+ function unflattenClipValues(values, clipKey) {
1776
+ const prefix = `${clipKey}.`;
1777
+ const result = {};
1778
+ 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);
1779
+ for (const { segments, value } of entries) {
1780
+ let node = result;
1781
+ for (let i = 0; i < segments.length - 1; i++) {
1782
+ const existing = node[segments[i]];
1783
+ node = isPlainObject(existing) ? existing : node[segments[i]] = {};
1784
+ }
1785
+ node[segments[segments.length - 1]] = cloneTimelineValue(value);
1786
+ }
1787
+ return result;
1788
+ }
1789
+ function cloneTimelineValue(value) {
1790
+ if (Array.isArray(value)) return value.map(cloneTimelineValue);
1791
+ if (!isPlainObject(value)) return value;
1792
+ return Object.fromEntries(
1793
+ Object.entries(value).map(([key, nested]) => [key, cloneTimelineValue(nested)])
1794
+ );
1795
+ }
1796
+ function clampTrackDelay(delay, at, trackDuration, timelineDuration) {
1797
+ return clamp(round2(delay), 0, Math.max(0, round2(timelineDuration - at - trackDuration)));
1798
+ }
1799
+ function clampClipMove(at, duration, timelineDuration) {
1800
+ return clamp(round2(at), 0, Math.max(0, timelineDuration - duration));
1801
+ }
1802
+ function clampClipResizeEnd(duration, at, timelineDuration) {
1803
+ return clamp(round2(duration), TIMELINE_MIN_CLIP_DURATION, timelineDuration - at);
1804
+ }
1805
+ function clampClipResizeStart(newAt, at, duration) {
1806
+ const clampedAt = clamp(round2(newAt), 0, at + duration - TIMELINE_MIN_CLIP_DURATION);
1807
+ return { at: clampedAt, duration: round2(at + duration - clampedAt) };
1808
+ }
1809
+ function clampStepResize(duration, at, otherStepsTotal, timelineDuration) {
1810
+ const max = Math.max(TIMELINE_MIN_CLIP_DURATION, timelineDuration - at - otherStepsTotal);
1811
+ return clamp(round2(duration), TIMELINE_MIN_CLIP_DURATION, max);
1812
+ }
1813
+ function normalizeTimelineValuesForCopy(values, clips) {
1814
+ const normalized = { ...values };
1815
+ for (const path of Object.keys(normalized)) {
1816
+ if (path.endsWith(".__mode")) delete normalized[path];
1817
+ }
1818
+ const normalizeTransitionAt = (transitionPath, durationPath) => {
1819
+ const raw = normalized[transitionPath];
1820
+ if (!isTransitionConfig(raw)) return;
1821
+ if (isPhysicsSpring(raw)) {
1822
+ normalized[durationPath] = transitionDefaultDuration(raw);
1823
+ }
1824
+ normalized[transitionPath] = normalizeStoredTransition(
1825
+ raw,
1826
+ numberValue(normalized[durationPath])
1827
+ );
1828
+ };
1829
+ for (const clip of clips) {
1830
+ for (const stepKey of clip.stepKeys ?? []) {
1831
+ normalizeTransitionAt(
1832
+ `${clip.key}.${stepKey}.transition`,
1833
+ `${clip.key}.${stepKey}.duration`
1834
+ );
1835
+ }
1836
+ normalizeTransitionAt(`${clip.key}.transition`, `${clip.key}.duration`);
1837
+ for (const track of clip.tracks ?? []) {
1838
+ const trackKey = `${clip.key}.${track.prop}`;
1839
+ for (const stepKey of track.stepKeys ?? []) {
1840
+ normalizeTransitionAt(
1841
+ `${trackKey}.${stepKey}.transition`,
1842
+ `${trackKey}.${stepKey}.duration`
1843
+ );
1844
+ }
1845
+ normalizeTransitionAt(`${trackKey}.transition`, `${trackKey}.duration`);
1846
+ if (normalized[`${trackKey}.delay`] === 0) {
1847
+ delete normalized[`${trackKey}.delay`];
1848
+ }
1849
+ }
1850
+ delete normalized[`${clip.key}.loop`];
1851
+ }
1852
+ return normalized;
1853
+ }
1854
+ function formatClock(time, tenths = false) {
1855
+ const safe = Math.max(0, time);
1856
+ const minutes = Math.floor(safe / 60);
1857
+ const seconds = safe - minutes * 60;
1858
+ const secondsText = tenths ? seconds.toFixed(1).padStart(4, "0") : String(Math.floor(seconds)).padStart(2, "0");
1859
+ return `${String(minutes).padStart(2, "0")}:${secondsText}`;
1860
+ }
1861
+ function formatSeconds(value) {
1862
+ return `${round2(value)}s`;
1863
+ }
1864
+ function formatStepLabel(stepKey) {
1865
+ const match = /^step(\d+)$/.exec(stepKey);
1866
+ return match ? `Step ${match[1]}` : formatLabel(stepKey);
1867
+ }
1868
+
1869
+ // src/timeline/adapter.ts
1870
+ function resolveTimelineLoop(loop) {
1871
+ if (typeof loop === "object" && loop !== null) {
1872
+ return {
1873
+ enabled: true,
1874
+ start: Number.isFinite(loop.from) ? Math.max(0, loop.from) : 0
1875
+ };
1876
+ }
1877
+ return { enabled: Boolean(loop), start: 0 };
1878
+ }
1879
+ function buildTimelineMeta(id, name, duration, parsed, loop) {
1880
+ const resolvedLoop = resolveTimelineLoop(loop);
1881
+ return {
1882
+ id,
1883
+ name,
1884
+ duration,
1885
+ loop: resolvedLoop.enabled,
1886
+ loopStart: resolvedLoop.start,
1887
+ clips: parsed.clips
1888
+ };
1889
+ }
1890
+ function buildTimelineValues(staticClips, transport, timelineDuration, loopStart, actions) {
1891
+ var _a;
1892
+ const result = {
1893
+ time: transport.time,
1894
+ playing: transport.playing,
1895
+ duration: timelineDuration,
1896
+ ...actions
1897
+ };
1898
+ const span = loopSpan(transport.duration, loopStart);
1899
+ const cycleTime = (span > 0 ? transport.wraps * span : 0) + transport.time;
1900
+ for (const clip of staticClips) {
1901
+ const state = computeClipState(clip, transport.time, cycleTime);
1902
+ if (clip.group) {
1903
+ const bucket = result[_a = clip.group] ?? (result[_a] = {});
1904
+ bucket[clip.childKey] = state;
1905
+ } else {
1906
+ result[clip.key] = state;
1907
+ }
1908
+ }
1909
+ return result;
1910
+ }
1911
+
1912
+ // src/store/TimelineUiStore.ts
1913
+ var TimelineUiStoreClass = class {
1914
+ constructor() {
1915
+ this.visible = true;
1916
+ this.initialized = false;
1917
+ this.controllers = /* @__PURE__ */ new Map();
1918
+ this.listeners = /* @__PURE__ */ new Set();
1919
+ }
1920
+ getVisible() {
1921
+ for (const controller of this.controllers.values()) {
1922
+ if (controller.visible !== void 0) return controller.visible;
1923
+ }
1924
+ return this.visible;
1925
+ }
1926
+ registerController(id, controller) {
1927
+ const previous = this.getVisible();
1928
+ if (!this.initialized) {
1929
+ this.visible = controller.defaultVisible;
1930
+ this.initialized = true;
1931
+ }
1932
+ this.controllers.set(id, controller);
1933
+ if (previous !== this.getVisible()) this.notify();
1934
+ return () => {
1935
+ const before = this.getVisible();
1936
+ this.controllers.delete(id);
1937
+ if (this.controllers.size === 0) this.initialized = false;
1938
+ if (before !== this.getVisible()) this.notify();
1939
+ };
1940
+ }
1941
+ updateController(id, controller) {
1942
+ if (!this.controllers.has(id)) return;
1943
+ const previous = this.getVisible();
1944
+ this.controllers.set(id, controller);
1945
+ if (previous !== this.getVisible()) this.notify();
1946
+ }
1947
+ requestVisible(visible) {
1948
+ const current = this.getVisible();
1949
+ if (current === visible) return;
1950
+ const controlled = Array.from(this.controllers.values()).filter(
1951
+ (controller) => controller.visible !== void 0
1952
+ );
1953
+ if (controlled.length > 0) {
1954
+ controlled.forEach((controller) => controller.onVisibilityChange?.(visible));
1955
+ return;
1956
+ }
1957
+ this.visible = visible;
1958
+ this.controllers.forEach((controller) => controller.onVisibilityChange?.(visible));
1959
+ this.notify();
1960
+ }
1961
+ toggle() {
1962
+ this.requestVisible(!this.getVisible());
1963
+ }
1964
+ subscribe(listener) {
1965
+ this.listeners.add(listener);
1966
+ return () => this.listeners.delete(listener);
1967
+ }
1968
+ notify() {
1969
+ this.listeners.forEach((listener) => listener());
1970
+ }
1971
+ };
1972
+ var TimelineUiStore = /* @__PURE__ */ new TimelineUiStoreClass();
1973
+
1974
+ // src/copy-instruction.ts
1975
+ function buildCopyInstruction(hookName, panelName, values) {
1976
+ const jsonStr = JSON.stringify(values, null, 2);
1977
+ if (hookName === "useDialTimeline" || hookName === "createDialTimeline") {
1978
+ return `Update the ${hookName} configuration for "${panelName}" with these values:
1979
+
1980
+ \`\`\`json
1981
+ ${jsonStr}
1982
+ \`\`\`
1983
+
1984
+ 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.
1985
+
1986
+ Add this comment immediately above the ${hookName} call as a production handoff note:
1987
+
1988
+ \`\`\`tsx
1989
+ // TODO(production): DialKit's clip.current values are the scrubbable authoring preview.
1990
+ // Replace them with equivalent real Motion animations using the tuned timeline
1991
+ // timings and transitions, then remove ${hookName} and <DialTimeline />.
1992
+ \`\`\``;
1993
+ }
1994
+ return `Update the ${hookName} configuration for "${panelName}" with these values:
1995
+
1996
+ \`\`\`json
1997
+ ${jsonStr}
1998
+ \`\`\`
1999
+
2000
+ Apply these values as the new defaults in the ${hookName} call.`;
2001
+ }
2002
+
2003
+ // src/env.ts
2004
+ 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;
2005
+
2006
+ // src/vue/useDialTimeline.ts
2007
+ var timelineInstance = 0;
2008
+ function useDialTimeline(name, config, options) {
2009
+ const hasStableId = options?.id !== void 0;
2010
+ const panelId = options?.id ?? `${name}-${++timelineInstance}`;
2011
+ const serializedConfig = computed2(() => JSON.stringify(config));
2012
+ const serializedPersist = computed2(() => JSON.stringify(options?.persist));
2013
+ const serializedLoop = computed2(() => JSON.stringify(options?.loop));
2014
+ const parsed = computed2(() => {
2015
+ serializedConfig.value;
2016
+ return parseTimelineConfig(config);
2017
+ });
2018
+ const flatValues = shallowRef2(DialStore.getValues(panelId));
2019
+ const transport = shallowRef2(TimelineStore.getTransport(panelId));
2020
+ const staticTimeline = computed2(() => computeStaticTimeline(parsed.value, flatValues.value));
2021
+ const meta = computed2(() => {
2022
+ serializedLoop.value;
2023
+ return buildTimelineMeta(
2024
+ panelId,
2025
+ name,
2026
+ staticTimeline.value.duration,
2027
+ parsed.value,
2028
+ options?.loop
2029
+ );
2030
+ });
2031
+ let mounted = false;
2032
+ let unsubscribeValues;
2033
+ let unsubscribeTransport;
2034
+ const play = () => TimelineStore.play(panelId);
2035
+ const pause = () => TimelineStore.pause(panelId);
2036
+ const replay = () => TimelineStore.replay(panelId);
2037
+ const seek = (time) => TimelineStore.seek(panelId, time);
2038
+ watch2([serializedConfig, serializedPersist], () => {
2039
+ if (!mounted) return;
2040
+ DialStore.updatePanel(panelId, name, parsed.value.dialConfig, void 0, {
2041
+ retainOnUnmount: hasStableId,
2042
+ persist: options?.persist,
2043
+ kind: "timeline"
2044
+ });
2045
+ flatValues.value = DialStore.getValues(panelId);
2046
+ });
2047
+ watch2(meta, (nextMeta) => {
2048
+ if (mounted) TimelineStore.update(nextMeta);
2049
+ });
2050
+ onMounted2(() => {
2051
+ unsubscribeValues = DialStore.subscribe(panelId, () => {
2052
+ flatValues.value = DialStore.getValues(panelId);
2053
+ });
2054
+ unsubscribeTransport = TimelineStore.subscribe(panelId, () => {
2055
+ transport.value = TimelineStore.getTransport(panelId);
2056
+ });
2057
+ DialStore.registerPanel(panelId, name, parsed.value.dialConfig, void 0, {
2058
+ retainOnUnmount: hasStableId,
2059
+ persist: options?.persist,
2060
+ kind: "timeline"
2061
+ });
2062
+ flatValues.value = DialStore.getValues(panelId);
2063
+ TimelineStore.register(meta.value, { autoplay: options?.autoplay ?? true });
2064
+ transport.value = TimelineStore.getTransport(panelId);
2065
+ mounted = true;
2066
+ });
2067
+ onUnmounted2(() => {
2068
+ mounted = false;
2069
+ unsubscribeValues?.();
2070
+ unsubscribeTransport?.();
2071
+ TimelineStore.unregister(panelId);
2072
+ DialStore.unregisterPanel(panelId);
2073
+ });
2074
+ return computed2(() => {
2075
+ const currentStatic = staticTimeline.value;
2076
+ return buildTimelineValues(
2077
+ currentStatic.clips,
2078
+ transport.value,
2079
+ currentStatic.duration,
2080
+ resolveTimelineLoop(options?.loop).start,
2081
+ { play, pause, replay, seek }
2082
+ );
2083
+ });
2084
+ }
2085
+
2086
+ // src/vue/directives/dialkit.ts
2087
+ import {
2088
+ createApp,
2089
+ defineComponent as defineComponent17,
2090
+ h as h17,
2091
+ shallowRef as shallowRef3
2092
+ } from "vue";
2093
+
2094
+ // src/vue/components/DialRoot.ts
2095
+ import { computed as computed6, defineComponent as defineComponent16, h as h16, nextTick as nextTick3, onMounted as onMounted12, onUnmounted as onUnmounted11, ref as ref14, Teleport as Teleport3 } from "vue";
2096
+
2097
+ // src/vue/components/Folder.ts
2098
+ import { defineComponent, h, onMounted as onMounted3, onUnmounted as onUnmounted3, ref as ref2 } from "vue";
2099
+ import { AnimatePresence, motion } from "motion-v";
2100
+
2101
+ // src/icons.ts
2102
+ var ICON_CHEVRON = "M6 9.5L12 15.5L18 9.5";
2103
+ var ICON_CHECK = "M5 12.75L10 19L19 5";
2104
+ var ICON_PAUSE = [
2105
+ "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",
2106
+ "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"
2107
+ ];
2108
+ 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";
2109
+ var ICON_TIMELINE = [
2110
+ "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",
2111
+ "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",
2112
+ "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"
2113
+ ];
2114
+ var ICON_CLIPBOARD = {
2115
+ board: "M8 6C8 4.34315 9.34315 3 11 3H13C14.6569 3 16 4.34315 16 6V7H8V6Z",
2116
+ 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",
2117
+ body: "M16 5H17C18.6569 5 20 6.34315 20 8V11M8 5H7C5.34315 5 4 6.34315 4 8V18C4 19.6569 5.34315 21 7 21H12"
2118
+ };
2119
+ var ICON_ADD_PRESET = [
2120
+ "M4 6H20",
2121
+ "M4 12H10",
2122
+ "M15 15L21 15",
2123
+ "M18 12V18",
2124
+ "M4 18H10"
2125
+ ];
2126
+ var ICON_TRASH = [
2127
+ "M5 6.5L5.80734 18.2064C5.91582 19.7794 7.22348 21 8.80023 21H15.1998C16.7765 21 18.0842 19.7794 18.1927 18.2064L19 6.5",
2128
+ "M10 11V16",
2129
+ "M14 11V16",
2130
+ "M3.5 6H20.5",
2131
+ "M8.07092 5.74621C8.42348 3.89745 10.0485 2.5 12 2.5C13.9515 2.5 15.5765 3.89745 15.9291 5.74621"
2132
+ ];
2133
+ var ICON_PANEL = {
2134
+ path: "M6.84766 11.75C6.78583 11.9899 6.75 12.2408 6.75 12.5C6.75 12.7592 6.78583 13.0101 6.84766 13.25H2C1.58579 13.25 1.25 12.9142 1.25 12.5C1.25 12.0858 1.58579 11.75 2 11.75H6.84766ZM14 11.75C14.4142 11.75 14.75 12.0858 14.75 12.5C14.75 12.9142 14.4142 13.25 14 13.25H12.6523C12.7142 13.0101 12.75 12.7592 12.75 12.5C12.75 12.2408 12.7142 11.9899 12.6523 11.75H14ZM3.09766 7.25C3.03583 7.48994 3 7.74075 3 8C3 8.25925 3.03583 8.51006 3.09766 8.75H2C1.58579 8.75 1.25 8.41421 1.25 8C1.25 7.58579 1.58579 7.25 2 7.25H3.09766ZM14 7.25C14.4142 7.25 14.75 7.58579 14.75 8C14.75 8.41421 14.4142 8.75 14 8.75H8.90234C8.96417 8.51006 9 8.25925 9 8C9 7.74075 8.96417 7.48994 8.90234 7.25H14ZM7.59766 2.75C7.53583 2.98994 7.5 3.24075 7.5 3.5C7.5 3.75925 7.53583 4.01006 7.59766 4.25H2C1.58579 4.25 1.25 3.91421 1.25 3.5C1.25 3.08579 1.58579 2.75 2 2.75H7.59766ZM14 2.75C14.4142 2.75 14.75 3.08579 14.75 3.5C14.75 3.91421 14.4142 4.25 14 4.25H13.4023C13.4642 4.01006 13.5 3.75925 13.5 3.5C13.5 3.24075 13.4642 2.98994 13.4023 2.75H14Z",
2135
+ circles: [
2136
+ { cx: "6", cy: "8", r: "0.998596" },
2137
+ { cx: "10.4999", cy: "3.5", r: "0.998657" },
2138
+ { cx: "9.75015", cy: "12.5", r: "0.997986" }
2139
+ ]
2140
+ };
2141
+
2142
+ // src/vue/components/Folder.ts
2143
+ var Folder = defineComponent({
2144
+ name: "DialKitFolder",
2145
+ props: {
2146
+ title: { type: String, required: true },
2147
+ defaultOpen: { type: Boolean, default: true },
2148
+ isRoot: { type: Boolean, default: false },
2149
+ inline: { type: Boolean, default: false },
2150
+ toolbar: {
2151
+ type: null,
2152
+ required: false,
2153
+ default: null
2154
+ },
2155
+ panelHeightOffset: {
2156
+ type: Number,
2157
+ default: 10
2158
+ }
2159
+ },
2160
+ emits: ["openChange"],
2161
+ setup(props, { emit, slots }) {
2162
+ const isOpen = ref2(props.defaultOpen);
2163
+ const isCollapsed = ref2(!props.defaultOpen);
2164
+ const contentRef = ref2(null);
2165
+ const contentHeight = ref2(void 0);
2166
+ const windowHeight = ref2(typeof window !== "undefined" ? window.innerHeight : 800);
2167
+ let resizeHandler = null;
2168
+ if (props.isRoot) {
2169
+ resizeHandler = () => {
2170
+ windowHeight.value = window.innerHeight;
2171
+ };
2172
+ window.addEventListener("resize", resizeHandler);
2173
+ }
2174
+ onUnmounted3(() => {
2175
+ if (resizeHandler) window.removeEventListener("resize", resizeHandler);
2176
+ });
2177
+ const handleToggle = () => {
2178
+ if (props.inline && props.isRoot) return;
2179
+ const next = !isOpen.value;
2180
+ isOpen.value = next;
2181
+ isCollapsed.value = !next;
2182
+ emit("openChange", next);
2183
+ };
2184
+ let ro = null;
2185
+ onMounted3(() => {
2186
+ if (!props.isRoot || typeof ResizeObserver === "undefined") return;
2187
+ const el = contentRef.value;
2188
+ if (!el) return;
2189
+ ro = new ResizeObserver(() => {
2190
+ if (isOpen.value) {
2191
+ const next = el.offsetHeight;
2192
+ if (contentHeight.value !== next) {
2193
+ contentHeight.value = next;
2194
+ }
2195
+ }
2196
+ });
2197
+ ro.observe(el);
2198
+ if (isOpen.value) {
2199
+ contentHeight.value = el.offsetHeight;
2200
+ }
2201
+ });
2202
+ onUnmounted3(() => {
2203
+ ro?.disconnect();
2204
+ });
2205
+ const renderHeader = () => h("div", {
2206
+ class: `dialkit-folder-header ${props.isRoot ? "dialkit-panel-header" : ""}`,
2207
+ onClick: handleToggle
2208
+ }, [
2209
+ h("div", { class: "dialkit-folder-header-top" }, [
2210
+ props.isRoot ? isOpen.value ? h("div", { class: "dialkit-folder-title-row" }, [
2211
+ h("span", { class: "dialkit-folder-title dialkit-folder-title-root" }, props.title)
2212
+ ]) : null : h("div", { class: "dialkit-folder-title-row" }, [
2213
+ h("span", { class: "dialkit-folder-title" }, props.title)
2214
+ ]),
2215
+ props.isRoot && !props.inline ? h("svg", { class: "dialkit-panel-icon", viewBox: "0 0 16 16", fill: "none" }, [
2216
+ h("path", {
2217
+ opacity: "0.5",
2218
+ d: ICON_PANEL.path,
2219
+ fill: "currentColor"
2220
+ }),
2221
+ ...ICON_PANEL.circles.map((c) => h("circle", { cx: c.cx, cy: c.cy, r: c.r, fill: "currentColor", stroke: "currentColor", "stroke-width": "1.25" }))
2222
+ ]) : null,
2223
+ !props.isRoot ? h(motion.svg, {
2224
+ class: "dialkit-folder-icon",
2225
+ viewBox: "0 0 24 24",
2226
+ fill: "none",
2227
+ stroke: "currentColor",
2228
+ "stroke-width": "2.5",
2229
+ "stroke-linecap": "round",
2230
+ "stroke-linejoin": "round",
2231
+ initial: false,
2232
+ animate: { rotate: isOpen.value ? 0 : 180 },
2233
+ transition: { type: "spring", visualDuration: 0.35, bounce: 0.15 }
2234
+ }, [h("path", { d: ICON_CHEVRON })]) : null
2235
+ ]),
2236
+ props.isRoot && props.toolbar && isOpen.value ? h("div", { class: "dialkit-panel-toolbar", onClick: (event) => event.stopPropagation() }, [props.toolbar()]) : null
2237
+ ]);
2238
+ const renderChildren = () => h("div", { class: "dialkit-folder-inner" }, slots.default ? slots.default() : []);
2239
+ const renderContent = () => {
2240
+ if (props.isRoot) {
2241
+ return isOpen.value ? h("div", { class: "dialkit-folder-content" }, [renderChildren()]) : null;
2242
+ }
2243
+ return h(AnimatePresence, { initial: false }, {
2244
+ default: () => isOpen.value ? [h(motion.div, {
2245
+ key: "dialkit-folder-content",
2246
+ class: "dialkit-folder-content",
2247
+ initial: { height: 0, opacity: 0 },
2248
+ animate: { height: "auto", opacity: 1 },
2249
+ exit: { height: 0, opacity: 0 },
2250
+ transition: { type: "spring", visualDuration: 0.35, bounce: 0.1 },
2251
+ style: { clipPath: "inset(0 -20px)" }
2252
+ }, [renderChildren()])] : []
2253
+ });
2254
+ };
2255
+ const folderContent = () => h("div", {
2256
+ ref: props.isRoot ? contentRef : void 0,
2257
+ class: `dialkit-folder ${props.isRoot ? "dialkit-folder-root" : ""}`,
2258
+ "data-open": String(isOpen.value)
2259
+ }, [
2260
+ renderHeader(),
2261
+ renderContent()
2262
+ ]);
2263
+ return () => {
2264
+ if (props.isRoot) {
2265
+ if (props.inline) {
2266
+ return h("div", { class: "dialkit-panel-inner dialkit-panel-inline" }, [folderContent()]);
2267
+ }
2268
+ const panelStyle = isOpen.value ? {
2269
+ width: 280,
2270
+ height: contentHeight.value !== void 0 ? Math.min(contentHeight.value + props.panelHeightOffset, windowHeight.value - 32) : "auto",
2271
+ borderRadius: 14,
2272
+ boxShadow: "var(--dial-shadow)",
2273
+ cursor: void 0,
2274
+ overflowY: "auto"
2275
+ } : {
2276
+ width: 42,
2277
+ height: 42,
2278
+ borderRadius: 21,
2279
+ boxShadow: "var(--dial-shadow-collapsed)",
2280
+ overflow: "hidden",
2281
+ cursor: "pointer"
2282
+ };
2283
+ return h(motion.div, {
2284
+ class: "dialkit-panel-inner",
2285
+ style: panelStyle,
2286
+ onClick: !isOpen.value ? handleToggle : void 0,
2287
+ "data-collapsed": String(isCollapsed.value),
2288
+ whilePress: !isOpen.value ? { scale: 0.9 } : void 0,
2289
+ transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 }
2290
+ }, [folderContent()]);
2291
+ }
2292
+ return folderContent();
2293
+ };
2294
+ }
2295
+ });
2296
+
2297
+ // src/vue/components/Panel.ts
2298
+ import { Fragment, defineComponent as defineComponent14, h as h14, onMounted as onMounted10, onUnmounted as onUnmounted9, ref as ref12 } from "vue";
2299
+ import { AnimatePresence as AnimatePresence4, motion as motion4 } from "motion-v";
2300
+
2301
+ // src/vue/components/Slider.ts
2302
+ import { defineComponent as defineComponent2, h as h2, computed as computed3, nextTick, onMounted as onMounted4, onUnmounted as onUnmounted4, ref as ref3, watch as watch3 } from "vue";
2303
+ import { animate, motionValue } from "motion-v";
2304
+
2305
+ // src/shortcut-utils.ts
2306
+ function decimalsForStep(step) {
2307
+ const s = step.toString();
2308
+ const dot = s.indexOf(".");
2309
+ return dot === -1 ? 0 : s.length - dot - 1;
2310
+ }
2311
+ function roundValue(val, step) {
2312
+ const raw = Math.round(val / step) * step;
2313
+ return parseFloat(raw.toFixed(decimalsForStep(step)));
2314
+ }
2315
+ function getEffectiveStep(control, shortcut) {
993
2316
  const min = control.min ?? 0;
994
2317
  const max = control.max ?? 1;
995
2318
  const range = max - min;
@@ -1083,9 +2406,9 @@ var Slider = defineComponent2({
1083
2406
  },
1084
2407
  emits: ["change"],
1085
2408
  setup(props, { emit }) {
1086
- const min = computed2(() => props.min ?? 0);
1087
- const max = computed2(() => props.max ?? 1);
1088
- const step = computed2(() => props.step ?? 0.01);
2409
+ const min = computed3(() => props.min ?? 0);
2410
+ const max = computed3(() => props.max ?? 1);
2411
+ const step = computed3(() => props.step ?? 0.01);
1089
2412
  const wrapperRef = ref3(null);
1090
2413
  const trackRef = ref3(null);
1091
2414
  const fillRef = ref3(null);
@@ -1105,9 +2428,9 @@ var Slider = defineComponent2({
1105
2428
  const handleOpacityMv = motionValue(0);
1106
2429
  const handleScaleXMv = motionValue(0.25);
1107
2430
  const handleScaleYMv = motionValue(1);
1108
- const percentage = computed2(() => (props.value - min.value) / (max.value - min.value) * 100);
1109
- const isActive = computed2(() => isInteracting.value || isHovered.value);
1110
- const displayValue = computed2(() => props.value.toFixed(decimalsForStep(step.value)));
2431
+ const percentage = computed3(() => (props.value - min.value) / (max.value - min.value) * 100);
2432
+ const isActive = computed3(() => isInteracting.value || isHovered.value);
2433
+ const displayValue = computed3(() => props.value.toFixed(decimalsForStep(step.value)));
1111
2434
  let pointerDownPos = null;
1112
2435
  let isClickFlag = true;
1113
2436
  let wrapperRect = null;
@@ -1296,15 +2619,15 @@ var Slider = defineComponent2({
1296
2619
  isValueHovered.value = false;
1297
2620
  }
1298
2621
  };
1299
- watch2(() => props.value, () => {
2622
+ watch3(() => props.value, () => {
1300
2623
  if (!isInteracting.value && !snapAnim) {
1301
2624
  fillPercent.jump(percentage.value);
1302
2625
  }
1303
2626
  });
1304
- watch2([isInteracting, isHovered, isDragging, () => props.value], () => {
2627
+ watch3([isInteracting, isHovered, isDragging, () => props.value], () => {
1305
2628
  animateHandleState();
1306
2629
  });
1307
- watch2([isValueHovered, showInput, isValueEditable], () => {
2630
+ watch3([isValueHovered, showInput, isValueEditable], () => {
1308
2631
  if (hoverTimeout) {
1309
2632
  clearTimeout(hoverTimeout);
1310
2633
  hoverTimeout = null;
@@ -1318,14 +2641,14 @@ var Slider = defineComponent2({
1318
2641
  isValueEditable.value = false;
1319
2642
  }
1320
2643
  });
1321
- watch2(showInput, async (visible) => {
2644
+ watch3(showInput, async (visible) => {
1322
2645
  if (!visible) return;
1323
2646
  await nextTick();
1324
2647
  inputRef.value?.focus();
1325
2648
  inputRef.value?.select();
1326
2649
  });
1327
- const discreteSteps = computed2(() => (max.value - min.value) / step.value);
1328
- const hashMarks = computed2(() => {
2650
+ const discreteSteps = computed3(() => (max.value - min.value) / step.value);
2651
+ const hashMarks = computed3(() => {
1329
2652
  const marks = [];
1330
2653
  if (discreteSteps.value <= 10) {
1331
2654
  const count = Math.max(0, Math.floor(discreteSteps.value) - 1);
@@ -1346,7 +2669,7 @@ var Slider = defineComponent2({
1346
2669
  let unsubHandleOpacity = null;
1347
2670
  let unsubHandleScaleX = null;
1348
2671
  let unsubHandleScaleY = null;
1349
- onMounted3(() => {
2672
+ onMounted4(() => {
1350
2673
  unsubFill = fillPercent.on("change", applyFillStyles);
1351
2674
  unsubRubber = rubberStretchPx.on("change", applyRubberStyles);
1352
2675
  unsubHandleOpacity = handleOpacityMv.on("change", applyHandleVisualStyles);
@@ -1357,7 +2680,7 @@ var Slider = defineComponent2({
1357
2680
  applyHandleVisualStyles();
1358
2681
  animateHandleState();
1359
2682
  });
1360
- onUnmounted3(() => {
2683
+ onUnmounted4(() => {
1361
2684
  if (hoverTimeout) clearTimeout(hoverTimeout);
1362
2685
  snapAnim?.stop();
1363
2686
  rubberAnim?.stop();
@@ -1446,7 +2769,7 @@ var Slider = defineComponent2({
1446
2769
  import { defineComponent as defineComponent4, h as h4 } from "vue";
1447
2770
 
1448
2771
  // src/vue/components/SegmentedControl.ts
1449
- import { defineComponent as defineComponent3, h as h3, nextTick as nextTick2, onMounted as onMounted4, onUnmounted as onUnmounted4, ref as ref4, watch as watch3 } from "vue";
2772
+ import { defineComponent as defineComponent3, h as h3, nextTick as nextTick2, onMounted as onMounted5, onUnmounted as onUnmounted5, ref as ref4, watch as watch4 } from "vue";
1450
2773
  import { animate as animate2 } from "motion";
1451
2774
  var SegmentedControl = defineComponent3({
1452
2775
  name: "DialKitSegmentedControl",
@@ -1517,7 +2840,7 @@ var SegmentedControl = defineComponent3({
1517
2840
  );
1518
2841
  };
1519
2842
  let ro;
1520
- onMounted4(() => {
2843
+ onMounted5(() => {
1521
2844
  nextTick2(() => {
1522
2845
  updatePill(false);
1523
2846
  hasAnimated = true;
@@ -1527,11 +2850,11 @@ var SegmentedControl = defineComponent3({
1527
2850
  ro.observe(containerRef.value);
1528
2851
  }
1529
2852
  });
1530
- onUnmounted4(() => {
2853
+ onUnmounted5(() => {
1531
2854
  pillAnim?.stop();
1532
2855
  ro?.disconnect();
1533
2856
  });
1534
- watch3(
2857
+ watch4(
1535
2858
  () => props.value,
1536
2859
  () => {
1537
2860
  updatePill(true);
@@ -1595,10 +2918,10 @@ var Toggle = defineComponent4({
1595
2918
  });
1596
2919
 
1597
2920
  // src/vue/components/SpringControl.ts
1598
- import { defineComponent as defineComponent6, h as h6, onMounted as onMounted5, onUnmounted as onUnmounted5, ref as ref5 } from "vue";
2921
+ import { defineComponent as defineComponent6, h as h6, onMounted as onMounted6, onUnmounted as onUnmounted6, ref as ref5 } from "vue";
1599
2922
 
1600
2923
  // src/vue/components/SpringVisualization.ts
1601
- import { defineComponent as defineComponent5, h as h5, computed as computed3 } from "vue";
2924
+ import { defineComponent as defineComponent5, h as h5, computed as computed4 } from "vue";
1602
2925
  function generateSpringCurve(stiffness, damping, mass, duration) {
1603
2926
  const points = [];
1604
2927
  const steps = 100;
@@ -1632,7 +2955,7 @@ var SpringVisualization = defineComponent5({
1632
2955
  setup(props) {
1633
2956
  const width = 256;
1634
2957
  const height = 140;
1635
- const pathData = computed3(() => {
2958
+ const pathData = computed4(() => {
1636
2959
  let stiffness;
1637
2960
  let damping;
1638
2961
  let mass;
@@ -1708,12 +3031,12 @@ var SpringControl = defineComponent6({
1708
3031
  setup(props, { emit }) {
1709
3032
  const mode = ref5(DialStore.getSpringMode(props.panelId, props.path));
1710
3033
  let unsub;
1711
- onMounted5(() => {
3034
+ onMounted6(() => {
1712
3035
  unsub = DialStore.subscribe(props.panelId, () => {
1713
3036
  mode.value = DialStore.getSpringMode(props.panelId, props.path);
1714
3037
  });
1715
3038
  });
1716
- onUnmounted5(() => {
3039
+ onUnmounted6(() => {
1717
3040
  unsub?.();
1718
3041
  });
1719
3042
  const isSimpleMode = () => mode.value === "simple";
@@ -1809,10 +3132,10 @@ var SpringControl = defineComponent6({
1809
3132
  });
1810
3133
 
1811
3134
  // src/vue/components/TransitionControl.ts
1812
- import { defineComponent as defineComponent8, h as h8, onMounted as onMounted6, onUnmounted as onUnmounted6, ref as ref6 } from "vue";
3135
+ import { defineComponent as defineComponent8, h as h8, onMounted as onMounted7, onUnmounted as onUnmounted7, ref as ref6 } from "vue";
1813
3136
 
1814
3137
  // src/vue/components/EasingVisualization.ts
1815
- import { defineComponent as defineComponent7, h as h7, computed as computed4 } from "vue";
3138
+ import { defineComponent as defineComponent7, h as h7, computed as computed5 } from "vue";
1816
3139
  var EasingVisualization = defineComponent7({
1817
3140
  name: "DialKitEasingVisualization",
1818
3141
  props: {
@@ -1826,7 +3149,7 @@ var EasingVisualization = defineComponent7({
1826
3149
  const pad = 10;
1827
3150
  const inner = size - pad * 2;
1828
3151
  const unit = inner / 2;
1829
- const curve = computed4(() => {
3152
+ const curve = computed5(() => {
1830
3153
  const [x1, y1, x2, y2] = props.easing.ease;
1831
3154
  const toSvg = (nx, ny) => ({
1832
3155
  x: pad + (nx + 0.5) * unit,
@@ -1929,18 +3252,20 @@ var TransitionControl = defineComponent8({
1929
3252
  value: {
1930
3253
  type: Object,
1931
3254
  required: true
1932
- }
3255
+ },
3256
+ hideDuration: { type: Boolean, default: false },
3257
+ durationControl: Object
1933
3258
  },
1934
3259
  emits: ["change"],
1935
3260
  setup(props, { emit }) {
1936
3261
  const mode = ref6(DialStore.getTransitionMode(props.panelId, props.path));
1937
3262
  let unsub;
1938
- onMounted6(() => {
3263
+ onMounted7(() => {
1939
3264
  unsub = DialStore.subscribe(props.panelId, () => {
1940
3265
  mode.value = DialStore.getTransitionMode(props.panelId, props.path);
1941
3266
  });
1942
3267
  });
1943
- onUnmounted6(() => unsub?.());
3268
+ onUnmounted7(() => unsub?.());
1944
3269
  const cache = {
1945
3270
  easing: props.value.type === "easing" ? { ...props.value } : { type: "easing", duration: 0.3, ease: [1, -0.4, 0.5, 1] },
1946
3271
  simple: props.value.type === "spring" && props.value.visualDuration !== void 0 ? { ...props.value } : { type: "spring", visualDuration: 0.3, bounce: 0.2 },
@@ -1992,6 +3317,18 @@ var TransitionControl = defineComponent8({
1992
3317
  const isSimpleSpring = mode.value === "simple";
1993
3318
  const currentSpring = spring();
1994
3319
  const currentEasing = easing();
3320
+ const durationSlider = !props.hideDuration && (isEasing || isSimpleSpring) ? h8(Slider, {
3321
+ label: "Duration",
3322
+ value: props.durationControl?.value ?? (isEasing ? currentEasing.duration : currentSpring.visualDuration ?? 0.3),
3323
+ min: props.durationControl?.min ?? 0.1,
3324
+ max: props.durationControl?.max ?? (isEasing ? 2 : 1),
3325
+ step: props.durationControl?.step ?? 0.05,
3326
+ unit: "s",
3327
+ onChange: props.durationControl?.onChange ?? ((next) => {
3328
+ if (isEasing) emit("change", { ...currentEasing, duration: next });
3329
+ else handleSpringUpdate("visualDuration", next);
3330
+ })
3331
+ }) : null;
1995
3332
  return h8(Folder, { title: props.label, defaultOpen: true }, {
1996
3333
  default: () => [
1997
3334
  h8("div", { style: { display: "flex", flexDirection: "column", gap: "6px" } }, [
@@ -2013,29 +3350,11 @@ var TransitionControl = defineComponent8({
2013
3350
  h8(Slider, { label: "y1", value: currentEasing.ease[1], min: -1, max: 2, step: 0.01, onChange: (next) => updateEase(1, next) }),
2014
3351
  h8(Slider, { label: "x2", value: currentEasing.ease[2], min: 0, max: 1, step: 0.01, onChange: (next) => updateEase(2, next) }),
2015
3352
  h8(Slider, { label: "y2", value: currentEasing.ease[3], min: -1, max: 2, step: 0.01, onChange: (next) => updateEase(3, next) }),
2016
- h8(Slider, {
2017
- label: "Duration",
2018
- value: currentEasing.duration,
2019
- min: 0.1,
2020
- max: 2,
2021
- step: 0.05,
2022
- unit: "s",
2023
- onChange: (next) => emit("change", { ...currentEasing, duration: next })
2024
- }),
2025
3353
  h8(EaseTextInput, {
2026
3354
  ease: currentEasing.ease,
2027
3355
  onChange: (next) => emit("change", { ...currentEasing, ease: next })
2028
3356
  })
2029
3357
  ] : isSimpleSpring ? [
2030
- h8(Slider, {
2031
- label: "Duration",
2032
- value: currentSpring.visualDuration ?? 0.3,
2033
- min: 0.1,
2034
- max: 1,
2035
- step: 0.05,
2036
- unit: "s",
2037
- onChange: (next) => handleSpringUpdate("visualDuration", next)
2038
- }),
2039
3358
  h8(Slider, {
2040
3359
  label: "Bounce",
2041
3360
  value: currentSpring.bounce ?? 0.2,
@@ -2069,7 +3388,8 @@ var TransitionControl = defineComponent8({
2069
3388
  step: 0.1,
2070
3389
  onChange: (next) => handleSpringUpdate("mass", next)
2071
3390
  })
2072
- ]
3391
+ ],
3392
+ durationSlider
2073
3393
  ])
2074
3394
  ]
2075
3395
  });
@@ -2105,7 +3425,7 @@ var TextControl = defineComponent9({
2105
3425
  });
2106
3426
 
2107
3427
  // src/vue/components/SelectControl.ts
2108
- import { Teleport, defineComponent as defineComponent10, h as h10, onMounted as onMounted7, ref as ref8, watch as watch4 } from "vue";
3428
+ import { Teleport, defineComponent as defineComponent10, h as h10, onMounted as onMounted8, ref as ref8, watch as watch5 } from "vue";
2109
3429
  import { AnimatePresence as AnimatePresence2, motion as motion2 } from "motion-v";
2110
3430
 
2111
3431
  // src/dropdown-position.ts
@@ -2182,7 +3502,7 @@ var SelectControl = defineComponent10({
2182
3502
  if (isOpen.value) closeDropdown();
2183
3503
  else openDropdown();
2184
3504
  };
2185
- watch4(isOpen, (open, _, onCleanup) => {
3505
+ watch5(isOpen, (open, _, onCleanup) => {
2186
3506
  if (!open) return;
2187
3507
  const handleViewportChange = () => updatePos();
2188
3508
  const handleDocumentClick = (event) => {
@@ -2200,7 +3520,7 @@ var SelectControl = defineComponent10({
2200
3520
  window.removeEventListener("scroll", handleViewportChange, true);
2201
3521
  });
2202
3522
  });
2203
- onMounted7(() => {
3523
+ onMounted8(() => {
2204
3524
  portalTarget.value = getDialKitPortalRoot(triggerRef.value) ?? document.body;
2205
3525
  });
2206
3526
  return () => h10("div", { class: "dialkit-select-row" }, [
@@ -2259,7 +3579,7 @@ var SelectControl = defineComponent10({
2259
3579
  });
2260
3580
 
2261
3581
  // src/vue/components/ColorControl.ts
2262
- import { defineComponent as defineComponent11, h as h11, ref as ref9, watch as watch5 } from "vue";
3582
+ import { defineComponent as defineComponent11, h as h11, ref as ref9, watch as watch6 } from "vue";
2263
3583
  var HEX_COLOR_REGEX = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/;
2264
3584
  function expandShorthandHex(hex) {
2265
3585
  if (hex.length !== 4) return hex;
@@ -2278,7 +3598,7 @@ var ColorControl = defineComponent11({
2278
3598
  const isEditing = ref9(false);
2279
3599
  const editValue = ref9(props.value);
2280
3600
  const colorInputRef = ref9(null);
2281
- watch5(() => props.value, (value) => {
3601
+ watch6(() => props.value, (value) => {
2282
3602
  if (!isEditing.value) editValue.value = value;
2283
3603
  });
2284
3604
  const submit = () => {
@@ -2333,7 +3653,7 @@ var ColorControl = defineComponent11({
2333
3653
  });
2334
3654
 
2335
3655
  // src/vue/components/PresetManager.ts
2336
- import { Teleport as Teleport2, defineComponent as defineComponent12, h as h12, ref as ref10, watch as watch6 } from "vue";
3656
+ import { Teleport as Teleport2, defineComponent as defineComponent12, h as h12, ref as ref10, watch as watch7 } from "vue";
2337
3657
  import { AnimatePresence as AnimatePresence3, motion as motion3 } from "motion-v";
2338
3658
  var PresetManager = defineComponent12({
2339
3659
  name: "DialKitPresetManager",
@@ -2383,7 +3703,7 @@ var PresetManager = defineComponent12({
2383
3703
  if (isOpen.value) close();
2384
3704
  else open();
2385
3705
  };
2386
- watch6(isOpen, (open2, _, onCleanup) => {
3706
+ watch7(isOpen, (open2, _, onCleanup) => {
2387
3707
  if (!open2) return;
2388
3708
  const handler = (event) => {
2389
3709
  const target = event.target;
@@ -2481,7 +3801,7 @@ var PresetManager = defineComponent12({
2481
3801
  });
2482
3802
 
2483
3803
  // src/vue/components/ShortcutListener.ts
2484
- import { defineComponent as defineComponent13, inject, onMounted as onMounted8, onUnmounted as onUnmounted7, provide, ref as ref11 } from "vue";
3804
+ import { defineComponent as defineComponent13, inject, onMounted as onMounted9, onUnmounted as onUnmounted8, provide, ref as ref11 } from "vue";
2485
3805
  var ShortcutKey = /* @__PURE__ */ Symbol("DialKitShortcut");
2486
3806
  function useShortcutContext() {
2487
3807
  return inject(ShortcutKey, {
@@ -2661,7 +3981,7 @@ var ShortcutListener = defineComponent13({
2661
3981
  activePanelId.value = null;
2662
3982
  activePath.value = null;
2663
3983
  };
2664
- onMounted8(() => {
3984
+ onMounted9(() => {
2665
3985
  window.addEventListener("keydown", handleKeyDown);
2666
3986
  window.addEventListener("keyup", handleKeyUp);
2667
3987
  window.addEventListener("wheel", handleWheel, { passive: false });
@@ -2670,7 +3990,7 @@ var ShortcutListener = defineComponent13({
2670
3990
  window.addEventListener("mousemove", handleMouseMove);
2671
3991
  window.addEventListener("blur", handleWindowBlur);
2672
3992
  });
2673
- onUnmounted7(() => {
3993
+ onUnmounted8(() => {
2674
3994
  window.removeEventListener("keydown", handleKeyDown);
2675
3995
  window.removeEventListener("keyup", handleKeyUp);
2676
3996
  window.removeEventListener("wheel", handleWheel);
@@ -2702,7 +4022,8 @@ var Panel = defineComponent14({
2702
4022
  variant: {
2703
4023
  type: String,
2704
4024
  default: "root"
2705
- }
4025
+ },
4026
+ toolbarExtra: Function
2706
4027
  },
2707
4028
  emits: ["openChange"],
2708
4029
  setup(props, { emit }) {
@@ -2714,14 +4035,14 @@ var Panel = defineComponent14({
2714
4035
  const hasShortcuts = () => Object.keys(DialStore.getPanel(props.panel.id)?.shortcuts ?? {}).length > 0;
2715
4036
  let unsubscribe;
2716
4037
  let copiedTimeout = null;
2717
- onMounted9(() => {
4038
+ onMounted10(() => {
2718
4039
  unsubscribe = DialStore.subscribe(props.panel.id, () => {
2719
4040
  values.value = DialStore.getValues(props.panel.id);
2720
4041
  presets.value = DialStore.getPresets(props.panel.id);
2721
4042
  activePresetId.value = DialStore.getActivePresetId(props.panel.id);
2722
4043
  });
2723
4044
  });
2724
- onUnmounted8(() => {
4045
+ onUnmounted9(() => {
2725
4046
  unsubscribe?.();
2726
4047
  if (copiedTimeout) {
2727
4048
  window.clearTimeout(copiedTimeout);
@@ -2923,7 +4244,8 @@ Apply these values as the new defaults in the useDialKit call.`;
2923
4244
  })
2924
4245
  ]),
2925
4246
  "Copy"
2926
- ])
4247
+ ]),
4248
+ props.toolbarExtra?.()
2927
4249
  ]);
2928
4250
  if (props.variant === "section") {
2929
4251
  return h14(Folder, {
@@ -2956,6 +4278,39 @@ Apply these values as the new defaults in the useDialKit call.`;
2956
4278
  }
2957
4279
  });
2958
4280
 
4281
+ // src/vue/components/Timeline/TimelineToggleButton.ts
4282
+ import { defineComponent as defineComponent15, h as h15, onMounted as onMounted11, onUnmounted as onUnmounted10, ref as ref13 } from "vue";
4283
+ var TimelineToggleButton = defineComponent15({
4284
+ name: "DialKitTimelineToggleButton",
4285
+ setup() {
4286
+ const visible = ref13(TimelineUiStore.getVisible());
4287
+ let unsubscribe;
4288
+ onMounted11(() => {
4289
+ unsubscribe = TimelineUiStore.subscribe(() => {
4290
+ visible.value = TimelineUiStore.getVisible();
4291
+ });
4292
+ });
4293
+ onUnmounted10(() => unsubscribe?.());
4294
+ return () => {
4295
+ const label = visible.value ? "Hide timeline" : "Show timeline";
4296
+ return h15("button", {
4297
+ class: "dialkit-toolbar-add dialkit-timeline-toolbar-toggle",
4298
+ "data-active": visible.value || void 0,
4299
+ "aria-pressed": visible.value,
4300
+ "aria-label": label,
4301
+ title: label,
4302
+ onClick: () => TimelineUiStore.toggle()
4303
+ }, [
4304
+ h15(
4305
+ "svg",
4306
+ { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true" },
4307
+ ICON_TIMELINE.map((path) => h15("path", { d: path, fill: "currentColor" }))
4308
+ )
4309
+ ]);
4310
+ };
4311
+ }
4312
+ });
4313
+
2959
4314
  // src/panel-drag.ts
2960
4315
  var PANEL_DRAG_THRESHOLD = 8;
2961
4316
  var COLLAPSED_PANEL_SIZE = 42;
@@ -3021,8 +4376,8 @@ function blockPanelDragClick(handle) {
3021
4376
  }
3022
4377
 
3023
4378
  // src/vue/components/DialRoot.ts
3024
- 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;
3025
- var DialRoot = defineComponent15({
4379
+ var isDevDefault2 = 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;
4380
+ var DialRoot = defineComponent16({
3026
4381
  name: "DialKitDialRoot",
3027
4382
  props: {
3028
4383
  position: {
@@ -3043,17 +4398,19 @@ var DialRoot = defineComponent15({
3043
4398
  },
3044
4399
  productionEnabled: {
3045
4400
  type: Boolean,
3046
- default: isDevDefault
4401
+ default: isDevDefault2
3047
4402
  }
3048
4403
  },
3049
4404
  emits: ["openChange"],
3050
4405
  setup(props, { emit }) {
3051
- const panels = ref13([]);
3052
- const mounted = ref13(false);
3053
- const panelRef = ref13(null);
3054
- const dragOffset = ref13(null);
3055
- const activePosition = ref13(props.position);
3056
- let unsubscribe;
4406
+ const panels = ref14([]);
4407
+ const timelines = ref14([]);
4408
+ const mounted = ref14(false);
4409
+ const panelRef = ref14(null);
4410
+ const dragOffset = ref14(null);
4411
+ const activePosition = ref14(props.position);
4412
+ let unsubscribePanels;
4413
+ let unsubscribeTimelines;
3057
4414
  let observer;
3058
4415
  let lastDragOffset = null;
3059
4416
  let dragging = false;
@@ -3146,24 +4503,73 @@ var DialRoot = defineComponent15({
3146
4503
  right: "auto",
3147
4504
  bottom: "auto"
3148
4505
  } : void 0;
3149
- const originX = computed5(() => props.mode === "inline" ? void 0 : getPanelOriginX(activePosition.value, dragOffset.value));
3150
- onMounted10(() => {
4506
+ const originX = computed6(() => props.mode === "inline" ? void 0 : getPanelOriginX(activePosition.value, dragOffset.value));
4507
+ onMounted12(() => {
3151
4508
  mounted.value = true;
3152
- panels.value = DialStore.getPanels();
4509
+ panels.value = DialStore.getPanels("panel");
4510
+ timelines.value = TimelineStore.getTimelines();
3153
4511
  syncPanelOpenStates();
3154
- unsubscribe = DialStore.subscribeGlobal(() => {
3155
- panels.value = DialStore.getPanels();
4512
+ unsubscribePanels = DialStore.subscribeGlobal(() => {
4513
+ panels.value = DialStore.getPanels("panel");
3156
4514
  syncPanelOpenStates();
3157
4515
  });
4516
+ unsubscribeTimelines = TimelineStore.subscribeGlobal(() => {
4517
+ timelines.value = TimelineStore.getTimelines();
4518
+ });
3158
4519
  nextTick3(connectObserver);
3159
4520
  });
3160
- onUnmounted9(() => {
3161
- unsubscribe?.();
4521
+ onUnmounted11(() => {
4522
+ unsubscribePanels?.();
4523
+ unsubscribeTimelines?.();
3162
4524
  observer?.disconnect();
3163
4525
  });
3164
- const renderContent = () => h15(ShortcutListener, null, {
3165
- default: () => h15("div", { class: "dialkit-root", "data-mode": props.mode, "data-theme": props.theme }, [
3166
- h15("div", {
4526
+ const timelineToggle = () => timelines.value.length > 0 ? h16(TimelineToggleButton) : null;
4527
+ const renderPanels = () => {
4528
+ if (panels.value.length === 0) {
4529
+ return [h16("div", { class: "dialkit-panel-wrapper" }, [
4530
+ h16(Folder, {
4531
+ title: "DialKit",
4532
+ defaultOpen: props.mode === "inline" || props.defaultOpen,
4533
+ isRoot: true,
4534
+ inline: props.mode === "inline",
4535
+ toolbar: timelineToggle,
4536
+ onOpenChange: handleRootOpenChange,
4537
+ panelHeightOffset: 2
4538
+ }, { default: () => [h16("div", { class: "dialkit-timeline-toolkit-only" }, "Timeline")] })
4539
+ ])];
4540
+ }
4541
+ if (panels.value.length > 1) {
4542
+ return [h16("div", { class: "dialkit-panel-wrapper" }, [
4543
+ h16(Folder, {
4544
+ title: "DialKit",
4545
+ defaultOpen: props.mode === "inline" || props.defaultOpen,
4546
+ isRoot: true,
4547
+ inline: props.mode === "inline",
4548
+ toolbar: timelineToggle,
4549
+ onOpenChange: handleRootOpenChange,
4550
+ panelHeightOffset: 2
4551
+ }, {
4552
+ default: () => panels.value.map((panel) => h16(Panel, {
4553
+ key: panel.id,
4554
+ panel,
4555
+ defaultOpen: true,
4556
+ variant: "section"
4557
+ }))
4558
+ })
4559
+ ])];
4560
+ }
4561
+ return panels.value.map((panel) => h16(Panel, {
4562
+ key: panel.id,
4563
+ panel,
4564
+ defaultOpen: props.mode === "inline" || props.defaultOpen,
4565
+ inline: props.mode === "inline",
4566
+ toolbarExtra: timelineToggle,
4567
+ onOpenChange: (open) => handlePanelOpenChange(panel.id, open)
4568
+ }));
4569
+ };
4570
+ const renderContent = () => h16(ShortcutListener, null, {
4571
+ default: () => h16("div", { class: "dialkit-root", "data-mode": props.mode, "data-theme": props.theme }, [
4572
+ h16("div", {
3167
4573
  ref: (el) => {
3168
4574
  panelRef.value = el;
3169
4575
  connectObserver();
@@ -3178,41 +4584,17 @@ var DialRoot = defineComponent15({
3178
4584
  onPointermove: props.mode === "inline" ? void 0 : handlePointerMove,
3179
4585
  onPointerup: props.mode === "inline" ? void 0 : handlePointerUp,
3180
4586
  onPointercancel: props.mode === "inline" ? void 0 : handlePointerUp
3181
- }, panels.value.length > 1 ? [
3182
- h15("div", { class: "dialkit-panel-wrapper" }, [
3183
- h15(Folder, {
3184
- title: "DialKit",
3185
- defaultOpen: props.mode === "inline" || props.defaultOpen,
3186
- isRoot: true,
3187
- inline: props.mode === "inline",
3188
- onOpenChange: handleRootOpenChange,
3189
- panelHeightOffset: 2
3190
- }, {
3191
- default: () => panels.value.map((panel) => h15(Panel, {
3192
- key: panel.id,
3193
- panel,
3194
- defaultOpen: true,
3195
- variant: "section"
3196
- }))
3197
- })
3198
- ])
3199
- ] : panels.value.map((panel) => h15(Panel, {
3200
- key: panel.id,
3201
- panel,
3202
- defaultOpen: props.mode === "inline" || props.defaultOpen,
3203
- inline: props.mode === "inline",
3204
- onOpenChange: (open) => handlePanelOpenChange(panel.id, open)
3205
- })))
4587
+ }, renderPanels())
3206
4588
  ])
3207
4589
  });
3208
4590
  return () => {
3209
- if (!props.productionEnabled || !mounted.value || typeof window === "undefined" || panels.value.length === 0) {
4591
+ if (!props.productionEnabled || !mounted.value || typeof window === "undefined" || panels.value.length === 0 && timelines.value.length === 0) {
3210
4592
  return null;
3211
4593
  }
3212
4594
  if (props.mode === "inline") {
3213
4595
  return renderContent();
3214
4596
  }
3215
- return h15(Teleport3, { to: "body" }, renderContent());
4597
+ return h16(Teleport3, { to: "body" }, renderContent());
3216
4598
  };
3217
4599
  }
3218
4600
  });
@@ -3230,11 +4612,11 @@ function mountDialRoot(el, value) {
3230
4612
  if (typeof window === "undefined") return;
3231
4613
  const host = document.createElement("div");
3232
4614
  el.appendChild(host);
3233
- const props = shallowRef2(normalizeDirectiveValue(value));
3234
- const RootHost = defineComponent16({
4615
+ const props = shallowRef3(normalizeDirectiveValue(value));
4616
+ const RootHost = defineComponent17({
3235
4617
  name: "DialKitDirectiveHost",
3236
4618
  setup() {
3237
- return () => h16(DialRoot, props.value);
4619
+ return () => h17(DialRoot, props.value);
3238
4620
  }
3239
4621
  });
3240
4622
  const app = createApp(RootHost);
@@ -3265,8 +4647,1129 @@ var vDialKit = {
3265
4647
  }
3266
4648
  };
3267
4649
 
4650
+ // src/vue/components/Timeline/DialTimeline.ts
4651
+ import {
4652
+ Teleport as Teleport4,
4653
+ computed as computed7,
4654
+ defineComponent as defineComponent19,
4655
+ h as h19,
4656
+ nextTick as nextTick4,
4657
+ onMounted as onMounted13,
4658
+ onUnmounted as onUnmounted12,
4659
+ ref as ref15,
4660
+ watch as watch8
4661
+ } from "vue";
4662
+
4663
+ // src/vue/components/ControlRenderer.ts
4664
+ import { Fragment as Fragment2, defineComponent as defineComponent18, h as h18 } from "vue";
4665
+ import { inject as inject2 } from "vue";
4666
+ var ControlRenderer = defineComponent18({
4667
+ name: "DialKitControlRenderer",
4668
+ props: {
4669
+ panelId: { type: String, required: true },
4670
+ controls: { type: Array, required: true },
4671
+ values: { type: Object, required: true },
4672
+ transitionDuration: Object
4673
+ },
4674
+ setup(props) {
4675
+ const shortcut = inject2(ShortcutKey, void 0);
4676
+ const renderControl = (control) => {
4677
+ const value = props.values[control.path];
4678
+ switch (control.type) {
4679
+ case "slider":
4680
+ return h18(Slider, {
4681
+ key: control.path,
4682
+ label: control.label,
4683
+ value,
4684
+ min: control.min,
4685
+ max: control.max,
4686
+ step: control.step,
4687
+ shortcut: control.shortcut,
4688
+ shortcutActive: shortcut?.activePanelId.value === props.panelId && shortcut.activePath.value === control.path,
4689
+ onChange: (next) => DialStore.updateValue(props.panelId, control.path, next)
4690
+ });
4691
+ case "toggle":
4692
+ return h18(Toggle, {
4693
+ key: control.path,
4694
+ label: control.label,
4695
+ checked: value,
4696
+ shortcut: control.shortcut,
4697
+ shortcutActive: shortcut?.activePanelId.value === props.panelId && shortcut.activePath.value === control.path,
4698
+ onChange: (next) => DialStore.updateValue(props.panelId, control.path, next)
4699
+ });
4700
+ case "spring":
4701
+ return h18(SpringControl, {
4702
+ key: control.path,
4703
+ panelId: props.panelId,
4704
+ path: control.path,
4705
+ label: control.label,
4706
+ spring: value,
4707
+ onChange: (next) => DialStore.updateValue(props.panelId, control.path, next)
4708
+ });
4709
+ case "transition":
4710
+ return h18(TransitionControl, {
4711
+ key: control.path,
4712
+ panelId: props.panelId,
4713
+ path: control.path,
4714
+ label: control.label,
4715
+ value,
4716
+ durationControl: props.transitionDuration,
4717
+ onChange: (next) => DialStore.updateValue(props.panelId, control.path, next)
4718
+ });
4719
+ case "folder":
4720
+ return h18(Folder, {
4721
+ key: control.path,
4722
+ title: control.label,
4723
+ defaultOpen: control.defaultOpen ?? true
4724
+ }, { default: () => (control.children ?? []).map(renderControl) });
4725
+ case "text":
4726
+ return h18(TextControl, {
4727
+ key: control.path,
4728
+ label: control.label,
4729
+ value,
4730
+ placeholder: control.placeholder,
4731
+ onChange: (next) => DialStore.updateValue(props.panelId, control.path, next)
4732
+ });
4733
+ case "select":
4734
+ return h18(SelectControl, {
4735
+ key: control.path,
4736
+ label: control.label,
4737
+ value,
4738
+ options: control.options ?? [],
4739
+ onChange: (next) => DialStore.updateValue(props.panelId, control.path, next)
4740
+ });
4741
+ case "color":
4742
+ return h18(ColorControl, {
4743
+ key: control.path,
4744
+ label: control.label,
4745
+ value,
4746
+ onChange: (next) => DialStore.updateValue(props.panelId, control.path, next)
4747
+ });
4748
+ case "action":
4749
+ return h18("button", {
4750
+ key: control.path,
4751
+ class: "dialkit-button",
4752
+ onClick: () => DialStore.triggerAction(props.panelId, control.path)
4753
+ }, control.label);
4754
+ default:
4755
+ return null;
4756
+ }
4757
+ };
4758
+ return () => h18(Fragment2, null, props.controls.map(renderControl));
4759
+ }
4760
+ });
4761
+
4762
+ // src/vue/components/Timeline/DialTimeline.ts
4763
+ var DRAG_THRESHOLD_PX = 3;
4764
+ var MAJOR_TICK_TARGET_PX = 140;
4765
+ var MILLISECOND_STEP = 1e-3;
4766
+ var SECOND_TICK_STEPS = [
4767
+ 1e-3,
4768
+ 2e-3,
4769
+ 5e-3,
4770
+ 0.01,
4771
+ 0.02,
4772
+ 0.05,
4773
+ 0.1,
4774
+ 0.2,
4775
+ 0.5,
4776
+ 1,
4777
+ 2,
4778
+ 5,
4779
+ 10,
4780
+ 15,
4781
+ 30,
4782
+ 60,
4783
+ 120,
4784
+ 300,
4785
+ 600
4786
+ ];
4787
+ var MIN_TIMELINE_MAX_ZOOM = 8;
4788
+ var PLAYHEAD_FLAG_WIDTH = 52;
4789
+ var POPOVER_WIDTH = 280;
4790
+ var ZOOM_DRAG_DISTANCE = 180;
4791
+ var DialTimeline = defineComponent19({
4792
+ name: "DialKitTimeline",
4793
+ props: {
4794
+ theme: { type: String, default: "system" },
4795
+ defaultVisible: { type: Boolean, default: true },
4796
+ visible: {
4797
+ type: Boolean,
4798
+ default: void 0
4799
+ },
4800
+ onVisibilityChange: Function,
4801
+ defaultOpen: { type: Boolean, default: true },
4802
+ productionEnabled: { type: Boolean, default: isDevDefault }
4803
+ },
4804
+ setup(props) {
4805
+ const timelines = ref15(TimelineStore.getTimelines());
4806
+ const dockVisible = ref15(TimelineUiStore.getVisible());
4807
+ const mounted = ref15(false);
4808
+ const controllerId = /* @__PURE__ */ Symbol("dialkit-timeline-visibility");
4809
+ let unsubscribeTimelines;
4810
+ let unsubscribeVisibility;
4811
+ let unregisterController;
4812
+ onMounted13(() => {
4813
+ mounted.value = true;
4814
+ unsubscribeVisibility = TimelineUiStore.subscribe(() => {
4815
+ dockVisible.value = TimelineUiStore.getVisible();
4816
+ });
4817
+ unregisterController = TimelineUiStore.registerController(controllerId, {
4818
+ visible: props.visible,
4819
+ defaultVisible: props.defaultVisible,
4820
+ onVisibilityChange: props.onVisibilityChange
4821
+ });
4822
+ dockVisible.value = TimelineUiStore.getVisible();
4823
+ unsubscribeTimelines = TimelineStore.subscribeGlobal(() => {
4824
+ timelines.value = TimelineStore.getTimelines();
4825
+ });
4826
+ });
4827
+ watch8(() => [props.visible, props.defaultVisible, props.onVisibilityChange], () => {
4828
+ TimelineUiStore.updateController(controllerId, {
4829
+ visible: props.visible,
4830
+ defaultVisible: props.defaultVisible,
4831
+ onVisibilityChange: props.onVisibilityChange
4832
+ });
4833
+ });
4834
+ onUnmounted12(() => {
4835
+ unregisterController?.();
4836
+ unsubscribeTimelines?.();
4837
+ unsubscribeVisibility?.();
4838
+ });
4839
+ return () => {
4840
+ if (!props.productionEnabled || !mounted.value || timelines.value.length === 0) return null;
4841
+ return h19(Teleport4, { to: "body" }, [
4842
+ h19("div", {
4843
+ class: "dialkit-root dialkit-timeline",
4844
+ "data-theme": props.theme,
4845
+ hidden: !dockVisible.value
4846
+ }, [
4847
+ h19("div", { class: "dialkit-timeline-dock" }, timelines.value.map((meta) => h19(TimelineSection, {
4848
+ key: meta.id,
4849
+ meta,
4850
+ defaultOpen: props.defaultOpen,
4851
+ theme: props.theme,
4852
+ dockVisible: dockVisible.value
4853
+ })))
4854
+ ])
4855
+ ]);
4856
+ };
4857
+ }
4858
+ });
4859
+ var PlayPauseButton = defineComponent19({
4860
+ props: { id: { type: String, required: true } },
4861
+ setup(props) {
4862
+ const playing = ref15(TimelineStore.getTransport(props.id).playing);
4863
+ let unsubscribe;
4864
+ onMounted13(() => {
4865
+ unsubscribe = TimelineStore.subscribe(props.id, () => {
4866
+ playing.value = TimelineStore.getTransport(props.id).playing;
4867
+ });
4868
+ });
4869
+ onUnmounted12(() => unsubscribe?.());
4870
+ return () => {
4871
+ const label = playing.value ? "Pause" : "Play";
4872
+ const icon = playing.value ? h19(
4873
+ "svg",
4874
+ { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", style: iconStyle },
4875
+ ICON_PAUSE.map((path) => h19("path", { d: path, fill: "currentColor" }))
4876
+ ) : h19("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", style: iconStyle }, [
4877
+ h19("path", { d: ICON_PLAY, fill: "currentColor" })
4878
+ ]);
4879
+ return h19("button", {
4880
+ class: "dialkit-toolbar-add",
4881
+ title: label,
4882
+ "aria-label": label,
4883
+ onClick: () => playing.value ? TimelineStore.pause(props.id) : TimelineStore.play(props.id)
4884
+ }, [h19("span", { style: { position: "relative", width: "16px", height: "16px" } }, [icon])]);
4885
+ };
4886
+ }
4887
+ });
4888
+ var iconStyle = {
4889
+ position: "absolute",
4890
+ inset: 0,
4891
+ width: "16px",
4892
+ height: "16px",
4893
+ color: "var(--dial-text-label)"
4894
+ };
4895
+ var TimelineOverview = defineComponent19({
4896
+ props: {
4897
+ id: { type: String, required: true },
4898
+ duration: { type: Number, required: true },
4899
+ viewStart: { type: Number, required: true },
4900
+ viewEnd: { type: Number, required: true },
4901
+ onNavigate: { type: Function, required: true }
4902
+ },
4903
+ setup(props) {
4904
+ const time = ref15(TimelineStore.getTransport(props.id).time);
4905
+ let scrub = null;
4906
+ let unsubscribe;
4907
+ onMounted13(() => {
4908
+ unsubscribe = TimelineStore.subscribe(props.id, () => {
4909
+ time.value = TimelineStore.getTransport(props.id).time;
4910
+ });
4911
+ });
4912
+ onUnmounted12(() => unsubscribe?.());
4913
+ const seek = (clientX) => {
4914
+ if (!scrub || scrub.rect.width <= 0 || props.duration <= 0) return;
4915
+ const next = clamp((clientX - scrub.rect.left) / scrub.rect.width * props.duration, 0, props.duration);
4916
+ TimelineStore.seek(props.id, next);
4917
+ props.onNavigate(next);
4918
+ };
4919
+ const finish = () => {
4920
+ if (scrub?.wasPlaying) TimelineStore.play(props.id);
4921
+ scrub = null;
4922
+ };
4923
+ return () => {
4924
+ const viewportWidth = props.duration > 0 ? (props.viewEnd - props.viewStart) / props.duration * 100 : 100;
4925
+ const playhead = props.duration > 0 ? time.value / props.duration * 100 : 0;
4926
+ return h19("div", {
4927
+ class: "dialkit-timeline-overview",
4928
+ title: "Drag to scrub the full timeline",
4929
+ onPointerdown: (event) => {
4930
+ event.preventDefault();
4931
+ event.currentTarget.setPointerCapture(event.pointerId);
4932
+ scrub = {
4933
+ wasPlaying: TimelineStore.getTransport(props.id).playing,
4934
+ rect: event.currentTarget.getBoundingClientRect()
4935
+ };
4936
+ TimelineStore.pause(props.id);
4937
+ seek(event.clientX);
4938
+ },
4939
+ onPointermove: (event) => scrub && seek(event.clientX),
4940
+ onPointerup: finish,
4941
+ onPointercancel: finish,
4942
+ onLostpointercapture: finish
4943
+ }, [
4944
+ h19("div", {
4945
+ class: "dialkit-timeline-overview-viewport",
4946
+ "data-zoomed": viewportWidth < 99.999 || void 0,
4947
+ style: { left: `${props.duration > 0 ? props.viewStart / props.duration * 100 : 0}%`, width: `${viewportWidth}%` }
4948
+ }),
4949
+ h19("div", { class: "dialkit-timeline-overview-progress", style: { width: `${playhead}%` } }),
4950
+ h19("div", { class: "dialkit-timeline-overview-playhead", style: { left: `${playhead}%` } })
4951
+ ]);
4952
+ };
4953
+ }
4954
+ });
4955
+ var TimelinePlayheadFlag = defineComponent19({
4956
+ props: {
4957
+ id: { type: String, required: true },
4958
+ duration: { type: Number, required: true },
4959
+ pxPerSecond: { type: Number, required: true },
4960
+ viewStart: { type: Number, required: true },
4961
+ viewEnd: { type: Number, required: true },
4962
+ laneWidth: { type: Number, required: true },
4963
+ ruler: Object,
4964
+ headerClearStart: { type: Number, required: true },
4965
+ headerClearEnd: { type: Number, required: true },
4966
+ onResetView: { type: Function, required: true }
4967
+ },
4968
+ setup(props) {
4969
+ const time = ref15(TimelineStore.getTransport(props.id).time);
4970
+ let unsubscribe;
4971
+ let scrub = null;
4972
+ let cleanup = null;
4973
+ onMounted13(() => {
4974
+ unsubscribe = TimelineStore.subscribe(props.id, () => {
4975
+ time.value = TimelineStore.getTransport(props.id).time;
4976
+ });
4977
+ });
4978
+ onUnmounted12(() => {
4979
+ unsubscribe?.();
4980
+ cleanup?.();
4981
+ });
4982
+ const seek = (clientX) => {
4983
+ if (!scrub || scrub.rect.width <= 0) return;
4984
+ TimelineStore.seek(props.id, clamp(
4985
+ scrub.viewStart + (clientX - scrub.rect.left) / scrub.rect.width * (scrub.viewEnd - scrub.viewStart),
4986
+ scrub.viewStart,
4987
+ scrub.viewEnd
4988
+ ));
4989
+ };
4990
+ return () => {
4991
+ if (time.value < props.viewStart || time.value > props.viewEnd || props.laneWidth <= 0) return null;
4992
+ const x = clamp((time.value - props.viewStart) * props.pxPerSecond, 0, props.laneWidth);
4993
+ const left = x - PLAYHEAD_FLAG_WIDTH / 2;
4994
+ const placement = left >= props.headerClearStart && left + PLAYHEAD_FLAG_WIDTH <= props.headerClearEnd ? "raised" : "lowered";
4995
+ return h19("div", {
4996
+ class: "dialkit-timeline-playhead-control",
4997
+ "data-edge": "center",
4998
+ "data-placement": placement,
4999
+ style: { left: `calc(var(--dial-timeline-label-w) + ${x}px)` },
5000
+ role: "slider",
5001
+ "aria-label": "Timeline current time",
5002
+ "aria-valuemin": 0,
5003
+ "aria-valuemax": props.duration,
5004
+ "aria-valuenow": time.value,
5005
+ title: "Drag to scrub the timeline",
5006
+ onPointerdown: (event) => {
5007
+ const rect = props.ruler?.getBoundingClientRect();
5008
+ if (!rect) return;
5009
+ event.preventDefault();
5010
+ event.stopPropagation();
5011
+ cleanup?.();
5012
+ const reset = event.shiftKey;
5013
+ scrub = {
5014
+ wasPlaying: TimelineStore.getTransport(props.id).playing,
5015
+ rect,
5016
+ viewStart: reset ? 0 : props.viewStart,
5017
+ viewEnd: reset ? props.duration : props.viewEnd
5018
+ };
5019
+ if (reset) props.onResetView();
5020
+ TimelineStore.pause(props.id);
5021
+ seek(event.clientX);
5022
+ const move = (next) => {
5023
+ next.preventDefault();
5024
+ seek(next.clientX);
5025
+ };
5026
+ const finish = () => {
5027
+ window.removeEventListener("pointermove", move);
5028
+ window.removeEventListener("pointerup", finish);
5029
+ window.removeEventListener("pointercancel", finish);
5030
+ if (scrub?.wasPlaying) TimelineStore.play(props.id);
5031
+ scrub = null;
5032
+ cleanup = null;
5033
+ };
5034
+ window.addEventListener("pointermove", move, { passive: false });
5035
+ window.addEventListener("pointerup", finish);
5036
+ window.addEventListener("pointercancel", finish);
5037
+ cleanup = finish;
5038
+ }
5039
+ }, [
5040
+ h19("div", { class: "dialkit-timeline-playhead-flag" }, time.value.toFixed(2)),
5041
+ h19("div", { class: "dialkit-timeline-playhead-stem" })
5042
+ ]);
5043
+ };
5044
+ }
5045
+ });
5046
+ function clampViewStart(start, duration, visibleDuration) {
5047
+ return clamp(start, 0, Math.max(0, duration - visibleDuration));
5048
+ }
5049
+ function formatRulerSeconds(time, step) {
5050
+ if (step >= 1 && Number.isInteger(time)) return formatClock(time);
5051
+ const decimals = Math.min(3, Math.max(1, Math.ceil(-Math.log10(step))));
5052
+ return `${time.toFixed(decimals)}s`;
5053
+ }
5054
+ var TimelineSection = defineComponent19({
5055
+ props: {
5056
+ meta: { type: Object, required: true },
5057
+ defaultOpen: { type: Boolean, required: true },
5058
+ theme: { type: String, required: true },
5059
+ dockVisible: { type: Boolean, required: true }
5060
+ },
5061
+ setup(props) {
5062
+ const open = ref15(props.defaultOpen);
5063
+ const copied = ref15(false);
5064
+ const popover = ref15(null);
5065
+ const collapsedGroups = ref15(/* @__PURE__ */ new Set());
5066
+ const expandedTracks = ref15(/* @__PURE__ */ new Set());
5067
+ const zoom = ref15(1);
5068
+ const viewStart = ref15(0);
5069
+ const values = ref15(DialStore.getValues(props.meta.id));
5070
+ const presets = ref15(DialStore.getPresets(props.meta.id));
5071
+ const activePresetId = ref15(DialStore.getActivePresetId(props.meta.id));
5072
+ const laneAreaRef = ref15(null);
5073
+ const titleRef = ref15(null);
5074
+ const actionsRef = ref15(null);
5075
+ const laneWidth = ref15(0);
5076
+ const flagClearRange = ref15({ start: 0, end: 0 });
5077
+ let unsubscribeValues;
5078
+ let resizeObserver;
5079
+ const measure = () => {
5080
+ if (!laneAreaRef.value || !titleRef.value || !actionsRef.value) return;
5081
+ const ruler = laneAreaRef.value.getBoundingClientRect();
5082
+ const title = titleRef.value.getBoundingClientRect();
5083
+ const actions = actionsRef.value.getBoundingClientRect();
5084
+ laneWidth.value = ruler.width;
5085
+ flagClearRange.value = {
5086
+ start: Math.round(title.right + 10 - ruler.left),
5087
+ end: Math.round(actions.left - 10 - ruler.left)
5088
+ };
5089
+ };
5090
+ const connectMeasure = async () => {
5091
+ resizeObserver?.disconnect();
5092
+ if (!open.value) return;
5093
+ await nextTick4();
5094
+ if (!laneAreaRef.value || !titleRef.value || !actionsRef.value) return;
5095
+ measure();
5096
+ resizeObserver = new ResizeObserver(measure);
5097
+ resizeObserver.observe(laneAreaRef.value);
5098
+ resizeObserver.observe(titleRef.value);
5099
+ resizeObserver.observe(actionsRef.value);
5100
+ };
5101
+ onMounted13(() => {
5102
+ unsubscribeValues = DialStore.subscribe(props.meta.id, () => {
5103
+ values.value = DialStore.getValues(props.meta.id);
5104
+ presets.value = DialStore.getPresets(props.meta.id);
5105
+ activePresetId.value = DialStore.getActivePresetId(props.meta.id);
5106
+ });
5107
+ void connectMeasure();
5108
+ });
5109
+ onUnmounted12(() => {
5110
+ unsubscribeValues?.();
5111
+ resizeObserver?.disconnect();
5112
+ });
5113
+ watch8(open, connectMeasure);
5114
+ watch8(() => props.dockVisible, (visible) => {
5115
+ if (!visible) popover.value = null;
5116
+ });
5117
+ const visibleDuration = computed7(() => props.meta.duration > 0 ? props.meta.duration / zoom.value : props.meta.duration);
5118
+ const safeViewStart = computed7(() => clampViewStart(viewStart.value, props.meta.duration, visibleDuration.value));
5119
+ const viewEnd = computed7(() => safeViewStart.value + visibleDuration.value);
5120
+ const pxPerSecond = computed7(() => visibleDuration.value > 0 && laneWidth.value > 0 ? laneWidth.value / visibleDuration.value : 0);
5121
+ const maxZoom = computed7(() => Math.max(
5122
+ MIN_TIMELINE_MAX_ZOOM,
5123
+ laneWidth.value > 0 && props.meta.duration > 0 ? MAJOR_TICK_TARGET_PX * props.meta.duration / (MILLISECOND_STEP * 10 * laneWidth.value) : MIN_TIMELINE_MAX_ZOOM
5124
+ ));
5125
+ watch8(maxZoom, (next) => {
5126
+ zoom.value = clamp(zoom.value, 1, next);
5127
+ }, { immediate: true });
5128
+ watch8([() => props.meta.duration, zoom], () => {
5129
+ viewStart.value = clampViewStart(viewStart.value, props.meta.duration, props.meta.duration / zoom.value);
5130
+ });
5131
+ const centerViewAt = (time) => {
5132
+ if (zoom.value <= 1 || props.meta.duration <= 0) return;
5133
+ const duration = props.meta.duration / zoom.value;
5134
+ viewStart.value = clampViewStart(time - duration / 2, props.meta.duration, duration);
5135
+ };
5136
+ const resetView = () => {
5137
+ zoom.value = 1;
5138
+ viewStart.value = 0;
5139
+ };
5140
+ let zoomDrag = null;
5141
+ let rulerScrub = null;
5142
+ let trackScrub = null;
5143
+ const seekRuler = (clientX) => {
5144
+ if (!rulerScrub || rulerScrub.rect.width <= 0) return;
5145
+ TimelineStore.seek(props.meta.id, clamp(
5146
+ rulerScrub.viewStart + (clientX - rulerScrub.rect.left) / rulerScrub.rect.width * rulerScrub.visibleDuration,
5147
+ rulerScrub.viewStart,
5148
+ rulerScrub.viewStart + rulerScrub.visibleDuration
5149
+ ));
5150
+ };
5151
+ const seekTrack = (clientX) => {
5152
+ if (!trackScrub || trackScrub.rect.width <= 0) return;
5153
+ TimelineStore.seek(props.meta.id, clamp(
5154
+ trackScrub.viewStart + (clientX - trackScrub.rect.left) / trackScrub.rect.width * trackScrub.visibleDuration,
5155
+ trackScrub.viewStart,
5156
+ trackScrub.viewStart + trackScrub.visibleDuration
5157
+ ));
5158
+ };
5159
+ const finishRuler = () => {
5160
+ if (rulerScrub?.wasPlaying) TimelineStore.play(props.meta.id);
5161
+ rulerScrub = null;
5162
+ zoomDrag = null;
5163
+ };
5164
+ const finishTrack = () => {
5165
+ if (trackScrub?.wasPlaying) TimelineStore.play(props.meta.id);
5166
+ trackScrub = null;
5167
+ };
5168
+ const handleCopy = () => {
5169
+ const normalized = normalizeTimelineValuesForCopy(DialStore.getValues(props.meta.id), props.meta.clips);
5170
+ void navigator.clipboard.writeText(buildCopyInstruction("useDialTimeline", props.meta.name, normalized));
5171
+ copied.value = true;
5172
+ window.setTimeout(() => {
5173
+ copied.value = false;
5174
+ }, 1500);
5175
+ };
5176
+ const handleAddPreset = () => DialStore.savePreset(props.meta.id, `Version ${presets.value.length + 2}`);
5177
+ const closePopover = () => {
5178
+ popover.value = null;
5179
+ };
5180
+ const openClipPopover = (clip, rect, stepKey) => {
5181
+ const target = stepKey ? `${clip.key}.${stepKey}` : clip.key;
5182
+ if (getClipControls(props.meta.id, target, stepKey ? void 0 : clipPopoverExclusions(clip)).length === 0) return;
5183
+ popover.value = popover.value?.clip.key === clip.key && popover.value.stepKey === stepKey ? null : {
5184
+ clip,
5185
+ stepKey,
5186
+ anchor: { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom, width: rect.width, height: rect.height }
5187
+ };
5188
+ };
5189
+ const toggleSet = (state, key) => {
5190
+ const next = new Set(state.value);
5191
+ if (next.has(key)) next.delete(key);
5192
+ else next.add(key);
5193
+ state.value = next;
5194
+ };
5195
+ const toggleTracks = (key) => toggleSet(expandedTracks, key);
5196
+ const toggleGroup = (key) => toggleSet(collapsedGroups, key);
5197
+ const handleBarClick = (clip, rect, stepKey) => {
5198
+ if (!stepKey && clip.tracks?.length) toggleTracks(clip.key);
5199
+ else openClipPopover(clip, rect, stepKey);
5200
+ };
5201
+ const ticks = computed7(() => {
5202
+ const raw = pxPerSecond.value > 0 ? MAJOR_TICK_TARGET_PX / pxPerSecond.value : 1;
5203
+ const adaptive = SECOND_TICK_STEPS.find((step) => step >= raw) ?? SECOND_TICK_STEPS[SECOND_TICK_STEPS.length - 1];
5204
+ const majorStep = zoom.value < 1.5 && props.meta.duration >= 1 ? Math.max(1, adaptive) : adaptive;
5205
+ const fineStep = majorStep / 10;
5206
+ const major = [];
5207
+ const medium = [];
5208
+ const fine = [];
5209
+ for (let time = Math.ceil((safeViewStart.value - 1e-6) / majorStep) * majorStep; time <= viewEnd.value + 1e-6; time += majorStep) {
5210
+ major.push(Number(time.toFixed(4)));
5211
+ }
5212
+ const first = Math.ceil((safeViewStart.value - 1e-6) / fineStep);
5213
+ const last = Math.floor((viewEnd.value + 1e-6) / fineStep);
5214
+ for (let index = first; index <= last; index++) {
5215
+ if (index % 10 === 0) continue;
5216
+ const tick = Number((index * fineStep).toFixed(6));
5217
+ if (index % 5 === 0) medium.push(tick);
5218
+ else fine.push(tick);
5219
+ }
5220
+ return { major, medium, fine, majorStep };
5221
+ });
5222
+ const renderRows = () => {
5223
+ const rows = [];
5224
+ let lastGroup;
5225
+ for (const clip of props.meta.clips) {
5226
+ if (clip.group !== lastGroup) {
5227
+ lastGroup = clip.group;
5228
+ if (clip.group) {
5229
+ const group = clip.group;
5230
+ const collapsed = collapsedGroups.value.has(group);
5231
+ rows.push(h19("div", { key: `group:${group}`, class: "dialkit-timeline-row dialkit-timeline-group-row" }, [
5232
+ h19("div", { class: "dialkit-timeline-label" }, [
5233
+ h19("button", {
5234
+ class: "dialkit-timeline-group-toggle",
5235
+ "data-open": !collapsed,
5236
+ title: collapsed ? "Expand layer" : "Collapse layer",
5237
+ onClick: () => toggleGroup(group)
5238
+ }, [chevronIcon()]),
5239
+ h19("span", formatLabel(group))
5240
+ ]),
5241
+ h19("div", { class: "dialkit-timeline-lane" })
5242
+ ]));
5243
+ }
5244
+ }
5245
+ if (clip.group && collapsedGroups.value.has(clip.group)) continue;
5246
+ const isProps = Boolean(clip.tracks?.length);
5247
+ const tracksOpen = isProps && expandedTracks.value.has(clip.key);
5248
+ const stat = computeClipStaticFromValues(values.value, clip, props.meta.duration);
5249
+ const selected = popover.value?.clip.key === clip.key;
5250
+ rows.push(h19("div", { key: clip.key, class: "dialkit-timeline-row", "data-grouped": clip.group ? "" : void 0 }, [
5251
+ h19("div", { class: "dialkit-timeline-label" }, [
5252
+ isProps ? h19("button", {
5253
+ class: "dialkit-timeline-group-toggle",
5254
+ "data-open": tracksOpen,
5255
+ title: tracksOpen ? "Collapse properties" : "Expand properties",
5256
+ onClick: (event) => {
5257
+ event.stopPropagation();
5258
+ toggleTracks(clip.key);
5259
+ }
5260
+ }, [chevronIcon()]) : null,
5261
+ clip.label
5262
+ ]),
5263
+ h19("div", { class: "dialkit-timeline-lane" }, [h19(TimelineClip, {
5264
+ timelineId: props.meta.id,
5265
+ clip,
5266
+ at: stat.at,
5267
+ duration: stat.duration,
5268
+ loop: stat.loop,
5269
+ steps: clip.stepKeys?.length ? stat.tracks[0]?.steps : void 0,
5270
+ fixedDuration: isProps ? true : stat.isPhysics,
5271
+ composite: isProps,
5272
+ pxPerSecond: pxPerSecond.value,
5273
+ viewStart: safeViewStart.value,
5274
+ timelineDuration: props.meta.duration,
5275
+ selected,
5276
+ selectedStepKey: selected ? popover.value?.stepKey : void 0,
5277
+ onClick: handleBarClick,
5278
+ onDrag: closePopover
5279
+ })])
5280
+ ]));
5281
+ if (!tracksOpen) continue;
5282
+ for (const trackRef of clip.tracks ?? []) {
5283
+ const track = stat.tracks.find((candidate) => candidate.prop === trackRef.prop);
5284
+ if (!track) continue;
5285
+ const trackKey = `${clip.key}.${trackRef.prop}`;
5286
+ const trackMeta = {
5287
+ key: trackKey,
5288
+ label: `${clip.label} \xB7 ${formatLabel(trackRef.prop)}`,
5289
+ color: clip.color,
5290
+ loop: clip.loop,
5291
+ group: clip.group,
5292
+ stepKeys: trackRef.stepKeys
5293
+ };
5294
+ const trackSelected = popover.value?.clip.key === trackKey;
5295
+ rows.push(h19("div", { key: trackKey, class: "dialkit-timeline-row dialkit-timeline-track-row", "data-grouped": clip.group ? "" : void 0 }, [
5296
+ h19("div", { class: "dialkit-timeline-label" }, formatLabel(trackRef.prop)),
5297
+ h19("div", { class: "dialkit-timeline-lane" }, [h19(TimelineClip, {
5298
+ timelineId: props.meta.id,
5299
+ clip: trackMeta,
5300
+ at: stat.at + track.delay,
5301
+ duration: track.duration,
5302
+ loop: stat.loop,
5303
+ steps: trackRef.stepKeys?.length ? track.steps : void 0,
5304
+ fixedDuration: !trackRef.stepKeys?.length && track.steps[0]?.isPhysics === true,
5305
+ baseAt: stat.at,
5306
+ delayMode: true,
5307
+ pxPerSecond: pxPerSecond.value,
5308
+ viewStart: safeViewStart.value,
5309
+ timelineDuration: props.meta.duration,
5310
+ selected: trackSelected,
5311
+ selectedStepKey: trackSelected ? popover.value?.stepKey : void 0,
5312
+ onClick: openClipPopover,
5313
+ onDrag: closePopover
5314
+ })])
5315
+ ]));
5316
+ }
5317
+ }
5318
+ return rows;
5319
+ };
5320
+ return () => h19("div", { class: "dialkit-timeline-section" }, [
5321
+ h19("div", { class: "dialkit-timeline-header", "data-open": open.value || void 0 }, [
5322
+ h19("div", { class: "dialkit-timeline-identity" }, [
5323
+ h19("span", { ref: titleRef, class: "dialkit-timeline-title" }, props.meta.name)
5324
+ ]),
5325
+ !open.value ? h19(TimelineOverview, {
5326
+ id: props.meta.id,
5327
+ duration: props.meta.duration,
5328
+ viewStart: safeViewStart.value,
5329
+ viewEnd: viewEnd.value,
5330
+ onNavigate: centerViewAt
5331
+ }) : null,
5332
+ h19("div", { ref: actionsRef, class: "dialkit-timeline-actions" }, [
5333
+ h19(PlayPauseButton, { id: props.meta.id }),
5334
+ h19("button", { class: "dialkit-toolbar-add", title: "Add timeline version", "aria-label": "Add timeline version", onClick: handleAddPreset }, [
5335
+ h19(
5336
+ "svg",
5337
+ { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.5", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" },
5338
+ ICON_ADD_PRESET.map((path) => h19("path", { d: path }))
5339
+ )
5340
+ ]),
5341
+ h19(PresetManager, { panelId: props.meta.id, presets: presets.value, activePresetId: activePresetId.value }),
5342
+ h19("button", {
5343
+ class: "dialkit-toolbar-add",
5344
+ title: "Copy parameters",
5345
+ "aria-label": copied.value ? "Copied parameters" : "Copy parameters",
5346
+ onClick: handleCopy
5347
+ }, [h19("span", { style: { position: "relative", width: "16px", height: "16px" } }, [
5348
+ copied.value ? h19("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", style: iconStyle }, [h19("path", { d: ICON_CHECK })]) : h19("svg", { viewBox: "0 0 24 24", fill: "none", style: iconStyle }, [
5349
+ h19("path", { d: ICON_CLIPBOARD.board, stroke: "currentColor", "stroke-width": "2", "stroke-linejoin": "round" }),
5350
+ h19("path", { d: ICON_CLIPBOARD.sparkle, fill: "currentColor" }),
5351
+ h19("path", { d: ICON_CLIPBOARD.body, stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" })
5352
+ ])
5353
+ ])]),
5354
+ h19("button", {
5355
+ class: "dialkit-timeline-chevron",
5356
+ "data-open": open.value,
5357
+ "aria-expanded": open.value,
5358
+ title: open.value ? "Collapse timeline" : "Expand timeline",
5359
+ onClick: () => {
5360
+ open.value = !open.value;
5361
+ }
5362
+ }, [chevronIcon()])
5363
+ ])
5364
+ ]),
5365
+ open.value ? h19("div", {
5366
+ class: "dialkit-timeline-body",
5367
+ onPointerdown: (event) => {
5368
+ const target = event.target;
5369
+ if (target.closest(".dialkit-timeline-label, button")) return;
5370
+ if (!event.shiftKey && target.closest(".dialkit-timeline-clip")) return;
5371
+ const rect = laneAreaRef.value?.getBoundingClientRect();
5372
+ if (!rect) return;
5373
+ event.preventDefault();
5374
+ event.currentTarget.setPointerCapture(event.pointerId);
5375
+ const reset = event.shiftKey;
5376
+ trackScrub = {
5377
+ wasPlaying: TimelineStore.getTransport(props.meta.id).playing,
5378
+ rect,
5379
+ viewStart: reset ? 0 : safeViewStart.value,
5380
+ visibleDuration: reset ? props.meta.duration : visibleDuration.value
5381
+ };
5382
+ if (reset) resetView();
5383
+ popover.value = null;
5384
+ TimelineStore.pause(props.meta.id);
5385
+ seekTrack(event.clientX);
5386
+ },
5387
+ onPointermove: (event) => trackScrub && seekTrack(event.clientX),
5388
+ onPointerup: finishTrack,
5389
+ onPointercancel: finishTrack,
5390
+ onLostpointercapture: finishTrack
5391
+ }, [h19("div", { class: "dialkit-timeline-grid" }, [
5392
+ h19("div", { class: "dialkit-timeline-row dialkit-timeline-ruler-row" }, [
5393
+ h19("div", { class: "dialkit-timeline-label" }),
5394
+ h19("div", {
5395
+ ref: laneAreaRef,
5396
+ class: "dialkit-timeline-ruler",
5397
+ title: "Drag to seek \xB7 Option-drag to zoom \xB7 Shift-drag to reset zoom",
5398
+ onPointerdown: (event) => {
5399
+ event.preventDefault();
5400
+ event.stopPropagation();
5401
+ const rect = event.currentTarget.getBoundingClientRect();
5402
+ if (rect.width <= 0) return;
5403
+ event.currentTarget.setPointerCapture(event.pointerId);
5404
+ if (!event.altKey) {
5405
+ const reset = event.shiftKey;
5406
+ rulerScrub = {
5407
+ wasPlaying: TimelineStore.getTransport(props.meta.id).playing,
5408
+ rect,
5409
+ viewStart: reset ? 0 : safeViewStart.value,
5410
+ visibleDuration: reset ? props.meta.duration : visibleDuration.value
5411
+ };
5412
+ if (reset) resetView();
5413
+ TimelineStore.pause(props.meta.id);
5414
+ seekRuler(event.clientX);
5415
+ return;
5416
+ }
5417
+ const ratio = clamp((event.clientX - rect.left) / rect.width, 0, 1);
5418
+ zoomDrag = {
5419
+ pointerX: event.clientX,
5420
+ zoom: zoom.value,
5421
+ anchorRatio: ratio,
5422
+ anchorTime: safeViewStart.value + ratio * visibleDuration.value,
5423
+ moved: false
5424
+ };
5425
+ },
5426
+ onPointermove: (event) => {
5427
+ if (rulerScrub) {
5428
+ seekRuler(event.clientX);
5429
+ return;
5430
+ }
5431
+ if (!zoomDrag || props.meta.duration <= 0) return;
5432
+ const dx = event.clientX - zoomDrag.pointerX;
5433
+ if (!zoomDrag.moved && Math.abs(dx) <= DRAG_THRESHOLD_PX) return;
5434
+ zoomDrag.moved = true;
5435
+ const nextZoom = clamp(zoomDrag.zoom * Math.exp(dx / ZOOM_DRAG_DISTANCE), 1, maxZoom.value);
5436
+ const duration = props.meta.duration / nextZoom;
5437
+ zoom.value = nextZoom;
5438
+ viewStart.value = clampViewStart(zoomDrag.anchorTime - zoomDrag.anchorRatio * duration, props.meta.duration, duration);
5439
+ },
5440
+ onPointerup: finishRuler,
5441
+ onPointercancel: finishRuler,
5442
+ onLostpointercapture: finishRuler
5443
+ }, [
5444
+ ...ticks.value.fine.map((time) => h19("div", { key: `fine:${time}`, class: "dialkit-timeline-tick dialkit-timeline-tick-fine", style: { left: `${(time - safeViewStart.value) * pxPerSecond.value}px` } })),
5445
+ ...ticks.value.medium.map((time) => h19("div", { key: `medium:${time}`, class: "dialkit-timeline-tick dialkit-timeline-tick-medium", style: { left: `${(time - safeViewStart.value) * pxPerSecond.value}px` } })),
5446
+ ...ticks.value.major.map((time) => h19("div", { key: time, class: "dialkit-timeline-tick", style: { left: `${(time - safeViewStart.value) * pxPerSecond.value}px` } }, [
5447
+ h19("span", { class: "dialkit-timeline-tick-label" }, formatRulerSeconds(time, ticks.value.majorStep))
5448
+ ]))
5449
+ ])
5450
+ ]),
5451
+ ...renderRows(),
5452
+ pxPerSecond.value > 0 ? h19(TimelinePlayheadFlag, {
5453
+ id: props.meta.id,
5454
+ duration: props.meta.duration,
5455
+ pxPerSecond: pxPerSecond.value,
5456
+ viewStart: safeViewStart.value,
5457
+ viewEnd: viewEnd.value,
5458
+ laneWidth: laneWidth.value,
5459
+ ruler: laneAreaRef.value ?? void 0,
5460
+ headerClearStart: flagClearRange.value.start,
5461
+ headerClearEnd: flagClearRange.value.end,
5462
+ onResetView: resetView
5463
+ }) : null
5464
+ ])]) : null,
5465
+ popover.value ? h19(ClipPopover, {
5466
+ panelId: props.meta.id,
5467
+ popover: popover.value,
5468
+ values: values.value,
5469
+ theme: props.theme,
5470
+ onClose: closePopover
5471
+ }) : null
5472
+ ]);
5473
+ }
5474
+ });
5475
+ function chevronIcon() {
5476
+ return h19("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.5", "stroke-linecap": "round", "stroke-linejoin": "round" }, [
5477
+ h19("path", { d: ICON_CHEVRON })
5478
+ ]);
5479
+ }
5480
+ var ClipPopover = defineComponent19({
5481
+ props: {
5482
+ panelId: { type: String, required: true },
5483
+ popover: { type: Object, required: true },
5484
+ values: { type: Object, required: true },
5485
+ theme: { type: String, required: true },
5486
+ onClose: { type: Function, required: true }
5487
+ },
5488
+ setup(props) {
5489
+ const element = ref15(null);
5490
+ const naturalHeight = ref15(0);
5491
+ const viewport = ref15(readViewport());
5492
+ let observer;
5493
+ const measure = () => {
5494
+ if (element.value) naturalHeight.value = element.value.scrollHeight + 2;
5495
+ };
5496
+ const updateViewport = () => {
5497
+ viewport.value = readViewport();
5498
+ };
5499
+ const outside = (event) => {
5500
+ const target = event.target;
5501
+ if (element.value?.contains(target) || target.closest?.(".dialkit-timeline-clip") || target.closest?.(".dialkit-timeline-label")) return;
5502
+ props.onClose();
5503
+ };
5504
+ const keydown = (event) => {
5505
+ if (event.key === "Escape") props.onClose();
5506
+ };
5507
+ onMounted13(() => {
5508
+ measure();
5509
+ observer = new ResizeObserver(measure);
5510
+ if (element.value) observer.observe(element.value.querySelector(".dialkit-timeline-popover-body") ?? element.value);
5511
+ window.addEventListener("resize", updateViewport);
5512
+ window.visualViewport?.addEventListener("resize", updateViewport);
5513
+ window.visualViewport?.addEventListener("scroll", updateViewport);
5514
+ document.addEventListener("pointerdown", outside, true);
5515
+ document.addEventListener("keydown", keydown);
5516
+ });
5517
+ onUnmounted12(() => {
5518
+ observer?.disconnect();
5519
+ window.removeEventListener("resize", updateViewport);
5520
+ window.visualViewport?.removeEventListener("resize", updateViewport);
5521
+ window.visualViewport?.removeEventListener("scroll", updateViewport);
5522
+ document.removeEventListener("pointerdown", outside, true);
5523
+ document.removeEventListener("keydown", keydown);
5524
+ });
5525
+ return () => {
5526
+ const { clip, stepKey } = props.popover;
5527
+ let controls;
5528
+ let title;
5529
+ if (stepKey) {
5530
+ controls = getClipControls(props.panelId, `${clip.key}.${stepKey}`);
5531
+ if (stepKey === clip.stepKeys?.[0]) {
5532
+ const from = getControlAt(props.panelId, `${clip.key}.from`);
5533
+ if (from) {
5534
+ const index = controls.findIndex((control) => control.path === `${clip.key}.${stepKey}.to`);
5535
+ controls = index >= 0 ? [...controls.slice(0, index), from, ...controls.slice(index)] : [...controls, from];
5536
+ }
5537
+ }
5538
+ title = `${clip.label} \xB7 ${formatStepLabel(stepKey)}`;
5539
+ } else {
5540
+ controls = getClipControls(props.panelId, clip.key, clipPopoverExclusions(clip));
5541
+ title = clip.label;
5542
+ }
5543
+ if (controls.length === 0) return null;
5544
+ const target = stepKey ? `${clip.key}.${stepKey}` : clip.key;
5545
+ const durationMeta = getControlAt(props.panelId, `${target}.duration`);
5546
+ const durationValue = durationMeta ? props.values[durationMeta.path] : void 0;
5547
+ const transitionDuration = durationMeta?.type === "slider" && typeof durationValue === "number" ? {
5548
+ value: durationValue,
5549
+ onChange: (next) => DialStore.updateValue(props.panelId, durationMeta.path, next),
5550
+ min: Math.max(TIMELINE_MIN_CLIP_DURATION, durationMeta.min ?? 0),
5551
+ max: durationMeta.max,
5552
+ step: durationMeta.step
5553
+ } : void 0;
5554
+ const current = viewport.value;
5555
+ const right = current.offsetLeft + current.width;
5556
+ const bottom = current.offsetTop + current.height;
5557
+ const width = Math.min(POPOVER_WIDTH, Math.max(220, current.width - 24));
5558
+ const left = clamp(props.popover.anchor.left + props.popover.anchor.width / 2 - width / 2, current.offsetLeft + 12, Math.max(current.offsetLeft + 12, right - width - 12));
5559
+ const above = Math.max(0, props.popover.anchor.top - current.offsetTop - 22);
5560
+ const below = Math.max(0, bottom - props.popover.anchor.bottom - 22);
5561
+ const placeAbove = naturalHeight.value === 0 ? above >= below : naturalHeight.value <= above || naturalHeight.value > below && above >= below;
5562
+ const availableHeight = placeAbove ? above : below;
5563
+ const renderedHeight = Math.min(naturalHeight.value || availableHeight, availableHeight);
5564
+ const rawTop = placeAbove ? props.popover.anchor.top - 10 - renderedHeight : props.popover.anchor.bottom + 10;
5565
+ const top = clamp(rawTop, current.offsetTop + 12, Math.max(current.offsetTop + 12, bottom - renderedHeight - 12));
5566
+ return h19(Teleport4, { to: "body" }, [h19("div", { class: "dialkit-root", "data-theme": props.theme }, [
5567
+ h19("div", {
5568
+ ref: element,
5569
+ class: "dialkit-timeline-popover",
5570
+ "data-placement": placeAbove ? "above" : "below",
5571
+ style: { left: `${left}px`, top: `${top}px`, width: `${width}px`, maxHeight: `${availableHeight}px`, visibility: naturalHeight.value > 0 ? "visible" : "hidden" },
5572
+ role: "dialog",
5573
+ "aria-label": `Edit ${title}`
5574
+ }, [
5575
+ h19("div", { class: "dialkit-timeline-popover-header" }, [
5576
+ h19("span", { class: "dialkit-timeline-popover-title" }, title),
5577
+ h19("button", { class: "dialkit-timeline-popover-close", title: "Close editor", "aria-label": "Close editor", onClick: props.onClose }, [
5578
+ h19("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round" }, [h19("path", { d: "M6 6L18 18M18 6L6 18" })])
5579
+ ])
5580
+ ]),
5581
+ h19("div", { class: "dialkit-timeline-popover-body" }, [h19(ControlRenderer, {
5582
+ panelId: props.panelId,
5583
+ controls,
5584
+ values: timelinePopoverDisplayValues(props.values, clip.key, clip.stepKeys, stepKey),
5585
+ transitionDuration
5586
+ })])
5587
+ ])
5588
+ ])]);
5589
+ };
5590
+ }
5591
+ });
5592
+ function readViewport() {
5593
+ return {
5594
+ width: window.visualViewport?.width ?? window.innerWidth,
5595
+ height: window.visualViewport?.height ?? window.innerHeight,
5596
+ offsetLeft: window.visualViewport?.offsetLeft ?? 0,
5597
+ offsetTop: window.visualViewport?.offsetTop ?? 0
5598
+ };
5599
+ }
5600
+ function clipPopoverExclusions(clip) {
5601
+ return /* @__PURE__ */ new Set([...clip.stepKeys ?? [], ...clip.tracks?.map((track) => track.prop) ?? []]);
5602
+ }
5603
+ function getClipControls(panelId, path, exclusions) {
5604
+ const panel = DialStore.getPanel(panelId);
5605
+ const folder = panel ? findControl(panel.controls, path) : null;
5606
+ if (!folder?.children) return [];
5607
+ return folder.children.filter((control) => {
5608
+ const key = control.path.slice(path.length + 1);
5609
+ return key !== "at" && key !== "duration" && !exclusions?.has(key);
5610
+ });
5611
+ }
5612
+ function getControlAt(panelId, path) {
5613
+ const panel = DialStore.getPanel(panelId);
5614
+ return panel ? findControl(panel.controls, path) : null;
5615
+ }
5616
+ var TimelineClip = defineComponent19({
5617
+ props: {
5618
+ timelineId: { type: String, required: true },
5619
+ clip: { type: Object, required: true },
5620
+ at: { type: Number, required: true },
5621
+ duration: { type: Number, required: true },
5622
+ loop: { type: String, required: true },
5623
+ steps: Array,
5624
+ fixedDuration: { type: Boolean, required: true },
5625
+ composite: Boolean,
5626
+ baseAt: { type: Number, default: 0 },
5627
+ delayMode: Boolean,
5628
+ pxPerSecond: { type: Number, required: true },
5629
+ viewStart: { type: Number, required: true },
5630
+ timelineDuration: { type: Number, required: true },
5631
+ selected: { type: Boolean, required: true },
5632
+ selectedStepKey: String,
5633
+ onClick: { type: Function, required: true },
5634
+ onDrag: { type: Function, required: true }
5635
+ },
5636
+ setup(props) {
5637
+ const dragging = ref15(false);
5638
+ let drag = null;
5639
+ const finish = (event) => {
5640
+ const previous = drag;
5641
+ drag = null;
5642
+ dragging.value = false;
5643
+ if (previous && !previous.moved && event) {
5644
+ const anchor = previous.clickEl ?? event.currentTarget;
5645
+ props.onClick(props.clip, anchor.getBoundingClientRect(), previous.clickEl?.dataset.step);
5646
+ }
5647
+ };
5648
+ return () => {
5649
+ const width = Math.max(props.duration * props.pxPerSecond, 14);
5650
+ const isSteps = Boolean(props.steps?.length);
5651
+ const looping = props.loop === "repeat" && props.duration > 0;
5652
+ const resizable = props.duration > 0 && !props.fixedDuration && !props.composite;
5653
+ const durationText = `${props.fixedDuration && !props.composite ? "~" : ""}${formatSeconds(props.duration)}`;
5654
+ const ghosts = [];
5655
+ if (looping) {
5656
+ const first = Math.max(1, Math.floor((props.viewStart - props.at) / props.duration));
5657
+ for (let offset = 0; offset < 256; offset++) {
5658
+ const index = first + offset;
5659
+ const start = props.at + props.duration * index;
5660
+ if (start >= props.timelineDuration - 1e-6) break;
5661
+ const duration = Math.min(props.duration, props.timelineDuration - start);
5662
+ ghosts.push(h19("div", {
5663
+ key: `ghost:${index}`,
5664
+ class: "dialkit-timeline-clip-ghost",
5665
+ "data-steps": isSteps || void 0,
5666
+ "aria-hidden": "true",
5667
+ style: { left: `${(start - props.viewStart) * props.pxPerSecond + 1}px`, width: `${Math.max(1, duration * props.pxPerSecond - 2)}px`, background: props.clip.color }
5668
+ }, props.steps?.map((step) => h19("span", { class: "dialkit-timeline-clip-ghost-segment", style: { width: `${step.duration * props.pxPerSecond}px` } }))));
5669
+ }
5670
+ }
5671
+ let cumulative = 0;
5672
+ const boundaries = props.steps?.map((step) => cumulative += step.duration) ?? [];
5673
+ const children = [];
5674
+ if (props.composite) {
5675
+ if (width > 56) children.push(h19("span", { class: "dialkit-timeline-clip-duration" }, durationText));
5676
+ } else if (isSteps) {
5677
+ for (const step of props.steps ?? []) {
5678
+ const segmentWidth = step.duration * props.pxPerSecond;
5679
+ children.push(h19("div", {
5680
+ key: step.key ?? "step",
5681
+ class: "dialkit-timeline-clip-segment",
5682
+ "data-step": step.key,
5683
+ "data-selected": props.selectedStepKey === step.key || void 0,
5684
+ style: { width: `${segmentWidth}px` }
5685
+ }, segmentWidth > 52 ? [h19("span", { class: "dialkit-timeline-clip-duration" }, formatSeconds(step.duration))] : []));
5686
+ }
5687
+ (props.steps ?? []).forEach((step, index) => {
5688
+ if (!step.isPhysics) children.push(h19("div", { key: `boundary:${step.key}`, class: "dialkit-timeline-clip-handle", "data-boundary": index, style: { left: `${boundaries[index] * props.pxPerSecond - 4}px` } }));
5689
+ });
5690
+ if (!props.steps?.[0]?.isPhysics) children.push(h19("div", { class: "dialkit-timeline-clip-handle", "data-edge": "start" }));
5691
+ } else {
5692
+ if (resizable) children.push(h19("div", { class: "dialkit-timeline-clip-handle", "data-edge": "start" }));
5693
+ if (width > 56) children.push(h19("span", { class: "dialkit-timeline-clip-duration" }, durationText));
5694
+ if (resizable) children.push(h19("div", { class: "dialkit-timeline-clip-handle", "data-edge": "end" }));
5695
+ }
5696
+ const title = props.composite ? `${props.clip.label} \u2014 composite of its property tracks${looping ? " \xB7 repeats through timeline" : ""} \xB7 click to expand` : `${props.clip.label} \u2014 ${formatSeconds(props.at)} for ${durationText}${props.fixedDuration ? " (duration set by spring physics)" : ""}${looping ? " \xB7 repeats through timeline" : ""}${props.delayMode ? " \xB7 drag to phase-shift" : ""}`;
5697
+ return [...ghosts, h19("div", {
5698
+ class: "dialkit-timeline-clip",
5699
+ "data-steps": isSteps || void 0,
5700
+ "data-composite": props.composite || void 0,
5701
+ "data-selected": props.selected || void 0,
5702
+ "data-dragging": dragging.value || void 0,
5703
+ style: { left: `${(props.at - props.viewStart) * props.pxPerSecond}px`, width: `${width}px`, background: props.composite ? `${props.clip.color}80` : props.clip.color },
5704
+ title,
5705
+ onPointerdown: (event) => {
5706
+ if (event.shiftKey) return;
5707
+ event.stopPropagation();
5708
+ const target = event.target;
5709
+ let mode = "move";
5710
+ let boundaryIndex;
5711
+ if (target.dataset.boundary !== void 0) {
5712
+ mode = "boundary";
5713
+ boundaryIndex = Number(target.dataset.boundary);
5714
+ } else if (!props.fixedDuration) {
5715
+ const edge = target.dataset.edge;
5716
+ if (edge) mode = edge;
5717
+ }
5718
+ drag = {
5719
+ mode,
5720
+ boundaryIndex,
5721
+ pointerX: event.clientX,
5722
+ at: props.at,
5723
+ duration: props.duration,
5724
+ stepDurations: props.steps?.map((step) => step.duration),
5725
+ clickEl: target.closest?.("[data-step]"),
5726
+ moved: false
5727
+ };
5728
+ event.currentTarget.setPointerCapture(event.pointerId);
5729
+ },
5730
+ onPointermove: (event) => {
5731
+ if (!drag || props.pxPerSecond <= 0) return;
5732
+ const dx = event.clientX - drag.pointerX;
5733
+ if (!drag.moved) {
5734
+ if (Math.abs(dx) <= DRAG_THRESHOLD_PX) return;
5735
+ drag.moved = true;
5736
+ dragging.value = true;
5737
+ props.onDrag();
5738
+ }
5739
+ const dt = dx / props.pxPerSecond;
5740
+ if (drag.mode === "boundary" && props.steps && drag.stepDurations) {
5741
+ const index = drag.boundaryIndex ?? 0;
5742
+ const others = drag.stepDurations.reduce((sum, duration, stepIndex) => stepIndex === index ? sum : sum + duration, 0);
5743
+ DialStore.updateValue(props.timelineId, `${props.clip.key}.${props.steps[index].key ?? ""}.duration`, clampStepResize(drag.stepDurations[index] + dt, drag.at, others, props.timelineDuration));
5744
+ } else if (drag.mode === "move") {
5745
+ if (props.delayMode) DialStore.updateValue(props.timelineId, `${props.clip.key}.delay`, clampTrackDelay(drag.at + dt - props.baseAt, props.baseAt, drag.duration, props.timelineDuration));
5746
+ else DialStore.updateValue(props.timelineId, `${props.clip.key}.at`, clampClipMove(drag.at + dt, drag.duration, props.timelineDuration));
5747
+ } else if (drag.mode === "end") {
5748
+ DialStore.updateValue(props.timelineId, `${props.clip.key}.duration`, clampClipResizeEnd(drag.duration + dt, drag.at, props.timelineDuration));
5749
+ } else if (props.steps && drag.stepDurations) {
5750
+ const next = clampClipResizeStart(Math.max(drag.at + dt, Math.max(props.baseAt, 0)), drag.at, drag.stepDurations[0]);
5751
+ DialStore.updateValues(props.timelineId, {
5752
+ [props.delayMode ? `${props.clip.key}.delay` : `${props.clip.key}.at`]: props.delayMode ? Math.max(0, next.at - props.baseAt) : next.at,
5753
+ [`${props.clip.key}.${props.steps[0].key ?? ""}.duration`]: next.duration
5754
+ });
5755
+ } else {
5756
+ const next = clampClipResizeStart(Math.max(drag.at + dt, Math.max(props.baseAt, 0)), drag.at, drag.duration);
5757
+ DialStore.updateValues(props.timelineId, {
5758
+ [props.delayMode ? `${props.clip.key}.delay` : `${props.clip.key}.at`]: props.delayMode ? Math.max(0, next.at - props.baseAt) : next.at,
5759
+ [`${props.clip.key}.duration`]: next.duration
5760
+ });
5761
+ }
5762
+ },
5763
+ onPointerup: finish,
5764
+ onPointercancel: () => finish(),
5765
+ onLostpointercapture: () => finish()
5766
+ }, children), looping ? h19("span", { class: "dialkit-timeline-loop-infinity", "aria-hidden": "true", title: "Repeats indefinitely" }, "\u221E") : null];
5767
+ };
5768
+ }
5769
+ });
5770
+
3268
5771
  // src/vue/components/ShortcutsMenu.ts
3269
- import { defineComponent as defineComponent17, h as h17, onUnmounted as onUnmounted10, ref as ref14, Teleport as Teleport4 } from "vue";
5772
+ import { defineComponent as defineComponent20, h as h20, onUnmounted as onUnmounted13, ref as ref16, Teleport as Teleport5 } from "vue";
3270
5773
  function formatShortcutKey(sc) {
3271
5774
  if (!sc.key) return "\u2014";
3272
5775
  const mod = sc.modifier === "alt" ? "\u2325" : sc.modifier === "shift" ? "\u21E7" : sc.modifier === "meta" ? "\u2318" : "";
@@ -3285,7 +5788,7 @@ function formatInteraction(sc) {
3285
5788
  return "scroll";
3286
5789
  }
3287
5790
  }
3288
- var ShortcutsMenu = defineComponent17({
5791
+ var ShortcutsMenu = defineComponent20({
3289
5792
  name: "DialKitShortcutsMenu",
3290
5793
  props: {
3291
5794
  panelId: {
@@ -3294,10 +5797,10 @@ var ShortcutsMenu = defineComponent17({
3294
5797
  }
3295
5798
  },
3296
5799
  setup(props) {
3297
- const isOpen = ref14(false);
3298
- const triggerRef = ref14(null);
3299
- const dropdownRef = ref14(null);
3300
- const pos = ref14({ top: 0, right: 0 });
5800
+ const isOpen = ref16(false);
5801
+ const triggerRef = ref16(null);
5802
+ const dropdownRef = ref16(null);
5803
+ const pos = ref16({ top: 0, right: 0 });
3301
5804
  const open = () => {
3302
5805
  const rect = triggerRef.value?.getBoundingClientRect();
3303
5806
  if (rect) {
@@ -3327,7 +5830,7 @@ var ShortcutsMenu = defineComponent17({
3327
5830
  mousedownHandler = null;
3328
5831
  }
3329
5832
  };
3330
- onUnmounted10(() => {
5833
+ onUnmounted13(() => {
3331
5834
  removeOutsideClickListener();
3332
5835
  });
3333
5836
  return () => {
@@ -3356,13 +5859,13 @@ var ShortcutsMenu = defineComponent17({
3356
5859
  removeOutsideClickListener();
3357
5860
  }
3358
5861
  return [
3359
- h17("button", {
5862
+ h20("button", {
3360
5863
  ref: triggerRef,
3361
5864
  class: "dialkit-shortcuts-trigger",
3362
5865
  onClick: toggle,
3363
5866
  title: "Keyboard shortcuts"
3364
5867
  }, [
3365
- h17("svg", {
5868
+ h20("svg", {
3366
5869
  viewBox: "0 0 24 24",
3367
5870
  fill: "none",
3368
5871
  stroke: "currentColor",
@@ -3370,16 +5873,16 @@ var ShortcutsMenu = defineComponent17({
3370
5873
  "stroke-linecap": "round",
3371
5874
  "stroke-linejoin": "round"
3372
5875
  }, [
3373
- h17("rect", { x: "2", y: "6", width: "20", height: "12", rx: "2" }),
3374
- h17("path", { d: "M6 10H6.01" }),
3375
- h17("path", { d: "M10 10H10.01" }),
3376
- h17("path", { d: "M14 10H14.01" }),
3377
- h17("path", { d: "M18 10H18.01" }),
3378
- h17("path", { d: "M8 14H16" })
5876
+ h20("rect", { x: "2", y: "6", width: "20", height: "12", rx: "2" }),
5877
+ h20("path", { d: "M6 10H6.01" }),
5878
+ h20("path", { d: "M10 10H10.01" }),
5879
+ h20("path", { d: "M14 10H14.01" }),
5880
+ h20("path", { d: "M18 10H18.01" }),
5881
+ h20("path", { d: "M8 14H16" })
3379
5882
  ])
3380
5883
  ]),
3381
- isOpen.value ? h17(Teleport4, { to: "body" }, [
3382
- h17("div", {
5884
+ isOpen.value ? h20(Teleport5, { to: "body" }, [
5885
+ h20("div", {
3383
5886
  ref: dropdownRef,
3384
5887
  class: "dialkit-root dialkit-shortcuts-dropdown",
3385
5888
  style: {
@@ -3388,19 +5891,19 @@ var ShortcutsMenu = defineComponent17({
3388
5891
  right: `${pos.value.right}px`
3389
5892
  }
3390
5893
  }, [
3391
- h17("div", { class: "dialkit-shortcuts-title" }, "Keyboard Shortcuts"),
3392
- h17(
5894
+ h20("div", { class: "dialkit-shortcuts-title" }, "Keyboard Shortcuts"),
5895
+ h20(
3393
5896
  "div",
3394
5897
  { class: "dialkit-shortcuts-list" },
3395
5898
  rows.map(
3396
- (row) => h17("div", { key: row.path, class: "dialkit-shortcuts-row" }, [
3397
- h17("span", { class: "dialkit-shortcuts-row-key" }, formatShortcutKey(row.shortcut)),
3398
- h17("span", { class: "dialkit-shortcuts-row-label" }, row.label),
3399
- h17("span", { class: "dialkit-shortcuts-row-mode" }, formatInteraction(row.shortcut))
5899
+ (row) => h20("div", { key: row.path, class: "dialkit-shortcuts-row" }, [
5900
+ h20("span", { class: "dialkit-shortcuts-row-key" }, formatShortcutKey(row.shortcut)),
5901
+ h20("span", { class: "dialkit-shortcuts-row-label" }, row.label),
5902
+ h20("span", { class: "dialkit-shortcuts-row-mode" }, formatInteraction(row.shortcut))
3400
5903
  ])
3401
5904
  )
3402
5905
  ),
3403
- h17("div", { class: "dialkit-shortcuts-hint" }, "See pill badges on controls for keys")
5906
+ h20("div", { class: "dialkit-shortcuts-hint" }, "See pill badges on controls for keys")
3404
5907
  ])
3405
5908
  ]) : null
3406
5909
  ];
@@ -3409,8 +5912,8 @@ var ShortcutsMenu = defineComponent17({
3409
5912
  });
3410
5913
 
3411
5914
  // src/vue/components/ButtonGroup.ts
3412
- import { defineComponent as defineComponent18, h as h18 } from "vue";
3413
- var ButtonGroup = defineComponent18({
5915
+ import { defineComponent as defineComponent21, h as h21 } from "vue";
5916
+ var ButtonGroup = defineComponent21({
3414
5917
  name: "DialKitButtonGroup",
3415
5918
  props: {
3416
5919
  buttons: {
@@ -3419,11 +5922,11 @@ var ButtonGroup = defineComponent18({
3419
5922
  }
3420
5923
  },
3421
5924
  setup(props) {
3422
- return () => h18(
5925
+ return () => h21(
3423
5926
  "div",
3424
5927
  { class: "dialkit-button-group" },
3425
5928
  props.buttons.map(
3426
- (button) => h18("button", { class: "dialkit-button", onClick: button.onClick }, button.label)
5929
+ (button) => h21("button", { class: "dialkit-button", onClick: button.onClick }, button.label)
3427
5930
  )
3428
5931
  );
3429
5932
  }
@@ -3431,8 +5934,10 @@ var ButtonGroup = defineComponent18({
3431
5934
  export {
3432
5935
  ButtonGroup,
3433
5936
  ColorControl,
5937
+ ControlRenderer,
3434
5938
  DialRoot,
3435
5939
  DialStore,
5940
+ DialTimeline,
3436
5941
  EasingVisualization,
3437
5942
  Folder,
3438
5943
  PresetManager,
@@ -3448,6 +5953,7 @@ export {
3448
5953
  TransitionControl,
3449
5954
  useDialKit,
3450
5955
  useDialKitController,
5956
+ useDialTimeline,
3451
5957
  useShortcutContext,
3452
5958
  vDialKit
3453
5959
  };