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