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