dialkit 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +433 -3
- package/dist/icons.d.ts +5 -1
- package/dist/icons.js +18 -0
- package/dist/icons.js.map +1 -1
- package/dist/index.cjs +3222 -479
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +274 -6
- package/dist/index.d.ts +274 -6
- package/dist/index.js +3154 -416
- package/dist/index.js.map +1 -1
- package/dist/solid/index.d.ts +225 -3
- package/dist/solid/index.js +5761 -2500
- package/dist/solid/index.js.map +1 -1
- package/dist/store/index.cjs +57 -16
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +14 -2
- package/dist/store/index.d.ts +14 -2
- package/dist/store/index.js +52 -16
- package/dist/store/index.js.map +1 -1
- package/dist/styles.css +788 -0
- package/dist/svelte/components/ControlRenderer.svelte +5 -2
- package/dist/svelte/components/ControlRenderer.svelte.d.ts +2 -0
- package/dist/svelte/components/ControlRenderer.svelte.d.ts.map +1 -1
- package/dist/svelte/components/DialRoot.svelte +43 -6
- package/dist/svelte/components/DialRoot.svelte.d.ts.map +1 -1
- package/dist/svelte/components/Panel.svelte +7 -1
- package/dist/svelte/components/Panel.svelte.d.ts +2 -0
- package/dist/svelte/components/Panel.svelte.d.ts.map +1 -1
- package/dist/svelte/components/Timeline/ClipPopover.svelte +206 -0
- package/dist/svelte/components/Timeline/ClipPopover.svelte.d.ts +26 -0
- package/dist/svelte/components/Timeline/ClipPopover.svelte.d.ts.map +1 -0
- package/dist/svelte/components/Timeline/DialTimeline.svelte +125 -0
- package/dist/svelte/components/Timeline/DialTimeline.svelte.d.ts +13 -0
- package/dist/svelte/components/Timeline/DialTimeline.svelte.d.ts.map +1 -0
- package/dist/svelte/components/Timeline/TimelineClip.svelte +233 -0
- package/dist/svelte/components/Timeline/TimelineClip.svelte.d.ts +24 -0
- package/dist/svelte/components/Timeline/TimelineClip.svelte.d.ts.map +1 -0
- package/dist/svelte/components/Timeline/TimelineSection.svelte +803 -0
- package/dist/svelte/components/Timeline/TimelineSection.svelte.d.ts +12 -0
- package/dist/svelte/components/Timeline/TimelineSection.svelte.d.ts.map +1 -0
- package/dist/svelte/components/Timeline/TimelineToggleButton.svelte +25 -0
- package/dist/svelte/components/Timeline/TimelineToggleButton.svelte.d.ts +4 -0
- package/dist/svelte/components/Timeline/TimelineToggleButton.svelte.d.ts.map +1 -0
- package/dist/svelte/components/TransitionControl.svelte +26 -11
- package/dist/svelte/components/TransitionControl.svelte.d.ts +9 -0
- package/dist/svelte/components/TransitionControl.svelte.d.ts.map +1 -1
- package/dist/svelte/createDialTimeline.svelte.d.ts +4 -0
- package/dist/svelte/createDialTimeline.svelte.d.ts.map +1 -0
- package/dist/svelte/createDialTimeline.svelte.js +73 -0
- package/dist/svelte/index.d.ts +4 -0
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +3 -0
- package/dist/svelte/theme-css.d.ts +1 -1
- package/dist/svelte/theme-css.d.ts.map +1 -1
- package/dist/svelte/theme-css.js +788 -0
- package/dist/timeline/index.cjs +1288 -0
- package/dist/timeline/index.cjs.map +1 -0
- package/dist/timeline/index.d.cts +443 -0
- package/dist/timeline/index.d.ts +443 -0
- package/dist/timeline/index.js +1233 -0
- package/dist/timeline/index.js.map +1 -0
- package/dist/vue/index.d.ts +273 -7
- package/dist/vue/index.js +4116 -1517
- package/dist/vue/index.js.map +1 -1
- package/package.json +23 -13
- package/dist/solid/index.cjs +0 -3536
- package/dist/solid/index.cjs.map +0 -1
- package/dist/solid/index.d.cts +0 -295
- package/dist/vue/index.cjs +0 -3497
- package/dist/vue/index.cjs.map +0 -1
- package/dist/vue/index.d.cts +0 -722
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/hooks/useDialKit.ts
|
|
4
|
-
import { useCallback, useEffect
|
|
4
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo, useRef as useRef2 } from "react";
|
|
5
5
|
|
|
6
6
|
// src/store/DialStore.ts
|
|
7
7
|
var EMPTY_VALUES = Object.freeze({});
|
|
@@ -71,6 +71,19 @@ function isSpringConfigValue(value) {
|
|
|
71
71
|
function isEasingConfigValue(value) {
|
|
72
72
|
return hasType(value, "easing");
|
|
73
73
|
}
|
|
74
|
+
function isHexColor(value) {
|
|
75
|
+
return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(value);
|
|
76
|
+
}
|
|
77
|
+
function formatLabel(key) {
|
|
78
|
+
return key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
|
|
79
|
+
}
|
|
80
|
+
function inferStep(min, max) {
|
|
81
|
+
const range = max - min;
|
|
82
|
+
if (range <= 1) return 0.01;
|
|
83
|
+
if (range <= 10) return 0.1;
|
|
84
|
+
if (range <= 100) return 1;
|
|
85
|
+
return 10;
|
|
86
|
+
}
|
|
74
87
|
function isActionConfigValue(value) {
|
|
75
88
|
return hasType(value, "action");
|
|
76
89
|
}
|
|
@@ -91,6 +104,9 @@ function getFirstOptionValue(options) {
|
|
|
91
104
|
var DialStoreClass = class {
|
|
92
105
|
constructor() {
|
|
93
106
|
this.panels = /* @__PURE__ */ new Map();
|
|
107
|
+
this.panelsSnapshot = [];
|
|
108
|
+
this.standardPanelsSnapshot = [];
|
|
109
|
+
this.timelinePanelsSnapshot = [];
|
|
94
110
|
this.listeners = /* @__PURE__ */ new Map();
|
|
95
111
|
this.globalListeners = /* @__PURE__ */ new Set();
|
|
96
112
|
this.snapshots = /* @__PURE__ */ new Map();
|
|
@@ -104,6 +120,12 @@ var DialStoreClass = class {
|
|
|
104
120
|
this.persistConfigs = /* @__PURE__ */ new Map();
|
|
105
121
|
}
|
|
106
122
|
registerPanel(id, name, config, shortcuts, options = {}) {
|
|
123
|
+
const existingPanel = this.panels.get(id);
|
|
124
|
+
if (existingPanel && existingPanel.kind !== options.kind) {
|
|
125
|
+
console.warn(
|
|
126
|
+
`[dialkit] Panel id "${id}" cannot be shared by a timeline and a standard panel; the most recent registration controls where it renders.`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
107
129
|
this.configurePanelRetention(id, options);
|
|
108
130
|
this.registrationCounts.set(id, (this.registrationCounts.get(id) ?? 0) + 1);
|
|
109
131
|
const controls = this.parseConfig(config, "", shortcuts);
|
|
@@ -115,7 +137,7 @@ var DialStoreClass = class {
|
|
|
115
137
|
const values = this.reconcileValues(defaultValues, previousValues, controlsByPath);
|
|
116
138
|
const previousBaseValues = this.baseValues.get(id) ?? persisted?.baseValues ?? persisted?.values ?? {};
|
|
117
139
|
const baseValues = this.reconcileValues(defaultValues, previousBaseValues, controlsByPath);
|
|
118
|
-
this.panels.set(id, { id, name, controls, values, shortcuts: shortcuts ?? {} });
|
|
140
|
+
this.panels.set(id, { id, name, controls, values, shortcuts: shortcuts ?? {}, kind: options.kind });
|
|
119
141
|
this.snapshots.set(id, { ...values });
|
|
120
142
|
this.baseValues.set(id, baseValues);
|
|
121
143
|
this.defaultValues.set(id, { ...defaultValues });
|
|
@@ -142,7 +164,7 @@ var DialStoreClass = class {
|
|
|
142
164
|
const defaultValues = this.flattenValues(config, "");
|
|
143
165
|
this.initTransitionModes(config, "", defaultValues);
|
|
144
166
|
const nextValues = this.reconcileValues(defaultValues, existing.values, controlsByPath);
|
|
145
|
-
const nextPanel = { id, name, controls, values: nextValues, shortcuts: shortcuts ?? existing.shortcuts };
|
|
167
|
+
const nextPanel = { id, name, controls, values: nextValues, shortcuts: shortcuts ?? existing.shortcuts, kind: options.kind ?? existing.kind };
|
|
146
168
|
this.panels.set(id, nextPanel);
|
|
147
169
|
this.snapshots.set(id, { ...nextValues });
|
|
148
170
|
const previousBaseValues = this.baseValues.get(id) ?? {};
|
|
@@ -167,8 +189,8 @@ var DialStoreClass = class {
|
|
|
167
189
|
}
|
|
168
190
|
this.registrationCounts.delete(id);
|
|
169
191
|
this.panels.delete(id);
|
|
170
|
-
this.listeners.delete(id);
|
|
171
|
-
this.actionListeners.delete(id);
|
|
192
|
+
if (this.listeners.get(id)?.size === 0) this.listeners.delete(id);
|
|
193
|
+
if (this.actionListeners.get(id)?.size === 0) this.actionListeners.delete(id);
|
|
172
194
|
if (!this.retainedPanels.has(id)) {
|
|
173
195
|
this.snapshots.delete(id);
|
|
174
196
|
this.baseValues.delete(id);
|
|
@@ -260,8 +282,10 @@ var DialStoreClass = class {
|
|
|
260
282
|
getValues(panelId) {
|
|
261
283
|
return this.snapshots.get(panelId) ?? EMPTY_VALUES;
|
|
262
284
|
}
|
|
263
|
-
getPanels() {
|
|
264
|
-
return
|
|
285
|
+
getPanels(kind) {
|
|
286
|
+
if (kind === "panel") return this.standardPanelsSnapshot;
|
|
287
|
+
if (kind === "timeline") return this.timelinePanelsSnapshot;
|
|
288
|
+
return this.panelsSnapshot;
|
|
265
289
|
}
|
|
266
290
|
getPanel(id) {
|
|
267
291
|
return this.panels.get(id);
|
|
@@ -272,7 +296,11 @@ var DialStoreClass = class {
|
|
|
272
296
|
}
|
|
273
297
|
this.listeners.get(panelId).add(listener);
|
|
274
298
|
return () => {
|
|
275
|
-
this.listeners.get(panelId)
|
|
299
|
+
const listeners = this.listeners.get(panelId);
|
|
300
|
+
listeners?.delete(listener);
|
|
301
|
+
if (listeners?.size === 0 && !this.panels.has(panelId)) {
|
|
302
|
+
this.listeners.delete(panelId);
|
|
303
|
+
}
|
|
276
304
|
};
|
|
277
305
|
}
|
|
278
306
|
subscribeGlobal(listener) {
|
|
@@ -285,7 +313,11 @@ var DialStoreClass = class {
|
|
|
285
313
|
}
|
|
286
314
|
this.actionListeners.get(panelId).add(listener);
|
|
287
315
|
return () => {
|
|
288
|
-
this.actionListeners.get(panelId)
|
|
316
|
+
const listeners = this.actionListeners.get(panelId);
|
|
317
|
+
listeners?.delete(listener);
|
|
318
|
+
if (listeners?.size === 0 && !this.panels.has(panelId)) {
|
|
319
|
+
this.actionListeners.delete(panelId);
|
|
320
|
+
}
|
|
289
321
|
};
|
|
290
322
|
}
|
|
291
323
|
triggerAction(panelId, path) {
|
|
@@ -480,6 +512,9 @@ var DialStoreClass = class {
|
|
|
480
512
|
this.listeners.get(panelId)?.forEach((fn) => fn());
|
|
481
513
|
}
|
|
482
514
|
notifyGlobal() {
|
|
515
|
+
this.panelsSnapshot = Array.from(this.panels.values());
|
|
516
|
+
this.standardPanelsSnapshot = this.panelsSnapshot.filter((panel) => panel.kind !== "timeline");
|
|
517
|
+
this.timelinePanelsSnapshot = this.panelsSnapshot.filter((panel) => panel.kind === "timeline");
|
|
483
518
|
this.globalListeners.forEach((fn) => fn());
|
|
484
519
|
}
|
|
485
520
|
initTransitionModes(config, prefix, values) {
|
|
@@ -595,10 +630,10 @@ var DialStoreClass = class {
|
|
|
595
630
|
return typeof value === "object" && value !== null && "type" in value && value.type === "text";
|
|
596
631
|
}
|
|
597
632
|
isHexColor(value) {
|
|
598
|
-
return
|
|
633
|
+
return isHexColor(value);
|
|
599
634
|
}
|
|
600
635
|
formatLabel(key) {
|
|
601
|
-
return key
|
|
636
|
+
return formatLabel(key);
|
|
602
637
|
}
|
|
603
638
|
inferRange(value) {
|
|
604
639
|
if (value >= 0 && value <= 1) {
|
|
@@ -614,11 +649,7 @@ var DialStoreClass = class {
|
|
|
614
649
|
}
|
|
615
650
|
}
|
|
616
651
|
inferStep(min, max) {
|
|
617
|
-
|
|
618
|
-
if (range <= 1) return 0.01;
|
|
619
|
-
if (range <= 10) return 0.1;
|
|
620
|
-
if (range <= 100) return 1;
|
|
621
|
-
return 10;
|
|
652
|
+
return inferStep(min, max);
|
|
622
653
|
}
|
|
623
654
|
normalizePreservedValue(existingValue, defaultValue, control) {
|
|
624
655
|
if (existingValue === void 0 || !control) {
|
|
@@ -690,31 +721,33 @@ var DialStoreClass = class {
|
|
|
690
721
|
return map;
|
|
691
722
|
}
|
|
692
723
|
};
|
|
693
|
-
var DialStore = new DialStoreClass();
|
|
724
|
+
var DialStore = /* @__PURE__ */ new DialStoreClass();
|
|
694
725
|
|
|
695
|
-
// src/hooks/
|
|
696
|
-
|
|
697
|
-
|
|
726
|
+
// src/hooks/useDialStorePanel.ts
|
|
727
|
+
import { useCallback, useEffect, useId, useRef, useSyncExternalStore } from "react";
|
|
728
|
+
function useSerialized(value) {
|
|
729
|
+
const ref = useRef();
|
|
730
|
+
if (!ref.current || !Object.is(ref.current.value, value)) {
|
|
731
|
+
ref.current = { value, text: JSON.stringify(value) };
|
|
732
|
+
}
|
|
733
|
+
return ref.current.text;
|
|
698
734
|
}
|
|
699
|
-
function
|
|
735
|
+
function useDialStorePanel(name, config, options = {}) {
|
|
700
736
|
const instanceId = useId();
|
|
701
|
-
const hasStableId = options
|
|
702
|
-
const panelId = options
|
|
737
|
+
const hasStableId = options.id !== void 0;
|
|
738
|
+
const panelId = options.id ?? `${name}-${instanceId}`;
|
|
703
739
|
const configRef = useRef(config);
|
|
704
|
-
const serializedConfig = JSON.stringify(config);
|
|
705
740
|
configRef.current = config;
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
persistRef.current = options?.persist;
|
|
712
|
-
const serializedShortcuts = JSON.stringify(options?.shortcuts);
|
|
713
|
-
const serializedPersist = JSON.stringify(options?.persist);
|
|
741
|
+
const optionsRef = useRef(options);
|
|
742
|
+
optionsRef.current = options;
|
|
743
|
+
const serializedConfig = useSerialized(config);
|
|
744
|
+
const serializedShortcuts = useSerialized(options.shortcuts);
|
|
745
|
+
const serializedPersist = useSerialized(options.persist);
|
|
714
746
|
useEffect(() => {
|
|
715
|
-
DialStore.registerPanel(panelId, name, configRef.current,
|
|
747
|
+
DialStore.registerPanel(panelId, name, configRef.current, optionsRef.current.shortcuts, {
|
|
716
748
|
retainOnUnmount: hasStableId,
|
|
717
|
-
persist:
|
|
749
|
+
persist: optionsRef.current.persist,
|
|
750
|
+
kind: optionsRef.current.kind
|
|
718
751
|
});
|
|
719
752
|
return () => DialStore.unregisterPanel(panelId);
|
|
720
753
|
}, [hasStableId, panelId, name]);
|
|
@@ -724,45 +757,61 @@ function useDialKitController(name, config, options) {
|
|
|
724
757
|
mountedRef.current = true;
|
|
725
758
|
return;
|
|
726
759
|
}
|
|
727
|
-
DialStore.updatePanel(panelId, name, configRef.current,
|
|
760
|
+
DialStore.updatePanel(panelId, name, configRef.current, optionsRef.current.shortcuts, {
|
|
728
761
|
retainOnUnmount: hasStableId,
|
|
729
|
-
persist:
|
|
762
|
+
persist: optionsRef.current.persist,
|
|
763
|
+
kind: optionsRef.current.kind
|
|
730
764
|
});
|
|
731
765
|
}, [hasStableId, panelId, name, serializedConfig, serializedShortcuts, serializedPersist]);
|
|
732
|
-
useEffect(() => {
|
|
733
|
-
return DialStore.subscribeActions(panelId, (action) => {
|
|
734
|
-
onActionRef.current?.(action);
|
|
735
|
-
});
|
|
736
|
-
}, [panelId]);
|
|
737
766
|
const subscribe = useCallback(
|
|
738
767
|
(callback) => DialStore.subscribe(panelId, callback),
|
|
739
768
|
[panelId]
|
|
740
769
|
);
|
|
741
|
-
const getSnapshot = useCallback(
|
|
742
|
-
() => DialStore.getValues(panelId),
|
|
743
|
-
[panelId]
|
|
744
|
-
);
|
|
770
|
+
const getSnapshot = useCallback(() => DialStore.getValues(panelId), [panelId]);
|
|
745
771
|
const flatValues = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
772
|
+
return { panelId, flatValues, serializedConfig };
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/hooks/useDialKit.ts
|
|
776
|
+
function useDialKit(name, config, options) {
|
|
777
|
+
return useDialKitController(name, config, options).values;
|
|
778
|
+
}
|
|
779
|
+
function useDialKitController(name, config, options) {
|
|
780
|
+
const { panelId, flatValues, serializedConfig } = useDialStorePanel(name, config, {
|
|
781
|
+
id: options?.id,
|
|
782
|
+
persist: options?.persist,
|
|
783
|
+
shortcuts: options?.shortcuts
|
|
784
|
+
});
|
|
785
|
+
const configRef = useRef2(config);
|
|
786
|
+
configRef.current = config;
|
|
787
|
+
const onActionRef = useRef2(options?.onAction);
|
|
788
|
+
onActionRef.current = options?.onAction;
|
|
789
|
+
useEffect2(() => {
|
|
790
|
+
return DialStore.subscribeActions(panelId, (action) => {
|
|
791
|
+
onActionRef.current?.(action);
|
|
792
|
+
});
|
|
793
|
+
}, [panelId]);
|
|
746
794
|
const values = useMemo(
|
|
747
795
|
() => resolveDialValues(configRef.current, flatValues),
|
|
796
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
748
797
|
[flatValues, serializedConfig]
|
|
749
798
|
);
|
|
750
|
-
const setValue =
|
|
799
|
+
const setValue = useCallback2(
|
|
751
800
|
(path, value) => {
|
|
752
801
|
DialStore.updateValue(panelId, path, value);
|
|
753
802
|
},
|
|
754
803
|
[panelId]
|
|
755
804
|
);
|
|
756
|
-
const setValues =
|
|
805
|
+
const setValues = useCallback2(
|
|
757
806
|
(nextValues) => {
|
|
758
807
|
DialStore.updateValues(panelId, flattenDialValueUpdates(configRef.current, nextValues));
|
|
759
808
|
},
|
|
760
809
|
[panelId]
|
|
761
810
|
);
|
|
762
|
-
const resetValues =
|
|
811
|
+
const resetValues = useCallback2(() => {
|
|
763
812
|
DialStore.resetValues(panelId);
|
|
764
813
|
}, [panelId]);
|
|
765
|
-
const getValues =
|
|
814
|
+
const getValues = useCallback2(
|
|
766
815
|
() => resolveDialValues(configRef.current, DialStore.getValues(panelId)),
|
|
767
816
|
[panelId]
|
|
768
817
|
);
|
|
@@ -779,16 +828,225 @@ function useDialKitController(name, config, options) {
|
|
|
779
828
|
}
|
|
780
829
|
|
|
781
830
|
// src/components/DialRoot.tsx
|
|
782
|
-
import { useEffect as
|
|
831
|
+
import { useEffect as useEffect9, useState as useState10, useRef as useRef12, useCallback as useCallback12 } from "react";
|
|
783
832
|
import { createPortal as createPortal3 } from "react-dom";
|
|
784
833
|
|
|
834
|
+
// src/store/TimelineStore.ts
|
|
835
|
+
function loopSpan(duration, loopStart) {
|
|
836
|
+
if (!Number.isFinite(duration) || duration <= 0) return 0;
|
|
837
|
+
if (!Number.isFinite(loopStart)) loopStart = 0;
|
|
838
|
+
const start = Math.min(Math.max(0, loopStart), duration);
|
|
839
|
+
return duration - start > 0 ? duration - start : duration;
|
|
840
|
+
}
|
|
841
|
+
function foldLoopTime(time, duration, loopStart = 0) {
|
|
842
|
+
if (!Number.isFinite(time) || !Number.isFinite(duration) || duration <= 0) {
|
|
843
|
+
return { time: 0, wraps: 0 };
|
|
844
|
+
}
|
|
845
|
+
if (time < duration) return { time, wraps: 0 };
|
|
846
|
+
const span = loopSpan(duration, loopStart);
|
|
847
|
+
const base = duration - span;
|
|
848
|
+
const over = time - base;
|
|
849
|
+
return { time: base + over % span, wraps: Math.floor(over / span) };
|
|
850
|
+
}
|
|
851
|
+
var TIMELINE_CLIP_COLORS = [
|
|
852
|
+
"#E8E8E8"
|
|
853
|
+
// neutral white — slightly off-white so the pure-white selection ring still reads
|
|
854
|
+
];
|
|
855
|
+
var EMPTY_TRANSPORT = Object.freeze({ time: 0, playing: false, duration: 0, wraps: 0 });
|
|
856
|
+
var TimelineStoreClass = class {
|
|
857
|
+
constructor() {
|
|
858
|
+
this.timelines = /* @__PURE__ */ new Map();
|
|
859
|
+
this.transports = /* @__PURE__ */ new Map();
|
|
860
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
861
|
+
this.globalListeners = /* @__PURE__ */ new Set();
|
|
862
|
+
this.registrationCounts = /* @__PURE__ */ new Map();
|
|
863
|
+
this.listCache = null;
|
|
864
|
+
this.rafId = null;
|
|
865
|
+
this.lastTick = 0;
|
|
866
|
+
this.tick = (now) => {
|
|
867
|
+
const dt = Math.max(0, (now - this.lastTick) / 1e3);
|
|
868
|
+
this.lastTick = now;
|
|
869
|
+
let anyPlaying = false;
|
|
870
|
+
for (const [id, transport] of this.transports) {
|
|
871
|
+
if (!transport.playing) continue;
|
|
872
|
+
const meta = this.timelines.get(id);
|
|
873
|
+
const duration = meta?.duration ?? transport.duration;
|
|
874
|
+
if (!Number.isFinite(duration) || duration <= 0) {
|
|
875
|
+
this.transports.set(id, { time: 0, playing: false, duration: 0, wraps: 0 });
|
|
876
|
+
this.notify(id);
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
let time = transport.time + dt;
|
|
880
|
+
let playing = true;
|
|
881
|
+
let wraps = transport.wraps;
|
|
882
|
+
if (time >= duration) {
|
|
883
|
+
if (meta?.loop) {
|
|
884
|
+
const folded = foldLoopTime(time, duration, meta.loopStart);
|
|
885
|
+
time = folded.time;
|
|
886
|
+
wraps += folded.wraps;
|
|
887
|
+
} else {
|
|
888
|
+
time = duration;
|
|
889
|
+
playing = false;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
this.transports.set(id, { time, playing, duration, wraps });
|
|
893
|
+
if (playing) anyPlaying = true;
|
|
894
|
+
this.notify(id);
|
|
895
|
+
}
|
|
896
|
+
this.rafId = anyPlaying ? window.requestAnimationFrame(this.tick) : null;
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
register(meta, options) {
|
|
900
|
+
const existing = this.timelines.get(meta.id);
|
|
901
|
+
if (existing && existing.name !== meta.name) {
|
|
902
|
+
console.warn(
|
|
903
|
+
`[dialkit] Timeline id "${meta.id}" is already registered by "${existing.name}"; "${meta.name}" will share and overwrite that transport.`
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
this.registrationCounts.set(meta.id, (this.registrationCounts.get(meta.id) ?? 0) + 1);
|
|
907
|
+
this.applyMeta(meta, options.autoplay);
|
|
908
|
+
}
|
|
909
|
+
update(meta) {
|
|
910
|
+
if (!this.timelines.has(meta.id)) return;
|
|
911
|
+
this.applyMeta(meta, false);
|
|
912
|
+
}
|
|
913
|
+
unregister(id) {
|
|
914
|
+
const nextCount = (this.registrationCounts.get(id) ?? 1) - 1;
|
|
915
|
+
if (nextCount > 0) {
|
|
916
|
+
this.registrationCounts.set(id, nextCount);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
this.registrationCounts.delete(id);
|
|
920
|
+
this.timelines.delete(id);
|
|
921
|
+
this.transports.delete(id);
|
|
922
|
+
if (this.listeners.get(id)?.size === 0) this.listeners.delete(id);
|
|
923
|
+
this.listCache = null;
|
|
924
|
+
this.notifyGlobal();
|
|
925
|
+
}
|
|
926
|
+
play(id) {
|
|
927
|
+
const transport = this.transports.get(id);
|
|
928
|
+
if (!transport || transport.duration <= 0 || transport.playing) return;
|
|
929
|
+
const restart = transport.time >= transport.duration;
|
|
930
|
+
this.transports.set(id, {
|
|
931
|
+
...transport,
|
|
932
|
+
time: restart ? 0 : transport.time,
|
|
933
|
+
wraps: restart ? 0 : transport.wraps,
|
|
934
|
+
playing: true
|
|
935
|
+
});
|
|
936
|
+
this.notify(id);
|
|
937
|
+
this.ensureLoop();
|
|
938
|
+
}
|
|
939
|
+
pause(id) {
|
|
940
|
+
const transport = this.transports.get(id);
|
|
941
|
+
if (!transport || !transport.playing) return;
|
|
942
|
+
this.transports.set(id, { ...transport, playing: false });
|
|
943
|
+
this.notify(id);
|
|
944
|
+
}
|
|
945
|
+
replay(id) {
|
|
946
|
+
const transport = this.transports.get(id);
|
|
947
|
+
if (!transport || transport.duration <= 0) return;
|
|
948
|
+
this.transports.set(id, { ...transport, time: 0, wraps: 0, playing: true });
|
|
949
|
+
this.notify(id);
|
|
950
|
+
this.ensureLoop();
|
|
951
|
+
}
|
|
952
|
+
seek(id, time) {
|
|
953
|
+
const transport = this.transports.get(id);
|
|
954
|
+
if (!transport || !Number.isFinite(time)) return;
|
|
955
|
+
const clamped = Math.min(transport.duration, Math.max(0, time));
|
|
956
|
+
this.transports.set(id, { ...transport, time: clamped, wraps: 0 });
|
|
957
|
+
this.notify(id);
|
|
958
|
+
}
|
|
959
|
+
getTransport(id) {
|
|
960
|
+
return this.transports.get(id) ?? EMPTY_TRANSPORT;
|
|
961
|
+
}
|
|
962
|
+
getTimeline(id) {
|
|
963
|
+
return this.timelines.get(id);
|
|
964
|
+
}
|
|
965
|
+
getTimelines() {
|
|
966
|
+
if (!this.listCache) {
|
|
967
|
+
this.listCache = Array.from(this.timelines.values());
|
|
968
|
+
}
|
|
969
|
+
return this.listCache;
|
|
970
|
+
}
|
|
971
|
+
subscribe(id, listener) {
|
|
972
|
+
if (!this.listeners.has(id)) {
|
|
973
|
+
this.listeners.set(id, /* @__PURE__ */ new Set());
|
|
974
|
+
}
|
|
975
|
+
this.listeners.get(id).add(listener);
|
|
976
|
+
return () => {
|
|
977
|
+
const listeners = this.listeners.get(id);
|
|
978
|
+
listeners?.delete(listener);
|
|
979
|
+
if (listeners?.size === 0 && !this.timelines.has(id)) {
|
|
980
|
+
this.listeners.delete(id);
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
subscribeGlobal(listener) {
|
|
985
|
+
this.globalListeners.add(listener);
|
|
986
|
+
return () => {
|
|
987
|
+
this.globalListeners.delete(listener);
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
applyMeta(meta, autoplay) {
|
|
991
|
+
const duration = Number.isFinite(meta.duration) ? Math.max(0, meta.duration) : 0;
|
|
992
|
+
const loopStart = Number.isFinite(meta.loopStart) ? Math.min(duration, Math.max(0, meta.loopStart)) : 0;
|
|
993
|
+
const safeMeta = { ...meta, duration, loopStart };
|
|
994
|
+
this.timelines.set(meta.id, safeMeta);
|
|
995
|
+
const existing = this.transports.get(meta.id);
|
|
996
|
+
if (existing) {
|
|
997
|
+
this.transports.set(meta.id, {
|
|
998
|
+
time: Math.min(existing.time, duration),
|
|
999
|
+
playing: duration > 0 && existing.playing,
|
|
1000
|
+
duration,
|
|
1001
|
+
wraps: existing.wraps
|
|
1002
|
+
});
|
|
1003
|
+
} else {
|
|
1004
|
+
const playing = duration > 0 && autoplay;
|
|
1005
|
+
this.transports.set(meta.id, { time: 0, playing, duration, wraps: 0 });
|
|
1006
|
+
if (playing) this.ensureLoop();
|
|
1007
|
+
}
|
|
1008
|
+
this.listCache = null;
|
|
1009
|
+
this.notify(meta.id);
|
|
1010
|
+
this.notifyGlobal();
|
|
1011
|
+
}
|
|
1012
|
+
ensureLoop() {
|
|
1013
|
+
if (this.rafId !== null || typeof window === "undefined") return;
|
|
1014
|
+
this.lastTick = performance.now();
|
|
1015
|
+
this.rafId = window.requestAnimationFrame(this.tick);
|
|
1016
|
+
}
|
|
1017
|
+
notify(id) {
|
|
1018
|
+
this.listeners.get(id)?.forEach((fn) => fn());
|
|
1019
|
+
}
|
|
1020
|
+
notifyGlobal() {
|
|
1021
|
+
this.globalListeners.forEach((fn) => fn());
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
var TimelineStore = /* @__PURE__ */ new TimelineStoreClass();
|
|
1025
|
+
|
|
1026
|
+
// src/env.ts
|
|
1027
|
+
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;
|
|
1028
|
+
|
|
785
1029
|
// src/components/Folder.tsx
|
|
786
|
-
import { useState, useRef as
|
|
1030
|
+
import { useState, useRef as useRef3, useEffect as useEffect3 } from "react";
|
|
787
1031
|
import { motion, AnimatePresence } from "motion/react";
|
|
788
1032
|
|
|
789
1033
|
// src/icons.ts
|
|
790
1034
|
var ICON_CHEVRON = "M6 9.5L12 15.5L18 9.5";
|
|
791
1035
|
var ICON_CHECK = "M5 12.75L10 19L19 5";
|
|
1036
|
+
var ICON_PAUSE = [
|
|
1037
|
+
"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",
|
|
1038
|
+
"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"
|
|
1039
|
+
];
|
|
1040
|
+
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";
|
|
1041
|
+
var ICON_REPLAY = [
|
|
1042
|
+
"M12 2.5C17.2466 2.50016 21.5 6.7534 21.5 12C21.5 17.2466 17.2466 21.4998 12 21.5C7.52191 21.5 3.76987 18.4025 2.76465 14.2344C2.63517 13.6975 2.96508 13.1578 3.50195 13.0283C4.03883 12.8988 4.57851 13.2288 4.70801 13.7656C5.5016 17.0563 8.46701 19.5 12 19.5C16.142 19.4998 19.5 16.142 19.5 12C19.5 7.85796 16.142 4.50016 12 4.5C9.32981 4.5 6.98389 5.89541 5.6543 8H7.5C8.05228 8 8.5 8.44772 8.5 9C8.5 9.55228 8.05228 10 7.5 10H3.5C2.94772 10 2.5 9.55228 2.5 9V5C2.5 4.44772 2.94772 4 3.5 4C4.05228 4 4.5 4.44772 4.5 5V6.16797C6.2376 3.93677 8.95063 2.5 12 2.5Z",
|
|
1043
|
+
"M10 9.94043C10 9.33379 10.6826 8.97849 11.1797 9.32617L14.1221 11.3857C14.5486 11.6843 14.5486 12.3157 14.1221 12.6143L11.1797 14.6738C10.6826 15.0215 10 14.6662 10 14.0596V9.94043Z"
|
|
1044
|
+
];
|
|
1045
|
+
var ICON_TIMELINE = [
|
|
1046
|
+
"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",
|
|
1047
|
+
"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",
|
|
1048
|
+
"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"
|
|
1049
|
+
];
|
|
792
1050
|
var ICON_CLIPBOARD = {
|
|
793
1051
|
board: "M8 6C8 4.34315 9.34315 3 11 3H13C14.6569 3 16 4.34315 16 6V7H8V6Z",
|
|
794
1052
|
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",
|
|
@@ -822,16 +1080,16 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
822
1080
|
function Folder({ title, children, defaultOpen = true, isRoot = false, inline = false, onOpenChange, toolbar, panelHeightOffset = 10 }) {
|
|
823
1081
|
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
824
1082
|
const [isCollapsed, setIsCollapsed] = useState(!defaultOpen);
|
|
825
|
-
const contentRef =
|
|
1083
|
+
const contentRef = useRef3(null);
|
|
826
1084
|
const [contentHeight, setContentHeight] = useState(void 0);
|
|
827
1085
|
const [windowHeight, setWindowHeight] = useState(typeof window !== "undefined" ? window.innerHeight : 800);
|
|
828
|
-
|
|
1086
|
+
useEffect3(() => {
|
|
829
1087
|
if (!isRoot) return;
|
|
830
1088
|
const onResize = () => setWindowHeight(window.innerHeight);
|
|
831
1089
|
window.addEventListener("resize", onResize);
|
|
832
1090
|
return () => window.removeEventListener("resize", onResize);
|
|
833
1091
|
}, [isRoot]);
|
|
834
|
-
|
|
1092
|
+
useEffect3(() => {
|
|
835
1093
|
const el = contentRef.current;
|
|
836
1094
|
if (!el) return;
|
|
837
1095
|
const ro = new ResizeObserver(() => {
|
|
@@ -932,11 +1190,43 @@ function Folder({ title, children, defaultOpen = true, isRoot = false, inline =
|
|
|
932
1190
|
}
|
|
933
1191
|
|
|
934
1192
|
// src/components/Panel.tsx
|
|
935
|
-
import { useCallback as
|
|
1193
|
+
import { useCallback as useCallback10, useState as useState9, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
936
1194
|
import { motion as motion5, AnimatePresence as AnimatePresence4 } from "motion/react";
|
|
937
1195
|
|
|
1196
|
+
// src/copy-instruction.ts
|
|
1197
|
+
function buildCopyInstruction(hookName, panelName, values) {
|
|
1198
|
+
const jsonStr = JSON.stringify(values, null, 2);
|
|
1199
|
+
if (hookName === "useDialTimeline" || hookName === "createDialTimeline") {
|
|
1200
|
+
return `Update the ${hookName} configuration for "${panelName}" with these values:
|
|
1201
|
+
|
|
1202
|
+
\`\`\`json
|
|
1203
|
+
${jsonStr}
|
|
1204
|
+
\`\`\`
|
|
1205
|
+
|
|
1206
|
+
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.
|
|
1207
|
+
|
|
1208
|
+
Add this comment immediately above the ${hookName} call as a production handoff note:
|
|
1209
|
+
|
|
1210
|
+
\`\`\`tsx
|
|
1211
|
+
// TODO(production): DialKit's clip.current values are the scrubbable authoring preview.
|
|
1212
|
+
// Replace them with equivalent real Motion animations using the tuned timeline
|
|
1213
|
+
// timings and transitions, then remove ${hookName} and <DialTimeline />.
|
|
1214
|
+
\`\`\``;
|
|
1215
|
+
}
|
|
1216
|
+
return `Update the ${hookName} configuration for "${panelName}" with these values:
|
|
1217
|
+
|
|
1218
|
+
\`\`\`json
|
|
1219
|
+
${jsonStr}
|
|
1220
|
+
\`\`\`
|
|
1221
|
+
|
|
1222
|
+
Apply these values as the new defaults in the ${hookName} call.`;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// src/components/ControlRenderer.tsx
|
|
1226
|
+
import { useContext } from "react";
|
|
1227
|
+
|
|
938
1228
|
// src/components/ShortcutListener.tsx
|
|
939
|
-
import { createContext, useEffect as
|
|
1229
|
+
import { createContext, useEffect as useEffect4, useRef as useRef4, useState as useState2, useCallback as useCallback3 } from "react";
|
|
940
1230
|
|
|
941
1231
|
// src/shortcut-utils.ts
|
|
942
1232
|
function decimalsForStep(step) {
|
|
@@ -1028,11 +1318,11 @@ import { jsx as jsx2 } from "react/jsx-runtime";
|
|
|
1028
1318
|
var ShortcutContext = createContext({ activePanelId: null, activePath: null });
|
|
1029
1319
|
function ShortcutListener({ children }) {
|
|
1030
1320
|
const [activeShortcut, setActiveShortcut] = useState2({ activePanelId: null, activePath: null });
|
|
1031
|
-
const activeKeysRef =
|
|
1032
|
-
const isDraggingRef =
|
|
1033
|
-
const lastMouseXRef =
|
|
1034
|
-
const dragAccumulatorRef =
|
|
1035
|
-
const resolveActiveTarget =
|
|
1321
|
+
const activeKeysRef = useRef4(/* @__PURE__ */ new Set());
|
|
1322
|
+
const isDraggingRef = useRef4(false);
|
|
1323
|
+
const lastMouseXRef = useRef4(null);
|
|
1324
|
+
const dragAccumulatorRef = useRef4(0);
|
|
1325
|
+
const resolveActiveTarget = useCallback3((interaction) => {
|
|
1036
1326
|
for (const key of activeKeysRef.current) {
|
|
1037
1327
|
const panels = DialStore.getPanels();
|
|
1038
1328
|
for (const panel of panels) {
|
|
@@ -1049,7 +1339,7 @@ function ShortcutListener({ children }) {
|
|
|
1049
1339
|
}
|
|
1050
1340
|
return null;
|
|
1051
1341
|
}, []);
|
|
1052
|
-
|
|
1342
|
+
useEffect4(() => {
|
|
1053
1343
|
const handleKeyDown = (e) => {
|
|
1054
1344
|
if (isInputFocused()) return;
|
|
1055
1345
|
const key = e.key.toLowerCase();
|
|
@@ -1211,7 +1501,7 @@ function ShortcutListener({ children }) {
|
|
|
1211
1501
|
}
|
|
1212
1502
|
|
|
1213
1503
|
// src/components/Slider.tsx
|
|
1214
|
-
import { useRef as
|
|
1504
|
+
import { useRef as useRef5, useState as useState3, useCallback as useCallback4, useEffect as useEffect5 } from "react";
|
|
1215
1505
|
import { motion as motion2, useMotionValue, useTransform, animate } from "motion/react";
|
|
1216
1506
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1217
1507
|
var CLICK_THRESHOLD = 3;
|
|
@@ -1229,11 +1519,11 @@ function Slider({
|
|
|
1229
1519
|
shortcut,
|
|
1230
1520
|
shortcutActive
|
|
1231
1521
|
}) {
|
|
1232
|
-
const wrapperRef =
|
|
1233
|
-
const trackRef =
|
|
1234
|
-
const inputRef =
|
|
1235
|
-
const labelRef =
|
|
1236
|
-
const valueSpanRef =
|
|
1522
|
+
const wrapperRef = useRef5(null);
|
|
1523
|
+
const trackRef = useRef5(null);
|
|
1524
|
+
const inputRef = useRef5(null);
|
|
1525
|
+
const labelRef = useRef5(null);
|
|
1526
|
+
const valueSpanRef = useRef5(null);
|
|
1237
1527
|
const [isInteracting, setIsInteracting] = useState3(false);
|
|
1238
1528
|
const [isDragging, setIsDragging] = useState3(false);
|
|
1239
1529
|
const [isHovered, setIsHovered] = useState3(false);
|
|
@@ -1241,12 +1531,12 @@ function Slider({
|
|
|
1241
1531
|
const [isValueEditable, setIsValueEditable] = useState3(false);
|
|
1242
1532
|
const [showInput, setShowInput] = useState3(false);
|
|
1243
1533
|
const [inputValue, setInputValue] = useState3("");
|
|
1244
|
-
const hoverTimeoutRef =
|
|
1245
|
-
const pointerDownPos =
|
|
1246
|
-
const isClickRef =
|
|
1247
|
-
const animRef =
|
|
1248
|
-
const wrapperRectRef =
|
|
1249
|
-
const scaleRef =
|
|
1534
|
+
const hoverTimeoutRef = useRef5(null);
|
|
1535
|
+
const pointerDownPos = useRef5(null);
|
|
1536
|
+
const isClickRef = useRef5(true);
|
|
1537
|
+
const animRef = useRef5(null);
|
|
1538
|
+
const wrapperRectRef = useRef5(null);
|
|
1539
|
+
const scaleRef = useRef5(1);
|
|
1250
1540
|
const percentage = (value - min) / (max - min) * 100;
|
|
1251
1541
|
const isActive = isInteracting || isHovered;
|
|
1252
1542
|
const fillPercent = useMotionValue(percentage);
|
|
@@ -1264,12 +1554,12 @@ function Slider({
|
|
|
1264
1554
|
rubberStretchPx,
|
|
1265
1555
|
(stretch) => stretch < 0 ? stretch : 0
|
|
1266
1556
|
);
|
|
1267
|
-
|
|
1557
|
+
useEffect5(() => {
|
|
1268
1558
|
if (!isInteracting && !animRef.current) {
|
|
1269
1559
|
fillPercent.jump(percentage);
|
|
1270
1560
|
}
|
|
1271
1561
|
}, [percentage, isInteracting, fillPercent]);
|
|
1272
|
-
const positionToValue =
|
|
1562
|
+
const positionToValue = useCallback4(
|
|
1273
1563
|
(clientX) => {
|
|
1274
1564
|
const rect = wrapperRectRef.current;
|
|
1275
1565
|
if (!rect) return value;
|
|
@@ -1282,11 +1572,11 @@ function Slider({
|
|
|
1282
1572
|
},
|
|
1283
1573
|
[min, max, value]
|
|
1284
1574
|
);
|
|
1285
|
-
const percentFromValue =
|
|
1575
|
+
const percentFromValue = useCallback4(
|
|
1286
1576
|
(v) => (v - min) / (max - min) * 100,
|
|
1287
1577
|
[min, max]
|
|
1288
1578
|
);
|
|
1289
|
-
const computeRubberStretch =
|
|
1579
|
+
const computeRubberStretch = useCallback4(
|
|
1290
1580
|
(clientX, sign) => {
|
|
1291
1581
|
const rect = wrapperRectRef.current;
|
|
1292
1582
|
if (!rect) return 0;
|
|
@@ -1296,7 +1586,7 @@ function Slider({
|
|
|
1296
1586
|
},
|
|
1297
1587
|
[]
|
|
1298
1588
|
);
|
|
1299
|
-
const handlePointerDown =
|
|
1589
|
+
const handlePointerDown = useCallback4(
|
|
1300
1590
|
(e) => {
|
|
1301
1591
|
if (showInput) return;
|
|
1302
1592
|
e.preventDefault();
|
|
@@ -1312,7 +1602,7 @@ function Slider({
|
|
|
1312
1602
|
},
|
|
1313
1603
|
[showInput]
|
|
1314
1604
|
);
|
|
1315
|
-
const handlePointerMove =
|
|
1605
|
+
const handlePointerMove = useCallback4(
|
|
1316
1606
|
(e) => {
|
|
1317
1607
|
if (!isInteracting || !pointerDownPos.current) return;
|
|
1318
1608
|
const dx = e.clientX - pointerDownPos.current.x;
|
|
@@ -1353,7 +1643,7 @@ function Slider({
|
|
|
1353
1643
|
computeRubberStretch
|
|
1354
1644
|
]
|
|
1355
1645
|
);
|
|
1356
|
-
const handlePointerUp =
|
|
1646
|
+
const handlePointerUp = useCallback4(
|
|
1357
1647
|
(e) => {
|
|
1358
1648
|
if (!isInteracting) return;
|
|
1359
1649
|
if (isClickRef.current) {
|
|
@@ -1397,7 +1687,7 @@ function Slider({
|
|
|
1397
1687
|
rubberStretchPx
|
|
1398
1688
|
]
|
|
1399
1689
|
);
|
|
1400
|
-
|
|
1690
|
+
useEffect5(() => {
|
|
1401
1691
|
if (isValueHovered && !showInput && !isValueEditable) {
|
|
1402
1692
|
hoverTimeoutRef.current = setTimeout(() => {
|
|
1403
1693
|
setIsValueEditable(true);
|
|
@@ -1415,7 +1705,7 @@ function Slider({
|
|
|
1415
1705
|
}
|
|
1416
1706
|
};
|
|
1417
1707
|
}, [isValueHovered, showInput, isValueEditable]);
|
|
1418
|
-
|
|
1708
|
+
useEffect5(() => {
|
|
1419
1709
|
if (showInput && inputRef.current) {
|
|
1420
1710
|
inputRef.current.focus();
|
|
1421
1711
|
inputRef.current.select();
|
|
@@ -1570,17 +1860,17 @@ function Slider({
|
|
|
1570
1860
|
}
|
|
1571
1861
|
|
|
1572
1862
|
// src/components/SegmentedControl.tsx
|
|
1573
|
-
import { useRef as
|
|
1863
|
+
import { useRef as useRef6, useState as useState4, useLayoutEffect, useCallback as useCallback5 } from "react";
|
|
1574
1864
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1575
1865
|
function SegmentedControl({
|
|
1576
1866
|
options,
|
|
1577
1867
|
value,
|
|
1578
1868
|
onChange
|
|
1579
1869
|
}) {
|
|
1580
|
-
const containerRef =
|
|
1581
|
-
const hasAnimated =
|
|
1870
|
+
const containerRef = useRef6(null);
|
|
1871
|
+
const hasAnimated = useRef6(false);
|
|
1582
1872
|
const [pillStyle, setPillStyle] = useState4(null);
|
|
1583
|
-
const measure =
|
|
1873
|
+
const measure = useCallback5(() => {
|
|
1584
1874
|
const container = containerRef.current;
|
|
1585
1875
|
if (!container) return;
|
|
1586
1876
|
const activeButton = container.querySelector('[data-active="true"]');
|
|
@@ -1645,47 +1935,118 @@ function Toggle({ label, checked, onChange, shortcut, shortcutActive }) {
|
|
|
1645
1935
|
] });
|
|
1646
1936
|
}
|
|
1647
1937
|
|
|
1648
|
-
// src/
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1938
|
+
// src/transition-math.ts
|
|
1939
|
+
function round2(value) {
|
|
1940
|
+
return Math.round(value * 100) / 100;
|
|
1941
|
+
}
|
|
1942
|
+
function clamp(value, min, max) {
|
|
1943
|
+
return Math.min(max, Math.max(min, value));
|
|
1944
|
+
}
|
|
1945
|
+
function isTransitionConfig(value) {
|
|
1946
|
+
return isSpringConfigValue(value) || isEasingConfigValue(value);
|
|
1947
|
+
}
|
|
1948
|
+
function isPhysicsSpring(transition) {
|
|
1949
|
+
return transition.type === "spring" && (transition.stiffness !== void 0 || transition.damping !== void 0 || transition.mass !== void 0);
|
|
1950
|
+
}
|
|
1951
|
+
function springParams(spring) {
|
|
1952
|
+
if (isPhysicsSpring(spring)) {
|
|
1953
|
+
return { stiffness: spring.stiffness ?? 200, damping: spring.damping ?? 25, mass: spring.mass ?? 1 };
|
|
1954
|
+
}
|
|
1955
|
+
const visualDuration = Math.max(0.05, spring.visualDuration ?? 0.3);
|
|
1956
|
+
const bounce = spring.bounce ?? 0.3;
|
|
1957
|
+
const root = 2 * Math.PI / (visualDuration * 1.2);
|
|
1958
|
+
const stiffness = root * root;
|
|
1959
|
+
const damping = 2 * Math.min(1, Math.max(0.05, 1 - bounce)) * Math.sqrt(stiffness);
|
|
1960
|
+
return { stiffness, damping, mass: 1 };
|
|
1961
|
+
}
|
|
1962
|
+
function springProgress(t, { stiffness, damping, mass }) {
|
|
1963
|
+
if (t <= 0) return 0;
|
|
1964
|
+
const w0 = Math.sqrt(stiffness / mass);
|
|
1965
|
+
const zeta = damping / (2 * Math.sqrt(stiffness * mass));
|
|
1966
|
+
if (zeta < 0.9999) {
|
|
1967
|
+
const wd2 = w0 * Math.sqrt(1 - zeta * zeta);
|
|
1968
|
+
return 1 - Math.exp(-zeta * w0 * t) * (Math.cos(wd2 * t) + zeta * w0 / wd2 * Math.sin(wd2 * t));
|
|
1969
|
+
}
|
|
1970
|
+
if (zeta < 1.0001) {
|
|
1971
|
+
return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
|
|
1972
|
+
}
|
|
1973
|
+
const wd = w0 * Math.sqrt(zeta * zeta - 1);
|
|
1974
|
+
const r1 = -zeta * w0 + wd;
|
|
1975
|
+
const r2 = -zeta * w0 - wd;
|
|
1976
|
+
return 1 + (r2 * Math.exp(r1 * t) - r1 * Math.exp(r2 * t)) / (r1 - r2);
|
|
1977
|
+
}
|
|
1978
|
+
function springSettleDuration(params) {
|
|
1979
|
+
const w0 = Math.sqrt(params.stiffness / params.mass);
|
|
1980
|
+
const zeta = params.damping / (2 * Math.sqrt(params.stiffness * params.mass));
|
|
1981
|
+
const decay = zeta >= 1 ? zeta * w0 - w0 * Math.sqrt(Math.max(0, zeta * zeta - 1)) : zeta * w0;
|
|
1982
|
+
const duration = Math.log(200) / Math.max(decay, 1e-6);
|
|
1983
|
+
return round2(clamp(duration, 0.05, 10));
|
|
1984
|
+
}
|
|
1985
|
+
function cubicBezierProgress(p, [x1, y1, x2, y2]) {
|
|
1986
|
+
if (p <= 0) return 0;
|
|
1987
|
+
if (p >= 1) return 1;
|
|
1988
|
+
const sampleX = (t2) => bezierAxis(t2, x1, x2);
|
|
1989
|
+
const sampleY = (t2) => bezierAxis(t2, y1, y2);
|
|
1990
|
+
let t = p;
|
|
1991
|
+
for (let i = 0; i < 8; i++) {
|
|
1992
|
+
const x = sampleX(t) - p;
|
|
1993
|
+
if (Math.abs(x) < 1e-5) return sampleY(t);
|
|
1994
|
+
const dx = bezierAxisDerivative(t, x1, x2);
|
|
1995
|
+
if (Math.abs(dx) < 1e-6) break;
|
|
1996
|
+
t -= x / dx;
|
|
1997
|
+
}
|
|
1998
|
+
let lo = 0;
|
|
1999
|
+
let hi = 1;
|
|
2000
|
+
t = p;
|
|
2001
|
+
while (hi - lo > 1e-5) {
|
|
2002
|
+
if (sampleX(t) < p) lo = t;
|
|
2003
|
+
else hi = t;
|
|
2004
|
+
t = (lo + hi) / 2;
|
|
2005
|
+
}
|
|
2006
|
+
return sampleY(t);
|
|
2007
|
+
}
|
|
2008
|
+
function bezierAxis(t, a1, a2) {
|
|
2009
|
+
return (1 - 3 * a2 + 3 * a1) * t * t * t + (3 * a2 - 6 * a1) * t * t + 3 * a1 * t;
|
|
2010
|
+
}
|
|
2011
|
+
function bezierAxisDerivative(t, a1, a2) {
|
|
2012
|
+
return 3 * (1 - 3 * a2 + 3 * a1) * t * t + 2 * (3 * a2 - 6 * a1) * t + 3 * a1;
|
|
2013
|
+
}
|
|
2014
|
+
function resolveClipTransition(raw, clipDuration) {
|
|
2015
|
+
const safeDuration = Math.max(0.05, clipDuration);
|
|
2016
|
+
if (raw.type === "easing") {
|
|
2017
|
+
return {
|
|
2018
|
+
transition: { ...raw, duration: safeDuration },
|
|
2019
|
+
duration: safeDuration,
|
|
2020
|
+
isPhysics: false
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
if (isPhysicsSpring(raw)) {
|
|
2024
|
+
return {
|
|
2025
|
+
transition: raw,
|
|
2026
|
+
duration: springSettleDuration(springParams(raw)),
|
|
2027
|
+
isPhysics: true
|
|
2028
|
+
};
|
|
1665
2029
|
}
|
|
1666
|
-
return
|
|
2030
|
+
return {
|
|
2031
|
+
transition: { type: "spring", bounce: raw.bounce ?? 0.2, visualDuration: safeDuration },
|
|
2032
|
+
duration: safeDuration,
|
|
2033
|
+
isPhysics: false
|
|
2034
|
+
};
|
|
1667
2035
|
}
|
|
2036
|
+
|
|
2037
|
+
// src/components/SpringVisualization.tsx
|
|
2038
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1668
2039
|
function SpringVisualization({ spring, isSimpleMode }) {
|
|
1669
2040
|
const width = 256;
|
|
1670
2041
|
const height = 140;
|
|
1671
|
-
|
|
1672
|
-
let damping;
|
|
1673
|
-
let mass;
|
|
1674
|
-
if (isSimpleMode) {
|
|
1675
|
-
const visualDuration = spring.visualDuration ?? 0.3;
|
|
1676
|
-
const bounce = spring.bounce ?? 0.2;
|
|
1677
|
-
mass = 1;
|
|
1678
|
-
stiffness = 2 * Math.PI / visualDuration;
|
|
1679
|
-
stiffness = Math.pow(stiffness, 2);
|
|
1680
|
-
const dampingRatio = 1 - bounce;
|
|
1681
|
-
damping = 2 * dampingRatio * Math.sqrt(stiffness * mass);
|
|
1682
|
-
} else {
|
|
1683
|
-
stiffness = spring.stiffness ?? 400;
|
|
1684
|
-
damping = spring.damping ?? 17;
|
|
1685
|
-
mass = spring.mass ?? 1;
|
|
1686
|
-
}
|
|
2042
|
+
const params = isSimpleMode ? springParams({ type: "spring", visualDuration: spring.visualDuration ?? 0.3, bounce: spring.bounce ?? 0.2 }) : springParams({ type: "spring", stiffness: spring.stiffness ?? 400, damping: spring.damping ?? 17, mass: spring.mass ?? 1 });
|
|
1687
2043
|
const duration = 2;
|
|
1688
|
-
const
|
|
2044
|
+
const steps = 100;
|
|
2045
|
+
const points = [];
|
|
2046
|
+
for (let i = 0; i <= steps; i++) {
|
|
2047
|
+
const time = i / steps * duration;
|
|
2048
|
+
points.push([time, springProgress(time, params)]);
|
|
2049
|
+
}
|
|
1689
2050
|
const values = points.map(([, value]) => value);
|
|
1690
2051
|
const minValue = Math.min(...values);
|
|
1691
2052
|
const maxValue = Math.max(...values);
|
|
@@ -1734,20 +2095,20 @@ function SpringVisualization({ spring, isSimpleMode }) {
|
|
|
1734
2095
|
}
|
|
1735
2096
|
|
|
1736
2097
|
// src/components/SpringControl.tsx
|
|
1737
|
-
import { useCallback as
|
|
2098
|
+
import { useCallback as useCallback6, useRef as useRef7, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
1738
2099
|
import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1739
2100
|
function SpringControl({ panelId, path, label, spring, onChange }) {
|
|
1740
|
-
const subscribe =
|
|
2101
|
+
const subscribe = useCallback6(
|
|
1741
2102
|
(callback) => DialStore.subscribe(panelId, callback),
|
|
1742
2103
|
[panelId]
|
|
1743
2104
|
);
|
|
1744
|
-
const getSnapshot =
|
|
2105
|
+
const getSnapshot = useCallback6(
|
|
1745
2106
|
() => DialStore.getSpringMode(panelId, path),
|
|
1746
2107
|
[panelId, path]
|
|
1747
2108
|
);
|
|
1748
2109
|
const mode = useSyncExternalStore2(subscribe, getSnapshot, getSnapshot);
|
|
1749
2110
|
const isSimpleMode = mode === "simple";
|
|
1750
|
-
const cache =
|
|
2111
|
+
const cache = useRef7({
|
|
1751
2112
|
simple: spring.visualDuration !== void 0 ? spring : { type: "spring", visualDuration: 0.3, bounce: 0.2 },
|
|
1752
2113
|
advanced: spring.stiffness !== void 0 ? spring : { type: "spring", stiffness: 200, damping: 25, mass: 1 }
|
|
1753
2114
|
});
|
|
@@ -1894,21 +2255,29 @@ function EasingVisualization({ easing }) {
|
|
|
1894
2255
|
}
|
|
1895
2256
|
|
|
1896
2257
|
// src/components/TransitionControl.tsx
|
|
1897
|
-
import { useCallback as
|
|
2258
|
+
import { useCallback as useCallback7, useRef as useRef8, useState as useState5, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
1898
2259
|
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1899
|
-
function TransitionControl({
|
|
1900
|
-
|
|
2260
|
+
function TransitionControl({
|
|
2261
|
+
panelId,
|
|
2262
|
+
path,
|
|
2263
|
+
label,
|
|
2264
|
+
value,
|
|
2265
|
+
onChange,
|
|
2266
|
+
hideDuration = false,
|
|
2267
|
+
durationControl
|
|
2268
|
+
}) {
|
|
2269
|
+
const subscribe = useCallback7(
|
|
1901
2270
|
(callback) => DialStore.subscribe(panelId, callback),
|
|
1902
2271
|
[panelId]
|
|
1903
2272
|
);
|
|
1904
|
-
const getSnapshot =
|
|
2273
|
+
const getSnapshot = useCallback7(
|
|
1905
2274
|
() => DialStore.getTransitionMode(panelId, path),
|
|
1906
2275
|
[panelId, path]
|
|
1907
2276
|
);
|
|
1908
2277
|
const mode = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
|
|
1909
2278
|
const isEasing = mode === "easing";
|
|
1910
2279
|
const isSimpleSpring = mode === "simple";
|
|
1911
|
-
const cache =
|
|
2280
|
+
const cache = useRef8({
|
|
1912
2281
|
easing: value.type === "easing" ? value : { type: "easing", duration: 0.3, ease: [1, -0.4, 0.5, 1] },
|
|
1913
2282
|
simple: value.type === "spring" && value.visualDuration !== void 0 ? value : { type: "spring", visualDuration: 0.3, bounce: 0.2 },
|
|
1914
2283
|
advanced: value.type === "spring" && value.stiffness !== void 0 ? value : { type: "spring", stiffness: 200, damping: 25, mass: 1 }
|
|
@@ -1946,6 +2315,21 @@ function TransitionControl({ panelId, path, label, value, onChange }) {
|
|
|
1946
2315
|
newEase[index] = val;
|
|
1947
2316
|
onChange({ ...easing, ease: newEase });
|
|
1948
2317
|
};
|
|
2318
|
+
const durationSlider = !hideDuration && (isEasing || isSimpleSpring) ? /* @__PURE__ */ jsx9(
|
|
2319
|
+
Slider,
|
|
2320
|
+
{
|
|
2321
|
+
label: "Duration",
|
|
2322
|
+
value: durationControl?.value ?? (isEasing ? easing.duration : spring.visualDuration ?? 0.3),
|
|
2323
|
+
onChange: durationControl?.onChange ?? ((next) => {
|
|
2324
|
+
if (isEasing) onChange({ ...easing, duration: next });
|
|
2325
|
+
else handleSpringUpdate("visualDuration", next);
|
|
2326
|
+
}),
|
|
2327
|
+
min: durationControl?.min ?? 0.1,
|
|
2328
|
+
max: durationControl?.max ?? (isEasing ? 2 : 1),
|
|
2329
|
+
step: durationControl?.step ?? 0.05,
|
|
2330
|
+
unit: "s"
|
|
2331
|
+
}
|
|
2332
|
+
) : null;
|
|
1949
2333
|
return /* @__PURE__ */ jsx9(Folder, { title: label, defaultOpen: true, children: /* @__PURE__ */ jsxs8("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
|
|
1950
2334
|
isEasing ? /* @__PURE__ */ jsx9(EasingVisualization, { easing }) : /* @__PURE__ */ jsx9(SpringVisualization, { spring, isSimpleMode: isSimpleSpring }),
|
|
1951
2335
|
/* @__PURE__ */ jsxs8("div", { className: "dialkit-labeled-control", children: [
|
|
@@ -1968,16 +2352,13 @@ function TransitionControl({ panelId, path, label, value, onChange }) {
|
|
|
1968
2352
|
/* @__PURE__ */ jsx9(Slider, { label: "y1", value: easing.ease[1], onChange: (v) => updateEase(1, v), min: -1, max: 2, step: 0.01 }),
|
|
1969
2353
|
/* @__PURE__ */ jsx9(Slider, { label: "x2", value: easing.ease[2], onChange: (v) => updateEase(2, v), min: 0, max: 1, step: 0.01 }),
|
|
1970
2354
|
/* @__PURE__ */ jsx9(Slider, { label: "y2", value: easing.ease[3], onChange: (v) => updateEase(3, v), min: -1, max: 2, step: 0.01 }),
|
|
1971
|
-
/* @__PURE__ */ jsx9(Slider, { label: "Duration", value: easing.duration, onChange: (v) => onChange({ ...easing, duration: v }), min: 0.1, max: 2, step: 0.05, unit: "s" }),
|
|
1972
2355
|
/* @__PURE__ */ jsx9(EaseTextInput, { ease: easing.ease, onChange: (newEase) => onChange({ ...easing, ease: newEase }) })
|
|
1973
|
-
] }) : isSimpleSpring ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
1974
|
-
/* @__PURE__ */ jsx9(Slider, { label: "Duration", value: spring.visualDuration ?? 0.3, onChange: (v) => handleSpringUpdate("visualDuration", v), min: 0.1, max: 1, step: 0.05, unit: "s" }),
|
|
1975
|
-
/* @__PURE__ */ jsx9(Slider, { label: "Bounce", value: spring.bounce ?? 0.2, onChange: (v) => handleSpringUpdate("bounce", v), min: 0, max: 1, step: 0.05 })
|
|
1976
|
-
] }) : /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2356
|
+
] }) : isSimpleSpring ? /* @__PURE__ */ jsx9(Slider, { label: "Bounce", value: spring.bounce ?? 0.2, onChange: (v) => handleSpringUpdate("bounce", v), min: 0, max: 1, step: 0.05 }) : /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
1977
2357
|
/* @__PURE__ */ jsx9(Slider, { label: "Stiffness", value: spring.stiffness ?? 400, onChange: (v) => handleSpringUpdate("stiffness", v), min: 1, max: 1e3, step: 10 }),
|
|
1978
2358
|
/* @__PURE__ */ jsx9(Slider, { label: "Damping", value: spring.damping ?? 17, onChange: (v) => handleSpringUpdate("damping", v), min: 1, max: 100, step: 1 }),
|
|
1979
2359
|
/* @__PURE__ */ jsx9(Slider, { label: "Mass", value: spring.mass ?? 1, onChange: (v) => handleSpringUpdate("mass", v), min: 0.1, max: 10, step: 0.1 })
|
|
1980
|
-
] })
|
|
2360
|
+
] }),
|
|
2361
|
+
durationSlider
|
|
1981
2362
|
] }) });
|
|
1982
2363
|
}
|
|
1983
2364
|
function formatEase(ease) {
|
|
@@ -2044,7 +2425,7 @@ function TextControl({ label, value, onChange, placeholder }) {
|
|
|
2044
2425
|
}
|
|
2045
2426
|
|
|
2046
2427
|
// src/components/SelectControl.tsx
|
|
2047
|
-
import { useState as useState6, useRef as
|
|
2428
|
+
import { useState as useState6, useRef as useRef9, useEffect as useEffect6, useCallback as useCallback8 } from "react";
|
|
2048
2429
|
import { createPortal } from "react-dom";
|
|
2049
2430
|
import { motion as motion3, AnimatePresence as AnimatePresence2 } from "motion/react";
|
|
2050
2431
|
|
|
@@ -2078,26 +2459,26 @@ function normalizeOptions(options) {
|
|
|
2078
2459
|
}
|
|
2079
2460
|
function SelectControl({ label, value, options, onChange }) {
|
|
2080
2461
|
const [isOpen, setIsOpen] = useState6(false);
|
|
2081
|
-
const triggerRef =
|
|
2082
|
-
const dropdownRef =
|
|
2462
|
+
const triggerRef = useRef9(null);
|
|
2463
|
+
const dropdownRef = useRef9(null);
|
|
2083
2464
|
const [portalTarget, setPortalTarget] = useState6(null);
|
|
2084
2465
|
const [pos, setPos] = useState6(null);
|
|
2085
2466
|
const normalized = normalizeOptions(options);
|
|
2086
2467
|
const selectedOption = normalized.find((o) => o.value === value);
|
|
2087
|
-
const updatePos =
|
|
2468
|
+
const updatePos = useCallback8(() => {
|
|
2088
2469
|
const el = triggerRef.current;
|
|
2089
2470
|
if (!el || !portalTarget) return;
|
|
2090
2471
|
const dropdownHeight = 8 + normalized.length * 36;
|
|
2091
2472
|
setPos(getDropdownPosition(el, portalTarget, { dropdownHeight }));
|
|
2092
2473
|
}, [normalized.length, portalTarget]);
|
|
2093
|
-
|
|
2474
|
+
useEffect6(() => {
|
|
2094
2475
|
setPortalTarget(getDialKitPortalRoot(triggerRef.current) ?? document.body);
|
|
2095
2476
|
}, []);
|
|
2096
|
-
|
|
2477
|
+
useEffect6(() => {
|
|
2097
2478
|
if (!isOpen) return;
|
|
2098
2479
|
updatePos();
|
|
2099
2480
|
}, [isOpen, updatePos]);
|
|
2100
|
-
|
|
2481
|
+
useEffect6(() => {
|
|
2101
2482
|
if (!isOpen) return;
|
|
2102
2483
|
const handleClick = (e) => {
|
|
2103
2484
|
const target = e.target;
|
|
@@ -2177,14 +2558,14 @@ function SelectControl({ label, value, options, onChange }) {
|
|
|
2177
2558
|
}
|
|
2178
2559
|
|
|
2179
2560
|
// src/components/ColorControl.tsx
|
|
2180
|
-
import { useState as useState7, useRef as
|
|
2561
|
+
import { useState as useState7, useRef as useRef10, useEffect as useEffect7 } from "react";
|
|
2181
2562
|
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2182
2563
|
var HEX_COLOR_REGEX = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/;
|
|
2183
2564
|
function ColorControl({ label, value, onChange }) {
|
|
2184
2565
|
const [isEditing, setIsEditing] = useState7(false);
|
|
2185
2566
|
const [editValue, setEditValue] = useState7(value);
|
|
2186
|
-
const colorInputRef =
|
|
2187
|
-
|
|
2567
|
+
const colorInputRef = useRef10(null);
|
|
2568
|
+
useEffect7(() => {
|
|
2188
2569
|
if (!isEditing) {
|
|
2189
2570
|
setEditValue(value);
|
|
2190
2571
|
}
|
|
@@ -2254,266 +2635,105 @@ function expandShorthandHex(hex) {
|
|
|
2254
2635
|
return `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;
|
|
2255
2636
|
}
|
|
2256
2637
|
|
|
2257
|
-
// src/components/
|
|
2258
|
-
import {
|
|
2259
|
-
|
|
2260
|
-
import { motion as motion4, AnimatePresence as AnimatePresence3 } from "motion/react";
|
|
2261
|
-
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2262
|
-
function PresetManager({ panelId, presets, activePresetId, onAdd }) {
|
|
2263
|
-
const [isOpen, setIsOpen] = useState8(false);
|
|
2264
|
-
const triggerRef = useRef10(null);
|
|
2265
|
-
const dropdownRef = useRef10(null);
|
|
2266
|
-
const [pos, setPos] = useState8({ top: 0, left: 0, width: 0 });
|
|
2267
|
-
const hasPresets = presets.length > 0;
|
|
2268
|
-
const activePreset = presets.find((p) => p.id === activePresetId);
|
|
2269
|
-
const open = useCallback8(() => {
|
|
2270
|
-
if (!hasPresets) return;
|
|
2271
|
-
const rect = triggerRef.current?.getBoundingClientRect();
|
|
2272
|
-
if (rect) {
|
|
2273
|
-
setPos({ top: rect.bottom + 4, left: rect.left, width: rect.width });
|
|
2274
|
-
}
|
|
2275
|
-
setIsOpen(true);
|
|
2276
|
-
}, [hasPresets]);
|
|
2277
|
-
const close = useCallback8(() => setIsOpen(false), []);
|
|
2278
|
-
const toggle = useCallback8(() => {
|
|
2279
|
-
if (isOpen) close();
|
|
2280
|
-
else open();
|
|
2281
|
-
}, [isOpen, open, close]);
|
|
2282
|
-
useEffect7(() => {
|
|
2283
|
-
if (!isOpen) return;
|
|
2284
|
-
const handler = (e) => {
|
|
2285
|
-
const target = e.target;
|
|
2286
|
-
if (triggerRef.current?.contains(target) || dropdownRef.current?.contains(target)) return;
|
|
2287
|
-
close();
|
|
2288
|
-
};
|
|
2289
|
-
document.addEventListener("mousedown", handler);
|
|
2290
|
-
return () => document.removeEventListener("mousedown", handler);
|
|
2291
|
-
}, [isOpen, close]);
|
|
2292
|
-
const handleSelect = (presetId) => {
|
|
2293
|
-
if (presetId) {
|
|
2294
|
-
DialStore.loadPreset(panelId, presetId);
|
|
2295
|
-
} else {
|
|
2296
|
-
DialStore.clearActivePreset(panelId);
|
|
2297
|
-
}
|
|
2298
|
-
close();
|
|
2299
|
-
};
|
|
2300
|
-
const handleDelete = (e, presetId) => {
|
|
2301
|
-
e.stopPropagation();
|
|
2302
|
-
DialStore.deletePreset(panelId, presetId);
|
|
2303
|
-
};
|
|
2304
|
-
return /* @__PURE__ */ jsxs12("div", { className: "dialkit-preset-manager", children: [
|
|
2305
|
-
/* @__PURE__ */ jsxs12(
|
|
2306
|
-
"button",
|
|
2307
|
-
{
|
|
2308
|
-
ref: triggerRef,
|
|
2309
|
-
className: "dialkit-preset-trigger",
|
|
2310
|
-
onClick: toggle,
|
|
2311
|
-
"data-open": String(isOpen),
|
|
2312
|
-
"data-has-preset": String(!!activePreset),
|
|
2313
|
-
"data-disabled": String(!hasPresets),
|
|
2314
|
-
children: [
|
|
2315
|
-
/* @__PURE__ */ jsx13("span", { className: "dialkit-preset-label", children: activePreset ? activePreset.name : "Version 1" }),
|
|
2316
|
-
/* @__PURE__ */ jsx13(
|
|
2317
|
-
motion4.svg,
|
|
2318
|
-
{
|
|
2319
|
-
className: "dialkit-select-chevron",
|
|
2320
|
-
viewBox: "0 0 24 24",
|
|
2321
|
-
fill: "none",
|
|
2322
|
-
stroke: "currentColor",
|
|
2323
|
-
strokeWidth: "2.5",
|
|
2324
|
-
strokeLinecap: "round",
|
|
2325
|
-
strokeLinejoin: "round",
|
|
2326
|
-
animate: { rotate: isOpen ? 180 : 0, opacity: hasPresets ? 0.6 : 0.25 },
|
|
2327
|
-
transition: { type: "spring", visualDuration: 0.2, bounce: 0.15 },
|
|
2328
|
-
children: /* @__PURE__ */ jsx13("path", { d: ICON_CHEVRON })
|
|
2329
|
-
}
|
|
2330
|
-
)
|
|
2331
|
-
]
|
|
2332
|
-
}
|
|
2333
|
-
),
|
|
2334
|
-
createPortal2(
|
|
2335
|
-
/* @__PURE__ */ jsx13(AnimatePresence3, { children: isOpen && /* @__PURE__ */ jsxs12(
|
|
2336
|
-
motion4.div,
|
|
2337
|
-
{
|
|
2338
|
-
ref: dropdownRef,
|
|
2339
|
-
className: "dialkit-root dialkit-preset-dropdown",
|
|
2340
|
-
style: { position: "fixed", top: pos.top, left: pos.left, minWidth: pos.width },
|
|
2341
|
-
initial: { opacity: 0, y: 4, scale: 0.97 },
|
|
2342
|
-
animate: { opacity: 1, y: 0, scale: 1 },
|
|
2343
|
-
exit: { opacity: 0, y: 4, scale: 0.97, pointerEvents: "none" },
|
|
2344
|
-
transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
|
|
2345
|
-
children: [
|
|
2346
|
-
/* @__PURE__ */ jsx13(
|
|
2347
|
-
"div",
|
|
2348
|
-
{
|
|
2349
|
-
className: "dialkit-preset-item",
|
|
2350
|
-
"data-active": String(!activePresetId),
|
|
2351
|
-
onClick: () => handleSelect(null),
|
|
2352
|
-
children: /* @__PURE__ */ jsx13("span", { className: "dialkit-preset-name", children: "Version 1" })
|
|
2353
|
-
}
|
|
2354
|
-
),
|
|
2355
|
-
presets.map((preset) => /* @__PURE__ */ jsxs12(
|
|
2356
|
-
"div",
|
|
2357
|
-
{
|
|
2358
|
-
className: "dialkit-preset-item",
|
|
2359
|
-
"data-active": String(preset.id === activePresetId),
|
|
2360
|
-
onClick: () => handleSelect(preset.id),
|
|
2361
|
-
children: [
|
|
2362
|
-
/* @__PURE__ */ jsx13("span", { className: "dialkit-preset-name", children: preset.name }),
|
|
2363
|
-
/* @__PURE__ */ jsx13(
|
|
2364
|
-
"button",
|
|
2365
|
-
{
|
|
2366
|
-
className: "dialkit-preset-delete",
|
|
2367
|
-
onClick: (e) => handleDelete(e, preset.id),
|
|
2368
|
-
title: "Delete preset",
|
|
2369
|
-
children: /* @__PURE__ */ jsx13("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: ICON_TRASH.map((d, i) => /* @__PURE__ */ jsx13("path", { d }, i)) })
|
|
2370
|
-
}
|
|
2371
|
-
)
|
|
2372
|
-
]
|
|
2373
|
-
},
|
|
2374
|
-
preset.id
|
|
2375
|
-
))
|
|
2376
|
-
]
|
|
2377
|
-
}
|
|
2378
|
-
) }),
|
|
2379
|
-
document.body
|
|
2380
|
-
)
|
|
2381
|
-
] });
|
|
2382
|
-
}
|
|
2383
|
-
|
|
2384
|
-
// src/components/Panel.tsx
|
|
2385
|
-
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2386
|
-
function Panel({ panel, defaultOpen = true, inline = false, onOpenChange, variant = "root" }) {
|
|
2387
|
-
const [copied, setCopied] = useState9(false);
|
|
2388
|
-
const [isPanelOpen, setIsPanelOpen] = useState9(defaultOpen);
|
|
2638
|
+
// src/components/ControlRenderer.tsx
|
|
2639
|
+
import { Fragment as Fragment3, jsx as jsx13 } from "react/jsx-runtime";
|
|
2640
|
+
function ControlRenderer({ panelId, controls, values, transitionDuration }) {
|
|
2389
2641
|
const shortcutCtx = useContext(ShortcutContext);
|
|
2390
|
-
const hasShortcuts = Object.keys(panel.shortcuts).length > 0;
|
|
2391
|
-
const subscribe = useCallback9(
|
|
2392
|
-
(callback) => DialStore.subscribe(panel.id, callback),
|
|
2393
|
-
[panel.id]
|
|
2394
|
-
);
|
|
2395
|
-
const getSnapshot = useCallback9(
|
|
2396
|
-
() => DialStore.getValues(panel.id),
|
|
2397
|
-
[panel.id]
|
|
2398
|
-
);
|
|
2399
|
-
const values = useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
|
|
2400
|
-
const presets = DialStore.getPresets(panel.id);
|
|
2401
|
-
const activePresetId = DialStore.getActivePresetId(panel.id);
|
|
2402
|
-
const handleAddPreset = () => {
|
|
2403
|
-
const nextNum = presets.length + 2;
|
|
2404
|
-
DialStore.savePreset(panel.id, `Version ${nextNum}`);
|
|
2405
|
-
};
|
|
2406
|
-
const handleCopy = () => {
|
|
2407
|
-
const jsonStr = JSON.stringify(values, null, 2);
|
|
2408
|
-
const instruction = `Update the useDialKit configuration for "${panel.name}" with these values:
|
|
2409
|
-
|
|
2410
|
-
\`\`\`json
|
|
2411
|
-
${jsonStr}
|
|
2412
|
-
\`\`\`
|
|
2413
|
-
|
|
2414
|
-
Apply these values as the new defaults in the useDialKit call.`;
|
|
2415
|
-
navigator.clipboard.writeText(instruction);
|
|
2416
|
-
setCopied(true);
|
|
2417
|
-
setTimeout(() => setCopied(false), 1500);
|
|
2418
|
-
};
|
|
2419
|
-
const handleOpenChange = useCallback9((open) => {
|
|
2420
|
-
setIsPanelOpen(open);
|
|
2421
|
-
onOpenChange?.(open);
|
|
2422
|
-
}, [onOpenChange]);
|
|
2423
2642
|
const renderControl = (control) => {
|
|
2424
2643
|
const value = values[control.path];
|
|
2425
2644
|
switch (control.type) {
|
|
2426
2645
|
case "slider":
|
|
2427
|
-
return /* @__PURE__ */
|
|
2646
|
+
return /* @__PURE__ */ jsx13(
|
|
2428
2647
|
Slider,
|
|
2429
2648
|
{
|
|
2430
2649
|
label: control.label,
|
|
2431
2650
|
value,
|
|
2432
|
-
onChange: (v) => DialStore.updateValue(
|
|
2651
|
+
onChange: (v) => DialStore.updateValue(panelId, control.path, v),
|
|
2433
2652
|
min: control.min,
|
|
2434
2653
|
max: control.max,
|
|
2435
2654
|
step: control.step,
|
|
2436
2655
|
shortcut: control.shortcut,
|
|
2437
|
-
shortcutActive: shortcutCtx.activePanelId ===
|
|
2656
|
+
shortcutActive: shortcutCtx.activePanelId === panelId && shortcutCtx.activePath === control.path
|
|
2438
2657
|
},
|
|
2439
2658
|
control.path
|
|
2440
2659
|
);
|
|
2441
2660
|
case "toggle":
|
|
2442
|
-
return /* @__PURE__ */
|
|
2661
|
+
return /* @__PURE__ */ jsx13(
|
|
2443
2662
|
Toggle,
|
|
2444
2663
|
{
|
|
2445
2664
|
label: control.label,
|
|
2446
2665
|
checked: value,
|
|
2447
|
-
onChange: (v) => DialStore.updateValue(
|
|
2666
|
+
onChange: (v) => DialStore.updateValue(panelId, control.path, v),
|
|
2448
2667
|
shortcut: control.shortcut,
|
|
2449
|
-
shortcutActive: shortcutCtx.activePanelId ===
|
|
2668
|
+
shortcutActive: shortcutCtx.activePanelId === panelId && shortcutCtx.activePath === control.path
|
|
2450
2669
|
},
|
|
2451
2670
|
control.path
|
|
2452
2671
|
);
|
|
2453
2672
|
case "spring":
|
|
2454
|
-
return /* @__PURE__ */
|
|
2673
|
+
return /* @__PURE__ */ jsx13(
|
|
2455
2674
|
SpringControl,
|
|
2456
2675
|
{
|
|
2457
|
-
panelId
|
|
2676
|
+
panelId,
|
|
2458
2677
|
path: control.path,
|
|
2459
2678
|
label: control.label,
|
|
2460
2679
|
spring: value,
|
|
2461
|
-
onChange: (v) => DialStore.updateValue(
|
|
2680
|
+
onChange: (v) => DialStore.updateValue(panelId, control.path, v)
|
|
2462
2681
|
},
|
|
2463
2682
|
control.path
|
|
2464
2683
|
);
|
|
2465
2684
|
case "transition":
|
|
2466
|
-
return /* @__PURE__ */
|
|
2685
|
+
return /* @__PURE__ */ jsx13(
|
|
2467
2686
|
TransitionControl,
|
|
2468
2687
|
{
|
|
2469
|
-
panelId
|
|
2688
|
+
panelId,
|
|
2470
2689
|
path: control.path,
|
|
2471
2690
|
label: control.label,
|
|
2472
2691
|
value,
|
|
2473
|
-
onChange: (v) => DialStore.updateValue(
|
|
2692
|
+
onChange: (v) => DialStore.updateValue(panelId, control.path, v),
|
|
2693
|
+
durationControl: transitionDuration
|
|
2474
2694
|
},
|
|
2475
2695
|
control.path
|
|
2476
2696
|
);
|
|
2477
2697
|
case "folder":
|
|
2478
|
-
return /* @__PURE__ */
|
|
2698
|
+
return /* @__PURE__ */ jsx13(Folder, { title: control.label, defaultOpen: control.defaultOpen ?? true, children: control.children?.map(renderControl) }, control.path);
|
|
2479
2699
|
case "text":
|
|
2480
|
-
return /* @__PURE__ */
|
|
2700
|
+
return /* @__PURE__ */ jsx13(
|
|
2481
2701
|
TextControl,
|
|
2482
2702
|
{
|
|
2483
2703
|
label: control.label,
|
|
2484
2704
|
value,
|
|
2485
|
-
onChange: (v) => DialStore.updateValue(
|
|
2705
|
+
onChange: (v) => DialStore.updateValue(panelId, control.path, v),
|
|
2486
2706
|
placeholder: control.placeholder
|
|
2487
2707
|
},
|
|
2488
2708
|
control.path
|
|
2489
2709
|
);
|
|
2490
2710
|
case "select":
|
|
2491
|
-
return /* @__PURE__ */
|
|
2711
|
+
return /* @__PURE__ */ jsx13(
|
|
2492
2712
|
SelectControl,
|
|
2493
2713
|
{
|
|
2494
2714
|
label: control.label,
|
|
2495
2715
|
value,
|
|
2496
2716
|
options: control.options ?? [],
|
|
2497
|
-
onChange: (v) => DialStore.updateValue(
|
|
2717
|
+
onChange: (v) => DialStore.updateValue(panelId, control.path, v)
|
|
2498
2718
|
},
|
|
2499
2719
|
control.path
|
|
2500
2720
|
);
|
|
2501
2721
|
case "color":
|
|
2502
|
-
return /* @__PURE__ */
|
|
2722
|
+
return /* @__PURE__ */ jsx13(
|
|
2503
2723
|
ColorControl,
|
|
2504
2724
|
{
|
|
2505
2725
|
label: control.label,
|
|
2506
2726
|
value,
|
|
2507
|
-
onChange: (v) => DialStore.updateValue(
|
|
2727
|
+
onChange: (v) => DialStore.updateValue(panelId, control.path, v)
|
|
2508
2728
|
},
|
|
2509
2729
|
control.path
|
|
2510
2730
|
);
|
|
2511
2731
|
case "action":
|
|
2512
|
-
return /* @__PURE__ */
|
|
2732
|
+
return /* @__PURE__ */ jsx13(
|
|
2513
2733
|
"button",
|
|
2514
2734
|
{
|
|
2515
2735
|
className: "dialkit-button",
|
|
2516
|
-
onClick: () => DialStore.triggerAction(
|
|
2736
|
+
onClick: () => DialStore.triggerAction(panelId, control.path),
|
|
2517
2737
|
children: control.label
|
|
2518
2738
|
},
|
|
2519
2739
|
control.path
|
|
@@ -2522,12 +2742,170 @@ Apply these values as the new defaults in the useDialKit call.`;
|
|
|
2522
2742
|
return null;
|
|
2523
2743
|
}
|
|
2524
2744
|
};
|
|
2525
|
-
|
|
2526
|
-
|
|
2745
|
+
return /* @__PURE__ */ jsx13(Fragment3, { children: controls.map(renderControl) });
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
// src/components/PresetManager.tsx
|
|
2749
|
+
import { useState as useState8, useRef as useRef11, useEffect as useEffect8, useCallback as useCallback9 } from "react";
|
|
2750
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
2751
|
+
import { motion as motion4, AnimatePresence as AnimatePresence3 } from "motion/react";
|
|
2752
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2753
|
+
function PresetManager({ panelId, presets, activePresetId, onAdd }) {
|
|
2754
|
+
const [isOpen, setIsOpen] = useState8(false);
|
|
2755
|
+
const triggerRef = useRef11(null);
|
|
2756
|
+
const dropdownRef = useRef11(null);
|
|
2757
|
+
const [pos, setPos] = useState8({ top: 0, left: 0, width: 0 });
|
|
2758
|
+
const hasPresets = presets.length > 0;
|
|
2759
|
+
const activePreset = presets.find((p) => p.id === activePresetId);
|
|
2760
|
+
const open = useCallback9(() => {
|
|
2761
|
+
if (!hasPresets) return;
|
|
2762
|
+
const rect = triggerRef.current?.getBoundingClientRect();
|
|
2763
|
+
if (rect) {
|
|
2764
|
+
setPos({ top: rect.bottom + 4, left: rect.left, width: rect.width });
|
|
2765
|
+
}
|
|
2766
|
+
setIsOpen(true);
|
|
2767
|
+
}, [hasPresets]);
|
|
2768
|
+
const close = useCallback9(() => setIsOpen(false), []);
|
|
2769
|
+
const toggle = useCallback9(() => {
|
|
2770
|
+
if (isOpen) close();
|
|
2771
|
+
else open();
|
|
2772
|
+
}, [isOpen, open, close]);
|
|
2773
|
+
useEffect8(() => {
|
|
2774
|
+
if (!isOpen) return;
|
|
2775
|
+
const handler = (e) => {
|
|
2776
|
+
const target = e.target;
|
|
2777
|
+
if (triggerRef.current?.contains(target) || dropdownRef.current?.contains(target)) return;
|
|
2778
|
+
close();
|
|
2779
|
+
};
|
|
2780
|
+
document.addEventListener("mousedown", handler);
|
|
2781
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
2782
|
+
}, [isOpen, close]);
|
|
2783
|
+
const handleSelect = (presetId) => {
|
|
2784
|
+
if (presetId) {
|
|
2785
|
+
DialStore.loadPreset(panelId, presetId);
|
|
2786
|
+
} else {
|
|
2787
|
+
DialStore.clearActivePreset(panelId);
|
|
2788
|
+
}
|
|
2789
|
+
close();
|
|
2790
|
+
};
|
|
2791
|
+
const handleDelete = (e, presetId) => {
|
|
2792
|
+
e.stopPropagation();
|
|
2793
|
+
DialStore.deletePreset(panelId, presetId);
|
|
2794
|
+
};
|
|
2795
|
+
return /* @__PURE__ */ jsxs12("div", { className: "dialkit-preset-manager", children: [
|
|
2796
|
+
/* @__PURE__ */ jsxs12(
|
|
2797
|
+
"button",
|
|
2798
|
+
{
|
|
2799
|
+
ref: triggerRef,
|
|
2800
|
+
className: "dialkit-preset-trigger",
|
|
2801
|
+
onClick: toggle,
|
|
2802
|
+
"data-open": String(isOpen),
|
|
2803
|
+
"data-has-preset": String(!!activePreset),
|
|
2804
|
+
"data-disabled": String(!hasPresets),
|
|
2805
|
+
children: [
|
|
2806
|
+
/* @__PURE__ */ jsx14("span", { className: "dialkit-preset-label", children: activePreset ? activePreset.name : "Version 1" }),
|
|
2807
|
+
/* @__PURE__ */ jsx14(
|
|
2808
|
+
motion4.svg,
|
|
2809
|
+
{
|
|
2810
|
+
className: "dialkit-select-chevron",
|
|
2811
|
+
viewBox: "0 0 24 24",
|
|
2812
|
+
fill: "none",
|
|
2813
|
+
stroke: "currentColor",
|
|
2814
|
+
strokeWidth: "2.5",
|
|
2815
|
+
strokeLinecap: "round",
|
|
2816
|
+
strokeLinejoin: "round",
|
|
2817
|
+
animate: { rotate: isOpen ? 180 : 0, opacity: hasPresets ? 0.6 : 0.25 },
|
|
2818
|
+
transition: { type: "spring", visualDuration: 0.2, bounce: 0.15 },
|
|
2819
|
+
children: /* @__PURE__ */ jsx14("path", { d: ICON_CHEVRON })
|
|
2820
|
+
}
|
|
2821
|
+
)
|
|
2822
|
+
]
|
|
2823
|
+
}
|
|
2824
|
+
),
|
|
2825
|
+
createPortal2(
|
|
2826
|
+
/* @__PURE__ */ jsx14(AnimatePresence3, { children: isOpen && /* @__PURE__ */ jsxs12(
|
|
2827
|
+
motion4.div,
|
|
2828
|
+
{
|
|
2829
|
+
ref: dropdownRef,
|
|
2830
|
+
className: "dialkit-root dialkit-preset-dropdown",
|
|
2831
|
+
style: { position: "fixed", top: pos.top, left: pos.left, minWidth: pos.width },
|
|
2832
|
+
initial: { opacity: 0, y: 4, scale: 0.97 },
|
|
2833
|
+
animate: { opacity: 1, y: 0, scale: 1 },
|
|
2834
|
+
exit: { opacity: 0, y: 4, scale: 0.97, pointerEvents: "none" },
|
|
2835
|
+
transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
|
|
2836
|
+
children: [
|
|
2837
|
+
/* @__PURE__ */ jsx14(
|
|
2838
|
+
"div",
|
|
2839
|
+
{
|
|
2840
|
+
className: "dialkit-preset-item",
|
|
2841
|
+
"data-active": String(!activePresetId),
|
|
2842
|
+
onClick: () => handleSelect(null),
|
|
2843
|
+
children: /* @__PURE__ */ jsx14("span", { className: "dialkit-preset-name", children: "Version 1" })
|
|
2844
|
+
}
|
|
2845
|
+
),
|
|
2846
|
+
presets.map((preset) => /* @__PURE__ */ jsxs12(
|
|
2847
|
+
"div",
|
|
2848
|
+
{
|
|
2849
|
+
className: "dialkit-preset-item",
|
|
2850
|
+
"data-active": String(preset.id === activePresetId),
|
|
2851
|
+
onClick: () => handleSelect(preset.id),
|
|
2852
|
+
children: [
|
|
2853
|
+
/* @__PURE__ */ jsx14("span", { className: "dialkit-preset-name", children: preset.name }),
|
|
2854
|
+
/* @__PURE__ */ jsx14(
|
|
2855
|
+
"button",
|
|
2856
|
+
{
|
|
2857
|
+
className: "dialkit-preset-delete",
|
|
2858
|
+
onClick: (e) => handleDelete(e, preset.id),
|
|
2859
|
+
title: "Delete preset",
|
|
2860
|
+
children: /* @__PURE__ */ jsx14("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: ICON_TRASH.map((d, i) => /* @__PURE__ */ jsx14("path", { d }, i)) })
|
|
2861
|
+
}
|
|
2862
|
+
)
|
|
2863
|
+
]
|
|
2864
|
+
},
|
|
2865
|
+
preset.id
|
|
2866
|
+
))
|
|
2867
|
+
]
|
|
2868
|
+
}
|
|
2869
|
+
) }),
|
|
2870
|
+
document.body
|
|
2871
|
+
)
|
|
2872
|
+
] });
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
// src/components/Panel.tsx
|
|
2876
|
+
import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2877
|
+
function Panel({ panel, defaultOpen = true, inline = false, onOpenChange, variant = "root", toolbarExtra }) {
|
|
2878
|
+
const [copied, setCopied] = useState9(false);
|
|
2879
|
+
const [isPanelOpen, setIsPanelOpen] = useState9(defaultOpen);
|
|
2880
|
+
const hasShortcuts = Object.keys(panel.shortcuts).length > 0;
|
|
2881
|
+
const subscribe = useCallback10(
|
|
2882
|
+
(callback) => DialStore.subscribe(panel.id, callback),
|
|
2883
|
+
[panel.id]
|
|
2884
|
+
);
|
|
2885
|
+
const getSnapshot = useCallback10(
|
|
2886
|
+
() => DialStore.getValues(panel.id),
|
|
2887
|
+
[panel.id]
|
|
2888
|
+
);
|
|
2889
|
+
const values = useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
|
|
2890
|
+
const presets = DialStore.getPresets(panel.id);
|
|
2891
|
+
const activePresetId = DialStore.getActivePresetId(panel.id);
|
|
2892
|
+
const handleAddPreset = () => {
|
|
2893
|
+
const nextNum = presets.length + 2;
|
|
2894
|
+
DialStore.savePreset(panel.id, `Version ${nextNum}`);
|
|
2895
|
+
};
|
|
2896
|
+
const handleCopy = () => {
|
|
2897
|
+
navigator.clipboard.writeText(buildCopyInstruction("useDialKit", panel.name, values));
|
|
2898
|
+
setCopied(true);
|
|
2899
|
+
setTimeout(() => setCopied(false), 1500);
|
|
2527
2900
|
};
|
|
2901
|
+
const handleOpenChange = useCallback10((open) => {
|
|
2902
|
+
setIsPanelOpen(open);
|
|
2903
|
+
onOpenChange?.(open);
|
|
2904
|
+
}, [onOpenChange]);
|
|
2905
|
+
const renderControls = () => /* @__PURE__ */ jsx15(ControlRenderer, { panelId: panel.id, controls: panel.controls, values });
|
|
2528
2906
|
const iconTransition = { type: "spring", visualDuration: 0.4, bounce: 0.1 };
|
|
2529
|
-
const toolbar = /* @__PURE__ */ jsxs13(
|
|
2530
|
-
/* @__PURE__ */
|
|
2907
|
+
const toolbar = /* @__PURE__ */ jsxs13(Fragment4, { children: [
|
|
2908
|
+
/* @__PURE__ */ jsx15(
|
|
2531
2909
|
motion5.button,
|
|
2532
2910
|
{
|
|
2533
2911
|
className: "dialkit-toolbar-add",
|
|
@@ -2535,10 +2913,10 @@ Apply these values as the new defaults in the useDialKit call.`;
|
|
|
2535
2913
|
title: "Add preset",
|
|
2536
2914
|
whileTap: { scale: 0.9 },
|
|
2537
2915
|
transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
|
|
2538
|
-
children: /* @__PURE__ */
|
|
2916
|
+
children: /* @__PURE__ */ jsx15("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: ICON_ADD_PRESET.map((d, i) => /* @__PURE__ */ jsx15("path", { d }, i)) })
|
|
2539
2917
|
}
|
|
2540
2918
|
),
|
|
2541
|
-
/* @__PURE__ */
|
|
2919
|
+
/* @__PURE__ */ jsx15(
|
|
2542
2920
|
PresetManager,
|
|
2543
2921
|
{
|
|
2544
2922
|
panelId: panel.id,
|
|
@@ -2547,7 +2925,7 @@ Apply these values as the new defaults in the useDialKit call.`;
|
|
|
2547
2925
|
onAdd: handleAddPreset
|
|
2548
2926
|
}
|
|
2549
2927
|
),
|
|
2550
|
-
/* @__PURE__ */
|
|
2928
|
+
/* @__PURE__ */ jsx15(
|
|
2551
2929
|
motion5.button,
|
|
2552
2930
|
{
|
|
2553
2931
|
className: "dialkit-toolbar-add",
|
|
@@ -2555,7 +2933,7 @@ Apply these values as the new defaults in the useDialKit call.`;
|
|
|
2555
2933
|
title: "Copy parameters",
|
|
2556
2934
|
whileTap: { scale: 0.9 },
|
|
2557
2935
|
transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
|
|
2558
|
-
children: /* @__PURE__ */
|
|
2936
|
+
children: /* @__PURE__ */ jsx15("span", { style: { position: "relative", width: 16, height: 16 }, children: /* @__PURE__ */ jsx15(AnimatePresence4, { initial: false, mode: "wait", children: copied ? /* @__PURE__ */ jsx15(
|
|
2559
2937
|
motion5.svg,
|
|
2560
2938
|
{
|
|
2561
2939
|
viewBox: "0 0 24 24",
|
|
@@ -2569,7 +2947,7 @@ Apply these values as the new defaults in the useDialKit call.`;
|
|
|
2569
2947
|
animate: { scale: 1, opacity: 1 },
|
|
2570
2948
|
exit: { scale: 0.8, opacity: 0 },
|
|
2571
2949
|
transition: { duration: 0.08 },
|
|
2572
|
-
children: /* @__PURE__ */
|
|
2950
|
+
children: /* @__PURE__ */ jsx15("path", { d: ICON_CHECK })
|
|
2573
2951
|
},
|
|
2574
2952
|
"check"
|
|
2575
2953
|
) : /* @__PURE__ */ jsxs13(
|
|
@@ -2583,23 +2961,116 @@ Apply these values as the new defaults in the useDialKit call.`;
|
|
|
2583
2961
|
exit: { scale: 0.8, opacity: 0 },
|
|
2584
2962
|
transition: { duration: 0.08 },
|
|
2585
2963
|
children: [
|
|
2586
|
-
/* @__PURE__ */
|
|
2587
|
-
/* @__PURE__ */
|
|
2588
|
-
/* @__PURE__ */
|
|
2964
|
+
/* @__PURE__ */ jsx15("path", { d: ICON_CLIPBOARD.board, stroke: "currentColor", strokeWidth: "2", strokeLinejoin: "round" }),
|
|
2965
|
+
/* @__PURE__ */ jsx15("path", { d: ICON_CLIPBOARD.sparkle, fill: "currentColor" }),
|
|
2966
|
+
/* @__PURE__ */ jsx15("path", { d: ICON_CLIPBOARD.body, stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
2589
2967
|
]
|
|
2590
2968
|
},
|
|
2591
2969
|
"clipboard"
|
|
2592
2970
|
) }) })
|
|
2593
2971
|
}
|
|
2594
|
-
)
|
|
2972
|
+
),
|
|
2973
|
+
toolbarExtra
|
|
2595
2974
|
] });
|
|
2596
2975
|
if (variant === "section") {
|
|
2597
2976
|
return /* @__PURE__ */ jsxs13(Folder, { title: panel.name, defaultOpen, onOpenChange: handleOpenChange, children: [
|
|
2598
|
-
/* @__PURE__ */
|
|
2977
|
+
/* @__PURE__ */ jsx15("div", { className: "dialkit-panel-section-toolbar", onClick: (e) => e.stopPropagation(), children: toolbar }),
|
|
2599
2978
|
renderControls()
|
|
2600
2979
|
] });
|
|
2601
2980
|
}
|
|
2602
|
-
return /* @__PURE__ */
|
|
2981
|
+
return /* @__PURE__ */ jsx15("div", { className: "dialkit-panel-wrapper", children: /* @__PURE__ */ jsx15(Folder, { title: panel.name, defaultOpen, isRoot: true, inline, onOpenChange: handleOpenChange, toolbar, children: renderControls() }) });
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
// src/components/Timeline/TimelineToggleButton.tsx
|
|
2985
|
+
import { useCallback as useCallback11, useSyncExternalStore as useSyncExternalStore5 } from "react";
|
|
2986
|
+
import { motion as motion6 } from "motion/react";
|
|
2987
|
+
|
|
2988
|
+
// src/store/TimelineUiStore.ts
|
|
2989
|
+
var TimelineUiStoreClass = class {
|
|
2990
|
+
constructor() {
|
|
2991
|
+
this.visible = true;
|
|
2992
|
+
this.initialized = false;
|
|
2993
|
+
this.controllers = /* @__PURE__ */ new Map();
|
|
2994
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
2995
|
+
}
|
|
2996
|
+
getVisible() {
|
|
2997
|
+
for (const controller of this.controllers.values()) {
|
|
2998
|
+
if (controller.visible !== void 0) return controller.visible;
|
|
2999
|
+
}
|
|
3000
|
+
return this.visible;
|
|
3001
|
+
}
|
|
3002
|
+
registerController(id, controller) {
|
|
3003
|
+
const previous = this.getVisible();
|
|
3004
|
+
if (!this.initialized) {
|
|
3005
|
+
this.visible = controller.defaultVisible;
|
|
3006
|
+
this.initialized = true;
|
|
3007
|
+
}
|
|
3008
|
+
this.controllers.set(id, controller);
|
|
3009
|
+
if (previous !== this.getVisible()) this.notify();
|
|
3010
|
+
return () => {
|
|
3011
|
+
const before = this.getVisible();
|
|
3012
|
+
this.controllers.delete(id);
|
|
3013
|
+
if (this.controllers.size === 0) this.initialized = false;
|
|
3014
|
+
if (before !== this.getVisible()) this.notify();
|
|
3015
|
+
};
|
|
3016
|
+
}
|
|
3017
|
+
updateController(id, controller) {
|
|
3018
|
+
if (!this.controllers.has(id)) return;
|
|
3019
|
+
const previous = this.getVisible();
|
|
3020
|
+
this.controllers.set(id, controller);
|
|
3021
|
+
if (previous !== this.getVisible()) this.notify();
|
|
3022
|
+
}
|
|
3023
|
+
requestVisible(visible) {
|
|
3024
|
+
const current = this.getVisible();
|
|
3025
|
+
if (current === visible) return;
|
|
3026
|
+
const controlled = Array.from(this.controllers.values()).filter(
|
|
3027
|
+
(controller) => controller.visible !== void 0
|
|
3028
|
+
);
|
|
3029
|
+
if (controlled.length > 0) {
|
|
3030
|
+
controlled.forEach((controller) => controller.onVisibilityChange?.(visible));
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
this.visible = visible;
|
|
3034
|
+
this.controllers.forEach((controller) => controller.onVisibilityChange?.(visible));
|
|
3035
|
+
this.notify();
|
|
3036
|
+
}
|
|
3037
|
+
toggle() {
|
|
3038
|
+
this.requestVisible(!this.getVisible());
|
|
3039
|
+
}
|
|
3040
|
+
subscribe(listener) {
|
|
3041
|
+
this.listeners.add(listener);
|
|
3042
|
+
return () => this.listeners.delete(listener);
|
|
3043
|
+
}
|
|
3044
|
+
notify() {
|
|
3045
|
+
this.listeners.forEach((listener) => listener());
|
|
3046
|
+
}
|
|
3047
|
+
};
|
|
3048
|
+
var TimelineUiStore = /* @__PURE__ */ new TimelineUiStoreClass();
|
|
3049
|
+
|
|
3050
|
+
// src/components/Timeline/TimelineToggleButton.tsx
|
|
3051
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
3052
|
+
function TimelineToggleButton() {
|
|
3053
|
+
const subscribe = useCallback11(
|
|
3054
|
+
(listener) => TimelineUiStore.subscribe(listener),
|
|
3055
|
+
[]
|
|
3056
|
+
);
|
|
3057
|
+
const getVisible = useCallback11(() => TimelineUiStore.getVisible(), []);
|
|
3058
|
+
const visible = useSyncExternalStore5(subscribe, getVisible, getVisible);
|
|
3059
|
+
const label = visible ? "Hide timeline" : "Show timeline";
|
|
3060
|
+
return /* @__PURE__ */ jsx16(
|
|
3061
|
+
motion6.button,
|
|
3062
|
+
{
|
|
3063
|
+
className: "dialkit-toolbar-add dialkit-timeline-toolbar-toggle",
|
|
3064
|
+
"data-active": visible || void 0,
|
|
3065
|
+
"aria-pressed": visible,
|
|
3066
|
+
"aria-label": label,
|
|
3067
|
+
title: label,
|
|
3068
|
+
onClick: () => TimelineUiStore.toggle(),
|
|
3069
|
+
whileTap: { scale: 0.9 },
|
|
3070
|
+
transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
|
|
3071
|
+
children: /* @__PURE__ */ jsx16("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: ICON_TIMELINE.map((d, i) => /* @__PURE__ */ jsx16("path", { d, fill: "currentColor" }, i)) })
|
|
3072
|
+
}
|
|
3073
|
+
);
|
|
2603
3074
|
}
|
|
2604
3075
|
|
|
2605
3076
|
// src/panel-drag.ts
|
|
@@ -2667,32 +3138,39 @@ function blockPanelDragClick(handle) {
|
|
|
2667
3138
|
}
|
|
2668
3139
|
|
|
2669
3140
|
// src/components/DialRoot.tsx
|
|
2670
|
-
import { jsx as
|
|
2671
|
-
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;
|
|
3141
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
2672
3142
|
function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover", theme = "system", productionEnabled = isDevDefault, onOpenChange }) {
|
|
2673
3143
|
if (!productionEnabled) return null;
|
|
2674
3144
|
const [panels, setPanels] = useState10([]);
|
|
3145
|
+
const [timelineCount, setTimelineCount] = useState10(0);
|
|
2675
3146
|
const [mounted, setMounted] = useState10(false);
|
|
2676
3147
|
const inline = mode === "inline";
|
|
2677
|
-
const panelRef =
|
|
3148
|
+
const panelRef = useRef12(null);
|
|
2678
3149
|
const [dragOffset, setDragOffset] = useState10(null);
|
|
2679
3150
|
const [activePosition, setActivePosition] = useState10(position);
|
|
2680
|
-
const lastDragOffset =
|
|
2681
|
-
const draggingRef =
|
|
2682
|
-
const dragStartRef =
|
|
2683
|
-
const didDragRef =
|
|
2684
|
-
const dragTargetRef =
|
|
2685
|
-
const panelOpenStatesRef =
|
|
2686
|
-
const rootOpenRef =
|
|
2687
|
-
|
|
3151
|
+
const lastDragOffset = useRef12(null);
|
|
3152
|
+
const draggingRef = useRef12(false);
|
|
3153
|
+
const dragStartRef = useRef12(null);
|
|
3154
|
+
const didDragRef = useRef12(false);
|
|
3155
|
+
const dragTargetRef = useRef12(null);
|
|
3156
|
+
const panelOpenStatesRef = useRef12(/* @__PURE__ */ new Map());
|
|
3157
|
+
const rootOpenRef = useRef12(null);
|
|
3158
|
+
useEffect9(() => {
|
|
2688
3159
|
setMounted(true);
|
|
2689
|
-
setPanels(DialStore.getPanels());
|
|
2690
|
-
|
|
2691
|
-
|
|
3160
|
+
setPanels(DialStore.getPanels("panel"));
|
|
3161
|
+
setTimelineCount(TimelineStore.getTimelines().length);
|
|
3162
|
+
const unsubscribePanels = DialStore.subscribeGlobal(() => {
|
|
3163
|
+
setPanels(DialStore.getPanels("panel"));
|
|
2692
3164
|
});
|
|
2693
|
-
|
|
3165
|
+
const unsubscribeTimelines = TimelineStore.subscribeGlobal(() => {
|
|
3166
|
+
setTimelineCount(TimelineStore.getTimelines().length);
|
|
3167
|
+
});
|
|
3168
|
+
return () => {
|
|
3169
|
+
unsubscribePanels();
|
|
3170
|
+
unsubscribeTimelines();
|
|
3171
|
+
};
|
|
2694
3172
|
}, []);
|
|
2695
|
-
|
|
3173
|
+
useEffect9(() => {
|
|
2696
3174
|
const fallbackOpen = inline || defaultOpen;
|
|
2697
3175
|
const nextStates = /* @__PURE__ */ new Map();
|
|
2698
3176
|
for (const panel of panels) {
|
|
@@ -2701,7 +3179,7 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
|
|
|
2701
3179
|
panelOpenStatesRef.current = nextStates;
|
|
2702
3180
|
rootOpenRef.current = Array.from(nextStates.values()).some(Boolean);
|
|
2703
3181
|
}, [defaultOpen, inline, panels]);
|
|
2704
|
-
|
|
3182
|
+
useEffect9(() => {
|
|
2705
3183
|
if (!panelRef.current || inline) return;
|
|
2706
3184
|
const observer = new MutationObserver(() => {
|
|
2707
3185
|
const inners = panelRef.current?.querySelectorAll(".dialkit-panel-inner");
|
|
@@ -2729,7 +3207,7 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
|
|
|
2729
3207
|
observer.observe(panelRef.current, { subtree: true, attributes: true, attributeFilter: ["data-collapsed"] });
|
|
2730
3208
|
return () => observer.disconnect();
|
|
2731
3209
|
}, [inline, dragOffset, position]);
|
|
2732
|
-
const handlePointerDown =
|
|
3210
|
+
const handlePointerDown = useCallback12((e) => {
|
|
2733
3211
|
const panel = panelRef.current;
|
|
2734
3212
|
const handle = getPanelDragHandle(e.target, panel);
|
|
2735
3213
|
if (!panel || !handle) return;
|
|
@@ -2739,13 +3217,13 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
|
|
|
2739
3217
|
draggingRef.current = true;
|
|
2740
3218
|
handle.setPointerCapture(e.pointerId);
|
|
2741
3219
|
}, []);
|
|
2742
|
-
const handlePointerMove =
|
|
3220
|
+
const handlePointerMove = useCallback12((e) => {
|
|
2743
3221
|
if (!draggingRef.current || !dragStartRef.current) return;
|
|
2744
3222
|
if (!didDragRef.current && !hasPanelDragMoved(dragStartRef.current, e.clientX, e.clientY)) return;
|
|
2745
3223
|
didDragRef.current = true;
|
|
2746
3224
|
setDragOffset(getPanelDragOffset(dragStartRef.current, e.clientX, e.clientY));
|
|
2747
3225
|
}, []);
|
|
2748
|
-
const handlePointerUp =
|
|
3226
|
+
const handlePointerUp = useCallback12((e) => {
|
|
2749
3227
|
if (!draggingRef.current) return;
|
|
2750
3228
|
draggingRef.current = false;
|
|
2751
3229
|
dragStartRef.current = null;
|
|
@@ -2761,7 +3239,7 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
|
|
|
2761
3239
|
}
|
|
2762
3240
|
dragTargetRef.current = null;
|
|
2763
3241
|
}, []);
|
|
2764
|
-
const handlePanelOpenChange =
|
|
3242
|
+
const handlePanelOpenChange = useCallback12((panelId, open) => {
|
|
2765
3243
|
panelOpenStatesRef.current.set(panelId, open);
|
|
2766
3244
|
const fallbackOpen = inline || defaultOpen;
|
|
2767
3245
|
const nextRootOpen = panels.some((panel) => panelOpenStatesRef.current.get(panel.id) ?? fallbackOpen);
|
|
@@ -2769,7 +3247,7 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
|
|
|
2769
3247
|
rootOpenRef.current = nextRootOpen;
|
|
2770
3248
|
onOpenChange?.(nextRootOpen);
|
|
2771
3249
|
}, [defaultOpen, inline, onOpenChange, panels]);
|
|
2772
|
-
const handleRootOpenChange =
|
|
3250
|
+
const handleRootOpenChange = useCallback12((open) => {
|
|
2773
3251
|
if (rootOpenRef.current === open) return;
|
|
2774
3252
|
rootOpenRef.current = open;
|
|
2775
3253
|
onOpenChange?.(open);
|
|
@@ -2777,7 +3255,7 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
|
|
|
2777
3255
|
if (!mounted || typeof window === "undefined") {
|
|
2778
3256
|
return null;
|
|
2779
3257
|
}
|
|
2780
|
-
if (panels.length === 0) {
|
|
3258
|
+
if (panels.length === 0 && timelineCount === 0) {
|
|
2781
3259
|
return null;
|
|
2782
3260
|
}
|
|
2783
3261
|
const dragStyle = dragOffset ? {
|
|
@@ -2788,7 +3266,8 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
|
|
|
2788
3266
|
} : void 0;
|
|
2789
3267
|
const originX = getPanelOriginX(activePosition, dragOffset);
|
|
2790
3268
|
const hasMultiplePanels = panels.length > 1;
|
|
2791
|
-
const
|
|
3269
|
+
const timelineToggle = timelineCount > 0 ? /* @__PURE__ */ jsx17(TimelineToggleButton, {}) : null;
|
|
3270
|
+
const content = /* @__PURE__ */ jsx17(ShortcutListener, { children: /* @__PURE__ */ jsx17("div", { className: "dialkit-root", "data-mode": mode, "data-theme": theme, children: /* @__PURE__ */ jsx17(
|
|
2792
3271
|
"div",
|
|
2793
3272
|
{
|
|
2794
3273
|
ref: panelRef,
|
|
@@ -2802,7 +3281,7 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
|
|
|
2802
3281
|
onPointerMove: !inline ? handlePointerMove : void 0,
|
|
2803
3282
|
onPointerUp: !inline ? handlePointerUp : void 0,
|
|
2804
3283
|
onPointerCancel: !inline ? handlePointerUp : void 0,
|
|
2805
|
-
children:
|
|
3284
|
+
children: panels.length === 0 ? /* @__PURE__ */ jsx17("div", { className: "dialkit-panel-wrapper", children: /* @__PURE__ */ jsx17(
|
|
2806
3285
|
Folder,
|
|
2807
3286
|
{
|
|
2808
3287
|
title: "DialKit",
|
|
@@ -2810,23 +3289,37 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
|
|
|
2810
3289
|
isRoot: true,
|
|
2811
3290
|
inline,
|
|
2812
3291
|
onOpenChange: handleRootOpenChange,
|
|
3292
|
+
toolbar: timelineToggle,
|
|
2813
3293
|
panelHeightOffset: 2,
|
|
2814
|
-
children:
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
3294
|
+
children: /* @__PURE__ */ jsx17("div", { className: "dialkit-timeline-toolkit-only", children: "Timeline" })
|
|
3295
|
+
}
|
|
3296
|
+
) }) : hasMultiplePanels ? /* @__PURE__ */ jsx17("div", { className: "dialkit-panel-wrapper", children: /* @__PURE__ */ jsx17(
|
|
3297
|
+
Folder,
|
|
3298
|
+
{
|
|
3299
|
+
title: "DialKit",
|
|
3300
|
+
defaultOpen: inline || defaultOpen,
|
|
3301
|
+
isRoot: true,
|
|
3302
|
+
inline,
|
|
3303
|
+
onOpenChange: handleRootOpenChange,
|
|
3304
|
+
toolbar: timelineToggle,
|
|
3305
|
+
panelHeightOffset: 2,
|
|
3306
|
+
children: panels.map((panel) => /* @__PURE__ */ jsx17(
|
|
3307
|
+
Panel,
|
|
3308
|
+
{
|
|
3309
|
+
panel,
|
|
3310
|
+
defaultOpen: true,
|
|
3311
|
+
variant: "section"
|
|
3312
|
+
},
|
|
3313
|
+
panel.id
|
|
2822
3314
|
))
|
|
2823
3315
|
}
|
|
2824
|
-
) }) : panels.map((panel) => /* @__PURE__ */
|
|
3316
|
+
) }) : panels.map((panel) => /* @__PURE__ */ jsx17(
|
|
2825
3317
|
Panel,
|
|
2826
3318
|
{
|
|
2827
3319
|
panel,
|
|
2828
3320
|
defaultOpen: inline || defaultOpen,
|
|
2829
3321
|
inline,
|
|
3322
|
+
toolbarExtra: timelineToggle,
|
|
2830
3323
|
onOpenChange: (open) => handlePanelOpenChange(panel.id, open)
|
|
2831
3324
|
},
|
|
2832
3325
|
panel.id
|
|
@@ -2839,10 +3332,2250 @@ function DialRoot({ position = "top-right", defaultOpen = true, mode = "popover"
|
|
|
2839
3332
|
return createPortal3(content, document.body);
|
|
2840
3333
|
}
|
|
2841
3334
|
|
|
3335
|
+
// src/hooks/useDialTimeline.ts
|
|
3336
|
+
import { useCallback as useCallback13, useEffect as useEffect10, useMemo as useMemo2, useRef as useRef13, useSyncExternalStore as useSyncExternalStore6 } from "react";
|
|
3337
|
+
|
|
3338
|
+
// src/timeline-core.ts
|
|
3339
|
+
var CLIP_VALUE_STEP = 0.01;
|
|
3340
|
+
var TIMELINE_MIN_CLIP_DURATION = 0.05;
|
|
3341
|
+
var DEFAULT_STEP_DURATION = 0.3;
|
|
3342
|
+
var DEFAULT_CLIP_TRANSITION = { type: "spring", bounce: 0.2 };
|
|
3343
|
+
var RESERVED_KEYS = /* @__PURE__ */ new Set(["time", "playing", "duration", "play", "pause", "replay", "seek"]);
|
|
3344
|
+
function isClipConfig(value) {
|
|
3345
|
+
return isPlainObject(value) && Number.isFinite(value.at);
|
|
3346
|
+
}
|
|
3347
|
+
function isGroupConfig(value) {
|
|
3348
|
+
if (!isPlainObject(value) || "at" in value) return false;
|
|
3349
|
+
const entries = Object.values(value);
|
|
3350
|
+
return entries.length > 0 && entries.some(isClipConfig);
|
|
3351
|
+
}
|
|
3352
|
+
function isPlainObject(value) {
|
|
3353
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3354
|
+
}
|
|
3355
|
+
function nonNegativeFinite(value, fallback = 0) {
|
|
3356
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : fallback;
|
|
3357
|
+
}
|
|
3358
|
+
function animatedDuration(value, fallback = DEFAULT_STEP_DURATION) {
|
|
3359
|
+
return Math.max(TIMELINE_MIN_CLIP_DURATION, nonNegativeFinite(value, fallback));
|
|
3360
|
+
}
|
|
3361
|
+
function transitionDefaultDuration(transition) {
|
|
3362
|
+
if (transition.type === "easing") return animatedDuration(transition.duration);
|
|
3363
|
+
if (isPhysicsSpring(transition)) {
|
|
3364
|
+
return animatedDuration(springSettleDuration(springParams(transition)));
|
|
3365
|
+
}
|
|
3366
|
+
if (transition.visualDuration !== void 0) return animatedDuration(transition.visualDuration);
|
|
3367
|
+
return animatedDuration(springSettleDuration(springParams(transition)));
|
|
3368
|
+
}
|
|
3369
|
+
function defaultStepDuration(step, inheritedTransition) {
|
|
3370
|
+
const curve = step.transition ?? inheritedTransition;
|
|
3371
|
+
if (curve && isPhysicsSpring(curve)) return transitionDefaultDuration(curve);
|
|
3372
|
+
if (step.duration !== void 0) return animatedDuration(step.duration);
|
|
3373
|
+
if (step.transition) return transitionDefaultDuration(step.transition);
|
|
3374
|
+
return DEFAULT_STEP_DURATION;
|
|
3375
|
+
}
|
|
3376
|
+
function defaultTrackDuration(track, inheritedTransition) {
|
|
3377
|
+
const curve = track.transition ?? inheritedTransition;
|
|
3378
|
+
if (track.steps?.length) {
|
|
3379
|
+
return track.steps.reduce((sum, step) => sum + defaultStepDuration(step, curve), 0);
|
|
3380
|
+
}
|
|
3381
|
+
if (curve && isPhysicsSpring(curve)) return transitionDefaultDuration(curve);
|
|
3382
|
+
if (track.duration !== void 0) return animatedDuration(track.duration);
|
|
3383
|
+
if (track.transition) return transitionDefaultDuration(track.transition);
|
|
3384
|
+
return DEFAULT_STEP_DURATION;
|
|
3385
|
+
}
|
|
3386
|
+
function defaultClipDuration(clip) {
|
|
3387
|
+
const defaultCurve = isTransitionConfig(clip.transition) ? clip.transition : DEFAULT_CLIP_TRANSITION;
|
|
3388
|
+
if (clip.props) {
|
|
3389
|
+
return Object.values(clip.props).reduce(
|
|
3390
|
+
(max, track) => Math.max(
|
|
3391
|
+
max,
|
|
3392
|
+
nonNegativeFinite(track.delay) + defaultTrackDuration(track, defaultCurve)
|
|
3393
|
+
),
|
|
3394
|
+
0
|
|
3395
|
+
);
|
|
3396
|
+
}
|
|
3397
|
+
if (clip.steps?.length) {
|
|
3398
|
+
return clip.steps.reduce((sum, step) => sum + defaultStepDuration(step, defaultCurve), 0);
|
|
3399
|
+
}
|
|
3400
|
+
const animating = Boolean(clip.transition || clip.from || clip.to);
|
|
3401
|
+
if (!animating) return nonNegativeFinite(clip.duration);
|
|
3402
|
+
if (isPhysicsSpring(defaultCurve)) return transitionDefaultDuration(defaultCurve);
|
|
3403
|
+
if (clip.duration !== void 0) return animatedDuration(clip.duration);
|
|
3404
|
+
if (isTransitionConfig(clip.transition)) return transitionDefaultDuration(clip.transition);
|
|
3405
|
+
return clip.from || clip.to ? transitionDefaultDuration(DEFAULT_CLIP_TRANSITION) : 0;
|
|
3406
|
+
}
|
|
3407
|
+
function normalizeLoopMode(value) {
|
|
3408
|
+
if (value === true || value === "mirror" || value === "repeat") return "repeat";
|
|
3409
|
+
return "off";
|
|
3410
|
+
}
|
|
3411
|
+
function normalizeStoredTransition(transition, clipDuration) {
|
|
3412
|
+
if (transition.type === "easing") {
|
|
3413
|
+
return { ...transition, duration: clipDuration };
|
|
3414
|
+
}
|
|
3415
|
+
if (isPhysicsSpring(transition)) {
|
|
3416
|
+
return transition;
|
|
3417
|
+
}
|
|
3418
|
+
return { type: "spring", bounce: transition.bounce ?? 0.2 };
|
|
3419
|
+
}
|
|
3420
|
+
function collectClipEntries(config) {
|
|
3421
|
+
const entries = [];
|
|
3422
|
+
for (const [key, value] of Object.entries(config)) {
|
|
3423
|
+
if (key === "duration") continue;
|
|
3424
|
+
if (RESERVED_KEYS.has(key)) {
|
|
3425
|
+
console.warn(`[dialkit] Timeline key "${key}" collides with a reserved key and was skipped.`);
|
|
3426
|
+
continue;
|
|
3427
|
+
}
|
|
3428
|
+
if (isClipConfig(value)) {
|
|
3429
|
+
entries.push({ path: key, childKey: key, clip: value });
|
|
3430
|
+
} else if (isGroupConfig(value)) {
|
|
3431
|
+
for (const [childKey, childClip] of Object.entries(value)) {
|
|
3432
|
+
if (isClipConfig(childClip)) {
|
|
3433
|
+
entries.push({ path: `${key}.${childKey}`, childKey, group: key, clip: childClip });
|
|
3434
|
+
} else {
|
|
3435
|
+
console.warn(
|
|
3436
|
+
`[dialkit] Timeline clip "${key}.${childKey}" is missing a numeric "at" and was skipped.`
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
} else {
|
|
3441
|
+
console.warn(
|
|
3442
|
+
`[dialkit] Timeline entry "${key}" is neither a clip (needs a numeric "at") nor a group of clips and was skipped.`
|
|
3443
|
+
);
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
return entries;
|
|
3447
|
+
}
|
|
3448
|
+
function definedValues(values) {
|
|
3449
|
+
if (!values) return void 0;
|
|
3450
|
+
const result = {};
|
|
3451
|
+
for (const [key, value] of Object.entries(values)) {
|
|
3452
|
+
if (value !== void 0) result[key] = value;
|
|
3453
|
+
}
|
|
3454
|
+
return result;
|
|
3455
|
+
}
|
|
3456
|
+
function setDialPath(dialConfig, path, value) {
|
|
3457
|
+
const segments = path.split(".");
|
|
3458
|
+
let node = dialConfig;
|
|
3459
|
+
for (const segment of segments.slice(0, -1)) {
|
|
3460
|
+
node = node[segment] ?? (node[segment] = {});
|
|
3461
|
+
}
|
|
3462
|
+
node[segments[segments.length - 1]] = value;
|
|
3463
|
+
}
|
|
3464
|
+
function parseTimelineConfig(config) {
|
|
3465
|
+
const entries = collectClipEntries(config);
|
|
3466
|
+
let maxEnd = 0;
|
|
3467
|
+
for (const { clip } of entries) {
|
|
3468
|
+
maxEnd = Math.max(maxEnd, nonNegativeFinite(clip.at) + defaultClipDuration(clip));
|
|
3469
|
+
}
|
|
3470
|
+
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;
|
|
3471
|
+
const dialConfig = {};
|
|
3472
|
+
const clips = [];
|
|
3473
|
+
entries.forEach(({ path, childKey, group, clip }, index) => {
|
|
3474
|
+
const raw = clip;
|
|
3475
|
+
if (raw.props && (raw.steps?.length || raw.from || raw.to)) {
|
|
3476
|
+
console.warn(
|
|
3477
|
+
`[dialkit] Timeline clip "${path}": "props" is mutually exclusive with from/to/steps \u2014 using "props".`
|
|
3478
|
+
);
|
|
3479
|
+
} else if (raw.steps?.length && raw.to) {
|
|
3480
|
+
console.warn(
|
|
3481
|
+
`[dialkit] Timeline clip "${path}": "to" is ignored when "steps" is present \u2014 each leg's "to" defines its targets.`
|
|
3482
|
+
);
|
|
3483
|
+
}
|
|
3484
|
+
const hasSteps = Boolean(clip.steps?.length) && !clip.props;
|
|
3485
|
+
const hasProps = Boolean(clip.props);
|
|
3486
|
+
const single = isTransitionConfig(clip.transition) ? clip.transition : void 0;
|
|
3487
|
+
const total = defaultClipDuration(clip);
|
|
3488
|
+
const defaultCurve = single ?? DEFAULT_CLIP_TRANSITION;
|
|
3489
|
+
const clipAt = nonNegativeFinite(clip.at);
|
|
3490
|
+
const clipDial = {
|
|
3491
|
+
at: [clipAt, 0, duration, CLIP_VALUE_STEP]
|
|
3492
|
+
};
|
|
3493
|
+
if (!hasSteps && !hasProps) {
|
|
3494
|
+
clipDial.duration = [total, 0, duration, CLIP_VALUE_STEP];
|
|
3495
|
+
}
|
|
3496
|
+
if (!hasSteps && !hasProps && (clip.transition || clip.from || clip.to)) {
|
|
3497
|
+
clipDial.transition = normalizeStoredTransition(defaultCurve, total);
|
|
3498
|
+
}
|
|
3499
|
+
let tracks;
|
|
3500
|
+
if (clip.props) {
|
|
3501
|
+
tracks = [];
|
|
3502
|
+
for (const [prop, track] of Object.entries(clip.props)) {
|
|
3503
|
+
if (TRACK_RESERVED.has(prop) || /^step\d+$/.test(prop)) {
|
|
3504
|
+
console.warn(`[dialkit] Timeline property "${prop}" collides with a clip field and was skipped.`);
|
|
3505
|
+
continue;
|
|
3506
|
+
}
|
|
3507
|
+
const trackDuration = defaultTrackDuration(track, defaultCurve);
|
|
3508
|
+
const trackCurve = track.transition ?? defaultCurve;
|
|
3509
|
+
const hasTrackSteps = Boolean(track.steps?.length);
|
|
3510
|
+
const trackDial = {
|
|
3511
|
+
delay: [nonNegativeFinite(track.delay), 0, duration, CLIP_VALUE_STEP]
|
|
3512
|
+
};
|
|
3513
|
+
if (!hasTrackSteps) {
|
|
3514
|
+
trackDial.duration = [trackDuration, 0, duration, CLIP_VALUE_STEP];
|
|
3515
|
+
trackDial.transition = normalizeStoredTransition(trackCurve, trackDuration);
|
|
3516
|
+
}
|
|
3517
|
+
const fromValue = track.from ?? (hasTrackSteps ? void 0 : track.to);
|
|
3518
|
+
if (hasTrackSteps && fromValue === void 0) {
|
|
3519
|
+
console.warn(
|
|
3520
|
+
`[dialkit] Timeline clip "${path}": track "${prop}" has steps but no "from" \u2014 declare its starting value.`
|
|
3521
|
+
);
|
|
3522
|
+
}
|
|
3523
|
+
if (fromValue !== void 0) {
|
|
3524
|
+
trackDial.from = scalarDial(prop, fromValue, hasTrackSteps ? track.steps[0]?.to : track.to);
|
|
3525
|
+
}
|
|
3526
|
+
if (!hasTrackSteps && track.to !== void 0) {
|
|
3527
|
+
trackDial.to = scalarDial(prop, track.to, fromValue);
|
|
3528
|
+
}
|
|
3529
|
+
let trackStepKeys;
|
|
3530
|
+
if (hasTrackSteps) {
|
|
3531
|
+
trackStepKeys = [];
|
|
3532
|
+
let previous = fromValue;
|
|
3533
|
+
track.steps.forEach((step, stepIndex) => {
|
|
3534
|
+
const stepKey = `step${stepIndex + 1}`;
|
|
3535
|
+
trackStepKeys.push(stepKey);
|
|
3536
|
+
const stepDuration = defaultStepDuration(step, trackCurve);
|
|
3537
|
+
const stepDial = {
|
|
3538
|
+
duration: [stepDuration, 0, duration, CLIP_VALUE_STEP],
|
|
3539
|
+
transition: normalizeStoredTransition(step.transition ?? trackCurve, stepDuration)
|
|
3540
|
+
};
|
|
3541
|
+
if (step.to !== void 0) {
|
|
3542
|
+
stepDial.to = scalarDial(prop, step.to, previous);
|
|
3543
|
+
previous = step.to;
|
|
3544
|
+
}
|
|
3545
|
+
trackDial[stepKey] = stepDial;
|
|
3546
|
+
});
|
|
3547
|
+
}
|
|
3548
|
+
clipDial[prop] = trackDial;
|
|
3549
|
+
tracks.push({ prop, stepKeys: trackStepKeys });
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
if (clip.from && !hasProps) {
|
|
3553
|
+
clipDial.from = withFromToRanges(
|
|
3554
|
+
clip.from,
|
|
3555
|
+
hasSteps ? definedValues(clip.steps[0]?.to) : clip.to
|
|
3556
|
+
);
|
|
3557
|
+
}
|
|
3558
|
+
if (!hasSteps && !hasProps && clip.to) {
|
|
3559
|
+
clipDial.to = withFromToRanges(clip.to, clip.from);
|
|
3560
|
+
}
|
|
3561
|
+
let stepKeys;
|
|
3562
|
+
if (hasSteps) {
|
|
3563
|
+
stepKeys = [];
|
|
3564
|
+
let running = clip.from;
|
|
3565
|
+
clip.steps.forEach((step, stepIndex) => {
|
|
3566
|
+
const stepKey = `step${stepIndex + 1}`;
|
|
3567
|
+
stepKeys.push(stepKey);
|
|
3568
|
+
const stepDuration = defaultStepDuration(step, defaultCurve);
|
|
3569
|
+
const stepDial = {
|
|
3570
|
+
duration: [stepDuration, 0, duration, CLIP_VALUE_STEP],
|
|
3571
|
+
transition: normalizeStoredTransition(step.transition ?? defaultCurve, stepDuration)
|
|
3572
|
+
};
|
|
3573
|
+
const stepTo = definedValues(step.to);
|
|
3574
|
+
if (stepTo) {
|
|
3575
|
+
for (const prop of Object.keys(stepTo)) {
|
|
3576
|
+
if (!running || !(prop in running)) {
|
|
3577
|
+
console.warn(
|
|
3578
|
+
`[dialkit] Timeline clip "${path}": property "${prop}" first animates in step ${stepIndex + 1} with no starting value \u2014 declare it in "from".`
|
|
3579
|
+
);
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
stepDial.to = withFromToRanges(stepTo, running);
|
|
3583
|
+
}
|
|
3584
|
+
clipDial[stepKey] = stepDial;
|
|
3585
|
+
running = { ...running ?? {}, ...stepTo ?? {} };
|
|
3586
|
+
});
|
|
3587
|
+
}
|
|
3588
|
+
setDialPath(dialConfig, path, clipDial);
|
|
3589
|
+
clips.push({
|
|
3590
|
+
key: path,
|
|
3591
|
+
label: formatLabel(childKey),
|
|
3592
|
+
color: TIMELINE_CLIP_COLORS[index % TIMELINE_CLIP_COLORS.length],
|
|
3593
|
+
loop: normalizeLoopMode(clip.loop),
|
|
3594
|
+
group,
|
|
3595
|
+
stepKeys,
|
|
3596
|
+
tracks
|
|
3597
|
+
});
|
|
3598
|
+
});
|
|
3599
|
+
return { duration, dialConfig, clips };
|
|
3600
|
+
}
|
|
3601
|
+
var TRACK_RESERVED = /* @__PURE__ */ new Set(["at", "duration", "loop", "from", "to", "transition", "delay"]);
|
|
3602
|
+
function scalarDial(prop, value, counterpart) {
|
|
3603
|
+
const record = withFromToRanges(
|
|
3604
|
+
{ [prop]: value },
|
|
3605
|
+
counterpart === void 0 ? void 0 : { [prop]: counterpart }
|
|
3606
|
+
);
|
|
3607
|
+
return record[prop];
|
|
3608
|
+
}
|
|
3609
|
+
var FROM_TO_RANGE_PRESETS = [
|
|
3610
|
+
[/^(x|y|z|tx|ty|offsetx|offsety|translatex|translatey)$/i, { min: -100, max: 100, step: 1 }],
|
|
3611
|
+
[/rotat|angle|skew/i, { min: -180, max: 180, step: 1 }],
|
|
3612
|
+
[/^scale/i, { min: 0, max: 2, step: 0.01 }],
|
|
3613
|
+
[/opacity|alpha/i, { min: 0, max: 1, step: 0.01 }],
|
|
3614
|
+
[/blur|radius|spread/i, { min: 0, max: 100, step: 1 }]
|
|
3615
|
+
];
|
|
3616
|
+
function inferFromToRange(key, value, counterpart) {
|
|
3617
|
+
const lo = Math.min(value, counterpart ?? value);
|
|
3618
|
+
const hi = Math.max(value, counterpart ?? value);
|
|
3619
|
+
const preset = FROM_TO_RANGE_PRESETS.find(([pattern]) => pattern.test(key))?.[1];
|
|
3620
|
+
if (preset) {
|
|
3621
|
+
return [value, Math.min(preset.min, lo), Math.max(preset.max, hi), preset.step];
|
|
3622
|
+
}
|
|
3623
|
+
if (lo >= 0 && hi <= 1) {
|
|
3624
|
+
return [value, 0, 1, 0.01];
|
|
3625
|
+
}
|
|
3626
|
+
const extent = Math.max(Math.abs(lo), Math.abs(hi), 1);
|
|
3627
|
+
const min = lo < 0 ? -extent * 2 : 0;
|
|
3628
|
+
const max = Math.max(extent * 2, hi);
|
|
3629
|
+
return [value, min, max, inferStep(min, max)];
|
|
3630
|
+
}
|
|
3631
|
+
function withFromToRanges(config, counterpart) {
|
|
3632
|
+
const result = {};
|
|
3633
|
+
for (const [key, value] of Object.entries(config)) {
|
|
3634
|
+
const other = counterpart?.[key];
|
|
3635
|
+
if (typeof value === "number") {
|
|
3636
|
+
result[key] = inferFromToRange(key, value, typeof other === "number" ? other : void 0);
|
|
3637
|
+
} else if (isPlainObject(value) && !("type" in value)) {
|
|
3638
|
+
result[key] = withFromToRanges(
|
|
3639
|
+
value,
|
|
3640
|
+
isPlainObject(other) && !("type" in other) ? other : void 0
|
|
3641
|
+
);
|
|
3642
|
+
} else {
|
|
3643
|
+
result[key] = value;
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
return result;
|
|
3647
|
+
}
|
|
3648
|
+
function curveStatic(transition, duration) {
|
|
3649
|
+
if (!transition) return { duration };
|
|
3650
|
+
if (transition.type === "easing") return { duration, ease: transition.ease };
|
|
3651
|
+
const spring = springParams(transition);
|
|
3652
|
+
return { duration, spring, settle: springSettleDuration(spring) };
|
|
3653
|
+
}
|
|
3654
|
+
function sampleCurve(curve, elapsed) {
|
|
3655
|
+
if (elapsed <= 0) return 0;
|
|
3656
|
+
if (curve.spring) {
|
|
3657
|
+
if (curve.settle !== void 0 && elapsed >= curve.settle) return 1;
|
|
3658
|
+
return springProgress(elapsed, curve.spring);
|
|
3659
|
+
}
|
|
3660
|
+
if (curve.ease) {
|
|
3661
|
+
return cubicBezierProgress(clamp(curve.duration > 0 ? elapsed / curve.duration : 1, 0, 1), curve.ease);
|
|
3662
|
+
}
|
|
3663
|
+
return curve.duration > 0 ? Math.min(1, elapsed / curve.duration) : 1;
|
|
3664
|
+
}
|
|
3665
|
+
function resolvedAtPath(resolved, path) {
|
|
3666
|
+
let node = resolved;
|
|
3667
|
+
for (const segment of path.split(".")) {
|
|
3668
|
+
node = isPlainObject(node) ? node[segment] : void 0;
|
|
3669
|
+
}
|
|
3670
|
+
return isPlainObject(node) ? node : {};
|
|
3671
|
+
}
|
|
3672
|
+
function computeStaticClips(parsed, flatValues) {
|
|
3673
|
+
const resolved = resolveDialValues(parsed.dialConfig, flatValues);
|
|
3674
|
+
return parsed.clips.map(
|
|
3675
|
+
(clip) => buildClipStatic(resolvedAtPath(resolved, clip.key), clip, parsed.duration)
|
|
3676
|
+
);
|
|
3677
|
+
}
|
|
3678
|
+
function computeStaticTimeline(parsed, flatValues) {
|
|
3679
|
+
let clips = computeStaticClips(parsed, flatValues);
|
|
3680
|
+
const maxEnd = clips.reduce(
|
|
3681
|
+
(end, clip) => Math.max(end, clip.at + clip.duration),
|
|
3682
|
+
parsed.duration
|
|
3683
|
+
);
|
|
3684
|
+
const duration = maxEnd > parsed.duration ? Math.ceil(maxEnd * 100 - 1e-4) / 100 : parsed.duration;
|
|
3685
|
+
if (duration !== parsed.duration) {
|
|
3686
|
+
clips = clips.map(
|
|
3687
|
+
(clip) => clip.loop === "repeat" ? { ...clip, end: duration } : clip
|
|
3688
|
+
);
|
|
3689
|
+
}
|
|
3690
|
+
return { duration, clips };
|
|
3691
|
+
}
|
|
3692
|
+
function computeClipStaticFromValues(values, clip, timelineDuration) {
|
|
3693
|
+
return buildClipStatic(unflattenClipValues(values, clip.key), clip, timelineDuration);
|
|
3694
|
+
}
|
|
3695
|
+
function buildClipStatic(clipResolved, clip, timelineDuration) {
|
|
3696
|
+
{
|
|
3697
|
+
const at = typeof clipResolved.at === "number" ? clipResolved.at : 0;
|
|
3698
|
+
const from = isPlainObject(clipResolved.from) ? clipResolved.from : void 0;
|
|
3699
|
+
const single = isTransitionConfig(clipResolved.transition) ? clipResolved.transition : void 0;
|
|
3700
|
+
const staticClip = {
|
|
3701
|
+
key: clip.key,
|
|
3702
|
+
childKey: clip.group ? clip.key.slice(clip.group.length + 1) : clip.key,
|
|
3703
|
+
group: clip.group,
|
|
3704
|
+
at,
|
|
3705
|
+
duration: 0,
|
|
3706
|
+
loop: "off",
|
|
3707
|
+
end: 0,
|
|
3708
|
+
isPhysics: false,
|
|
3709
|
+
from,
|
|
3710
|
+
tracks: [],
|
|
3711
|
+
explicitSteps: Boolean(clip.stepKeys?.length)
|
|
3712
|
+
};
|
|
3713
|
+
if (clip.tracks?.length) {
|
|
3714
|
+
const tracks = clip.tracks.map(({ prop, stepKeys }) => {
|
|
3715
|
+
const trackResolved = isPlainObject(clipResolved[prop]) ? clipResolved[prop] : {};
|
|
3716
|
+
const delay = typeof trackResolved.delay === "number" ? trackResolved.delay : 0;
|
|
3717
|
+
const fromValue = trackResolved.from;
|
|
3718
|
+
let steps;
|
|
3719
|
+
let trackDuration = 0;
|
|
3720
|
+
if (stepKeys?.length) {
|
|
3721
|
+
let running = fromValue;
|
|
3722
|
+
steps = stepKeys.map((stepKey) => {
|
|
3723
|
+
const stepResolved = isPlainObject(trackResolved[stepKey]) ? trackResolved[stepKey] : {};
|
|
3724
|
+
const storedDuration = typeof stepResolved.duration === "number" ? stepResolved.duration : 0;
|
|
3725
|
+
const raw = isTransitionConfig(stepResolved.transition) ? stepResolved.transition : void 0;
|
|
3726
|
+
const effective = raw ? resolveClipTransition(raw, storedDuration) : { transition: void 0, duration: storedDuration, isPhysics: false };
|
|
3727
|
+
const toValue = stepResolved.to;
|
|
3728
|
+
const step = {
|
|
3729
|
+
key: stepKey,
|
|
3730
|
+
offset: trackDuration,
|
|
3731
|
+
duration: effective.duration,
|
|
3732
|
+
isPhysics: effective.isPhysics,
|
|
3733
|
+
start: running === void 0 ? {} : { [prop]: running },
|
|
3734
|
+
to: toValue === void 0 ? {} : { [prop]: toValue },
|
|
3735
|
+
curve: curveStatic(effective.transition, effective.duration)
|
|
3736
|
+
};
|
|
3737
|
+
if (toValue !== void 0) running = toValue;
|
|
3738
|
+
trackDuration += effective.duration;
|
|
3739
|
+
return step;
|
|
3740
|
+
});
|
|
3741
|
+
} else {
|
|
3742
|
+
const storedDuration = typeof trackResolved.duration === "number" ? trackResolved.duration : 0;
|
|
3743
|
+
const raw = isTransitionConfig(trackResolved.transition) ? trackResolved.transition : void 0;
|
|
3744
|
+
const effective = raw ? resolveClipTransition(raw, storedDuration) : { transition: void 0, duration: storedDuration, isPhysics: false };
|
|
3745
|
+
const toValue = trackResolved.to;
|
|
3746
|
+
trackDuration = effective.duration;
|
|
3747
|
+
steps = [
|
|
3748
|
+
{
|
|
3749
|
+
key: null,
|
|
3750
|
+
offset: 0,
|
|
3751
|
+
duration: effective.duration,
|
|
3752
|
+
isPhysics: effective.isPhysics,
|
|
3753
|
+
start: fromValue === void 0 ? {} : { [prop]: fromValue },
|
|
3754
|
+
to: toValue === void 0 ? {} : { [prop]: toValue },
|
|
3755
|
+
curve: curveStatic(effective.transition, effective.duration)
|
|
3756
|
+
}
|
|
3757
|
+
];
|
|
3758
|
+
}
|
|
3759
|
+
return { prop, delay, duration: trackDuration, steps };
|
|
3760
|
+
});
|
|
3761
|
+
staticClip.tracks = tracks;
|
|
3762
|
+
staticClip.props = tracks.map((track) => track.prop);
|
|
3763
|
+
staticClip.duration = tracks.reduce((max, track) => Math.max(max, track.delay + track.duration), 0);
|
|
3764
|
+
staticClip.from = Object.fromEntries(
|
|
3765
|
+
tracks.map((track) => [track.prop, track.steps[0].start[track.prop]])
|
|
3766
|
+
);
|
|
3767
|
+
staticClip.to = Object.fromEntries(
|
|
3768
|
+
tracks.map((track) => {
|
|
3769
|
+
const last = track.steps[track.steps.length - 1];
|
|
3770
|
+
return [track.prop, last.to[track.prop] ?? last.start[track.prop]];
|
|
3771
|
+
})
|
|
3772
|
+
);
|
|
3773
|
+
staticClip.loop = staticClip.duration > 0 ? clip.loop : "off";
|
|
3774
|
+
staticClip.end = staticClip.loop === "off" ? staticClip.at + staticClip.duration : timelineDuration;
|
|
3775
|
+
return staticClip;
|
|
3776
|
+
}
|
|
3777
|
+
if (clip.stepKeys?.length) {
|
|
3778
|
+
let running = { ...from ?? {} };
|
|
3779
|
+
let offset = 0;
|
|
3780
|
+
const steps = clip.stepKeys.map((stepKey) => {
|
|
3781
|
+
const stepResolved = isPlainObject(clipResolved[stepKey]) ? clipResolved[stepKey] : {};
|
|
3782
|
+
const storedDuration = typeof stepResolved.duration === "number" ? stepResolved.duration : 0;
|
|
3783
|
+
const raw = isTransitionConfig(stepResolved.transition) ? stepResolved.transition : void 0;
|
|
3784
|
+
const effective = raw ? resolveClipTransition(raw, storedDuration) : { transition: void 0, duration: storedDuration, isPhysics: false };
|
|
3785
|
+
const to = isPlainObject(stepResolved.to) ? stepResolved.to : {};
|
|
3786
|
+
const step = {
|
|
3787
|
+
key: stepKey,
|
|
3788
|
+
offset,
|
|
3789
|
+
duration: effective.duration,
|
|
3790
|
+
isPhysics: effective.isPhysics,
|
|
3791
|
+
start: running,
|
|
3792
|
+
to,
|
|
3793
|
+
curve: curveStatic(effective.transition, effective.duration)
|
|
3794
|
+
};
|
|
3795
|
+
running = { ...running, ...to };
|
|
3796
|
+
offset += effective.duration;
|
|
3797
|
+
return step;
|
|
3798
|
+
});
|
|
3799
|
+
staticClip.tracks = [{ delay: 0, duration: offset, steps }];
|
|
3800
|
+
staticClip.duration = offset;
|
|
3801
|
+
staticClip.to = running;
|
|
3802
|
+
} else {
|
|
3803
|
+
const storedDuration = typeof clipResolved.duration === "number" ? clipResolved.duration : 0;
|
|
3804
|
+
const to = isPlainObject(clipResolved.to) ? clipResolved.to : void 0;
|
|
3805
|
+
if (single) {
|
|
3806
|
+
const effective = resolveClipTransition(single, storedDuration);
|
|
3807
|
+
staticClip.duration = effective.duration;
|
|
3808
|
+
staticClip.isPhysics = effective.isPhysics;
|
|
3809
|
+
staticClip.transition = effective.transition;
|
|
3810
|
+
staticClip.css = transitionToCss(effective.transition);
|
|
3811
|
+
staticClip.to = to;
|
|
3812
|
+
if (from && to) {
|
|
3813
|
+
staticClip.tracks = [
|
|
3814
|
+
{
|
|
3815
|
+
delay: 0,
|
|
3816
|
+
duration: effective.duration,
|
|
3817
|
+
steps: [
|
|
3818
|
+
{
|
|
3819
|
+
key: null,
|
|
3820
|
+
offset: 0,
|
|
3821
|
+
duration: effective.duration,
|
|
3822
|
+
isPhysics: effective.isPhysics,
|
|
3823
|
+
start: from,
|
|
3824
|
+
to,
|
|
3825
|
+
curve: curveStatic(effective.transition, effective.duration)
|
|
3826
|
+
}
|
|
3827
|
+
]
|
|
3828
|
+
}
|
|
3829
|
+
];
|
|
3830
|
+
}
|
|
3831
|
+
} else {
|
|
3832
|
+
staticClip.duration = storedDuration;
|
|
3833
|
+
staticClip.to = to;
|
|
3834
|
+
if (from && to) {
|
|
3835
|
+
const base = resolveClipTransition(DEFAULT_CLIP_TRANSITION, storedDuration);
|
|
3836
|
+
staticClip.duration = base.duration;
|
|
3837
|
+
staticClip.tracks = [
|
|
3838
|
+
{
|
|
3839
|
+
delay: 0,
|
|
3840
|
+
duration: base.duration,
|
|
3841
|
+
steps: [
|
|
3842
|
+
{
|
|
3843
|
+
key: null,
|
|
3844
|
+
offset: 0,
|
|
3845
|
+
duration: base.duration,
|
|
3846
|
+
isPhysics: false,
|
|
3847
|
+
start: from,
|
|
3848
|
+
to,
|
|
3849
|
+
curve: curveStatic(base.transition, base.duration)
|
|
3850
|
+
}
|
|
3851
|
+
]
|
|
3852
|
+
}
|
|
3853
|
+
];
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
if (staticClip.tracks.length) {
|
|
3858
|
+
const props = new Set(Object.keys(from ?? {}));
|
|
3859
|
+
for (const track of staticClip.tracks) {
|
|
3860
|
+
for (const step of track.steps) {
|
|
3861
|
+
for (const prop of Object.keys(step.to)) props.add(prop);
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
staticClip.props = Array.from(props);
|
|
3865
|
+
}
|
|
3866
|
+
staticClip.loop = staticClip.duration > 0 ? clip.loop : "off";
|
|
3867
|
+
staticClip.end = staticClip.loop === "off" ? staticClip.at + staticClip.duration : timelineDuration;
|
|
3868
|
+
return staticClip;
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
3871
|
+
function stepAtPosition(steps, pos) {
|
|
3872
|
+
for (const step of steps) {
|
|
3873
|
+
if (pos < step.offset + step.duration) return step;
|
|
3874
|
+
}
|
|
3875
|
+
return steps[steps.length - 1];
|
|
3876
|
+
}
|
|
3877
|
+
function evalPropAtPos(steps, prop, pos) {
|
|
3878
|
+
const step = stepAtPosition(steps, pos);
|
|
3879
|
+
const within = Math.max(0, pos - step.offset);
|
|
3880
|
+
if (prop in step.to) {
|
|
3881
|
+
const eased = sampleCurve(step.curve, within);
|
|
3882
|
+
return interpolateResolved(step.start[prop], step.to[prop], eased);
|
|
3883
|
+
}
|
|
3884
|
+
return step.start[prop];
|
|
3885
|
+
}
|
|
3886
|
+
function computeClipState(clip, time, cycleTime = time) {
|
|
3887
|
+
const total = clip.duration;
|
|
3888
|
+
const looping = clip.loop === "repeat" && total > 0;
|
|
3889
|
+
const started = time >= clip.at || looping && cycleTime > time;
|
|
3890
|
+
const done = time >= clip.end;
|
|
3891
|
+
const elapsed = time - clip.at;
|
|
3892
|
+
const phaseElapsed = looping ? cycleTime - clip.at : elapsed;
|
|
3893
|
+
const fold = (e) => looping ? e % total : e;
|
|
3894
|
+
const basePos = started ? fold(Math.max(0, phaseElapsed)) : 0;
|
|
3895
|
+
const progress = total > 0 ? clamp(basePos / total, 0, 1) : started ? 1 : 0;
|
|
3896
|
+
let current;
|
|
3897
|
+
let stepIndex = 0;
|
|
3898
|
+
if (clip.tracks.length && clip.props?.length) {
|
|
3899
|
+
current = {};
|
|
3900
|
+
for (const track of clip.tracks) {
|
|
3901
|
+
const props = track.prop !== void 0 ? [track.prop] : clip.props;
|
|
3902
|
+
for (const prop of props) {
|
|
3903
|
+
const startValue = track.steps[0]?.start[prop];
|
|
3904
|
+
if (!started) {
|
|
3905
|
+
if (startValue !== void 0) current[prop] = startValue;
|
|
3906
|
+
continue;
|
|
3907
|
+
}
|
|
3908
|
+
const phase = phaseElapsed - track.delay;
|
|
3909
|
+
if (phase <= 0) {
|
|
3910
|
+
if (startValue !== void 0) current[prop] = startValue;
|
|
3911
|
+
continue;
|
|
3912
|
+
}
|
|
3913
|
+
const pos = looping && track.duration > 0 ? phase % track.duration : phase;
|
|
3914
|
+
const value = evalPropAtPos(track.steps, prop, pos);
|
|
3915
|
+
if (value !== void 0) current[prop] = value;
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
const shared = clip.tracks[0];
|
|
3919
|
+
if (started && clip.explicitSteps && shared.prop === void 0) {
|
|
3920
|
+
stepIndex = shared.steps.indexOf(stepAtPosition(shared.steps, basePos));
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
return {
|
|
3924
|
+
at: clip.at,
|
|
3925
|
+
duration: clip.duration,
|
|
3926
|
+
loop: clip.loop,
|
|
3927
|
+
started,
|
|
3928
|
+
active: started && !done,
|
|
3929
|
+
done,
|
|
3930
|
+
progress,
|
|
3931
|
+
step: clip.explicitSteps ? stepIndex : void 0,
|
|
3932
|
+
from: clip.from,
|
|
3933
|
+
to: clip.to,
|
|
3934
|
+
animate: started ? clip.to : clip.from,
|
|
3935
|
+
transition: clip.transition,
|
|
3936
|
+
css: clip.css,
|
|
3937
|
+
current
|
|
3938
|
+
};
|
|
3939
|
+
}
|
|
3940
|
+
function interpolateResolved(from, to, p) {
|
|
3941
|
+
if (typeof from === "number" && typeof to === "number") {
|
|
3942
|
+
return from + (to - from) * p;
|
|
3943
|
+
}
|
|
3944
|
+
if (typeof from === "string" && typeof to === "string") {
|
|
3945
|
+
const mixed = mixHexColors(from, to, p);
|
|
3946
|
+
if (mixed) return mixed;
|
|
3947
|
+
}
|
|
3948
|
+
if (isPlainObject(from) && isPlainObject(to)) {
|
|
3949
|
+
const result = {};
|
|
3950
|
+
for (const key of Object.keys(from)) {
|
|
3951
|
+
result[key] = key in to ? interpolateResolved(from[key], to[key], p) : from[key];
|
|
3952
|
+
}
|
|
3953
|
+
for (const key of Object.keys(to)) {
|
|
3954
|
+
if (!(key in from)) result[key] = to[key];
|
|
3955
|
+
}
|
|
3956
|
+
return result;
|
|
3957
|
+
}
|
|
3958
|
+
return p < 0.5 ? from : to;
|
|
3959
|
+
}
|
|
3960
|
+
function parseHex(hex) {
|
|
3961
|
+
if (!isHexColor(hex)) return null;
|
|
3962
|
+
let h = hex.slice(1);
|
|
3963
|
+
if (h.length === 3) h = h.split("").map((c) => c + c).join("");
|
|
3964
|
+
return [
|
|
3965
|
+
parseInt(h.slice(0, 2), 16),
|
|
3966
|
+
parseInt(h.slice(2, 4), 16),
|
|
3967
|
+
parseInt(h.slice(4, 6), 16),
|
|
3968
|
+
h.length === 8 ? parseInt(h.slice(6, 8), 16) : 255
|
|
3969
|
+
];
|
|
3970
|
+
}
|
|
3971
|
+
function mixHexColors(a, b, p) {
|
|
3972
|
+
const ca = parseHex(a);
|
|
3973
|
+
const cb = parseHex(b);
|
|
3974
|
+
if (!ca || !cb) return null;
|
|
3975
|
+
const t = clamp(p, 0, 1);
|
|
3976
|
+
const mixed = ca.map((v, i) => Math.round(v + (cb[i] - v) * t));
|
|
3977
|
+
const hex = (n) => n.toString(16).padStart(2, "0");
|
|
3978
|
+
const rgb = `#${hex(mixed[0])}${hex(mixed[1])}${hex(mixed[2])}`;
|
|
3979
|
+
return mixed[3] === 255 ? rgb : `${rgb}${hex(mixed[3])}`;
|
|
3980
|
+
}
|
|
3981
|
+
function transitionToCss(transition) {
|
|
3982
|
+
if (!transition) return void 0;
|
|
3983
|
+
if (transition.type === "easing") {
|
|
3984
|
+
return {
|
|
3985
|
+
transitionDuration: `${round2(transition.duration)}s`,
|
|
3986
|
+
transitionTimingFunction: `cubic-bezier(${transition.ease.map((v) => round2(v)).join(", ")})`
|
|
3987
|
+
};
|
|
3988
|
+
}
|
|
3989
|
+
const params = springParams(transition);
|
|
3990
|
+
const dampingRatio = params.damping / (2 * Math.sqrt(params.stiffness * params.mass));
|
|
3991
|
+
const duration = transition.visualDuration ?? springSettleDuration(params);
|
|
3992
|
+
const bounce = transition.bounce ?? Math.max(0, round2(1 - dampingRatio));
|
|
3993
|
+
return {
|
|
3994
|
+
transitionDuration: `${round2(duration)}s`,
|
|
3995
|
+
transitionTimingFunction: bounce > 0.05 ? `cubic-bezier(0.34, ${round2(1.2 + bounce)}, 0.64, 1)` : "cubic-bezier(0.25, 0.6, 0.35, 1)"
|
|
3996
|
+
};
|
|
3997
|
+
}
|
|
3998
|
+
function timelinePopoverDisplayValues(values, clipKey, stepKeys, stepKey) {
|
|
3999
|
+
const display = { ...values };
|
|
4000
|
+
const swap = (path, duration) => {
|
|
4001
|
+
const raw = display[path];
|
|
4002
|
+
if (isTransitionConfig(raw)) display[path] = resolveClipTransition(raw, duration).transition;
|
|
4003
|
+
};
|
|
4004
|
+
if (stepKey) {
|
|
4005
|
+
swap(`${clipKey}.${stepKey}.transition`, numberValue(values[`${clipKey}.${stepKey}.duration`]));
|
|
4006
|
+
return display;
|
|
4007
|
+
}
|
|
4008
|
+
const cycle = stepKeys?.length ? stepKeys.reduce((sum, sk) => sum + numberValue(values[`${clipKey}.${sk}.duration`]), 0) : numberValue(values[`${clipKey}.duration`]);
|
|
4009
|
+
swap(`${clipKey}.transition`, cycle);
|
|
4010
|
+
return display;
|
|
4011
|
+
}
|
|
4012
|
+
function numberValue(value) {
|
|
4013
|
+
return typeof value === "number" ? value : 0;
|
|
4014
|
+
}
|
|
4015
|
+
function unflattenClipValues(values, clipKey) {
|
|
4016
|
+
const prefix = `${clipKey}.`;
|
|
4017
|
+
const result = {};
|
|
4018
|
+
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);
|
|
4019
|
+
for (const { segments, value } of entries) {
|
|
4020
|
+
let node = result;
|
|
4021
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
4022
|
+
const existing = node[segments[i]];
|
|
4023
|
+
node = isPlainObject(existing) ? existing : node[segments[i]] = {};
|
|
4024
|
+
}
|
|
4025
|
+
node[segments[segments.length - 1]] = cloneTimelineValue(value);
|
|
4026
|
+
}
|
|
4027
|
+
return result;
|
|
4028
|
+
}
|
|
4029
|
+
function cloneTimelineValue(value) {
|
|
4030
|
+
if (Array.isArray(value)) return value.map(cloneTimelineValue);
|
|
4031
|
+
if (!isPlainObject(value)) return value;
|
|
4032
|
+
return Object.fromEntries(
|
|
4033
|
+
Object.entries(value).map(([key, nested]) => [key, cloneTimelineValue(nested)])
|
|
4034
|
+
);
|
|
4035
|
+
}
|
|
4036
|
+
function clampTrackDelay(delay, at, trackDuration, timelineDuration) {
|
|
4037
|
+
return clamp(round2(delay), 0, Math.max(0, round2(timelineDuration - at - trackDuration)));
|
|
4038
|
+
}
|
|
4039
|
+
function clampClipMove(at, duration, timelineDuration) {
|
|
4040
|
+
return clamp(round2(at), 0, Math.max(0, timelineDuration - duration));
|
|
4041
|
+
}
|
|
4042
|
+
function clampClipResizeEnd(duration, at, timelineDuration) {
|
|
4043
|
+
return clamp(round2(duration), TIMELINE_MIN_CLIP_DURATION, timelineDuration - at);
|
|
4044
|
+
}
|
|
4045
|
+
function clampClipResizeStart(newAt, at, duration) {
|
|
4046
|
+
const clampedAt = clamp(round2(newAt), 0, at + duration - TIMELINE_MIN_CLIP_DURATION);
|
|
4047
|
+
return { at: clampedAt, duration: round2(at + duration - clampedAt) };
|
|
4048
|
+
}
|
|
4049
|
+
function clampStepResize(duration, at, otherStepsTotal, timelineDuration) {
|
|
4050
|
+
const max = Math.max(TIMELINE_MIN_CLIP_DURATION, timelineDuration - at - otherStepsTotal);
|
|
4051
|
+
return clamp(round2(duration), TIMELINE_MIN_CLIP_DURATION, max);
|
|
4052
|
+
}
|
|
4053
|
+
function normalizeTimelineValuesForCopy(values, clips) {
|
|
4054
|
+
const normalized = { ...values };
|
|
4055
|
+
for (const path of Object.keys(normalized)) {
|
|
4056
|
+
if (path.endsWith(".__mode")) delete normalized[path];
|
|
4057
|
+
}
|
|
4058
|
+
const normalizeTransitionAt = (transitionPath, durationPath) => {
|
|
4059
|
+
const raw = normalized[transitionPath];
|
|
4060
|
+
if (!isTransitionConfig(raw)) return;
|
|
4061
|
+
if (isPhysicsSpring(raw)) {
|
|
4062
|
+
normalized[durationPath] = transitionDefaultDuration(raw);
|
|
4063
|
+
}
|
|
4064
|
+
normalized[transitionPath] = normalizeStoredTransition(
|
|
4065
|
+
raw,
|
|
4066
|
+
numberValue(normalized[durationPath])
|
|
4067
|
+
);
|
|
4068
|
+
};
|
|
4069
|
+
for (const clip of clips) {
|
|
4070
|
+
for (const stepKey of clip.stepKeys ?? []) {
|
|
4071
|
+
normalizeTransitionAt(
|
|
4072
|
+
`${clip.key}.${stepKey}.transition`,
|
|
4073
|
+
`${clip.key}.${stepKey}.duration`
|
|
4074
|
+
);
|
|
4075
|
+
}
|
|
4076
|
+
normalizeTransitionAt(`${clip.key}.transition`, `${clip.key}.duration`);
|
|
4077
|
+
for (const track of clip.tracks ?? []) {
|
|
4078
|
+
const trackKey = `${clip.key}.${track.prop}`;
|
|
4079
|
+
for (const stepKey of track.stepKeys ?? []) {
|
|
4080
|
+
normalizeTransitionAt(
|
|
4081
|
+
`${trackKey}.${stepKey}.transition`,
|
|
4082
|
+
`${trackKey}.${stepKey}.duration`
|
|
4083
|
+
);
|
|
4084
|
+
}
|
|
4085
|
+
normalizeTransitionAt(`${trackKey}.transition`, `${trackKey}.duration`);
|
|
4086
|
+
if (normalized[`${trackKey}.delay`] === 0) {
|
|
4087
|
+
delete normalized[`${trackKey}.delay`];
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
delete normalized[`${clip.key}.loop`];
|
|
4091
|
+
}
|
|
4092
|
+
return normalized;
|
|
4093
|
+
}
|
|
4094
|
+
function formatClock(time, tenths = false) {
|
|
4095
|
+
const safe = Math.max(0, time);
|
|
4096
|
+
const minutes = Math.floor(safe / 60);
|
|
4097
|
+
const seconds = safe - minutes * 60;
|
|
4098
|
+
const secondsText = tenths ? seconds.toFixed(1).padStart(4, "0") : String(Math.floor(seconds)).padStart(2, "0");
|
|
4099
|
+
return `${String(minutes).padStart(2, "0")}:${secondsText}`;
|
|
4100
|
+
}
|
|
4101
|
+
function formatSeconds(value) {
|
|
4102
|
+
return `${round2(value)}s`;
|
|
4103
|
+
}
|
|
4104
|
+
function formatStepLabel(stepKey) {
|
|
4105
|
+
const match = /^step(\d+)$/.exec(stepKey);
|
|
4106
|
+
return match ? `Step ${match[1]}` : formatLabel(stepKey);
|
|
4107
|
+
}
|
|
4108
|
+
|
|
4109
|
+
// src/timeline/adapter.ts
|
|
4110
|
+
function resolveTimelineLoop(loop) {
|
|
4111
|
+
if (typeof loop === "object" && loop !== null) {
|
|
4112
|
+
return {
|
|
4113
|
+
enabled: true,
|
|
4114
|
+
start: Number.isFinite(loop.from) ? Math.max(0, loop.from) : 0
|
|
4115
|
+
};
|
|
4116
|
+
}
|
|
4117
|
+
return { enabled: Boolean(loop), start: 0 };
|
|
4118
|
+
}
|
|
4119
|
+
function buildTimelineMeta(id, name, duration, parsed, loop) {
|
|
4120
|
+
const resolvedLoop = resolveTimelineLoop(loop);
|
|
4121
|
+
return {
|
|
4122
|
+
id,
|
|
4123
|
+
name,
|
|
4124
|
+
duration,
|
|
4125
|
+
loop: resolvedLoop.enabled,
|
|
4126
|
+
loopStart: resolvedLoop.start,
|
|
4127
|
+
clips: parsed.clips
|
|
4128
|
+
};
|
|
4129
|
+
}
|
|
4130
|
+
function buildTimelineValues(staticClips, transport, timelineDuration, loopStart, actions) {
|
|
4131
|
+
var _a;
|
|
4132
|
+
const result = {
|
|
4133
|
+
time: transport.time,
|
|
4134
|
+
playing: transport.playing,
|
|
4135
|
+
duration: timelineDuration,
|
|
4136
|
+
...actions
|
|
4137
|
+
};
|
|
4138
|
+
const span = loopSpan(transport.duration, loopStart);
|
|
4139
|
+
const cycleTime = (span > 0 ? transport.wraps * span : 0) + transport.time;
|
|
4140
|
+
for (const clip of staticClips) {
|
|
4141
|
+
const state = computeClipState(clip, transport.time, cycleTime);
|
|
4142
|
+
if (clip.group) {
|
|
4143
|
+
const bucket = result[_a = clip.group] ?? (result[_a] = {});
|
|
4144
|
+
bucket[clip.childKey] = state;
|
|
4145
|
+
} else {
|
|
4146
|
+
result[clip.key] = state;
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
return result;
|
|
4150
|
+
}
|
|
4151
|
+
|
|
4152
|
+
// src/hooks/useDialTimeline.ts
|
|
4153
|
+
function useDialTimeline(name, config, options) {
|
|
4154
|
+
const serializedConfig = useSerialized(config);
|
|
4155
|
+
const parsed = useMemo2(() => parseTimelineConfig(config), [serializedConfig]);
|
|
4156
|
+
const { panelId, flatValues } = useDialStorePanel(name, parsed.dialConfig, {
|
|
4157
|
+
id: options?.id,
|
|
4158
|
+
persist: options?.persist,
|
|
4159
|
+
kind: "timeline"
|
|
4160
|
+
});
|
|
4161
|
+
const staticTimeline = useMemo2(
|
|
4162
|
+
() => computeStaticTimeline(parsed, flatValues),
|
|
4163
|
+
[parsed, flatValues]
|
|
4164
|
+
);
|
|
4165
|
+
const timelineDuration = staticTimeline.duration;
|
|
4166
|
+
const staticClips = staticTimeline.clips;
|
|
4167
|
+
const parsedRef = useRef13(parsed);
|
|
4168
|
+
parsedRef.current = parsed;
|
|
4169
|
+
const optionsRef = useRef13(options);
|
|
4170
|
+
optionsRef.current = options;
|
|
4171
|
+
const { start: loopStart } = resolveTimelineLoop(options?.loop);
|
|
4172
|
+
const buildMeta = useCallback13(
|
|
4173
|
+
() => buildTimelineMeta(panelId, name, timelineDuration, parsedRef.current, options?.loop),
|
|
4174
|
+
[panelId, name, timelineDuration, options?.loop]
|
|
4175
|
+
);
|
|
4176
|
+
const buildMetaRef = useRef13(buildMeta);
|
|
4177
|
+
buildMetaRef.current = buildMeta;
|
|
4178
|
+
useEffect10(() => {
|
|
4179
|
+
TimelineStore.register(buildMetaRef.current(), { autoplay: optionsRef.current?.autoplay ?? true });
|
|
4180
|
+
return () => TimelineStore.unregister(panelId);
|
|
4181
|
+
}, [panelId, name]);
|
|
4182
|
+
const mountedRef = useRef13(false);
|
|
4183
|
+
useEffect10(() => {
|
|
4184
|
+
if (!mountedRef.current) {
|
|
4185
|
+
mountedRef.current = true;
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
TimelineStore.update(buildMeta());
|
|
4189
|
+
}, [buildMeta, parsed]);
|
|
4190
|
+
const subscribeTransport = useCallback13(
|
|
4191
|
+
(callback) => TimelineStore.subscribe(panelId, callback),
|
|
4192
|
+
[panelId]
|
|
4193
|
+
);
|
|
4194
|
+
const getTransport = useCallback13(() => TimelineStore.getTransport(panelId), [panelId]);
|
|
4195
|
+
const transport = useSyncExternalStore6(subscribeTransport, getTransport, getTransport);
|
|
4196
|
+
const play = useCallback13(() => TimelineStore.play(panelId), [panelId]);
|
|
4197
|
+
const pause = useCallback13(() => TimelineStore.pause(panelId), [panelId]);
|
|
4198
|
+
const replay = useCallback13(() => TimelineStore.replay(panelId), [panelId]);
|
|
4199
|
+
const seek = useCallback13((time) => TimelineStore.seek(panelId, time), [panelId]);
|
|
4200
|
+
return useMemo2(
|
|
4201
|
+
() => buildTimelineValues(staticClips, transport, timelineDuration, loopStart, {
|
|
4202
|
+
play,
|
|
4203
|
+
pause,
|
|
4204
|
+
replay,
|
|
4205
|
+
seek
|
|
4206
|
+
}),
|
|
4207
|
+
[staticClips, transport, timelineDuration, loopStart, play, pause, replay, seek]
|
|
4208
|
+
);
|
|
4209
|
+
}
|
|
4210
|
+
|
|
4211
|
+
// src/components/Timeline/DialTimeline.tsx
|
|
4212
|
+
import { memo, useCallback as useCallback14, useEffect as useEffect11, useLayoutEffect as useLayoutEffect2, useRef as useRef14, useState as useState11, useSyncExternalStore as useSyncExternalStore7 } from "react";
|
|
4213
|
+
import { createPortal as createPortal4 } from "react-dom";
|
|
4214
|
+
import { AnimatePresence as AnimatePresence5, motion as motion7 } from "motion/react";
|
|
4215
|
+
import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
4216
|
+
var DRAG_THRESHOLD_PX = 3;
|
|
4217
|
+
var MAJOR_TICK_TARGET_PX = 140;
|
|
4218
|
+
var MILLISECOND_STEP = 1e-3;
|
|
4219
|
+
var SECOND_TICK_STEPS = [
|
|
4220
|
+
1e-3,
|
|
4221
|
+
2e-3,
|
|
4222
|
+
5e-3,
|
|
4223
|
+
0.01,
|
|
4224
|
+
0.02,
|
|
4225
|
+
0.05,
|
|
4226
|
+
0.1,
|
|
4227
|
+
0.2,
|
|
4228
|
+
0.5,
|
|
4229
|
+
1,
|
|
4230
|
+
2,
|
|
4231
|
+
5,
|
|
4232
|
+
10,
|
|
4233
|
+
15,
|
|
4234
|
+
30,
|
|
4235
|
+
60,
|
|
4236
|
+
120,
|
|
4237
|
+
300,
|
|
4238
|
+
600
|
|
4239
|
+
];
|
|
4240
|
+
var MIN_TIMELINE_MAX_ZOOM = 8;
|
|
4241
|
+
var PLAYHEAD_FLAG_WIDTH = 52;
|
|
4242
|
+
var PLAYHEAD_FLAG_EDGE_OVERHANG = 1;
|
|
4243
|
+
var POPOVER_WIDTH = 280;
|
|
4244
|
+
var ZOOM_DRAG_DISTANCE = 180;
|
|
4245
|
+
var DEFAULT_DOCK_MAX_HEIGHT = 400;
|
|
4246
|
+
var MIN_DOCK_MAX_HEIGHT = 120;
|
|
4247
|
+
var subscribeGlobalTimelines = (callback) => TimelineStore.subscribeGlobal(callback);
|
|
4248
|
+
var getTimelines = () => TimelineStore.getTimelines();
|
|
4249
|
+
var subscribeTimelineVisibility = (callback) => TimelineUiStore.subscribe(callback);
|
|
4250
|
+
var getTimelineVisibility = () => TimelineUiStore.getVisible();
|
|
4251
|
+
var DialTimeline = memo(function DialTimeline2({
|
|
4252
|
+
theme = "system",
|
|
4253
|
+
defaultVisible = true,
|
|
4254
|
+
visible,
|
|
4255
|
+
onVisibilityChange,
|
|
4256
|
+
defaultOpen = true,
|
|
4257
|
+
productionEnabled = isDevDefault
|
|
4258
|
+
}) {
|
|
4259
|
+
if (!productionEnabled) return null;
|
|
4260
|
+
return /* @__PURE__ */ jsx18(
|
|
4261
|
+
DialTimelineDock,
|
|
4262
|
+
{
|
|
4263
|
+
theme,
|
|
4264
|
+
defaultVisible,
|
|
4265
|
+
visible,
|
|
4266
|
+
onVisibilityChange,
|
|
4267
|
+
defaultOpen
|
|
4268
|
+
}
|
|
4269
|
+
);
|
|
4270
|
+
});
|
|
4271
|
+
function DialTimelineDock({
|
|
4272
|
+
theme,
|
|
4273
|
+
defaultVisible,
|
|
4274
|
+
visible,
|
|
4275
|
+
onVisibilityChange,
|
|
4276
|
+
defaultOpen
|
|
4277
|
+
}) {
|
|
4278
|
+
const [mounted, setMounted] = useState11(false);
|
|
4279
|
+
const [dockMaxHeight, setDockMaxHeight] = useState11(DEFAULT_DOCK_MAX_HEIGHT);
|
|
4280
|
+
const visibilityControllerId = useRef14(/* @__PURE__ */ Symbol("dialkit-timeline-visibility"));
|
|
4281
|
+
const dockRef = useRef14(null);
|
|
4282
|
+
const resizeCleanupRef = useRef14(null);
|
|
4283
|
+
useEffect11(() => TimelineUiStore.registerController(visibilityControllerId.current, {
|
|
4284
|
+
visible,
|
|
4285
|
+
defaultVisible,
|
|
4286
|
+
onVisibilityChange
|
|
4287
|
+
}), []);
|
|
4288
|
+
useEffect11(() => {
|
|
4289
|
+
TimelineUiStore.updateController(visibilityControllerId.current, {
|
|
4290
|
+
visible,
|
|
4291
|
+
defaultVisible,
|
|
4292
|
+
onVisibilityChange
|
|
4293
|
+
});
|
|
4294
|
+
}, [defaultVisible, onVisibilityChange, visible]);
|
|
4295
|
+
useEffect11(() => {
|
|
4296
|
+
setMounted(true);
|
|
4297
|
+
}, []);
|
|
4298
|
+
useEffect11(() => () => resizeCleanupRef.current?.(), []);
|
|
4299
|
+
const handleResizePointerDown = useCallback14((e) => {
|
|
4300
|
+
const dock = dockRef.current;
|
|
4301
|
+
if (!dock) return;
|
|
4302
|
+
e.preventDefault();
|
|
4303
|
+
e.stopPropagation();
|
|
4304
|
+
resizeCleanupRef.current?.();
|
|
4305
|
+
const pointerY = e.clientY;
|
|
4306
|
+
const startHeight = dock.getBoundingClientRect().height;
|
|
4307
|
+
const handlePointerMove = (event) => {
|
|
4308
|
+
event.preventDefault();
|
|
4309
|
+
const viewportMax = Math.max(MIN_DOCK_MAX_HEIGHT, window.innerHeight - 24);
|
|
4310
|
+
setDockMaxHeight(clamp(startHeight + pointerY - event.clientY, MIN_DOCK_MAX_HEIGHT, viewportMax));
|
|
4311
|
+
};
|
|
4312
|
+
const finishResize = () => {
|
|
4313
|
+
window.removeEventListener("pointermove", handlePointerMove);
|
|
4314
|
+
window.removeEventListener("pointerup", finishResize);
|
|
4315
|
+
window.removeEventListener("pointercancel", finishResize);
|
|
4316
|
+
resizeCleanupRef.current = null;
|
|
4317
|
+
};
|
|
4318
|
+
window.addEventListener("pointermove", handlePointerMove, { passive: false });
|
|
4319
|
+
window.addEventListener("pointerup", finishResize);
|
|
4320
|
+
window.addEventListener("pointercancel", finishResize);
|
|
4321
|
+
resizeCleanupRef.current = finishResize;
|
|
4322
|
+
}, []);
|
|
4323
|
+
const timelines = useSyncExternalStore7(subscribeGlobalTimelines, getTimelines, getTimelines);
|
|
4324
|
+
const dockVisible = useSyncExternalStore7(
|
|
4325
|
+
subscribeTimelineVisibility,
|
|
4326
|
+
getTimelineVisibility,
|
|
4327
|
+
getTimelineVisibility
|
|
4328
|
+
);
|
|
4329
|
+
if (!mounted || typeof window === "undefined" || timelines.length === 0) {
|
|
4330
|
+
return null;
|
|
4331
|
+
}
|
|
4332
|
+
return createPortal4(
|
|
4333
|
+
/* @__PURE__ */ jsxs14("div", { className: "dialkit-root dialkit-timeline", "data-theme": theme, hidden: !dockVisible, children: [
|
|
4334
|
+
/* @__PURE__ */ jsx18(
|
|
4335
|
+
"div",
|
|
4336
|
+
{
|
|
4337
|
+
className: "dialkit-timeline-resize-handle",
|
|
4338
|
+
onPointerDown: handleResizePointerDown,
|
|
4339
|
+
role: "separator",
|
|
4340
|
+
"aria-label": "Resize timeline height",
|
|
4341
|
+
"aria-orientation": "horizontal",
|
|
4342
|
+
title: "Drag to resize timeline"
|
|
4343
|
+
}
|
|
4344
|
+
),
|
|
4345
|
+
/* @__PURE__ */ jsx18(
|
|
4346
|
+
"div",
|
|
4347
|
+
{
|
|
4348
|
+
ref: dockRef,
|
|
4349
|
+
className: "dialkit-timeline-dock",
|
|
4350
|
+
style: { maxHeight: `min(${dockMaxHeight}px, calc(100vh - 24px))` },
|
|
4351
|
+
children: timelines.map((timeline) => /* @__PURE__ */ jsx18(
|
|
4352
|
+
TimelineSection,
|
|
4353
|
+
{
|
|
4354
|
+
meta: timeline,
|
|
4355
|
+
defaultOpen,
|
|
4356
|
+
theme,
|
|
4357
|
+
dockVisible
|
|
4358
|
+
},
|
|
4359
|
+
timeline.id
|
|
4360
|
+
))
|
|
4361
|
+
}
|
|
4362
|
+
)
|
|
4363
|
+
] }),
|
|
4364
|
+
document.body
|
|
4365
|
+
);
|
|
4366
|
+
}
|
|
4367
|
+
function useTransportSubscribe(id) {
|
|
4368
|
+
return useCallback14((callback) => TimelineStore.subscribe(id, callback), [id]);
|
|
4369
|
+
}
|
|
4370
|
+
function PlayPauseButton({ id }) {
|
|
4371
|
+
const subscribe = useTransportSubscribe(id);
|
|
4372
|
+
const getPlaying = useCallback14(() => TimelineStore.getTransport(id).playing, [id]);
|
|
4373
|
+
const playing = useSyncExternalStore7(subscribe, getPlaying, getPlaying);
|
|
4374
|
+
return /* @__PURE__ */ jsx18(
|
|
4375
|
+
motion7.button,
|
|
4376
|
+
{
|
|
4377
|
+
className: "dialkit-toolbar-add",
|
|
4378
|
+
onClick: () => playing ? TimelineStore.pause(id) : TimelineStore.play(id),
|
|
4379
|
+
title: playing ? "Pause" : "Play",
|
|
4380
|
+
"aria-label": playing ? "Pause" : "Play",
|
|
4381
|
+
whileTap: { scale: 0.9 },
|
|
4382
|
+
transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
|
|
4383
|
+
children: /* @__PURE__ */ jsx18("span", { style: { position: "relative", width: 16, height: 16 }, children: /* @__PURE__ */ jsx18(AnimatePresence5, { initial: false, mode: "wait", children: playing ? /* @__PURE__ */ jsx18(
|
|
4384
|
+
motion7.svg,
|
|
4385
|
+
{
|
|
4386
|
+
viewBox: "0 0 24 24",
|
|
4387
|
+
fill: "none",
|
|
4388
|
+
"aria-hidden": "true",
|
|
4389
|
+
style: { position: "absolute", inset: 0, width: 16, height: 16, color: "var(--dial-text-label)" },
|
|
4390
|
+
initial: { scale: 0.8, opacity: 0 },
|
|
4391
|
+
animate: { scale: 1, opacity: 1 },
|
|
4392
|
+
exit: { scale: 0.8, opacity: 0 },
|
|
4393
|
+
transition: { duration: 0.08 },
|
|
4394
|
+
children: ICON_PAUSE.map((d, i) => /* @__PURE__ */ jsx18("path", { d, fill: "currentColor" }, i))
|
|
4395
|
+
},
|
|
4396
|
+
"pause"
|
|
4397
|
+
) : /* @__PURE__ */ jsx18(
|
|
4398
|
+
motion7.svg,
|
|
4399
|
+
{
|
|
4400
|
+
viewBox: "0 0 24 24",
|
|
4401
|
+
fill: "none",
|
|
4402
|
+
"aria-hidden": "true",
|
|
4403
|
+
style: { position: "absolute", inset: 0, width: 16, height: 16, color: "var(--dial-text-label)" },
|
|
4404
|
+
initial: { scale: 0.8, opacity: 0 },
|
|
4405
|
+
animate: { scale: 1, opacity: 1 },
|
|
4406
|
+
exit: { scale: 0.8, opacity: 0 },
|
|
4407
|
+
transition: { duration: 0.08 },
|
|
4408
|
+
children: /* @__PURE__ */ jsx18("path", { d: ICON_PLAY, fill: "currentColor" })
|
|
4409
|
+
},
|
|
4410
|
+
"play"
|
|
4411
|
+
) }) })
|
|
4412
|
+
}
|
|
4413
|
+
);
|
|
4414
|
+
}
|
|
4415
|
+
function ReplayButton({ onReplay }) {
|
|
4416
|
+
return /* @__PURE__ */ jsx18(
|
|
4417
|
+
motion7.button,
|
|
4418
|
+
{
|
|
4419
|
+
className: "dialkit-toolbar-add",
|
|
4420
|
+
onClick: onReplay,
|
|
4421
|
+
title: "Replay",
|
|
4422
|
+
"aria-label": "Replay",
|
|
4423
|
+
whileTap: { scale: 0.9 },
|
|
4424
|
+
transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
|
|
4425
|
+
children: /* @__PURE__ */ jsx18("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: ICON_REPLAY.map((d, i) => /* @__PURE__ */ jsx18("path", { d, fill: "currentColor" }, i)) })
|
|
4426
|
+
}
|
|
4427
|
+
);
|
|
4428
|
+
}
|
|
4429
|
+
function TimelinePlayheadFlag({
|
|
4430
|
+
id,
|
|
4431
|
+
duration,
|
|
4432
|
+
pxPerSecond,
|
|
4433
|
+
viewStart,
|
|
4434
|
+
viewEnd,
|
|
4435
|
+
laneWidth,
|
|
4436
|
+
rulerRef,
|
|
4437
|
+
onResetView
|
|
4438
|
+
}) {
|
|
4439
|
+
const subscribe = useTransportSubscribe(id);
|
|
4440
|
+
const getTime = useCallback14(() => TimelineStore.getTransport(id).time, [id]);
|
|
4441
|
+
const time = useSyncExternalStore7(subscribe, getTime, getTime);
|
|
4442
|
+
const scrubRef = useRef14(null);
|
|
4443
|
+
const cleanupScrubRef = useRef14(null);
|
|
4444
|
+
const seekFromClientX = useCallback14((clientX) => {
|
|
4445
|
+
const rect = scrubRef.current?.rect;
|
|
4446
|
+
const scrub = scrubRef.current;
|
|
4447
|
+
const contentWidth = rect?.width ?? 0;
|
|
4448
|
+
if (!rect || !scrub || contentWidth <= 0) return;
|
|
4449
|
+
const nextTime = clamp(
|
|
4450
|
+
scrub.viewStart + (clientX - rect.left) / contentWidth * (scrub.viewEnd - scrub.viewStart),
|
|
4451
|
+
scrub.viewStart,
|
|
4452
|
+
scrub.viewEnd
|
|
4453
|
+
);
|
|
4454
|
+
TimelineStore.seek(id, nextTime);
|
|
4455
|
+
}, [id]);
|
|
4456
|
+
const handlePointerDown = useCallback14((e) => {
|
|
4457
|
+
const rect = rulerRef.current?.getBoundingClientRect();
|
|
4458
|
+
if (!rect) return;
|
|
4459
|
+
e.preventDefault();
|
|
4460
|
+
e.stopPropagation();
|
|
4461
|
+
cleanupScrubRef.current?.();
|
|
4462
|
+
const resetView = e.shiftKey;
|
|
4463
|
+
scrubRef.current = {
|
|
4464
|
+
wasPlaying: TimelineStore.getTransport(id).playing,
|
|
4465
|
+
rect,
|
|
4466
|
+
viewStart: resetView ? 0 : viewStart,
|
|
4467
|
+
viewEnd: resetView ? duration : viewEnd
|
|
4468
|
+
};
|
|
4469
|
+
if (resetView) onResetView();
|
|
4470
|
+
TimelineStore.pause(id);
|
|
4471
|
+
seekFromClientX(e.clientX);
|
|
4472
|
+
const handleWindowPointerMove = (event) => {
|
|
4473
|
+
event.preventDefault();
|
|
4474
|
+
seekFromClientX(event.clientX);
|
|
4475
|
+
};
|
|
4476
|
+
const finishWindowScrub = () => {
|
|
4477
|
+
window.removeEventListener("pointermove", handleWindowPointerMove);
|
|
4478
|
+
window.removeEventListener("pointerup", finishWindowScrub);
|
|
4479
|
+
window.removeEventListener("pointercancel", finishWindowScrub);
|
|
4480
|
+
if (scrubRef.current?.wasPlaying) TimelineStore.play(id);
|
|
4481
|
+
scrubRef.current = null;
|
|
4482
|
+
cleanupScrubRef.current = null;
|
|
4483
|
+
};
|
|
4484
|
+
window.addEventListener("pointermove", handleWindowPointerMove, { passive: false });
|
|
4485
|
+
window.addEventListener("pointerup", finishWindowScrub);
|
|
4486
|
+
window.addEventListener("pointercancel", finishWindowScrub);
|
|
4487
|
+
cleanupScrubRef.current = finishWindowScrub;
|
|
4488
|
+
}, [duration, id, onResetView, rulerRef, seekFromClientX, viewEnd, viewStart]);
|
|
4489
|
+
useEffect11(() => () => cleanupScrubRef.current?.(), []);
|
|
4490
|
+
if (time < viewStart || time > viewEnd || laneWidth <= 0) return null;
|
|
4491
|
+
const x = clamp(
|
|
4492
|
+
(time - viewStart) * pxPerSecond,
|
|
4493
|
+
0,
|
|
4494
|
+
laneWidth
|
|
4495
|
+
);
|
|
4496
|
+
const flagCenter = clamp(
|
|
4497
|
+
x,
|
|
4498
|
+
PLAYHEAD_FLAG_WIDTH / 2 - PLAYHEAD_FLAG_EDGE_OVERHANG,
|
|
4499
|
+
laneWidth - PLAYHEAD_FLAG_WIDTH / 2 + PLAYHEAD_FLAG_EDGE_OVERHANG
|
|
4500
|
+
);
|
|
4501
|
+
const flagOffset = flagCenter - x;
|
|
4502
|
+
const edge = flagOffset > 0.5 ? "start" : flagOffset < -0.5 ? "end" : "center";
|
|
4503
|
+
return /* @__PURE__ */ jsxs14(
|
|
4504
|
+
"div",
|
|
4505
|
+
{
|
|
4506
|
+
className: "dialkit-timeline-playhead-control",
|
|
4507
|
+
"data-edge": edge,
|
|
4508
|
+
style: {
|
|
4509
|
+
left: `calc(var(--dial-timeline-label-w) + ${x}px)`,
|
|
4510
|
+
"--dial-timeline-playhead-flag-offset": `${flagOffset}px`
|
|
4511
|
+
},
|
|
4512
|
+
onPointerDown: handlePointerDown,
|
|
4513
|
+
role: "slider",
|
|
4514
|
+
"aria-label": "Timeline current time",
|
|
4515
|
+
"aria-valuemin": 0,
|
|
4516
|
+
"aria-valuemax": duration,
|
|
4517
|
+
"aria-valuenow": time,
|
|
4518
|
+
title: "Drag to scrub the timeline",
|
|
4519
|
+
children: [
|
|
4520
|
+
/* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-playhead-stem" }),
|
|
4521
|
+
/* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-playhead-anchor", children: /* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-playhead-flag", children: time.toFixed(2) }) })
|
|
4522
|
+
]
|
|
4523
|
+
}
|
|
4524
|
+
);
|
|
4525
|
+
}
|
|
4526
|
+
function TimelineOverview({
|
|
4527
|
+
id,
|
|
4528
|
+
duration,
|
|
4529
|
+
viewStart,
|
|
4530
|
+
viewEnd,
|
|
4531
|
+
onNavigate
|
|
4532
|
+
}) {
|
|
4533
|
+
const subscribe = useTransportSubscribe(id);
|
|
4534
|
+
const getTime = useCallback14(() => TimelineStore.getTransport(id).time, [id]);
|
|
4535
|
+
const time = useSyncExternalStore7(subscribe, getTime, getTime);
|
|
4536
|
+
const scrubRef = useRef14(null);
|
|
4537
|
+
const seekFromClientX = useCallback14((clientX) => {
|
|
4538
|
+
const rect = scrubRef.current?.rect;
|
|
4539
|
+
if (!rect || rect.width <= 0 || duration <= 0) return;
|
|
4540
|
+
const nextTime = clamp((clientX - rect.left) / rect.width * duration, 0, duration);
|
|
4541
|
+
TimelineStore.seek(id, nextTime);
|
|
4542
|
+
onNavigate(nextTime);
|
|
4543
|
+
}, [duration, id, onNavigate]);
|
|
4544
|
+
const handlePointerDown = useCallback14((e) => {
|
|
4545
|
+
e.preventDefault();
|
|
4546
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
4547
|
+
scrubRef.current = {
|
|
4548
|
+
wasPlaying: TimelineStore.getTransport(id).playing,
|
|
4549
|
+
rect: e.currentTarget.getBoundingClientRect()
|
|
4550
|
+
};
|
|
4551
|
+
TimelineStore.pause(id);
|
|
4552
|
+
seekFromClientX(e.clientX);
|
|
4553
|
+
}, [id, seekFromClientX]);
|
|
4554
|
+
const handlePointerMove = useCallback14((e) => {
|
|
4555
|
+
if (scrubRef.current) seekFromClientX(e.clientX);
|
|
4556
|
+
}, [seekFromClientX]);
|
|
4557
|
+
const finishScrub = useCallback14(() => {
|
|
4558
|
+
if (scrubRef.current?.wasPlaying) TimelineStore.play(id);
|
|
4559
|
+
scrubRef.current = null;
|
|
4560
|
+
}, [id]);
|
|
4561
|
+
const viewportLeft = duration > 0 ? viewStart / duration * 100 : 0;
|
|
4562
|
+
const viewportWidth = duration > 0 ? (viewEnd - viewStart) / duration * 100 : 100;
|
|
4563
|
+
const playheadLeft = duration > 0 ? time / duration * 100 : 0;
|
|
4564
|
+
return /* @__PURE__ */ jsxs14(
|
|
4565
|
+
"div",
|
|
4566
|
+
{
|
|
4567
|
+
className: "dialkit-timeline-overview",
|
|
4568
|
+
onPointerDown: handlePointerDown,
|
|
4569
|
+
onPointerMove: handlePointerMove,
|
|
4570
|
+
onPointerUp: finishScrub,
|
|
4571
|
+
onPointerCancel: finishScrub,
|
|
4572
|
+
onLostPointerCapture: finishScrub,
|
|
4573
|
+
title: "Drag to scrub the full timeline",
|
|
4574
|
+
children: [
|
|
4575
|
+
/* @__PURE__ */ jsx18(
|
|
4576
|
+
"div",
|
|
4577
|
+
{
|
|
4578
|
+
className: "dialkit-timeline-overview-viewport",
|
|
4579
|
+
"data-zoomed": viewportWidth < 99.999 || void 0,
|
|
4580
|
+
style: { left: `${viewportLeft}%`, width: `${viewportWidth}%` }
|
|
4581
|
+
}
|
|
4582
|
+
),
|
|
4583
|
+
/* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-overview-progress", style: { width: `${playheadLeft}%` } }),
|
|
4584
|
+
/* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-overview-playhead", style: { left: `${playheadLeft}%` } })
|
|
4585
|
+
]
|
|
4586
|
+
}
|
|
4587
|
+
);
|
|
4588
|
+
}
|
|
4589
|
+
function clampViewStart(start, duration, visibleDuration) {
|
|
4590
|
+
return clamp(start, 0, Math.max(0, duration - visibleDuration));
|
|
4591
|
+
}
|
|
4592
|
+
function formatRulerSeconds(time, step) {
|
|
4593
|
+
if (step >= 1 && Number.isInteger(time)) return formatClock(time);
|
|
4594
|
+
const decimals = Math.min(3, Math.max(1, Math.ceil(-Math.log10(step))));
|
|
4595
|
+
return `${time.toFixed(decimals)}s`;
|
|
4596
|
+
}
|
|
4597
|
+
var TimelineSection = memo(function TimelineSection2({
|
|
4598
|
+
meta,
|
|
4599
|
+
defaultOpen,
|
|
4600
|
+
theme,
|
|
4601
|
+
dockVisible
|
|
4602
|
+
}) {
|
|
4603
|
+
const [open, setOpen] = useState11(defaultOpen);
|
|
4604
|
+
const [copied, setCopied] = useState11(false);
|
|
4605
|
+
const [popover, setPopover] = useState11(null);
|
|
4606
|
+
const [collapsedGroups, setCollapsedGroups] = useState11(() => /* @__PURE__ */ new Set());
|
|
4607
|
+
const [expandedTracks, setExpandedTracks] = useState11(() => /* @__PURE__ */ new Set());
|
|
4608
|
+
const [zoom, setZoom] = useState11(1);
|
|
4609
|
+
const [viewStart, setViewStart] = useState11(0);
|
|
4610
|
+
const subscribeValues = useCallback14(
|
|
4611
|
+
(callback) => DialStore.subscribe(meta.id, callback),
|
|
4612
|
+
[meta.id]
|
|
4613
|
+
);
|
|
4614
|
+
const getValues = useCallback14(() => DialStore.getValues(meta.id), [meta.id]);
|
|
4615
|
+
const values = useSyncExternalStore7(subscribeValues, getValues, getValues);
|
|
4616
|
+
const presets = DialStore.getPresets(meta.id);
|
|
4617
|
+
const activePresetId = DialStore.getActivePresetId(meta.id);
|
|
4618
|
+
const laneAreaRef = useRef14(null);
|
|
4619
|
+
const horizontalScrollRef = useRef14(null);
|
|
4620
|
+
const [laneWidth, setLaneWidth] = useState11(0);
|
|
4621
|
+
useLayoutEffect2(() => {
|
|
4622
|
+
if (!open) return;
|
|
4623
|
+
const ruler = laneAreaRef.current;
|
|
4624
|
+
if (!ruler) return;
|
|
4625
|
+
const measure = () => {
|
|
4626
|
+
setLaneWidth(ruler.getBoundingClientRect().width);
|
|
4627
|
+
};
|
|
4628
|
+
measure();
|
|
4629
|
+
const observer = new ResizeObserver(measure);
|
|
4630
|
+
observer.observe(ruler);
|
|
4631
|
+
return () => observer.disconnect();
|
|
4632
|
+
}, [open]);
|
|
4633
|
+
const visibleDuration = meta.duration > 0 ? meta.duration / zoom : meta.duration;
|
|
4634
|
+
const safeViewStart = clampViewStart(viewStart, meta.duration, visibleDuration);
|
|
4635
|
+
const viewEnd = safeViewStart + visibleDuration;
|
|
4636
|
+
const pxPerSecond = visibleDuration > 0 && laneWidth > 0 ? laneWidth / visibleDuration : 0;
|
|
4637
|
+
const millisecondReadableZoom = laneWidth > 0 && meta.duration > 0 ? MAJOR_TICK_TARGET_PX * meta.duration / (MILLISECOND_STEP * 10 * laneWidth) : MIN_TIMELINE_MAX_ZOOM;
|
|
4638
|
+
const maxZoom = Math.max(MIN_TIMELINE_MAX_ZOOM, millisecondReadableZoom);
|
|
4639
|
+
useEffect11(() => {
|
|
4640
|
+
setZoom((current) => clamp(current, 1, maxZoom));
|
|
4641
|
+
}, [maxZoom]);
|
|
4642
|
+
useEffect11(() => {
|
|
4643
|
+
setViewStart((current) => clampViewStart(current, meta.duration, meta.duration / zoom));
|
|
4644
|
+
}, [meta.duration, zoom]);
|
|
4645
|
+
useLayoutEffect2(() => {
|
|
4646
|
+
const scroller = horizontalScrollRef.current;
|
|
4647
|
+
if (!scroller || pxPerSecond <= 0) return;
|
|
4648
|
+
const nextScrollLeft = safeViewStart * pxPerSecond;
|
|
4649
|
+
if (Math.abs(scroller.scrollLeft - nextScrollLeft) > 0.5) {
|
|
4650
|
+
scroller.scrollLeft = nextScrollLeft;
|
|
4651
|
+
}
|
|
4652
|
+
}, [open, pxPerSecond, safeViewStart]);
|
|
4653
|
+
useEffect11(() => {
|
|
4654
|
+
if (!dockVisible) setPopover(null);
|
|
4655
|
+
}, [dockVisible]);
|
|
4656
|
+
const centerViewAt = useCallback14((time) => {
|
|
4657
|
+
if (zoom <= 1 || meta.duration <= 0) return;
|
|
4658
|
+
const windowDuration = meta.duration / zoom;
|
|
4659
|
+
setViewStart(clampViewStart(time - windowDuration / 2, meta.duration, windowDuration));
|
|
4660
|
+
}, [meta.duration, zoom]);
|
|
4661
|
+
const resetView = useCallback14(() => {
|
|
4662
|
+
setZoom(1);
|
|
4663
|
+
setViewStart(0);
|
|
4664
|
+
}, []);
|
|
4665
|
+
const handleReplay = useCallback14(() => {
|
|
4666
|
+
setViewStart(0);
|
|
4667
|
+
TimelineStore.replay(meta.id);
|
|
4668
|
+
}, [meta.id]);
|
|
4669
|
+
const handleHorizontalScroll = useCallback14((e) => {
|
|
4670
|
+
if (pxPerSecond <= 0) return;
|
|
4671
|
+
setViewStart(clampViewStart(
|
|
4672
|
+
e.currentTarget.scrollLeft / pxPerSecond,
|
|
4673
|
+
meta.duration,
|
|
4674
|
+
visibleDuration
|
|
4675
|
+
));
|
|
4676
|
+
}, [meta.duration, pxPerSecond, visibleDuration]);
|
|
4677
|
+
const handleTimelineWheel = useCallback14((e) => {
|
|
4678
|
+
const scroller = horizontalScrollRef.current;
|
|
4679
|
+
if (!scroller || zoom <= 1) return;
|
|
4680
|
+
const horizontalDelta = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.shiftKey ? e.deltaY : 0;
|
|
4681
|
+
if (horizontalDelta === 0) return;
|
|
4682
|
+
e.preventDefault();
|
|
4683
|
+
scroller.scrollLeft += horizontalDelta;
|
|
4684
|
+
}, [zoom]);
|
|
4685
|
+
const zoomDragRef = useRef14(null);
|
|
4686
|
+
const rulerScrubRef = useRef14(null);
|
|
4687
|
+
const seekRulerFromClientX = useCallback14((clientX) => {
|
|
4688
|
+
const scrub = rulerScrubRef.current;
|
|
4689
|
+
const contentWidth = scrub?.rect.width ?? 0;
|
|
4690
|
+
if (!scrub || contentWidth <= 0) return;
|
|
4691
|
+
TimelineStore.seek(
|
|
4692
|
+
meta.id,
|
|
4693
|
+
clamp(
|
|
4694
|
+
scrub.viewStart + (clientX - scrub.rect.left) / contentWidth * scrub.visibleDuration,
|
|
4695
|
+
scrub.viewStart,
|
|
4696
|
+
scrub.viewStart + scrub.visibleDuration
|
|
4697
|
+
)
|
|
4698
|
+
);
|
|
4699
|
+
}, [meta.id]);
|
|
4700
|
+
const handleRulerPointerDown = useCallback14((e) => {
|
|
4701
|
+
e.preventDefault();
|
|
4702
|
+
e.stopPropagation();
|
|
4703
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
4704
|
+
const contentWidth = rect.width;
|
|
4705
|
+
if (contentWidth <= 0) return;
|
|
4706
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
4707
|
+
if (!e.altKey) {
|
|
4708
|
+
const resetView2 = e.shiftKey;
|
|
4709
|
+
rulerScrubRef.current = {
|
|
4710
|
+
wasPlaying: TimelineStore.getTransport(meta.id).playing,
|
|
4711
|
+
rect,
|
|
4712
|
+
viewStart: resetView2 ? 0 : safeViewStart,
|
|
4713
|
+
visibleDuration: resetView2 ? meta.duration : visibleDuration
|
|
4714
|
+
};
|
|
4715
|
+
if (resetView2) {
|
|
4716
|
+
setZoom(1);
|
|
4717
|
+
setViewStart(0);
|
|
4718
|
+
}
|
|
4719
|
+
TimelineStore.pause(meta.id);
|
|
4720
|
+
seekRulerFromClientX(e.clientX);
|
|
4721
|
+
return;
|
|
4722
|
+
}
|
|
4723
|
+
const anchorRatio = clamp((e.clientX - rect.left) / contentWidth, 0, 1);
|
|
4724
|
+
zoomDragRef.current = {
|
|
4725
|
+
pointerX: e.clientX,
|
|
4726
|
+
rect,
|
|
4727
|
+
zoom,
|
|
4728
|
+
viewStart: safeViewStart,
|
|
4729
|
+
anchorRatio,
|
|
4730
|
+
anchorTime: safeViewStart + anchorRatio * visibleDuration,
|
|
4731
|
+
moved: false
|
|
4732
|
+
};
|
|
4733
|
+
}, [meta.duration, meta.id, safeViewStart, seekRulerFromClientX, visibleDuration, zoom]);
|
|
4734
|
+
const handleRulerPointerMove = useCallback14((e) => {
|
|
4735
|
+
if (rulerScrubRef.current) {
|
|
4736
|
+
seekRulerFromClientX(e.clientX);
|
|
4737
|
+
return;
|
|
4738
|
+
}
|
|
4739
|
+
const drag = zoomDragRef.current;
|
|
4740
|
+
if (!drag || meta.duration <= 0) return;
|
|
4741
|
+
const dx = e.clientX - drag.pointerX;
|
|
4742
|
+
if (!drag.moved && Math.abs(dx) <= DRAG_THRESHOLD_PX) return;
|
|
4743
|
+
drag.moved = true;
|
|
4744
|
+
const nextZoom = clamp(drag.zoom * Math.exp(dx / ZOOM_DRAG_DISTANCE), 1, maxZoom);
|
|
4745
|
+
const nextVisibleDuration = meta.duration / nextZoom;
|
|
4746
|
+
const nextStart = clampViewStart(
|
|
4747
|
+
drag.anchorTime - drag.anchorRatio * nextVisibleDuration,
|
|
4748
|
+
meta.duration,
|
|
4749
|
+
nextVisibleDuration
|
|
4750
|
+
);
|
|
4751
|
+
setZoom(nextZoom);
|
|
4752
|
+
setViewStart(nextStart);
|
|
4753
|
+
}, [maxZoom, meta.duration, seekRulerFromClientX]);
|
|
4754
|
+
const handleRulerPointerUp = useCallback14(() => {
|
|
4755
|
+
if (rulerScrubRef.current?.wasPlaying) TimelineStore.play(meta.id);
|
|
4756
|
+
rulerScrubRef.current = null;
|
|
4757
|
+
zoomDragRef.current = null;
|
|
4758
|
+
}, [meta.id]);
|
|
4759
|
+
const handleRulerPointerCancel = useCallback14(() => {
|
|
4760
|
+
if (rulerScrubRef.current?.wasPlaying) TimelineStore.play(meta.id);
|
|
4761
|
+
rulerScrubRef.current = null;
|
|
4762
|
+
zoomDragRef.current = null;
|
|
4763
|
+
}, [meta.id]);
|
|
4764
|
+
const trackScrubRef = useRef14(null);
|
|
4765
|
+
const seekTrackFromClientX = useCallback14((clientX) => {
|
|
4766
|
+
const scrub = trackScrubRef.current;
|
|
4767
|
+
const contentWidth = scrub?.rect.width ?? 0;
|
|
4768
|
+
if (!scrub || contentWidth <= 0) return;
|
|
4769
|
+
const nextTime = clamp(
|
|
4770
|
+
scrub.viewStart + (clientX - scrub.rect.left) / contentWidth * scrub.visibleDuration,
|
|
4771
|
+
scrub.viewStart,
|
|
4772
|
+
scrub.viewStart + scrub.visibleDuration
|
|
4773
|
+
);
|
|
4774
|
+
TimelineStore.seek(meta.id, nextTime);
|
|
4775
|
+
}, [meta.id]);
|
|
4776
|
+
const handleTrackPointerDown = useCallback14((e) => {
|
|
4777
|
+
const target = e.target;
|
|
4778
|
+
if (target.closest(".dialkit-timeline-label, button")) return;
|
|
4779
|
+
if (!e.shiftKey && target.closest(".dialkit-timeline-clip")) return;
|
|
4780
|
+
const rect = laneAreaRef.current?.getBoundingClientRect();
|
|
4781
|
+
if (!rect) return;
|
|
4782
|
+
e.preventDefault();
|
|
4783
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
4784
|
+
const resetView2 = e.shiftKey;
|
|
4785
|
+
trackScrubRef.current = {
|
|
4786
|
+
wasPlaying: TimelineStore.getTransport(meta.id).playing,
|
|
4787
|
+
rect,
|
|
4788
|
+
viewStart: resetView2 ? 0 : safeViewStart,
|
|
4789
|
+
visibleDuration: resetView2 ? meta.duration : visibleDuration
|
|
4790
|
+
};
|
|
4791
|
+
if (resetView2) {
|
|
4792
|
+
setZoom(1);
|
|
4793
|
+
setViewStart(0);
|
|
4794
|
+
}
|
|
4795
|
+
setPopover(null);
|
|
4796
|
+
TimelineStore.pause(meta.id);
|
|
4797
|
+
seekTrackFromClientX(e.clientX);
|
|
4798
|
+
}, [meta.duration, meta.id, safeViewStart, seekTrackFromClientX, visibleDuration]);
|
|
4799
|
+
const handleTrackPointerMove = useCallback14((e) => {
|
|
4800
|
+
if (trackScrubRef.current) seekTrackFromClientX(e.clientX);
|
|
4801
|
+
}, [seekTrackFromClientX]);
|
|
4802
|
+
const finishTrackScrub = useCallback14(() => {
|
|
4803
|
+
if (trackScrubRef.current?.wasPlaying) TimelineStore.play(meta.id);
|
|
4804
|
+
trackScrubRef.current = null;
|
|
4805
|
+
}, [meta.id]);
|
|
4806
|
+
const handleCopy = useCallback14(() => {
|
|
4807
|
+
const normalized = normalizeTimelineValuesForCopy(DialStore.getValues(meta.id), meta.clips);
|
|
4808
|
+
navigator.clipboard.writeText(buildCopyInstruction("useDialTimeline", meta.name, normalized));
|
|
4809
|
+
setCopied(true);
|
|
4810
|
+
setTimeout(() => setCopied(false), 1500);
|
|
4811
|
+
}, [meta.clips, meta.id, meta.name]);
|
|
4812
|
+
const handleAddPreset = useCallback14(() => {
|
|
4813
|
+
DialStore.savePreset(meta.id, `Version ${presets.length + 2}`);
|
|
4814
|
+
}, [meta.id, presets.length]);
|
|
4815
|
+
const closePopover = useCallback14(() => setPopover(null), []);
|
|
4816
|
+
const openClipPopover = useCallback14(
|
|
4817
|
+
(clip, rect, stepKey) => {
|
|
4818
|
+
const targetPath = stepKey ? `${clip.key}.${stepKey}` : clip.key;
|
|
4819
|
+
const exclude = stepKey ? void 0 : clipPopoverExclusions(clip);
|
|
4820
|
+
if (getClipControls(meta.id, targetPath, exclude).length === 0) return;
|
|
4821
|
+
setPopover(
|
|
4822
|
+
(prev) => prev?.clip.key === clip.key && prev?.stepKey === stepKey ? null : {
|
|
4823
|
+
clip,
|
|
4824
|
+
stepKey,
|
|
4825
|
+
anchor: {
|
|
4826
|
+
left: rect.left,
|
|
4827
|
+
top: rect.top,
|
|
4828
|
+
right: rect.right,
|
|
4829
|
+
bottom: rect.bottom,
|
|
4830
|
+
width: rect.width,
|
|
4831
|
+
height: rect.height
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4834
|
+
);
|
|
4835
|
+
},
|
|
4836
|
+
[meta.id]
|
|
4837
|
+
);
|
|
4838
|
+
const toggleTracks = useCallback14((clipKey) => {
|
|
4839
|
+
setExpandedTracks((prev) => {
|
|
4840
|
+
const next = new Set(prev);
|
|
4841
|
+
if (next.has(clipKey)) next.delete(clipKey);
|
|
4842
|
+
else next.add(clipKey);
|
|
4843
|
+
return next;
|
|
4844
|
+
});
|
|
4845
|
+
}, []);
|
|
4846
|
+
const handleBarClick = useCallback14(
|
|
4847
|
+
(clip, rect, stepKey) => {
|
|
4848
|
+
if (!stepKey && clip.tracks?.length) {
|
|
4849
|
+
toggleTracks(clip.key);
|
|
4850
|
+
return;
|
|
4851
|
+
}
|
|
4852
|
+
openClipPopover(clip, rect, stepKey);
|
|
4853
|
+
},
|
|
4854
|
+
[openClipPopover, toggleTracks]
|
|
4855
|
+
);
|
|
4856
|
+
const toggleGroup = useCallback14((group) => {
|
|
4857
|
+
setCollapsedGroups((prev) => {
|
|
4858
|
+
const next = new Set(prev);
|
|
4859
|
+
if (next.has(group)) next.delete(group);
|
|
4860
|
+
else next.add(group);
|
|
4861
|
+
return next;
|
|
4862
|
+
});
|
|
4863
|
+
}, []);
|
|
4864
|
+
const rawStep = pxPerSecond > 0 ? MAJOR_TICK_TARGET_PX / pxPerSecond : 1;
|
|
4865
|
+
const adaptiveMajorStep = SECOND_TICK_STEPS.find((step) => step >= rawStep) ?? SECOND_TICK_STEPS[SECOND_TICK_STEPS.length - 1];
|
|
4866
|
+
const majorStep = zoom < 1.5 && meta.duration >= 1 ? Math.max(1, adaptiveMajorStep) : adaptiveMajorStep;
|
|
4867
|
+
const fineTickStep = majorStep / 10;
|
|
4868
|
+
const majorTicks = [];
|
|
4869
|
+
const mediumTicks = [];
|
|
4870
|
+
const fineTicks = [];
|
|
4871
|
+
const firstMajorTick = Math.ceil((safeViewStart - 1e-6) / majorStep) * majorStep;
|
|
4872
|
+
for (let t = firstMajorTick; t <= viewEnd + 1e-6; t += majorStep) {
|
|
4873
|
+
majorTicks.push(Number(t.toFixed(4)));
|
|
4874
|
+
}
|
|
4875
|
+
const firstFineIndex = Math.ceil((safeViewStart - 1e-6) / fineTickStep);
|
|
4876
|
+
const lastFineIndex = Math.floor((viewEnd + 1e-6) / fineTickStep);
|
|
4877
|
+
for (let index = firstFineIndex; index <= lastFineIndex; index++) {
|
|
4878
|
+
if (index % 10 === 0) continue;
|
|
4879
|
+
const tick = Number((index * fineTickStep).toFixed(6));
|
|
4880
|
+
if (index % 5 === 0) mediumTicks.push(tick);
|
|
4881
|
+
else fineTicks.push(tick);
|
|
4882
|
+
}
|
|
4883
|
+
const rows = [];
|
|
4884
|
+
let lastGroup;
|
|
4885
|
+
for (const clip of meta.clips) {
|
|
4886
|
+
if (clip.group !== lastGroup) {
|
|
4887
|
+
lastGroup = clip.group;
|
|
4888
|
+
if (clip.group) {
|
|
4889
|
+
const group = clip.group;
|
|
4890
|
+
const isCollapsed = collapsedGroups.has(group);
|
|
4891
|
+
rows.push(
|
|
4892
|
+
/* @__PURE__ */ jsxs14("div", { className: "dialkit-timeline-row dialkit-timeline-group-row", children: [
|
|
4893
|
+
/* @__PURE__ */ jsxs14("div", { className: "dialkit-timeline-label", children: [
|
|
4894
|
+
/* @__PURE__ */ jsx18(
|
|
4895
|
+
"button",
|
|
4896
|
+
{
|
|
4897
|
+
className: "dialkit-timeline-group-toggle",
|
|
4898
|
+
"data-open": !isCollapsed,
|
|
4899
|
+
onClick: () => toggleGroup(group),
|
|
4900
|
+
title: isCollapsed ? "Expand layer" : "Collapse layer",
|
|
4901
|
+
children: /* @__PURE__ */ jsx18("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx18("path", { d: ICON_CHEVRON }) })
|
|
4902
|
+
}
|
|
4903
|
+
),
|
|
4904
|
+
/* @__PURE__ */ jsx18("span", { children: formatLabel(group) })
|
|
4905
|
+
] }),
|
|
4906
|
+
/* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-lane" })
|
|
4907
|
+
] }, `group:${group}`)
|
|
4908
|
+
);
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
if (clip.group && collapsedGroups.has(clip.group)) continue;
|
|
4912
|
+
const isProps = Boolean(clip.tracks?.length);
|
|
4913
|
+
const tracksOpen = isProps && expandedTracks.has(clip.key);
|
|
4914
|
+
const stat = computeClipStaticFromValues(values, clip, meta.duration);
|
|
4915
|
+
rows.push(
|
|
4916
|
+
/* @__PURE__ */ jsxs14("div", { className: "dialkit-timeline-row", "data-grouped": clip.group ? "" : void 0, children: [
|
|
4917
|
+
/* @__PURE__ */ jsxs14("div", { className: "dialkit-timeline-label", children: [
|
|
4918
|
+
isProps ? /* @__PURE__ */ jsx18(
|
|
4919
|
+
"button",
|
|
4920
|
+
{
|
|
4921
|
+
className: "dialkit-timeline-group-toggle",
|
|
4922
|
+
"data-open": tracksOpen,
|
|
4923
|
+
onClick: (e) => {
|
|
4924
|
+
e.stopPropagation();
|
|
4925
|
+
toggleTracks(clip.key);
|
|
4926
|
+
},
|
|
4927
|
+
title: tracksOpen ? "Collapse properties" : "Expand properties",
|
|
4928
|
+
children: /* @__PURE__ */ jsx18("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx18("path", { d: ICON_CHEVRON }) })
|
|
4929
|
+
}
|
|
4930
|
+
) : null,
|
|
4931
|
+
clip.label
|
|
4932
|
+
] }),
|
|
4933
|
+
/* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-lane", children: /* @__PURE__ */ jsx18(
|
|
4934
|
+
TimelineClip,
|
|
4935
|
+
{
|
|
4936
|
+
timelineId: meta.id,
|
|
4937
|
+
clip,
|
|
4938
|
+
at: stat.at,
|
|
4939
|
+
duration: stat.duration,
|
|
4940
|
+
loop: stat.loop,
|
|
4941
|
+
steps: clip.stepKeys?.length ? stat.tracks[0]?.steps : void 0,
|
|
4942
|
+
fixedDuration: isProps ? true : stat.isPhysics,
|
|
4943
|
+
composite: isProps,
|
|
4944
|
+
pxPerSecond,
|
|
4945
|
+
viewStart: safeViewStart,
|
|
4946
|
+
timelineDuration: meta.duration,
|
|
4947
|
+
selected: popover?.clip.key === clip.key,
|
|
4948
|
+
selectedStepKey: popover?.clip.key === clip.key ? popover.stepKey : void 0,
|
|
4949
|
+
onClick: handleBarClick,
|
|
4950
|
+
onDrag: closePopover
|
|
4951
|
+
}
|
|
4952
|
+
) })
|
|
4953
|
+
] }, clip.key)
|
|
4954
|
+
);
|
|
4955
|
+
if (tracksOpen) {
|
|
4956
|
+
for (const trackRef of clip.tracks ?? []) {
|
|
4957
|
+
const track = stat.tracks.find((candidate) => candidate.prop === trackRef.prop);
|
|
4958
|
+
if (!track) continue;
|
|
4959
|
+
const trackKey = `${clip.key}.${trackRef.prop}`;
|
|
4960
|
+
const trackMeta = {
|
|
4961
|
+
key: trackKey,
|
|
4962
|
+
label: `${clip.label} \xB7 ${formatLabel(trackRef.prop)}`,
|
|
4963
|
+
color: clip.color,
|
|
4964
|
+
loop: clip.loop,
|
|
4965
|
+
group: clip.group,
|
|
4966
|
+
stepKeys: trackRef.stepKeys
|
|
4967
|
+
};
|
|
4968
|
+
rows.push(
|
|
4969
|
+
/* @__PURE__ */ jsxs14(
|
|
4970
|
+
"div",
|
|
4971
|
+
{
|
|
4972
|
+
className: "dialkit-timeline-row dialkit-timeline-track-row",
|
|
4973
|
+
"data-grouped": clip.group ? "" : void 0,
|
|
4974
|
+
children: [
|
|
4975
|
+
/* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-label", children: formatLabel(trackRef.prop) }),
|
|
4976
|
+
/* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-lane", children: /* @__PURE__ */ jsx18(
|
|
4977
|
+
TimelineClip,
|
|
4978
|
+
{
|
|
4979
|
+
timelineId: meta.id,
|
|
4980
|
+
clip: trackMeta,
|
|
4981
|
+
at: stat.at + track.delay,
|
|
4982
|
+
duration: track.duration,
|
|
4983
|
+
loop: stat.loop,
|
|
4984
|
+
steps: trackRef.stepKeys?.length ? track.steps : void 0,
|
|
4985
|
+
fixedDuration: !trackRef.stepKeys?.length && track.steps[0]?.isPhysics === true,
|
|
4986
|
+
baseAt: stat.at,
|
|
4987
|
+
delayMode: true,
|
|
4988
|
+
pxPerSecond,
|
|
4989
|
+
viewStart: safeViewStart,
|
|
4990
|
+
timelineDuration: meta.duration,
|
|
4991
|
+
selected: popover?.clip.key === trackKey,
|
|
4992
|
+
selectedStepKey: popover?.clip.key === trackKey ? popover.stepKey : void 0,
|
|
4993
|
+
onClick: openClipPopover,
|
|
4994
|
+
onDrag: closePopover
|
|
4995
|
+
}
|
|
4996
|
+
) })
|
|
4997
|
+
]
|
|
4998
|
+
},
|
|
4999
|
+
trackKey
|
|
5000
|
+
)
|
|
5001
|
+
);
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
return /* @__PURE__ */ jsxs14("div", { className: "dialkit-timeline-section", children: [
|
|
5006
|
+
/* @__PURE__ */ jsxs14("div", { className: "dialkit-timeline-header", "data-open": open || void 0, children: [
|
|
5007
|
+
/* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-identity", children: /* @__PURE__ */ jsx18("span", { className: "dialkit-timeline-title", children: meta.name }) }),
|
|
5008
|
+
!open && /* @__PURE__ */ jsx18(
|
|
5009
|
+
TimelineOverview,
|
|
5010
|
+
{
|
|
5011
|
+
id: meta.id,
|
|
5012
|
+
duration: meta.duration,
|
|
5013
|
+
viewStart: safeViewStart,
|
|
5014
|
+
viewEnd,
|
|
5015
|
+
onNavigate: centerViewAt
|
|
5016
|
+
}
|
|
5017
|
+
),
|
|
5018
|
+
/* @__PURE__ */ jsxs14("div", { className: "dialkit-timeline-actions", children: [
|
|
5019
|
+
/* @__PURE__ */ jsx18(PlayPauseButton, { id: meta.id }),
|
|
5020
|
+
/* @__PURE__ */ jsx18(ReplayButton, { onReplay: handleReplay }),
|
|
5021
|
+
/* @__PURE__ */ jsx18(
|
|
5022
|
+
motion7.button,
|
|
5023
|
+
{
|
|
5024
|
+
className: "dialkit-toolbar-add",
|
|
5025
|
+
onClick: handleAddPreset,
|
|
5026
|
+
title: "Add timeline version",
|
|
5027
|
+
"aria-label": "Add timeline version",
|
|
5028
|
+
whileTap: { scale: 0.9 },
|
|
5029
|
+
transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
|
|
5030
|
+
children: /* @__PURE__ */ jsx18("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: ICON_ADD_PRESET.map((d, i) => /* @__PURE__ */ jsx18("path", { d }, i)) })
|
|
5031
|
+
}
|
|
5032
|
+
),
|
|
5033
|
+
/* @__PURE__ */ jsx18(
|
|
5034
|
+
PresetManager,
|
|
5035
|
+
{
|
|
5036
|
+
panelId: meta.id,
|
|
5037
|
+
presets,
|
|
5038
|
+
activePresetId,
|
|
5039
|
+
onAdd: handleAddPreset
|
|
5040
|
+
}
|
|
5041
|
+
),
|
|
5042
|
+
/* @__PURE__ */ jsx18(
|
|
5043
|
+
motion7.button,
|
|
5044
|
+
{
|
|
5045
|
+
className: "dialkit-toolbar-add",
|
|
5046
|
+
onClick: handleCopy,
|
|
5047
|
+
title: "Copy parameters",
|
|
5048
|
+
"aria-label": copied ? "Copied parameters" : "Copy parameters",
|
|
5049
|
+
whileTap: { scale: 0.9 },
|
|
5050
|
+
transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
|
|
5051
|
+
children: /* @__PURE__ */ jsx18("span", { style: { position: "relative", width: 16, height: 16 }, children: /* @__PURE__ */ jsx18(AnimatePresence5, { initial: false, mode: "wait", children: copied ? /* @__PURE__ */ jsx18(
|
|
5052
|
+
motion7.svg,
|
|
5053
|
+
{
|
|
5054
|
+
viewBox: "0 0 24 24",
|
|
5055
|
+
fill: "none",
|
|
5056
|
+
stroke: "currentColor",
|
|
5057
|
+
strokeWidth: "2",
|
|
5058
|
+
strokeLinecap: "round",
|
|
5059
|
+
strokeLinejoin: "round",
|
|
5060
|
+
"aria-hidden": "true",
|
|
5061
|
+
style: { position: "absolute", inset: 0, width: 16, height: 16, color: "var(--dial-text-label)" },
|
|
5062
|
+
initial: { scale: 0.8, opacity: 0 },
|
|
5063
|
+
animate: { scale: 1, opacity: 1 },
|
|
5064
|
+
exit: { scale: 0.8, opacity: 0 },
|
|
5065
|
+
transition: { duration: 0.08 },
|
|
5066
|
+
children: /* @__PURE__ */ jsx18("path", { d: ICON_CHECK })
|
|
5067
|
+
},
|
|
5068
|
+
"check"
|
|
5069
|
+
) : /* @__PURE__ */ jsxs14(
|
|
5070
|
+
motion7.svg,
|
|
5071
|
+
{
|
|
5072
|
+
viewBox: "0 0 24 24",
|
|
5073
|
+
fill: "none",
|
|
5074
|
+
"aria-hidden": "true",
|
|
5075
|
+
style: { position: "absolute", inset: 0, width: 16, height: 16, color: "var(--dial-text-label)" },
|
|
5076
|
+
initial: { scale: 0.8, opacity: 0 },
|
|
5077
|
+
animate: { scale: 1, opacity: 1 },
|
|
5078
|
+
exit: { scale: 0.8, opacity: 0 },
|
|
5079
|
+
transition: { duration: 0.08 },
|
|
5080
|
+
children: [
|
|
5081
|
+
/* @__PURE__ */ jsx18("path", { d: ICON_CLIPBOARD.board, stroke: "currentColor", strokeWidth: "2", strokeLinejoin: "round" }),
|
|
5082
|
+
/* @__PURE__ */ jsx18("path", { d: ICON_CLIPBOARD.sparkle, fill: "currentColor" }),
|
|
5083
|
+
/* @__PURE__ */ jsx18("path", { d: ICON_CLIPBOARD.body, stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
5084
|
+
]
|
|
5085
|
+
},
|
|
5086
|
+
"clipboard"
|
|
5087
|
+
) }) })
|
|
5088
|
+
}
|
|
5089
|
+
),
|
|
5090
|
+
/* @__PURE__ */ jsx18(
|
|
5091
|
+
"button",
|
|
5092
|
+
{
|
|
5093
|
+
className: "dialkit-timeline-chevron",
|
|
5094
|
+
"data-open": open,
|
|
5095
|
+
"aria-expanded": open,
|
|
5096
|
+
onClick: () => setOpen(!open),
|
|
5097
|
+
title: open ? "Collapse timeline" : "Expand timeline",
|
|
5098
|
+
children: /* @__PURE__ */ jsx18("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx18("path", { d: ICON_CHEVRON }) })
|
|
5099
|
+
}
|
|
5100
|
+
)
|
|
5101
|
+
] })
|
|
5102
|
+
] }),
|
|
5103
|
+
open && /* @__PURE__ */ jsxs14(
|
|
5104
|
+
"div",
|
|
5105
|
+
{
|
|
5106
|
+
className: "dialkit-timeline-body",
|
|
5107
|
+
onWheel: handleTimelineWheel,
|
|
5108
|
+
onPointerDown: handleTrackPointerDown,
|
|
5109
|
+
onPointerMove: handleTrackPointerMove,
|
|
5110
|
+
onPointerUp: finishTrackScrub,
|
|
5111
|
+
onPointerCancel: finishTrackScrub,
|
|
5112
|
+
onLostPointerCapture: finishTrackScrub,
|
|
5113
|
+
children: [
|
|
5114
|
+
/* @__PURE__ */ jsxs14("div", { className: "dialkit-timeline-grid", children: [
|
|
5115
|
+
/* @__PURE__ */ jsxs14("div", { className: "dialkit-timeline-row dialkit-timeline-ruler-row", children: [
|
|
5116
|
+
/* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-label" }),
|
|
5117
|
+
/* @__PURE__ */ jsxs14(
|
|
5118
|
+
"div",
|
|
5119
|
+
{
|
|
5120
|
+
ref: laneAreaRef,
|
|
5121
|
+
className: "dialkit-timeline-ruler",
|
|
5122
|
+
onPointerDown: handleRulerPointerDown,
|
|
5123
|
+
onPointerMove: handleRulerPointerMove,
|
|
5124
|
+
onPointerUp: handleRulerPointerUp,
|
|
5125
|
+
onPointerCancel: handleRulerPointerCancel,
|
|
5126
|
+
onLostPointerCapture: handleRulerPointerCancel,
|
|
5127
|
+
title: "Drag to seek \xB7 Option-drag to zoom \xB7 Shift-drag to reset zoom",
|
|
5128
|
+
children: [
|
|
5129
|
+
fineTicks.map((t) => /* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-tick dialkit-timeline-tick-fine", style: { left: (t - safeViewStart) * pxPerSecond } }, `fine:${t}`)),
|
|
5130
|
+
mediumTicks.map((t) => /* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-tick dialkit-timeline-tick-medium", style: { left: (t - safeViewStart) * pxPerSecond } }, `medium:${t}`)),
|
|
5131
|
+
majorTicks.map((t) => /* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-tick", style: { left: (t - safeViewStart) * pxPerSecond }, children: /* @__PURE__ */ jsx18("span", { className: "dialkit-timeline-tick-label", children: formatRulerSeconds(t, majorStep) }) }, t))
|
|
5132
|
+
]
|
|
5133
|
+
}
|
|
5134
|
+
)
|
|
5135
|
+
] }),
|
|
5136
|
+
rows,
|
|
5137
|
+
pxPerSecond > 0 && /* @__PURE__ */ jsx18(
|
|
5138
|
+
TimelinePlayheadFlag,
|
|
5139
|
+
{
|
|
5140
|
+
id: meta.id,
|
|
5141
|
+
duration: meta.duration,
|
|
5142
|
+
pxPerSecond,
|
|
5143
|
+
viewStart: safeViewStart,
|
|
5144
|
+
viewEnd,
|
|
5145
|
+
laneWidth,
|
|
5146
|
+
rulerRef: laneAreaRef,
|
|
5147
|
+
onResetView: resetView
|
|
5148
|
+
}
|
|
5149
|
+
)
|
|
5150
|
+
] }),
|
|
5151
|
+
zoom > 1 && /* @__PURE__ */ jsxs14("div", { className: "dialkit-timeline-scroll-row", children: [
|
|
5152
|
+
/* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-label" }),
|
|
5153
|
+
/* @__PURE__ */ jsx18(
|
|
5154
|
+
"div",
|
|
5155
|
+
{
|
|
5156
|
+
ref: horizontalScrollRef,
|
|
5157
|
+
className: "dialkit-timeline-horizontal-scroll",
|
|
5158
|
+
onScroll: handleHorizontalScroll,
|
|
5159
|
+
"aria-label": "Timeline horizontal scroll",
|
|
5160
|
+
children: /* @__PURE__ */ jsx18("div", { style: { width: laneWidth * zoom } })
|
|
5161
|
+
}
|
|
5162
|
+
)
|
|
5163
|
+
] })
|
|
5164
|
+
]
|
|
5165
|
+
}
|
|
5166
|
+
),
|
|
5167
|
+
popover && /* @__PURE__ */ jsx18(
|
|
5168
|
+
ClipPopover,
|
|
5169
|
+
{
|
|
5170
|
+
panelId: meta.id,
|
|
5171
|
+
popover,
|
|
5172
|
+
values,
|
|
5173
|
+
theme,
|
|
5174
|
+
onClose: closePopover
|
|
5175
|
+
}
|
|
5176
|
+
)
|
|
5177
|
+
] });
|
|
5178
|
+
});
|
|
5179
|
+
function ClipPopover({
|
|
5180
|
+
panelId,
|
|
5181
|
+
popover,
|
|
5182
|
+
values,
|
|
5183
|
+
theme,
|
|
5184
|
+
onClose
|
|
5185
|
+
}) {
|
|
5186
|
+
const ref = useRef14(null);
|
|
5187
|
+
const [naturalHeight, setNaturalHeight] = useState11(0);
|
|
5188
|
+
const [viewport, setViewport] = useState11(() => ({
|
|
5189
|
+
width: window.visualViewport?.width ?? window.innerWidth,
|
|
5190
|
+
height: window.visualViewport?.height ?? window.innerHeight,
|
|
5191
|
+
offsetLeft: window.visualViewport?.offsetLeft ?? 0,
|
|
5192
|
+
offsetTop: window.visualViewport?.offsetTop ?? 0
|
|
5193
|
+
}));
|
|
5194
|
+
useLayoutEffect2(() => {
|
|
5195
|
+
const element = ref.current;
|
|
5196
|
+
if (!element) return;
|
|
5197
|
+
const measure = () => setNaturalHeight(element.scrollHeight + 2);
|
|
5198
|
+
measure();
|
|
5199
|
+
const observer = new ResizeObserver(measure);
|
|
5200
|
+
const body = element.querySelector(".dialkit-timeline-popover-body");
|
|
5201
|
+
observer.observe(body ?? element);
|
|
5202
|
+
return () => observer.disconnect();
|
|
5203
|
+
}, [popover.clip.key, popover.stepKey]);
|
|
5204
|
+
useEffect11(() => {
|
|
5205
|
+
const updateViewport = () => setViewport({
|
|
5206
|
+
width: window.visualViewport?.width ?? window.innerWidth,
|
|
5207
|
+
height: window.visualViewport?.height ?? window.innerHeight,
|
|
5208
|
+
offsetLeft: window.visualViewport?.offsetLeft ?? 0,
|
|
5209
|
+
offsetTop: window.visualViewport?.offsetTop ?? 0
|
|
5210
|
+
});
|
|
5211
|
+
window.addEventListener("resize", updateViewport);
|
|
5212
|
+
window.visualViewport?.addEventListener("resize", updateViewport);
|
|
5213
|
+
window.visualViewport?.addEventListener("scroll", updateViewport);
|
|
5214
|
+
return () => {
|
|
5215
|
+
window.removeEventListener("resize", updateViewport);
|
|
5216
|
+
window.visualViewport?.removeEventListener("resize", updateViewport);
|
|
5217
|
+
window.visualViewport?.removeEventListener("scroll", updateViewport);
|
|
5218
|
+
};
|
|
5219
|
+
}, []);
|
|
5220
|
+
useEffect11(() => {
|
|
5221
|
+
const handlePointerDown = (e) => {
|
|
5222
|
+
const target = e.target;
|
|
5223
|
+
if (ref.current?.contains(target)) return;
|
|
5224
|
+
if (target.closest?.(".dialkit-timeline-clip")) return;
|
|
5225
|
+
if (target.closest?.(".dialkit-timeline-label")) return;
|
|
5226
|
+
onClose();
|
|
5227
|
+
};
|
|
5228
|
+
const handleKeyDown = (e) => {
|
|
5229
|
+
if (e.key === "Escape") onClose();
|
|
5230
|
+
};
|
|
5231
|
+
document.addEventListener("pointerdown", handlePointerDown, true);
|
|
5232
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
5233
|
+
return () => {
|
|
5234
|
+
document.removeEventListener("pointerdown", handlePointerDown, true);
|
|
5235
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
5236
|
+
};
|
|
5237
|
+
}, [onClose]);
|
|
5238
|
+
const { clip, stepKey } = popover;
|
|
5239
|
+
let controls;
|
|
5240
|
+
let title;
|
|
5241
|
+
if (stepKey) {
|
|
5242
|
+
controls = getClipControls(panelId, `${clip.key}.${stepKey}`);
|
|
5243
|
+
if (stepKey === clip.stepKeys?.[0]) {
|
|
5244
|
+
const from = getControlAt(panelId, `${clip.key}.from`);
|
|
5245
|
+
if (from) {
|
|
5246
|
+
const toIndex = controls.findIndex((control) => control.path === `${clip.key}.${stepKey}.to`);
|
|
5247
|
+
controls = toIndex >= 0 ? [...controls.slice(0, toIndex), from, ...controls.slice(toIndex)] : [...controls, from];
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
title = `${clip.label} \xB7 ${formatStepLabel(stepKey)}`;
|
|
5251
|
+
} else {
|
|
5252
|
+
controls = getClipControls(panelId, clip.key, clipPopoverExclusions(clip));
|
|
5253
|
+
title = clip.label;
|
|
5254
|
+
}
|
|
5255
|
+
if (controls.length === 0) return null;
|
|
5256
|
+
const targetPath = stepKey ? `${clip.key}.${stepKey}` : clip.key;
|
|
5257
|
+
const durationMeta = getControlAt(panelId, `${targetPath}.duration`);
|
|
5258
|
+
const durationValue = durationMeta ? values[durationMeta.path] : void 0;
|
|
5259
|
+
const transitionDuration = durationMeta?.type === "slider" && typeof durationValue === "number" ? {
|
|
5260
|
+
value: durationValue,
|
|
5261
|
+
onChange: (next) => DialStore.updateValue(panelId, durationMeta.path, next),
|
|
5262
|
+
min: Math.max(TIMELINE_MIN_CLIP_DURATION, durationMeta.min ?? 0),
|
|
5263
|
+
max: durationMeta.max,
|
|
5264
|
+
step: durationMeta.step
|
|
5265
|
+
} : void 0;
|
|
5266
|
+
const displayValues = timelinePopoverDisplayValues(values, clip.key, clip.stepKeys, stepKey);
|
|
5267
|
+
const viewportRight = viewport.offsetLeft + viewport.width;
|
|
5268
|
+
const viewportBottom = viewport.offsetTop + viewport.height;
|
|
5269
|
+
const popoverWidth = Math.min(POPOVER_WIDTH, Math.max(220, viewport.width - 24));
|
|
5270
|
+
const left = clamp(
|
|
5271
|
+
popover.anchor.left + popover.anchor.width / 2 - popoverWidth / 2,
|
|
5272
|
+
viewport.offsetLeft + 12,
|
|
5273
|
+
Math.max(viewport.offsetLeft + 12, viewportRight - popoverWidth - 12)
|
|
5274
|
+
);
|
|
5275
|
+
const spaceAbove = Math.max(0, popover.anchor.top - viewport.offsetTop - 22);
|
|
5276
|
+
const spaceBelow = Math.max(0, viewportBottom - popover.anchor.bottom - 22);
|
|
5277
|
+
const placeAbove = naturalHeight === 0 ? spaceAbove >= spaceBelow : naturalHeight <= spaceAbove || naturalHeight > spaceBelow && spaceAbove >= spaceBelow;
|
|
5278
|
+
const availableHeight = placeAbove ? spaceAbove : spaceBelow;
|
|
5279
|
+
const renderedHeight = Math.min(naturalHeight || availableHeight, availableHeight);
|
|
5280
|
+
const unclampedTop = placeAbove ? popover.anchor.top - 10 - renderedHeight : popover.anchor.bottom + 10;
|
|
5281
|
+
const top = clamp(
|
|
5282
|
+
unclampedTop,
|
|
5283
|
+
viewport.offsetTop + 12,
|
|
5284
|
+
Math.max(viewport.offsetTop + 12, viewportBottom - renderedHeight - 12)
|
|
5285
|
+
);
|
|
5286
|
+
return createPortal4(
|
|
5287
|
+
/* @__PURE__ */ jsx18("div", { className: "dialkit-root", "data-theme": theme, children: /* @__PURE__ */ jsxs14(
|
|
5288
|
+
"div",
|
|
5289
|
+
{
|
|
5290
|
+
ref,
|
|
5291
|
+
className: "dialkit-timeline-popover",
|
|
5292
|
+
"data-placement": placeAbove ? "above" : "below",
|
|
5293
|
+
style: {
|
|
5294
|
+
left,
|
|
5295
|
+
top,
|
|
5296
|
+
width: popoverWidth,
|
|
5297
|
+
maxHeight: availableHeight,
|
|
5298
|
+
visibility: naturalHeight > 0 ? "visible" : "hidden"
|
|
5299
|
+
},
|
|
5300
|
+
role: "dialog",
|
|
5301
|
+
"aria-label": `Edit ${title}`,
|
|
5302
|
+
children: [
|
|
5303
|
+
/* @__PURE__ */ jsxs14("div", { className: "dialkit-timeline-popover-header", children: [
|
|
5304
|
+
/* @__PURE__ */ jsx18("span", { className: "dialkit-timeline-popover-title", children: title }),
|
|
5305
|
+
/* @__PURE__ */ jsx18("button", { className: "dialkit-timeline-popover-close", onClick: onClose, title: "Close editor", "aria-label": "Close editor", children: /* @__PURE__ */ jsx18("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsx18("path", { d: "M6 6L18 18M18 6L6 18" }) }) })
|
|
5306
|
+
] }),
|
|
5307
|
+
/* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-popover-body", children: /* @__PURE__ */ jsx18(
|
|
5308
|
+
ControlRenderer,
|
|
5309
|
+
{
|
|
5310
|
+
panelId,
|
|
5311
|
+
controls,
|
|
5312
|
+
values: displayValues,
|
|
5313
|
+
transitionDuration
|
|
5314
|
+
}
|
|
5315
|
+
) })
|
|
5316
|
+
]
|
|
5317
|
+
}
|
|
5318
|
+
) }),
|
|
5319
|
+
document.body
|
|
5320
|
+
);
|
|
5321
|
+
}
|
|
5322
|
+
function clipPopoverExclusions(clip) {
|
|
5323
|
+
return /* @__PURE__ */ new Set([
|
|
5324
|
+
...clip.stepKeys ?? [],
|
|
5325
|
+
...clip.tracks?.map((track) => track.prop) ?? []
|
|
5326
|
+
]);
|
|
5327
|
+
}
|
|
5328
|
+
function getClipControls(panelId, controlPath, excludeChildren) {
|
|
5329
|
+
const panel = DialStore.getPanel(panelId);
|
|
5330
|
+
const folder = panel ? findControl(panel.controls, controlPath) : null;
|
|
5331
|
+
if (!folder?.children) return [];
|
|
5332
|
+
return folder.children.filter((control) => {
|
|
5333
|
+
const childKey = control.path.slice(controlPath.length + 1);
|
|
5334
|
+
if (childKey === "at" || childKey === "duration") return false;
|
|
5335
|
+
return !excludeChildren?.has(childKey);
|
|
5336
|
+
});
|
|
5337
|
+
}
|
|
5338
|
+
function getControlAt(panelId, path) {
|
|
5339
|
+
const panel = DialStore.getPanel(panelId);
|
|
5340
|
+
return panel ? findControl(panel.controls, path) : null;
|
|
5341
|
+
}
|
|
5342
|
+
function TimelineClip({
|
|
5343
|
+
timelineId,
|
|
5344
|
+
clip,
|
|
5345
|
+
at,
|
|
5346
|
+
duration,
|
|
5347
|
+
loop,
|
|
5348
|
+
steps,
|
|
5349
|
+
fixedDuration,
|
|
5350
|
+
composite = false,
|
|
5351
|
+
baseAt = 0,
|
|
5352
|
+
delayMode = false,
|
|
5353
|
+
pxPerSecond,
|
|
5354
|
+
viewStart,
|
|
5355
|
+
timelineDuration,
|
|
5356
|
+
selected,
|
|
5357
|
+
selectedStepKey,
|
|
5358
|
+
onClick,
|
|
5359
|
+
onDrag
|
|
5360
|
+
}) {
|
|
5361
|
+
const dragRef = useRef14(null);
|
|
5362
|
+
const [dragging, setDragging] = useState11(false);
|
|
5363
|
+
const isSteps = Boolean(steps?.length);
|
|
5364
|
+
const handlePointerDown = useCallback14(
|
|
5365
|
+
(e) => {
|
|
5366
|
+
if (e.shiftKey) return;
|
|
5367
|
+
e.stopPropagation();
|
|
5368
|
+
const target = e.target;
|
|
5369
|
+
let mode = "move";
|
|
5370
|
+
let boundaryIndex;
|
|
5371
|
+
const boundary = target.dataset?.boundary;
|
|
5372
|
+
if (boundary !== void 0) {
|
|
5373
|
+
mode = "boundary";
|
|
5374
|
+
boundaryIndex = Number(boundary);
|
|
5375
|
+
} else if (!fixedDuration) {
|
|
5376
|
+
const edge = target.dataset?.edge;
|
|
5377
|
+
if (edge) mode = edge;
|
|
5378
|
+
}
|
|
5379
|
+
dragRef.current = {
|
|
5380
|
+
mode,
|
|
5381
|
+
boundaryIndex,
|
|
5382
|
+
pointerX: e.clientX,
|
|
5383
|
+
at,
|
|
5384
|
+
duration,
|
|
5385
|
+
stepDurations: steps?.map((step) => step.duration),
|
|
5386
|
+
clickEl: target.closest?.("[data-step]") ?? null,
|
|
5387
|
+
moved: false
|
|
5388
|
+
};
|
|
5389
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
5390
|
+
},
|
|
5391
|
+
[at, duration, fixedDuration, steps]
|
|
5392
|
+
);
|
|
5393
|
+
const handlePointerMove = useCallback14(
|
|
5394
|
+
(e) => {
|
|
5395
|
+
const drag = dragRef.current;
|
|
5396
|
+
if (!drag || pxPerSecond <= 0) return;
|
|
5397
|
+
const dx = e.clientX - drag.pointerX;
|
|
5398
|
+
if (!drag.moved) {
|
|
5399
|
+
if (Math.abs(dx) <= DRAG_THRESHOLD_PX) return;
|
|
5400
|
+
drag.moved = true;
|
|
5401
|
+
setDragging(true);
|
|
5402
|
+
onDrag();
|
|
5403
|
+
}
|
|
5404
|
+
const dt = dx / pxPerSecond;
|
|
5405
|
+
if (drag.mode === "boundary" && steps && drag.stepDurations) {
|
|
5406
|
+
const index = drag.boundaryIndex ?? 0;
|
|
5407
|
+
const others = drag.stepDurations.reduce((sum, d, j) => j === index ? sum : sum + d, 0);
|
|
5408
|
+
DialStore.updateValue(
|
|
5409
|
+
timelineId,
|
|
5410
|
+
`${clip.key}.${steps[index].key ?? ""}.duration`,
|
|
5411
|
+
clampStepResize(drag.stepDurations[index] + dt, drag.at, others, timelineDuration)
|
|
5412
|
+
);
|
|
5413
|
+
} else if (drag.mode === "move") {
|
|
5414
|
+
if (delayMode) {
|
|
5415
|
+
DialStore.updateValue(
|
|
5416
|
+
timelineId,
|
|
5417
|
+
`${clip.key}.delay`,
|
|
5418
|
+
clampTrackDelay(drag.at + dt - baseAt, baseAt, drag.duration, timelineDuration)
|
|
5419
|
+
);
|
|
5420
|
+
} else {
|
|
5421
|
+
DialStore.updateValue(timelineId, `${clip.key}.at`, clampClipMove(drag.at + dt, drag.duration, timelineDuration));
|
|
5422
|
+
}
|
|
5423
|
+
} else if (drag.mode === "end") {
|
|
5424
|
+
DialStore.updateValue(
|
|
5425
|
+
timelineId,
|
|
5426
|
+
`${clip.key}.duration`,
|
|
5427
|
+
clampClipResizeEnd(drag.duration + dt, drag.at, timelineDuration)
|
|
5428
|
+
);
|
|
5429
|
+
} else if (steps && drag.stepDurations) {
|
|
5430
|
+
const limit = Math.max(baseAt, 0);
|
|
5431
|
+
const next = clampClipResizeStart(Math.max(drag.at + dt, limit), drag.at, drag.stepDurations[0]);
|
|
5432
|
+
DialStore.updateValues(timelineId, {
|
|
5433
|
+
[delayMode ? `${clip.key}.delay` : `${clip.key}.at`]: delayMode ? Math.max(0, next.at - baseAt) : next.at,
|
|
5434
|
+
[`${clip.key}.${steps[0].key ?? ""}.duration`]: next.duration
|
|
5435
|
+
});
|
|
5436
|
+
} else {
|
|
5437
|
+
const limit = Math.max(baseAt, 0);
|
|
5438
|
+
const next = clampClipResizeStart(Math.max(drag.at + dt, limit), drag.at, drag.duration);
|
|
5439
|
+
DialStore.updateValues(timelineId, {
|
|
5440
|
+
[delayMode ? `${clip.key}.delay` : `${clip.key}.at`]: delayMode ? Math.max(0, next.at - baseAt) : next.at,
|
|
5441
|
+
[`${clip.key}.duration`]: next.duration
|
|
5442
|
+
});
|
|
5443
|
+
}
|
|
5444
|
+
},
|
|
5445
|
+
[baseAt, clip.key, delayMode, onDrag, pxPerSecond, steps, timelineId, timelineDuration]
|
|
5446
|
+
);
|
|
5447
|
+
const handlePointerUp = useCallback14(
|
|
5448
|
+
(e) => {
|
|
5449
|
+
const drag = dragRef.current;
|
|
5450
|
+
dragRef.current = null;
|
|
5451
|
+
setDragging(false);
|
|
5452
|
+
if (drag && !drag.moved) {
|
|
5453
|
+
const stepKey = drag.clickEl?.dataset?.step;
|
|
5454
|
+
const anchorEl = drag.clickEl ?? e.currentTarget;
|
|
5455
|
+
onClick(clip, anchorEl.getBoundingClientRect(), stepKey);
|
|
5456
|
+
}
|
|
5457
|
+
},
|
|
5458
|
+
[clip, onClick]
|
|
5459
|
+
);
|
|
5460
|
+
const handlePointerCancel = useCallback14(() => {
|
|
5461
|
+
dragRef.current = null;
|
|
5462
|
+
setDragging(false);
|
|
5463
|
+
}, []);
|
|
5464
|
+
const width = Math.max(duration * pxPerSecond, 14);
|
|
5465
|
+
const resizable = duration > 0 && !fixedDuration && !composite;
|
|
5466
|
+
const durationText = `${fixedDuration && !composite ? "~" : ""}${formatSeconds(duration)}`;
|
|
5467
|
+
const looping = loop === "repeat" && duration > 0;
|
|
5468
|
+
const ghostCycles = [];
|
|
5469
|
+
if (looping) {
|
|
5470
|
+
const maxGhostCycles = 256;
|
|
5471
|
+
const firstGhostIndex = Math.max(1, Math.floor((viewStart - at) / duration));
|
|
5472
|
+
for (let offset = 0; offset < maxGhostCycles; offset++) {
|
|
5473
|
+
const index = firstGhostIndex + offset;
|
|
5474
|
+
const start = at + duration * index;
|
|
5475
|
+
if (start >= timelineDuration - 1e-6) break;
|
|
5476
|
+
ghostCycles.push({
|
|
5477
|
+
start,
|
|
5478
|
+
duration: Math.min(duration, timelineDuration - start),
|
|
5479
|
+
index
|
|
5480
|
+
});
|
|
5481
|
+
}
|
|
5482
|
+
}
|
|
5483
|
+
const boundaryOffsets = [];
|
|
5484
|
+
if (steps) {
|
|
5485
|
+
let cumulative = 0;
|
|
5486
|
+
for (const step of steps) {
|
|
5487
|
+
cumulative += step.duration;
|
|
5488
|
+
boundaryOffsets.push(cumulative);
|
|
5489
|
+
}
|
|
5490
|
+
}
|
|
5491
|
+
const barTitle = composite ? `${clip.label} \u2014 composite of its property tracks${looping ? " \xB7 repeats through timeline" : ""} \xB7 click to expand` : `${clip.label} \u2014 ${formatSeconds(at)} for ${durationText}${fixedDuration ? " (duration set by spring physics)" : ""}${looping ? " \xB7 repeats through timeline" : ""}${delayMode ? " \xB7 drag to phase-shift" : ""}`;
|
|
5492
|
+
return /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
5493
|
+
ghostCycles.map((cycle) => {
|
|
5494
|
+
const ghostWidth = Math.max(1, cycle.duration * pxPerSecond - 2);
|
|
5495
|
+
return /* @__PURE__ */ jsx18(
|
|
5496
|
+
"div",
|
|
5497
|
+
{
|
|
5498
|
+
className: "dialkit-timeline-clip-ghost",
|
|
5499
|
+
"data-steps": isSteps || void 0,
|
|
5500
|
+
"aria-hidden": "true",
|
|
5501
|
+
style: {
|
|
5502
|
+
left: (cycle.start - viewStart) * pxPerSecond + 1,
|
|
5503
|
+
width: ghostWidth,
|
|
5504
|
+
background: clip.color
|
|
5505
|
+
},
|
|
5506
|
+
children: steps?.map((step, stepIndex) => /* @__PURE__ */ jsx18(
|
|
5507
|
+
"span",
|
|
5508
|
+
{
|
|
5509
|
+
className: "dialkit-timeline-clip-ghost-segment",
|
|
5510
|
+
style: { width: step.duration * pxPerSecond }
|
|
5511
|
+
},
|
|
5512
|
+
step.key ?? `step:${stepIndex}`
|
|
5513
|
+
))
|
|
5514
|
+
},
|
|
5515
|
+
`ghost:${cycle.index}`
|
|
5516
|
+
);
|
|
5517
|
+
}),
|
|
5518
|
+
/* @__PURE__ */ jsx18(
|
|
5519
|
+
"div",
|
|
5520
|
+
{
|
|
5521
|
+
className: "dialkit-timeline-clip",
|
|
5522
|
+
"data-steps": isSteps || void 0,
|
|
5523
|
+
"data-composite": composite || void 0,
|
|
5524
|
+
"data-selected": selected || void 0,
|
|
5525
|
+
"data-dragging": dragging || void 0,
|
|
5526
|
+
style: {
|
|
5527
|
+
left: (at - viewStart) * pxPerSecond,
|
|
5528
|
+
width,
|
|
5529
|
+
background: composite ? `${clip.color}80` : clip.color
|
|
5530
|
+
},
|
|
5531
|
+
onPointerDown: handlePointerDown,
|
|
5532
|
+
onPointerMove: handlePointerMove,
|
|
5533
|
+
onPointerUp: handlePointerUp,
|
|
5534
|
+
onPointerCancel: handlePointerCancel,
|
|
5535
|
+
onLostPointerCapture: handlePointerCancel,
|
|
5536
|
+
title: barTitle,
|
|
5537
|
+
children: composite ? /* @__PURE__ */ jsx18(Fragment5, { children: width > 56 && /* @__PURE__ */ jsx18("span", { className: "dialkit-timeline-clip-duration", children: durationText }) }) : isSteps ? /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
5538
|
+
steps.map((step) => {
|
|
5539
|
+
const segmentWidth = step.duration * pxPerSecond;
|
|
5540
|
+
return /* @__PURE__ */ jsx18(
|
|
5541
|
+
"div",
|
|
5542
|
+
{
|
|
5543
|
+
className: "dialkit-timeline-clip-segment",
|
|
5544
|
+
"data-step": step.key ?? void 0,
|
|
5545
|
+
"data-selected": selectedStepKey === step.key || void 0,
|
|
5546
|
+
style: { width: segmentWidth },
|
|
5547
|
+
children: segmentWidth > 52 && /* @__PURE__ */ jsx18("span", { className: "dialkit-timeline-clip-duration", children: formatSeconds(step.duration) })
|
|
5548
|
+
},
|
|
5549
|
+
step.key ?? "step"
|
|
5550
|
+
);
|
|
5551
|
+
}),
|
|
5552
|
+
steps.map(
|
|
5553
|
+
(step, index) => step.isPhysics ? null : /* @__PURE__ */ jsx18(
|
|
5554
|
+
"div",
|
|
5555
|
+
{
|
|
5556
|
+
className: "dialkit-timeline-clip-handle",
|
|
5557
|
+
"data-boundary": index,
|
|
5558
|
+
style: { left: boundaryOffsets[index] * pxPerSecond - 4 }
|
|
5559
|
+
},
|
|
5560
|
+
`boundary:${step.key}`
|
|
5561
|
+
)
|
|
5562
|
+
),
|
|
5563
|
+
!steps[0].isPhysics && /* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-clip-handle", "data-edge": "start" })
|
|
5564
|
+
] }) : /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
5565
|
+
resizable && /* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-clip-handle", "data-edge": "start" }),
|
|
5566
|
+
width > 56 && /* @__PURE__ */ jsx18("span", { className: "dialkit-timeline-clip-duration", children: durationText }),
|
|
5567
|
+
resizable && /* @__PURE__ */ jsx18("div", { className: "dialkit-timeline-clip-handle", "data-edge": "end" })
|
|
5568
|
+
] })
|
|
5569
|
+
}
|
|
5570
|
+
),
|
|
5571
|
+
looping && /* @__PURE__ */ jsx18("span", { className: "dialkit-timeline-loop-infinity", "aria-hidden": "true", title: "Repeats indefinitely", children: "\u221E" })
|
|
5572
|
+
] });
|
|
5573
|
+
}
|
|
5574
|
+
|
|
2842
5575
|
// src/components/ButtonGroup.tsx
|
|
2843
|
-
import { jsx as
|
|
5576
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
2844
5577
|
function ButtonGroup({ buttons }) {
|
|
2845
|
-
return /* @__PURE__ */
|
|
5578
|
+
return /* @__PURE__ */ jsx19("div", { className: "dialkit-button-group", children: buttons.map((button, index) => /* @__PURE__ */ jsx19(
|
|
2846
5579
|
"button",
|
|
2847
5580
|
{
|
|
2848
5581
|
className: "dialkit-button",
|
|
@@ -2854,10 +5587,10 @@ function ButtonGroup({ buttons }) {
|
|
|
2854
5587
|
}
|
|
2855
5588
|
|
|
2856
5589
|
// src/components/ShortcutsMenu.tsx
|
|
2857
|
-
import { useState as
|
|
2858
|
-
import { createPortal as
|
|
2859
|
-
import { motion as
|
|
2860
|
-
import { Fragment as
|
|
5590
|
+
import { useState as useState12, useRef as useRef15, useEffect as useEffect12, useCallback as useCallback15 } from "react";
|
|
5591
|
+
import { createPortal as createPortal5 } from "react-dom";
|
|
5592
|
+
import { motion as motion8, AnimatePresence as AnimatePresence6 } from "motion/react";
|
|
5593
|
+
import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2861
5594
|
function formatShortcutKey(sc) {
|
|
2862
5595
|
if (!sc.key) return "\u2014";
|
|
2863
5596
|
const mod = sc.modifier === "alt" ? "\u2325" : sc.modifier === "shift" ? "\u21E7" : sc.modifier === "meta" ? "\u2318" : "";
|
|
@@ -2877,23 +5610,23 @@ function formatInteraction(sc) {
|
|
|
2877
5610
|
}
|
|
2878
5611
|
}
|
|
2879
5612
|
function ShortcutsMenu({ panelId }) {
|
|
2880
|
-
const [isOpen, setIsOpen] =
|
|
2881
|
-
const triggerRef =
|
|
2882
|
-
const dropdownRef =
|
|
2883
|
-
const [pos, setPos] =
|
|
2884
|
-
const open =
|
|
5613
|
+
const [isOpen, setIsOpen] = useState12(false);
|
|
5614
|
+
const triggerRef = useRef15(null);
|
|
5615
|
+
const dropdownRef = useRef15(null);
|
|
5616
|
+
const [pos, setPos] = useState12({ top: 0, right: 0 });
|
|
5617
|
+
const open = useCallback15(() => {
|
|
2885
5618
|
const rect = triggerRef.current?.getBoundingClientRect();
|
|
2886
5619
|
if (rect) {
|
|
2887
5620
|
setPos({ top: rect.bottom + 4, right: window.innerWidth - rect.right });
|
|
2888
5621
|
}
|
|
2889
5622
|
setIsOpen(true);
|
|
2890
5623
|
}, []);
|
|
2891
|
-
const close =
|
|
2892
|
-
const toggle =
|
|
5624
|
+
const close = useCallback15(() => setIsOpen(false), []);
|
|
5625
|
+
const toggle = useCallback15(() => {
|
|
2893
5626
|
if (isOpen) close();
|
|
2894
5627
|
else open();
|
|
2895
5628
|
}, [isOpen, open, close]);
|
|
2896
|
-
|
|
5629
|
+
useEffect12(() => {
|
|
2897
5630
|
if (!isOpen) return;
|
|
2898
5631
|
const handler = (e) => {
|
|
2899
5632
|
const target = e.target;
|
|
@@ -2924,9 +5657,9 @@ function ShortcutsMenu({ panelId }) {
|
|
|
2924
5657
|
label: findLabel(panel.controls)
|
|
2925
5658
|
};
|
|
2926
5659
|
});
|
|
2927
|
-
return /* @__PURE__ */
|
|
2928
|
-
/* @__PURE__ */
|
|
2929
|
-
|
|
5660
|
+
return /* @__PURE__ */ jsxs15(Fragment6, { children: [
|
|
5661
|
+
/* @__PURE__ */ jsx20(
|
|
5662
|
+
motion8.button,
|
|
2930
5663
|
{
|
|
2931
5664
|
ref: triggerRef,
|
|
2932
5665
|
className: "dialkit-shortcuts-trigger",
|
|
@@ -2934,19 +5667,19 @@ function ShortcutsMenu({ panelId }) {
|
|
|
2934
5667
|
title: "Keyboard shortcuts",
|
|
2935
5668
|
whileTap: { scale: 0.9 },
|
|
2936
5669
|
transition: { type: "spring", visualDuration: 0.15, bounce: 0.3 },
|
|
2937
|
-
children: /* @__PURE__ */
|
|
2938
|
-
/* @__PURE__ */
|
|
2939
|
-
/* @__PURE__ */
|
|
2940
|
-
/* @__PURE__ */
|
|
2941
|
-
/* @__PURE__ */
|
|
2942
|
-
/* @__PURE__ */
|
|
2943
|
-
/* @__PURE__ */
|
|
5670
|
+
children: /* @__PURE__ */ jsxs15("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
5671
|
+
/* @__PURE__ */ jsx20("rect", { x: "2", y: "6", width: "20", height: "12", rx: "2" }),
|
|
5672
|
+
/* @__PURE__ */ jsx20("path", { d: "M6 10H6.01" }),
|
|
5673
|
+
/* @__PURE__ */ jsx20("path", { d: "M10 10H10.01" }),
|
|
5674
|
+
/* @__PURE__ */ jsx20("path", { d: "M14 10H14.01" }),
|
|
5675
|
+
/* @__PURE__ */ jsx20("path", { d: "M18 10H18.01" }),
|
|
5676
|
+
/* @__PURE__ */ jsx20("path", { d: "M8 14H16" })
|
|
2944
5677
|
] })
|
|
2945
5678
|
}
|
|
2946
5679
|
),
|
|
2947
|
-
|
|
2948
|
-
/* @__PURE__ */
|
|
2949
|
-
|
|
5680
|
+
createPortal5(
|
|
5681
|
+
/* @__PURE__ */ jsx20(AnimatePresence6, { children: isOpen && /* @__PURE__ */ jsxs15(
|
|
5682
|
+
motion8.div,
|
|
2950
5683
|
{
|
|
2951
5684
|
ref: dropdownRef,
|
|
2952
5685
|
className: "dialkit-root dialkit-shortcuts-dropdown",
|
|
@@ -2956,13 +5689,13 @@ function ShortcutsMenu({ panelId }) {
|
|
|
2956
5689
|
exit: { opacity: 0, y: 4, scale: 0.97, pointerEvents: "none" },
|
|
2957
5690
|
transition: { type: "spring", visualDuration: 0.15, bounce: 0 },
|
|
2958
5691
|
children: [
|
|
2959
|
-
/* @__PURE__ */
|
|
2960
|
-
/* @__PURE__ */
|
|
2961
|
-
/* @__PURE__ */
|
|
2962
|
-
/* @__PURE__ */
|
|
2963
|
-
/* @__PURE__ */
|
|
5692
|
+
/* @__PURE__ */ jsx20("div", { className: "dialkit-shortcuts-title", children: "Keyboard Shortcuts" }),
|
|
5693
|
+
/* @__PURE__ */ jsx20("div", { className: "dialkit-shortcuts-list", children: rows.map((row) => /* @__PURE__ */ jsxs15("div", { className: "dialkit-shortcuts-row", children: [
|
|
5694
|
+
/* @__PURE__ */ jsx20("span", { className: "dialkit-shortcuts-row-key", children: formatShortcutKey(row.shortcut) }),
|
|
5695
|
+
/* @__PURE__ */ jsx20("span", { className: "dialkit-shortcuts-row-label", children: row.label }),
|
|
5696
|
+
/* @__PURE__ */ jsx20("span", { className: "dialkit-shortcuts-row-mode", children: formatInteraction(row.shortcut) })
|
|
2964
5697
|
] }, row.path)) }),
|
|
2965
|
-
/* @__PURE__ */
|
|
5698
|
+
/* @__PURE__ */ jsx20("div", { className: "dialkit-shortcuts-hint", children: "See pill badges on controls for keys" })
|
|
2966
5699
|
]
|
|
2967
5700
|
}
|
|
2968
5701
|
) }),
|
|
@@ -2973,8 +5706,10 @@ function ShortcutsMenu({ panelId }) {
|
|
|
2973
5706
|
export {
|
|
2974
5707
|
ButtonGroup,
|
|
2975
5708
|
ColorControl,
|
|
5709
|
+
ControlRenderer,
|
|
2976
5710
|
DialRoot,
|
|
2977
5711
|
DialStore,
|
|
5712
|
+
DialTimeline,
|
|
2978
5713
|
EasingVisualization,
|
|
2979
5714
|
Folder,
|
|
2980
5715
|
PresetManager,
|
|
@@ -2984,9 +5719,12 @@ export {
|
|
|
2984
5719
|
SpringControl,
|
|
2985
5720
|
SpringVisualization,
|
|
2986
5721
|
TextControl,
|
|
5722
|
+
TimelineStore,
|
|
2987
5723
|
Toggle,
|
|
2988
5724
|
TransitionControl,
|
|
5725
|
+
formatClock,
|
|
2989
5726
|
useDialKit,
|
|
2990
|
-
useDialKitController
|
|
5727
|
+
useDialKitController,
|
|
5728
|
+
useDialTimeline
|
|
2991
5729
|
};
|
|
2992
5730
|
//# sourceMappingURL=index.js.map
|