@ixfx/components 0.2.3 → 0.2.5
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/LICENSE +1 -1
- package/bundle/chunk-pbuEa-1d.js +13 -0
- package/bundle/index.d.ts +4702 -569
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +34929 -2800
- package/bundle/index.js.map +1 -1
- package/bundle/style.css +133 -0
- package/dist/ac-text.d.ts +97 -0
- package/dist/ac-text.d.ts.map +1 -0
- package/dist/ac-text.js +484 -0
- package/dist/ac-text.js.map +1 -0
- package/dist/button-DgKUzE5o.js +121 -0
- package/dist/button-DgKUzE5o.js.map +1 -0
- package/dist/button.d.ts +21 -0
- package/dist/button.d.ts.map +1 -0
- package/dist/button.js +3 -0
- package/dist/checkbox.d.ts +34 -0
- package/dist/checkbox.d.ts.map +1 -0
- package/dist/checkbox.js +251 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/chunk-pbuEa-1d.js +13 -0
- package/dist/colour-C3MQIjFJ-BJV-QC-3.js +4138 -0
- package/dist/colour-C3MQIjFJ-BJV-QC-3.js.map +1 -0
- package/dist/colour-picker-Bw-AZoWx.js +922 -0
- package/dist/colour-picker-Bw-AZoWx.js.map +1 -0
- package/dist/colour-picker.d.ts +2 -0
- package/dist/colour-picker.js +2 -0
- package/dist/crumbs.d.ts +92 -0
- package/dist/crumbs.d.ts.map +1 -0
- package/dist/crumbs.js +789 -0
- package/dist/crumbs.js.map +1 -0
- package/dist/data-display.d.ts +24 -12
- package/dist/data-display.d.ts.map +1 -1
- package/dist/data-display.js +96 -105
- package/dist/data-display.js.map +1 -1
- package/dist/decorate-BZbkqIhA.js +9 -0
- package/dist/defaults-FEZaGiYZ.js +38 -0
- package/dist/defaults-FEZaGiYZ.js.map +1 -0
- package/dist/dist-By5URG0Y.js +1290 -0
- package/dist/dist-By5URG0Y.js.map +1 -0
- package/dist/dist-D7kJgnSE.js +1039 -0
- package/dist/dist-D7kJgnSE.js.map +1 -0
- package/dist/editable-label.d.ts +71 -0
- package/dist/editable-label.d.ts.map +1 -0
- package/dist/editable-label.js +279 -0
- package/dist/editable-label.js.map +1 -0
- package/dist/hex-editor-Gib1Grg2.d.ts +131 -0
- package/dist/hex-editor-Gib1Grg2.d.ts.map +1 -0
- package/dist/hex-editor-Wd6ZtZA2.js +1147 -0
- package/dist/hex-editor-Wd6ZtZA2.js.map +1 -0
- package/dist/hex.d.ts +2 -0
- package/dist/hex.js +2 -0
- package/dist/highlight-CAjwQ4GE.js +50 -0
- package/dist/highlight-CAjwQ4GE.js.map +1 -0
- package/dist/icon-BR5aTIiP.js +100 -0
- package/dist/icon-BR5aTIiP.js.map +1 -0
- package/dist/icon-DQKdfR7U.d.ts +36 -0
- package/dist/icon-DQKdfR7U.d.ts.map +1 -0
- package/dist/icons.d.ts +3 -0
- package/dist/icons.js +9 -0
- package/dist/icons.js.map +1 -0
- package/dist/incr-search-BjCNClnC.js +311 -0
- package/dist/incr-search-BjCNClnC.js.map +1 -0
- package/dist/incr-search.d.ts +2 -0
- package/dist/incr-search.js +3 -0
- package/dist/index-B1hNR7Z9.d.ts +36 -0
- package/dist/index-B1hNR7Z9.d.ts.map +1 -0
- package/dist/index-C5dK8RZa.d.ts +23 -0
- package/dist/index-C5dK8RZa.d.ts.map +1 -0
- package/dist/index-C9iBN908.d.ts +160 -0
- package/dist/index-C9iBN908.d.ts.map +1 -0
- package/dist/index-CPVcTTXo.d.ts +396 -0
- package/dist/index-CPVcTTXo.d.ts.map +1 -0
- package/dist/index-CQxanqXF.d.ts +183 -0
- package/dist/index-CQxanqXF.d.ts.map +1 -0
- package/dist/index-CpbBI3n7.d.ts +1239 -0
- package/dist/index-CpbBI3n7.d.ts.map +1 -0
- package/dist/index.d.ts +422 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5729 -14
- package/dist/index.js.map +1 -1
- package/dist/interaction-CQDQJf2Q.js +1 -0
- package/dist/keyboard-De_vOHLg.js +20 -0
- package/dist/keyboard-De_vOHLg.js.map +1 -0
- package/dist/labelled-input-base-DS6-6C0r.js +155 -0
- package/dist/labelled-input-base-DS6-6C0r.js.map +1 -0
- package/dist/labelled-input-base-DnRh4reU.d.ts +43 -0
- package/dist/labelled-input-base-DnRh4reU.d.ts.map +1 -0
- package/dist/labelled-radial-input.d.ts +34 -0
- package/dist/labelled-radial-input.d.ts.map +1 -0
- package/dist/labelled-radial-input.js +188 -0
- package/dist/labelled-radial-input.js.map +1 -0
- package/dist/labelled-range-input.d.ts +2 -0
- package/dist/labelled-range-input.js +132 -0
- package/dist/labelled-range-input.js.map +1 -0
- package/dist/led.d.ts +25 -0
- package/dist/led.d.ts.map +1 -0
- package/dist/led.js +92 -0
- package/dist/led.js.map +1 -0
- package/dist/menu-BV6UFx_z.js +813 -0
- package/dist/menu-BV6UFx_z.js.map +1 -0
- package/dist/menu-item-Cikacp4F.d.ts +128 -0
- package/dist/menu-item-Cikacp4F.d.ts.map +1 -0
- package/dist/menu-item-DEzL_Yjk.js +766 -0
- package/dist/menu-item-DEzL_Yjk.js.map +1 -0
- package/dist/menu.d.ts +3 -0
- package/dist/menu.js +3 -0
- package/dist/miller.d.ts +324 -0
- package/dist/miller.d.ts.map +1 -0
- package/dist/miller.js +1343 -0
- package/dist/miller.js.map +1 -0
- package/dist/narrowed-text.d.ts +72 -0
- package/dist/narrowed-text.d.ts.map +1 -0
- package/dist/narrowed-text.js +571 -0
- package/dist/narrowed-text.js.map +1 -0
- package/dist/panel.d.ts +43 -0
- package/dist/panel.d.ts.map +1 -0
- package/dist/panel.js +408 -0
- package/dist/panel.js.map +1 -0
- package/dist/plots.d.ts +2 -0
- package/dist/plots.js +2 -0
- package/dist/polar-pad.d.ts +67 -0
- package/dist/polar-pad.d.ts.map +1 -0
- package/dist/polar-pad.js +537 -0
- package/dist/polar-pad.js.map +1 -0
- package/dist/popup-Ddx9LCzY.js +24 -0
- package/dist/popup-Ddx9LCzY.js.map +1 -0
- package/dist/prominence-CreDBdCs.js +98 -0
- package/dist/prominence-CreDBdCs.js.map +1 -0
- package/dist/radial-input-CNJ8asY2.js +849 -0
- package/dist/radial-input-CNJ8asY2.js.map +1 -0
- package/dist/radial-input-xSTd4kEz.d.ts +70 -0
- package/dist/radial-input-xSTd4kEz.d.ts.map +1 -0
- package/dist/radial-input.d.ts +2 -0
- package/dist/radial-input.js +2 -0
- package/dist/range-input.d.ts +43 -0
- package/dist/range-input.d.ts.map +1 -0
- package/dist/range-input.js +459 -0
- package/dist/range-input.js.map +1 -0
- package/dist/range.d.ts +28 -0
- package/dist/range.d.ts.map +1 -0
- package/dist/range.js +136 -0
- package/dist/range.js.map +1 -0
- package/dist/registry-CZEB1YI9.js +21 -0
- package/dist/registry-CZEB1YI9.js.map +1 -0
- package/dist/selecthorizontal.d.ts +13 -8
- package/dist/selecthorizontal.d.ts.map +1 -1
- package/dist/selecthorizontal.js +21 -25
- package/dist/selecthorizontal.js.map +1 -1
- package/dist/snap-container.d.ts +50 -0
- package/dist/snap-container.d.ts.map +1 -0
- package/dist/snap-container.js +297 -0
- package/dist/snap-container.js.map +1 -0
- package/dist/split-layout.d.ts +56 -0
- package/dist/split-layout.d.ts.map +1 -0
- package/dist/split-layout.js +545 -0
- package/dist/split-layout.js.map +1 -0
- package/dist/style.css +133 -0
- package/dist/swipe.d.ts +48 -0
- package/dist/swipe.d.ts.map +1 -0
- package/dist/swipe.js +204 -0
- package/dist/swipe.js.map +1 -0
- package/dist/tab-list-CFF7uSYH.d.ts +66 -0
- package/dist/tab-list-CFF7uSYH.d.ts.map +1 -0
- package/dist/tabs.d.ts +2 -0
- package/dist/tabs.js +203 -0
- package/dist/tabs.js.map +1 -0
- package/dist/tickled-controller-Cq1Va0rR.d.ts +218 -0
- package/dist/tickled-controller-Cq1Va0rR.d.ts.map +1 -0
- package/dist/tickled-styles-Bg3QbcrD.js +327 -0
- package/dist/tickled-styles-Bg3QbcrD.js.map +1 -0
- package/dist/timeline-DBWJsSjd.js +1748 -0
- package/dist/timeline-DBWJsSjd.js.map +1 -0
- package/dist/timeline.d.ts +2 -0
- package/dist/timeline.js +2 -0
- package/dist/tooltip-DMxmBNky.js +104 -0
- package/dist/tooltip-DMxmBNky.js.map +1 -0
- package/dist/tooltip-kfUBWYVB.d.ts +26 -0
- package/dist/tooltip-kfUBWYVB.d.ts.map +1 -0
- package/dist/tree-component-CQpoPo4s.d.ts +326 -0
- package/dist/tree-component-CQpoPo4s.d.ts.map +1 -0
- package/dist/tree-data-model-DGvRVOFY.js +190 -0
- package/dist/tree-data-model-DGvRVOFY.js.map +1 -0
- package/dist/tree.d.ts +100 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +1144 -0
- package/dist/tree.js.map +1 -0
- package/dist/types-BKaqnpwx.d.ts +8 -0
- package/dist/types-BKaqnpwx.d.ts.map +1 -0
- package/dist/xy-axis-BANbgrI5.js +6243 -0
- package/dist/xy-axis-BANbgrI5.js.map +1 -0
- package/dist/xy-axis-CIFok7MF.d.ts +141 -0
- package/dist/xy-axis-CIFok7MF.d.ts.map +1 -0
- package/dist/xy-pad.d.ts +81 -0
- package/dist/xy-pad.d.ts.map +1 -0
- package/dist/xy-pad.js +435 -0
- package/dist/xy-pad.js.map +1 -0
- package/package.json +48 -39
- package/README.md +0 -14
- package/dist/charts/frequency-histogram-plot.d.ts +0 -33
- package/dist/charts/frequency-histogram-plot.d.ts.map +0 -1
- package/dist/charts/frequency-histogram-plot.js +0 -54
- package/dist/charts/frequency-histogram-plot.js.map +0 -1
- package/dist/charts/histogram-vis.d.ts +0 -57
- package/dist/charts/histogram-vis.d.ts.map +0 -1
- package/dist/charts/histogram-vis.js +0 -222
- package/dist/charts/histogram-vis.js.map +0 -1
- package/dist/charts/plot.d.ts +0 -199
- package/dist/charts/plot.d.ts.map +0 -1
- package/dist/charts/plot.js +0 -914
- package/dist/charts/plot.js.map +0 -1
- package/dist/crumb-nav.d.ts +0 -41
- package/dist/crumb-nav.d.ts.map +0 -1
- package/dist/crumb-nav.js +0 -198
- package/dist/crumb-nav.js.map +0 -1
- package/dist/crumb-navigation.d.ts +0 -30
- package/dist/crumb-navigation.d.ts.map +0 -1
- package/dist/crumb-navigation.js +0 -307
- package/dist/crumb-navigation.js.map +0 -1
- package/dist/dial.d.ts +0 -89
- package/dist/dial.d.ts.map +0 -1
- package/dist/dial.js +0 -506
- package/dist/dial.js.map +0 -1
- package/dist/my-element.d.ts +0 -14
- package/dist/my-element.d.ts.map +0 -1
- package/dist/my-element.js +0 -44
- package/dist/my-element.js.map +0 -1
- package/dist/split-layout/split-layout.d.ts +0 -35
- package/dist/split-layout/split-layout.d.ts.map +0 -1
- package/dist/split-layout/split-layout.js +0 -220
- package/dist/split-layout/split-layout.js.map +0 -1
- package/dist/svg-edit-label.d.ts +0 -2
- package/dist/svg-edit-label.d.ts.map +0 -1
- package/dist/svg-edit-label.js +0 -110
- package/dist/svg-edit-label.js.map +0 -1
- package/dist/tabs/tab-layout-item.d.ts +0 -10
- package/dist/tabs/tab-layout-item.d.ts.map +0 -1
- package/dist/tabs/tab-layout-item.js +0 -20
- package/dist/tabs/tab-layout-item.js.map +0 -1
- package/dist/tabs/tab-layout.d.ts +0 -10
- package/dist/tabs/tab-layout.d.ts.map +0 -1
- package/dist/tabs/tab-layout.js +0 -25
- package/dist/tabs/tab-layout.js.map +0 -1
- package/dist/tabs/tab-list-item.d.ts +0 -18
- package/dist/tabs/tab-list-item.d.ts.map +0 -1
- package/dist/tabs/tab-list-item.js +0 -63
- package/dist/tabs/tab-list-item.js.map +0 -1
- package/dist/tabs/tab-list.d.ts +0 -21
- package/dist/tabs/tab-list.d.ts.map +0 -1
- package/dist/tabs/tab-list.js +0 -148
- package/dist/tabs/tab-list.js.map +0 -1
- package/dist/tabs/tab-panel.d.ts +0 -11
- package/dist/tabs/tab-panel.d.ts.map +0 -1
- package/dist/tabs/tab-panel.js +0 -30
- package/dist/tabs/tab-panel.js.map +0 -1
- package/dist/tabs/tab-panels.d.ts +0 -30
- package/dist/tabs/tab-panels.d.ts.map +0 -1
- package/dist/tabs/tab-panels.js +0 -80
- package/dist/tabs/tab-panels.js.map +0 -1
- package/dist/util/circle-arc.d.ts +0 -34
- package/dist/util/circle-arc.d.ts.map +0 -1
- package/dist/util/circle-arc.js +0 -77
- package/dist/util/circle-arc.js.map +0 -1
- package/dist/util/compare.d.ts +0 -53
- package/dist/util/compare.d.ts.map +0 -1
- package/dist/util/compare.js +0 -91
- package/dist/util/compare.js.map +0 -1
- package/dist/util/content-editable.d.ts +0 -2
- package/dist/util/content-editable.d.ts.map +0 -1
- package/dist/util/content-editable.js +0 -13
- package/dist/util/content-editable.js.map +0 -1
- package/dist/util/drag-controller.d.ts +0 -13
- package/dist/util/drag-controller.d.ts.map +0 -1
- package/dist/util/drag-controller.js +0 -21
- package/dist/util/drag-controller.js.map +0 -1
- package/dist/util/key-value.d.ts +0 -5
- package/dist/util/key-value.d.ts.map +0 -1
- package/dist/util/key-value.js +0 -30
- package/dist/util/key-value.js.map +0 -1
- package/dist/util/primitive-types.d.ts +0 -6
- package/dist/util/primitive-types.d.ts.map +0 -1
- package/dist/util/primitive-types.js +0 -2
- package/dist/util/primitive-types.js.map +0 -1
- package/dist/util/svg-editable-text.d.ts +0 -22
- package/dist/util/svg-editable-text.d.ts.map +0 -1
- package/dist/util/svg-editable-text.js +0 -121
- package/dist/util/svg-editable-text.js.map +0 -1
- package/dist/util/time.d.ts +0 -2
- package/dist/util/time.d.ts.map +0 -1
- package/dist/util/time.js +0 -2
- package/dist/util/time.js.map +0 -1
|
@@ -0,0 +1,1748 @@
|
|
|
1
|
+
import { t as __decorate } from "./decorate-BZbkqIhA.js";
|
|
2
|
+
import { LitElement, css, html, nothing } from "lit";
|
|
3
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
4
|
+
import "@clinth/vcanvas";
|
|
5
|
+
//#region src/timeline/timeline-draw.ts
|
|
6
|
+
const NICE_STEPS = [
|
|
7
|
+
1,
|
|
8
|
+
2,
|
|
9
|
+
5,
|
|
10
|
+
10
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* Dispatch to adaptive or fixed grid mode.
|
|
14
|
+
*/
|
|
15
|
+
const resolveGrid = (viewRange, userScale, viewportWidth, mode = `adaptive`, fixedMinorStep, ppu) => {
|
|
16
|
+
if (mode === `fixed` && fixedMinorStep !== void 0 && ppu !== void 0) return resolveFixedGrid(fixedMinorStep, ppu);
|
|
17
|
+
return resolveAdaptiveGrid(viewRange, userScale, viewportWidth);
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Compute a nice major/minor grid resolution for the current view.
|
|
21
|
+
* Uses a 1-2-5-10 "nice number" algorithm.
|
|
22
|
+
*/
|
|
23
|
+
const resolveAdaptiveGrid = (viewRange, userScale, viewportWidth) => {
|
|
24
|
+
const span = viewRange.max - viewRange.min;
|
|
25
|
+
if (span <= 0 || viewportWidth <= 0) return {
|
|
26
|
+
majorStep: 100,
|
|
27
|
+
minorDivisions: 4,
|
|
28
|
+
minorStep: 25
|
|
29
|
+
};
|
|
30
|
+
const targetPx = 80 * scaleMultiplier(userScale);
|
|
31
|
+
const rawStep = span / Math.max(2, Math.floor(viewportWidth / targetPx));
|
|
32
|
+
const magnitude = Math.pow(10, Math.floor(Math.log10(rawStep)));
|
|
33
|
+
let bestStep = magnitude;
|
|
34
|
+
for (const n of NICE_STEPS) {
|
|
35
|
+
const candidate = n * magnitude;
|
|
36
|
+
if (candidate >= rawStep * .8) {
|
|
37
|
+
bestStep = candidate;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const normalised = bestStep / magnitude;
|
|
42
|
+
const minorDivisions = normalised === 5 || normalised === 10 ? 5 : 4;
|
|
43
|
+
const minorStep = bestStep / minorDivisions;
|
|
44
|
+
return {
|
|
45
|
+
majorStep: bestStep,
|
|
46
|
+
minorDivisions,
|
|
47
|
+
minorStep
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Fixed grid: user specifies a base minor step in logical units.
|
|
52
|
+
* If the minor step at current zoom would produce lines < 5px apart,
|
|
53
|
+
* double the effective step until they're >= 5px.
|
|
54
|
+
* Major = 4× effective minor.
|
|
55
|
+
*/
|
|
56
|
+
const resolveFixedGrid = (baseMinorStep, ppu) => {
|
|
57
|
+
const MIN_PX_BETWEEN = 5;
|
|
58
|
+
let effectiveMinor = baseMinorStep;
|
|
59
|
+
while (effectiveMinor * ppu < MIN_PX_BETWEEN && effectiveMinor < 0xe8d4a51000) effectiveMinor *= 2;
|
|
60
|
+
const minorDivisions = 4;
|
|
61
|
+
return {
|
|
62
|
+
majorStep: effectiveMinor * minorDivisions,
|
|
63
|
+
minorDivisions,
|
|
64
|
+
minorStep: effectiveMinor
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
const scaleMultiplier = (scale) => {
|
|
68
|
+
switch (scale) {
|
|
69
|
+
case `x-wide`: return 2;
|
|
70
|
+
case `wide`: return 1.5;
|
|
71
|
+
case `normal`: return 1;
|
|
72
|
+
case `slim`: return .7;
|
|
73
|
+
case `x-slim`: return .5;
|
|
74
|
+
default: return 1;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const drawMajorMinorGrid = (ctx, region, resolution, altMajorBg, vars, ppu) => {
|
|
78
|
+
const { majorStep, minorStep } = resolution;
|
|
79
|
+
if (majorStep <= 0) return;
|
|
80
|
+
const regionEnd = region.x + region.width;
|
|
81
|
+
if (altMajorBg) {
|
|
82
|
+
ctx.fillStyle = vars[`--timeline-alt-major-bg`] ?? `rgba(255,255,255,0.03)`;
|
|
83
|
+
const firstMajor = Math.floor(region.x / majorStep);
|
|
84
|
+
for (let i = firstMajor; i * majorStep < regionEnd; i++) if (i % 2 !== 0) {
|
|
85
|
+
const x = i * majorStep * ppu;
|
|
86
|
+
ctx.fillRect(x, region.y, majorStep * ppu, region.height);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (minorStep > 0) {
|
|
90
|
+
ctx.strokeStyle = vars[`--timeline-grid-minor-color`] ?? `rgba(255,255,255,0.05)`;
|
|
91
|
+
ctx.lineWidth = parseFloat(vars[`--timeline-grid-minor-width`] ?? `0.5`);
|
|
92
|
+
ctx.beginPath();
|
|
93
|
+
const firstMinor = Math.floor(region.x / minorStep) * minorStep;
|
|
94
|
+
for (let val = firstMinor; val < regionEnd; val += minorStep) {
|
|
95
|
+
if (Math.abs(val % majorStep) < minorStep * .01) continue;
|
|
96
|
+
const px = val * ppu;
|
|
97
|
+
ctx.moveTo(px, region.y);
|
|
98
|
+
ctx.lineTo(px, region.y + region.height);
|
|
99
|
+
}
|
|
100
|
+
ctx.stroke();
|
|
101
|
+
}
|
|
102
|
+
ctx.strokeStyle = vars[`--timeline-grid-major-color`] ?? `rgba(255,255,255,0.12)`;
|
|
103
|
+
ctx.lineWidth = parseFloat(vars[`--timeline-grid-major-width`] ?? `1`);
|
|
104
|
+
ctx.beginPath();
|
|
105
|
+
const firstMajor = Math.floor(region.x / majorStep) * majorStep;
|
|
106
|
+
for (let val = firstMajor; val < regionEnd; val += majorStep) {
|
|
107
|
+
const px = val * ppu;
|
|
108
|
+
ctx.moveTo(px, region.y);
|
|
109
|
+
ctx.lineTo(px, region.y + region.height);
|
|
110
|
+
}
|
|
111
|
+
ctx.stroke();
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Draw the axis header band. With 2D virtualization the axis is "sticky" —
|
|
115
|
+
* it is always drawn at the current vertical scroll position (`scrollTop`)
|
|
116
|
+
* so it appears fixed at the viewport top.
|
|
117
|
+
*/
|
|
118
|
+
const drawAxis = (ctx, region, resolution, axisHeight, formatLabel, vars, ppu, scrollTop = 0) => {
|
|
119
|
+
const { majorStep } = resolution;
|
|
120
|
+
if (majorStep <= 0 || axisHeight <= 0) return;
|
|
121
|
+
const axisTop = scrollTop;
|
|
122
|
+
const axisBottom = scrollTop + axisHeight;
|
|
123
|
+
if (region.y + region.height < axisTop || region.y > axisBottom) return;
|
|
124
|
+
const regionEnd = region.x + region.width;
|
|
125
|
+
const bgHeight = axisHeight - 6;
|
|
126
|
+
ctx.fillStyle = vars[`--timeline-bg`] ?? `#111`;
|
|
127
|
+
const bgLeft = region.x * ppu;
|
|
128
|
+
ctx.fillRect(bgLeft, axisTop, region.width * ppu, bgHeight);
|
|
129
|
+
ctx.strokeStyle = vars[`--timeline-grid-major-color`] ?? `rgba(255,255,255,0.12)`;
|
|
130
|
+
ctx.lineWidth = parseFloat(vars[`--timeline-grid-major-width`] ?? `1`);
|
|
131
|
+
ctx.beginPath();
|
|
132
|
+
const firstMajor = Math.ceil(region.x / majorStep) * majorStep;
|
|
133
|
+
for (let val = firstMajor; val < regionEnd; val += majorStep) {
|
|
134
|
+
const px = val * ppu;
|
|
135
|
+
ctx.moveTo(px, axisTop + bgHeight);
|
|
136
|
+
ctx.lineTo(px, axisBottom);
|
|
137
|
+
}
|
|
138
|
+
ctx.stroke();
|
|
139
|
+
ctx.strokeStyle = vars[`--timeline-grid-major-color`] ?? `rgba(255,255,255,0.12)`;
|
|
140
|
+
ctx.lineWidth = 1;
|
|
141
|
+
ctx.beginPath();
|
|
142
|
+
ctx.moveTo(bgLeft, axisBottom);
|
|
143
|
+
ctx.lineTo(bgLeft + region.width * ppu, axisBottom);
|
|
144
|
+
ctx.stroke();
|
|
145
|
+
ctx.fillStyle = vars[`--timeline-axis-label-color`] ?? `rgba(255,255,255,0.5)`;
|
|
146
|
+
ctx.font = `11px system-ui, sans-serif`;
|
|
147
|
+
ctx.textBaseline = `middle`;
|
|
148
|
+
ctx.textAlign = `left`;
|
|
149
|
+
const labelPadding = 4;
|
|
150
|
+
for (let val = firstMajor; val < regionEnd; val += majorStep) {
|
|
151
|
+
const px = val * ppu;
|
|
152
|
+
ctx.fillText(formatLabel(val), px + labelPadding, axisTop + bgHeight / 2);
|
|
153
|
+
}
|
|
154
|
+
ctx.textAlign = `start`;
|
|
155
|
+
};
|
|
156
|
+
const drawTrackBackgrounds = (ctx, region, trackLayout, tracks, vars, ppu) => {
|
|
157
|
+
const bgDefault = vars[`--timeline-track-bg`] ?? `#1a1a1a`;
|
|
158
|
+
const bgAlt = vars[`--timeline-track-bg-alt`] ?? `#1e1e1e`;
|
|
159
|
+
for (let i = 0; i < trackLayout.length; i++) {
|
|
160
|
+
const layout = trackLayout[i];
|
|
161
|
+
if (layout.y + layout.height < region.y) continue;
|
|
162
|
+
if (layout.y > region.y + region.height) break;
|
|
163
|
+
const track = tracks.find((t) => t.key === layout.trackKey);
|
|
164
|
+
ctx.fillStyle = track?.color ? track.color + `22` : i % 2 === 0 ? bgDefault : bgAlt;
|
|
165
|
+
ctx.fillRect(region.x * ppu, layout.y, region.width * ppu, layout.height);
|
|
166
|
+
ctx.strokeStyle = vars[`--timeline-grid-major-color`] ?? `rgba(255,255,255,0.12)`;
|
|
167
|
+
ctx.lineWidth = .5;
|
|
168
|
+
ctx.beginPath();
|
|
169
|
+
ctx.moveTo(region.x * ppu, layout.y + layout.height);
|
|
170
|
+
ctx.lineTo((region.x + region.width) * ppu, layout.y + layout.height);
|
|
171
|
+
ctx.stroke();
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const drawEventRects = (ctx, region, events, trackLayout, selectedIds, tickledId, drag, vars, ppu) => {
|
|
175
|
+
const eventBg = vars[`--timeline-event-bg`] ?? `#3498db`;
|
|
176
|
+
const eventSelectedBg = vars[`--timeline-event-selected-bg`] ?? `#5dade2`;
|
|
177
|
+
const eventTickledBg = vars[`--timeline-event-tickled-bg`] ?? `#444`;
|
|
178
|
+
const eventLabelColor = vars[`--timeline-event-label-color`] ?? `#fff`;
|
|
179
|
+
const radius = parseFloat(vars[`--timeline-event-radius`] ?? `4`);
|
|
180
|
+
const regionEnd = region.x + region.width;
|
|
181
|
+
const padding = 3;
|
|
182
|
+
for (const evt of events) {
|
|
183
|
+
if (evt.end < region.x || evt.start > regionEnd) continue;
|
|
184
|
+
const layout = trackLayout.find((l) => l.trackKey === evt.trackKey);
|
|
185
|
+
if (!layout) continue;
|
|
186
|
+
if (layout.y + layout.height < region.y) continue;
|
|
187
|
+
if (layout.y > region.y + region.height) continue;
|
|
188
|
+
let isDragging = false;
|
|
189
|
+
if (drag.kind === `event-move` && drag.eventId === evt.id) isDragging = true;
|
|
190
|
+
else if (drag.kind === `event-start` && drag.eventId === evt.id) isDragging = true;
|
|
191
|
+
else if (drag.kind === `event-end` && drag.eventId === evt.id) isDragging = true;
|
|
192
|
+
const x = evt.start * ppu;
|
|
193
|
+
const w = (evt.end - evt.start) * ppu;
|
|
194
|
+
const y = layout.y + padding;
|
|
195
|
+
const h = layout.height - padding * 2;
|
|
196
|
+
if (w < 1) continue;
|
|
197
|
+
const isSelected = selectedIds.has(evt.id);
|
|
198
|
+
const isTickled = tickledId === evt.id;
|
|
199
|
+
let fill;
|
|
200
|
+
if (isSelected) fill = eventSelectedBg;
|
|
201
|
+
else if (isTickled) fill = eventTickledBg;
|
|
202
|
+
else fill = evt.color ?? eventBg;
|
|
203
|
+
ctx.fillStyle = fill;
|
|
204
|
+
ctx.beginPath();
|
|
205
|
+
drawRoundRect(ctx, x, y, w, h, Math.min(radius, w / 2, h / 2));
|
|
206
|
+
ctx.fill();
|
|
207
|
+
if (isDragging) {
|
|
208
|
+
ctx.strokeStyle = `#fff`;
|
|
209
|
+
ctx.lineWidth = 1.5;
|
|
210
|
+
ctx.beginPath();
|
|
211
|
+
drawRoundRect(ctx, x, y, w, h, Math.min(radius, w / 2, h / 2));
|
|
212
|
+
ctx.stroke();
|
|
213
|
+
}
|
|
214
|
+
if (isSelected && !isDragging) {
|
|
215
|
+
ctx.strokeStyle = eventSelectedBg;
|
|
216
|
+
ctx.lineWidth = 1;
|
|
217
|
+
ctx.beginPath();
|
|
218
|
+
drawRoundRect(ctx, x, y, w, h, Math.min(radius, w / 2, h / 2));
|
|
219
|
+
ctx.stroke();
|
|
220
|
+
}
|
|
221
|
+
if (w >= 40 && evt.label) {
|
|
222
|
+
ctx.fillStyle = eventLabelColor;
|
|
223
|
+
ctx.font = `11px system-ui, sans-serif`;
|
|
224
|
+
ctx.textBaseline = `middle`;
|
|
225
|
+
ctx.save();
|
|
226
|
+
ctx.beginPath();
|
|
227
|
+
ctx.rect(x + 4, y, w - 8, h);
|
|
228
|
+
ctx.clip();
|
|
229
|
+
ctx.fillText(evt.label, x + 6, y + h / 2);
|
|
230
|
+
ctx.restore();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const drawMarquee = (ctx, drag, ppu) => {
|
|
235
|
+
if (drag.kind !== `marquee`) return;
|
|
236
|
+
const x1 = Math.min(drag.startLogicalX, drag.currentLogicalX) * ppu;
|
|
237
|
+
const x2 = Math.max(drag.startLogicalX, drag.currentLogicalX) * ppu;
|
|
238
|
+
const y1 = Math.min(drag.startY, drag.currentY);
|
|
239
|
+
const y2 = Math.max(drag.startY, drag.currentY);
|
|
240
|
+
ctx.fillStyle = `rgba(100, 150, 255, 0.15)`;
|
|
241
|
+
ctx.fillRect(x1, y1, x2 - x1, y2 - y1);
|
|
242
|
+
ctx.strokeStyle = `rgba(100, 150, 255, 0.6)`;
|
|
243
|
+
ctx.lineWidth = 1;
|
|
244
|
+
ctx.strokeRect(x1, y1, x2 - x1, y2 - y1);
|
|
245
|
+
};
|
|
246
|
+
const drawRoundRect = (ctx, x, y, w, h, r) => {
|
|
247
|
+
r = Math.max(0, r);
|
|
248
|
+
ctx.moveTo(x + r, y);
|
|
249
|
+
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
250
|
+
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
251
|
+
ctx.arcTo(x, y + h, x, y, r);
|
|
252
|
+
ctx.arcTo(x, y, x + w, y, r);
|
|
253
|
+
ctx.closePath();
|
|
254
|
+
};
|
|
255
|
+
const TIMELINE_CSS_VARS = [
|
|
256
|
+
`--timeline-bg`,
|
|
257
|
+
`--timeline-track-bg`,
|
|
258
|
+
`--timeline-track-bg-alt`,
|
|
259
|
+
`--timeline-alt-major-bg`,
|
|
260
|
+
`--timeline-grid-major-color`,
|
|
261
|
+
`--timeline-grid-minor-color`,
|
|
262
|
+
`--timeline-grid-major-width`,
|
|
263
|
+
`--timeline-grid-minor-width`,
|
|
264
|
+
`--timeline-event-bg`,
|
|
265
|
+
`--timeline-event-selected-bg`,
|
|
266
|
+
`--timeline-event-tickled-bg`,
|
|
267
|
+
`--timeline-event-label-color`,
|
|
268
|
+
`--timeline-event-radius`,
|
|
269
|
+
`--timeline-axis-label-color`
|
|
270
|
+
];
|
|
271
|
+
const resolveCssVars = (host) => {
|
|
272
|
+
const computed = getComputedStyle(host);
|
|
273
|
+
const vars = {};
|
|
274
|
+
for (const v of TIMELINE_CSS_VARS) vars[v] = computed.getPropertyValue(v).trim();
|
|
275
|
+
return vars;
|
|
276
|
+
};
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/timeline/timeline-interactions.ts
|
|
279
|
+
/** Hit zone near the edge of an event (px in screen space) */
|
|
280
|
+
const EDGE_HIT_PX = 8;
|
|
281
|
+
/**
|
|
282
|
+
* Convert a vcanvas logical (pixel) coordinate to timeline logical units.
|
|
283
|
+
* vcanvas pixel coordinates are in the 1:1 pixel space; divide by ppu then add min.
|
|
284
|
+
*/
|
|
285
|
+
const canvasToTimeline = (canvasX, ppu, min) => canvasX / ppu + min;
|
|
286
|
+
/**
|
|
287
|
+
* Convert screen-space pixel distance to timeline logical units.
|
|
288
|
+
* Used for edge hit threshold: how many logical units does EDGE_HIT_PX correspond to.
|
|
289
|
+
*/
|
|
290
|
+
const pxToLogicalUnits = (ppu) => ppu > 0 ? 1 / ppu : 1;
|
|
291
|
+
const hitTest = (logicalX, y, events, trackLayout, ppu) => {
|
|
292
|
+
const track = trackLayout.find((tl) => y >= tl.y && y < tl.y + tl.height);
|
|
293
|
+
if (!track) return {
|
|
294
|
+
kind: `empty`,
|
|
295
|
+
logicalX,
|
|
296
|
+
y,
|
|
297
|
+
trackKey: void 0
|
|
298
|
+
};
|
|
299
|
+
const edgeThreshold = EDGE_HIT_PX * pxToLogicalUnits(ppu);
|
|
300
|
+
const trackEvents = events.filter((e) => e.trackKey === track.trackKey);
|
|
301
|
+
for (const evt of trackEvents) {
|
|
302
|
+
if (logicalX < evt.start || logicalX > evt.end) continue;
|
|
303
|
+
if (logicalX <= evt.start + edgeThreshold) return {
|
|
304
|
+
kind: `event-start`,
|
|
305
|
+
eventId: evt.id
|
|
306
|
+
};
|
|
307
|
+
if (logicalX >= evt.end - edgeThreshold) return {
|
|
308
|
+
kind: `event-end`,
|
|
309
|
+
eventId: evt.id
|
|
310
|
+
};
|
|
311
|
+
return {
|
|
312
|
+
kind: `event-move`,
|
|
313
|
+
eventId: evt.id,
|
|
314
|
+
trackKey: track.trackKey,
|
|
315
|
+
offsetX: logicalX - evt.start
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
kind: `empty`,
|
|
320
|
+
logicalX,
|
|
321
|
+
y,
|
|
322
|
+
trackKey: track.trackKey
|
|
323
|
+
};
|
|
324
|
+
};
|
|
325
|
+
const trackAtY = (y, trackLayout) => {
|
|
326
|
+
return trackLayout.find((tl) => y >= tl.y && y < tl.y + tl.height)?.trackKey;
|
|
327
|
+
};
|
|
328
|
+
/** Sensitivity: how many pixels of vertical drag = 1 e-fold of zoom */
|
|
329
|
+
const ZOOM_SENSITIVITY_PX = 100;
|
|
330
|
+
/**
|
|
331
|
+
* Subtract a deadzone from a value.
|
|
332
|
+
* Returns 0 if |value| <= deadzone, otherwise value shifted toward zero by deadzone.
|
|
333
|
+
*/
|
|
334
|
+
const applyDeadzone = (value, deadzone) => Math.abs(value) > deadzone ? value - Math.sign(value) * deadzone : 0;
|
|
335
|
+
/**
|
|
336
|
+
* Compute new ppu + viewMin from an axis-zoom drag.
|
|
337
|
+
* Vertical delta (dy) controls zoom (up = zoom in, down = zoom out), anchored at `anchorUnit`.
|
|
338
|
+
* Horizontal delta (dx) controls pan.
|
|
339
|
+
*
|
|
340
|
+
* Returns { ppu, viewMin } — the caller uses host.setZoomPan() to apply.
|
|
341
|
+
*/
|
|
342
|
+
const computeAxisZoom = (startViewMin, startPpu, anchorUnit, dx, dy) => {
|
|
343
|
+
const newPpu = startPpu * Math.exp(-dy / ZOOM_SENSITIVITY_PX);
|
|
344
|
+
let newMin = anchorUnit - (anchorUnit - startViewMin) * startPpu / newPpu;
|
|
345
|
+
if (newPpu > 0) newMin += -dx / newPpu;
|
|
346
|
+
return {
|
|
347
|
+
ppu: newPpu,
|
|
348
|
+
viewMin: newMin
|
|
349
|
+
};
|
|
350
|
+
};
|
|
351
|
+
/**
|
|
352
|
+
* Compute new vertical view range from a v-axis-zoom drag.
|
|
353
|
+
* Horizontal delta (dx) controls zoom (right = zoom in, left = zoom out),
|
|
354
|
+
* anchored at `anchorBaseY` (a position in base-height units).
|
|
355
|
+
* Vertical delta (dy) controls pan (drag down = scroll down through tracks).
|
|
356
|
+
*
|
|
357
|
+
* Returns { vMin, vMax } — the caller uses host.setVerticalZoomPan() to apply.
|
|
358
|
+
*/
|
|
359
|
+
const computeVerticalAxisZoom = (startVMin, startVMax, startVScale, anchorBaseY, dx, dy) => {
|
|
360
|
+
const startSpan = startVMax - startVMin;
|
|
361
|
+
const newSpan = startSpan * Math.exp(-dx / ZOOM_SENSITIVITY_PX);
|
|
362
|
+
let newMin = anchorBaseY - (startSpan > 0 ? (anchorBaseY - startVMin) / startSpan : .5) * newSpan;
|
|
363
|
+
if (startVScale > 0) newMin += dy / startVScale;
|
|
364
|
+
return {
|
|
365
|
+
vMin: newMin,
|
|
366
|
+
vMax: newMin + newSpan
|
|
367
|
+
};
|
|
368
|
+
};
|
|
369
|
+
/**
|
|
370
|
+
* Attaches pointer and keyboard interaction handlers to the timeline.
|
|
371
|
+
* Returns a cleanup function.
|
|
372
|
+
*/
|
|
373
|
+
const attachTimelineInteractions = (host, vcanvasEl) => {
|
|
374
|
+
const ac = new AbortController();
|
|
375
|
+
const signal = ac.signal;
|
|
376
|
+
vcanvasEl.addEventListener(`pointerdown`, (e) => {
|
|
377
|
+
const ppu = host.pxPerUnit;
|
|
378
|
+
const canvasCoord = vcanvasEl.convertFromViewportToCanvas(e.offsetX, e.offsetY);
|
|
379
|
+
const lx = canvasToTimeline(canvasCoord.x, ppu, host.min);
|
|
380
|
+
const ly = canvasCoord.y;
|
|
381
|
+
const axisTop = vcanvasEl.logicalScrollTop;
|
|
382
|
+
if (ly >= axisTop && ly < axisTop + host.axisHeight) {
|
|
383
|
+
vcanvasEl.setPointerCapture(e.pointerId);
|
|
384
|
+
host._drag = {
|
|
385
|
+
kind: `axis-zoom`,
|
|
386
|
+
anchorUnit: lx,
|
|
387
|
+
startScreenX: e.screenX,
|
|
388
|
+
startScreenY: e.screenY,
|
|
389
|
+
startViewMin: host._viewRange.min,
|
|
390
|
+
startPpu: ppu
|
|
391
|
+
};
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const hit = hitTest(lx, ly, host._internalEvents, host._trackLayout, ppu);
|
|
395
|
+
if (e.shiftKey && hit.kind === `event-move`) {
|
|
396
|
+
toggleSelection(host, hit.eventId);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (hit.kind === `event-move`) {
|
|
400
|
+
vcanvasEl.setPointerCapture(e.pointerId);
|
|
401
|
+
host._drag = {
|
|
402
|
+
kind: `event-move`,
|
|
403
|
+
eventId: hit.eventId,
|
|
404
|
+
originTrackKey: hit.trackKey,
|
|
405
|
+
offsetX: hit.offsetX
|
|
406
|
+
};
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (hit.kind === `event-start`) {
|
|
410
|
+
vcanvasEl.setPointerCapture(e.pointerId);
|
|
411
|
+
host._drag = {
|
|
412
|
+
kind: `event-start`,
|
|
413
|
+
eventId: hit.eventId
|
|
414
|
+
};
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (hit.kind === `event-end`) {
|
|
418
|
+
vcanvasEl.setPointerCapture(e.pointerId);
|
|
419
|
+
host._drag = {
|
|
420
|
+
kind: `event-end`,
|
|
421
|
+
eventId: hit.eventId
|
|
422
|
+
};
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (hit.kind === `empty`) {
|
|
426
|
+
vcanvasEl.setPointerCapture(e.pointerId);
|
|
427
|
+
host._drag = {
|
|
428
|
+
kind: `marquee`,
|
|
429
|
+
startLogicalX: lx,
|
|
430
|
+
startY: ly,
|
|
431
|
+
currentLogicalX: lx,
|
|
432
|
+
currentY: ly
|
|
433
|
+
};
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
}, { signal });
|
|
437
|
+
vcanvasEl.addEventListener(`pointermove`, (e) => {
|
|
438
|
+
const drag = host._drag;
|
|
439
|
+
if (drag.kind === `none`) {
|
|
440
|
+
handleHover(host, vcanvasEl, e);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const ppu = host.pxPerUnit;
|
|
444
|
+
const canvasCoord = vcanvasEl.convertFromViewportToCanvas(e.offsetX, e.offsetY);
|
|
445
|
+
const lx = canvasToTimeline(canvasCoord.x, ppu, host.min);
|
|
446
|
+
const ly = canvasCoord.y;
|
|
447
|
+
if (drag.kind === `axis-zoom`) {
|
|
448
|
+
const rawDx = e.screenX - drag.startScreenX;
|
|
449
|
+
const rawDy = e.screenY - drag.startScreenY;
|
|
450
|
+
if (Math.abs(rawDx) + Math.abs(rawDy) < 5) return;
|
|
451
|
+
const dx = applyDeadzone(rawDx, 4);
|
|
452
|
+
const dy = applyDeadzone(rawDy, 4);
|
|
453
|
+
const result = computeAxisZoom(drag.startViewMin, drag.startPpu, drag.anchorUnit, dx, dy);
|
|
454
|
+
host.setZoomPan(result.viewMin, result.ppu);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (drag.kind === `marquee`) {
|
|
458
|
+
host._drag = {
|
|
459
|
+
...drag,
|
|
460
|
+
currentLogicalX: lx,
|
|
461
|
+
currentY: ly
|
|
462
|
+
};
|
|
463
|
+
vcanvasEl.invalidateAll();
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (drag.kind === `event-move`) {
|
|
467
|
+
const evt = host._internalEvents.find((ev) => ev.id === drag.eventId);
|
|
468
|
+
if (!evt) return;
|
|
469
|
+
const duration = evt.end - evt.start;
|
|
470
|
+
const rawLx = lx;
|
|
471
|
+
const rawLy = ly;
|
|
472
|
+
const newStart = lx - drag.offsetX;
|
|
473
|
+
const newEnd = newStart + duration;
|
|
474
|
+
const newTrackKey = trackAtY(ly, host._trackLayout) ?? evt.trackKey;
|
|
475
|
+
const ppu = host.pxPerUnit;
|
|
476
|
+
const grid = resolveGrid(host._viewRange, host.userScale, vcanvasEl.viewportWidth, host.gridMode, host.minorStep, ppu);
|
|
477
|
+
const snapped = host.processDrag(drag.eventId, rawLx, rawLy, newStart, newEnd, newTrackKey, drag.originTrackKey, grid);
|
|
478
|
+
host._internalEvents = host._internalEvents.map((ev) => ev.id === drag.eventId ? {
|
|
479
|
+
...ev,
|
|
480
|
+
start: snapped.start,
|
|
481
|
+
end: snapped.end,
|
|
482
|
+
trackKey: snapped.trackKey
|
|
483
|
+
} : ev);
|
|
484
|
+
vcanvasEl.invalidateAll();
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (drag.kind === `event-start`) {
|
|
488
|
+
const evt = host._internalEvents.find((ev) => ev.id === drag.eventId);
|
|
489
|
+
if (!evt) return;
|
|
490
|
+
const newStart = Math.min(lx, evt.end - 1);
|
|
491
|
+
host._internalEvents = host._internalEvents.map((ev) => ev.id === drag.eventId ? {
|
|
492
|
+
...ev,
|
|
493
|
+
start: newStart
|
|
494
|
+
} : ev);
|
|
495
|
+
vcanvasEl.invalidateAll();
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (drag.kind === `event-end`) {
|
|
499
|
+
const evt = host._internalEvents.find((ev) => ev.id === drag.eventId);
|
|
500
|
+
if (!evt) return;
|
|
501
|
+
const newEnd = Math.max(lx, evt.start + 1);
|
|
502
|
+
host._internalEvents = host._internalEvents.map((ev) => ev.id === drag.eventId ? {
|
|
503
|
+
...ev,
|
|
504
|
+
end: newEnd
|
|
505
|
+
} : ev);
|
|
506
|
+
vcanvasEl.invalidateAll();
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
}, { signal });
|
|
510
|
+
vcanvasEl.addEventListener(`pointerup`, (e) => {
|
|
511
|
+
const drag = host._drag;
|
|
512
|
+
vcanvasEl.releasePointerCapture(e.pointerId);
|
|
513
|
+
if (drag.kind === `axis-zoom`) {
|
|
514
|
+
host._drag = { kind: `none` };
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (drag.kind === `marquee`) {
|
|
518
|
+
const x1 = Math.min(drag.startLogicalX, drag.currentLogicalX);
|
|
519
|
+
const x2 = Math.max(drag.startLogicalX, drag.currentLogicalX);
|
|
520
|
+
const y1 = Math.min(drag.startY, drag.currentY);
|
|
521
|
+
const y2 = Math.max(drag.startY, drag.currentY);
|
|
522
|
+
if (Math.abs(x2 - x1) < 2 && Math.abs(y2 - y1) < 2) {
|
|
523
|
+
host._selectedIds = /* @__PURE__ */ new Set();
|
|
524
|
+
fireSelectionChange(host);
|
|
525
|
+
} else {
|
|
526
|
+
const selected = /* @__PURE__ */ new Set();
|
|
527
|
+
for (const evt of host._internalEvents) {
|
|
528
|
+
const layout = host._trackLayout.find((l) => l.trackKey === evt.trackKey);
|
|
529
|
+
if (!layout) continue;
|
|
530
|
+
const evtTop = layout.y + 3;
|
|
531
|
+
const evtBottom = layout.y + layout.height - 3;
|
|
532
|
+
if (evt.end >= x1 && evt.start <= x2 && evtBottom >= y1 && evtTop <= y2) selected.add(evt.id);
|
|
533
|
+
}
|
|
534
|
+
host._selectedIds = selected;
|
|
535
|
+
fireSelectionChange(host);
|
|
536
|
+
}
|
|
537
|
+
host._drag = { kind: `none` };
|
|
538
|
+
vcanvasEl.invalidateAll();
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (drag.kind === `event-move`) {
|
|
542
|
+
const evt = host._internalEvents.find((ev) => ev.id === drag.eventId);
|
|
543
|
+
if (evt) host.dispatchEvent(new CustomEvent(`event-move`, {
|
|
544
|
+
detail: {
|
|
545
|
+
eventId: evt.id,
|
|
546
|
+
fromTrackKey: drag.originTrackKey,
|
|
547
|
+
toTrackKey: evt.trackKey,
|
|
548
|
+
start: evt.start,
|
|
549
|
+
end: evt.end
|
|
550
|
+
},
|
|
551
|
+
bubbles: true,
|
|
552
|
+
composed: true
|
|
553
|
+
}));
|
|
554
|
+
host._drag = { kind: `none` };
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (drag.kind === `event-start` || drag.kind === `event-end`) {
|
|
558
|
+
const evt = host._internalEvents.find((ev) => ev.id === drag.eventId);
|
|
559
|
+
if (evt) host.dispatchEvent(new CustomEvent(`event-resize`, {
|
|
560
|
+
detail: {
|
|
561
|
+
eventId: evt.id,
|
|
562
|
+
start: evt.start,
|
|
563
|
+
end: evt.end
|
|
564
|
+
},
|
|
565
|
+
bubbles: true,
|
|
566
|
+
composed: true
|
|
567
|
+
}));
|
|
568
|
+
host._drag = { kind: `none` };
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
host._drag = { kind: `none` };
|
|
572
|
+
}, { signal });
|
|
573
|
+
vcanvasEl.addEventListener(`pointerleave`, () => {
|
|
574
|
+
if (host._tickledId !== void 0) {
|
|
575
|
+
host._tickledId = void 0;
|
|
576
|
+
vcanvasEl.invalidateAll();
|
|
577
|
+
}
|
|
578
|
+
}, { signal });
|
|
579
|
+
return () => ac.abort();
|
|
580
|
+
};
|
|
581
|
+
const handleHover = (host, vcanvasEl, e) => {
|
|
582
|
+
const ppu = host.pxPerUnit;
|
|
583
|
+
const canvasCoord = vcanvasEl.convertFromViewportToCanvas(e.offsetX, e.offsetY);
|
|
584
|
+
const lx = canvasToTimeline(canvasCoord.x, ppu, host.min);
|
|
585
|
+
const ly = canvasCoord.y;
|
|
586
|
+
const axisTop = vcanvasEl.logicalScrollTop;
|
|
587
|
+
if (ly >= axisTop && ly < axisTop + host.axisHeight) {
|
|
588
|
+
vcanvasEl.style.cursor = `col-resize`;
|
|
589
|
+
if (host._tickledId !== void 0) {
|
|
590
|
+
host._tickledId = void 0;
|
|
591
|
+
vcanvasEl.invalidateAll();
|
|
592
|
+
}
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
const hit = hitTest(lx, ly, host._internalEvents, host._trackLayout, ppu);
|
|
596
|
+
let newTickledId;
|
|
597
|
+
if (hit.kind === `event-move` || hit.kind === `event-start` || hit.kind === `event-end`) newTickledId = hit.eventId;
|
|
598
|
+
if (hit.kind === `event-start` || hit.kind === `event-end`) vcanvasEl.style.cursor = `ew-resize`;
|
|
599
|
+
else if (hit.kind === `event-move`) vcanvasEl.style.cursor = `grab`;
|
|
600
|
+
else vcanvasEl.style.cursor = `crosshair`;
|
|
601
|
+
if (newTickledId !== host._tickledId) {
|
|
602
|
+
host._tickledId = newTickledId;
|
|
603
|
+
vcanvasEl.invalidateAll();
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
const toggleSelection = (host, eventId) => {
|
|
607
|
+
const next = new Set(host._selectedIds);
|
|
608
|
+
if (next.has(eventId)) next.delete(eventId);
|
|
609
|
+
else next.add(eventId);
|
|
610
|
+
host._selectedIds = next;
|
|
611
|
+
fireSelectionChange(host);
|
|
612
|
+
host.getVCanvas()?.invalidateAll();
|
|
613
|
+
};
|
|
614
|
+
const fireSelectionChange = (host) => {
|
|
615
|
+
host.dispatchEvent(new CustomEvent(`selection-change`, {
|
|
616
|
+
detail: { selectedIds: [...host._selectedIds] },
|
|
617
|
+
bubbles: true,
|
|
618
|
+
composed: true
|
|
619
|
+
}));
|
|
620
|
+
};
|
|
621
|
+
//#endregion
|
|
622
|
+
//#region src/timeline/timeline-styles.ts
|
|
623
|
+
const timelineStyles = css`
|
|
624
|
+
:host {
|
|
625
|
+
/* Layout */
|
|
626
|
+
--timeline-axis-height: 24px;
|
|
627
|
+
--timeline-tab-width: 120px;
|
|
628
|
+
|
|
629
|
+
/* Backgrounds */
|
|
630
|
+
--timeline-bg: var(--content, #111);
|
|
631
|
+
--timeline-track-bg: var(--surface-2);
|
|
632
|
+
--timeline-track-bg-alt: var(--surface-3);
|
|
633
|
+
--timeline-alt-major-bg: color-mix(in oklch, var(--surface-3) 40%, transparent);
|
|
634
|
+
--timeline-meta-bg: var(--surface-2);
|
|
635
|
+
|
|
636
|
+
/* Grid */
|
|
637
|
+
--timeline-grid-major-color: var(--surface-5);
|
|
638
|
+
--timeline-grid-minor-color: var(--surface-4);
|
|
639
|
+
--timeline-grid-major-width: 1px;
|
|
640
|
+
--timeline-grid-minor-width: 0.5px;
|
|
641
|
+
|
|
642
|
+
/* Events */
|
|
643
|
+
--timeline-event-bg: var(--accent);
|
|
644
|
+
--timeline-event-selected-bg: color-mix(in oklch, var(--accent) 80%, white);
|
|
645
|
+
--timeline-event-tickled-bg: var(--surface-6);
|
|
646
|
+
--timeline-event-label-color: var(--accent-text);
|
|
647
|
+
--timeline-event-radius: var(--radius-s, 4px);
|
|
648
|
+
|
|
649
|
+
/* Tabs */
|
|
650
|
+
--timeline-tab-bg: var(--surface-3);
|
|
651
|
+
--timeline-tab-text: var(--surface-3-text);
|
|
652
|
+
--timeline-tab-selected-bg: var(--accent);
|
|
653
|
+
|
|
654
|
+
/* Axis */
|
|
655
|
+
--timeline-axis-label-color: var(--surface-muted-text);
|
|
656
|
+
|
|
657
|
+
/* Resizer */
|
|
658
|
+
--timeline-resizer-color: var(--surface-5);
|
|
659
|
+
|
|
660
|
+
display: grid;
|
|
661
|
+
grid-template-rows: 1fr auto auto auto;
|
|
662
|
+
background: var(--timeline-bg);
|
|
663
|
+
overflow: hidden;
|
|
664
|
+
position: relative;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/* ── Content wrapper ───────────────────────────────────────────────── */
|
|
668
|
+
|
|
669
|
+
.content-wrapper {
|
|
670
|
+
display: grid;
|
|
671
|
+
grid-template-columns: 1fr auto;
|
|
672
|
+
overflow: hidden;
|
|
673
|
+
min-height: 0;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/* ── Vertical zoom (right sidebar) ─────────────────────────────────── */
|
|
677
|
+
|
|
678
|
+
.v-zoom {
|
|
679
|
+
padding: 6px 4px;
|
|
680
|
+
padding-top: var(--timeline-axis-height);
|
|
681
|
+
display: flex;
|
|
682
|
+
align-items: stretch;
|
|
683
|
+
width: 30px;
|
|
684
|
+
box-sizing: content-box;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.v-zoom ixfx-range-multi {
|
|
688
|
+
height: 100%;
|
|
689
|
+
width: 100%;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/* ── Content area ──────────────────────────────────────────────────── */
|
|
693
|
+
|
|
694
|
+
.content-area {
|
|
695
|
+
position: relative;
|
|
696
|
+
display: grid;
|
|
697
|
+
grid-template-columns: var(--timeline-tab-width) 1fr;
|
|
698
|
+
overflow: hidden;
|
|
699
|
+
min-height: 0;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/* ── Tab column (sits beside vcanvas) ────────────────────────────── */
|
|
703
|
+
|
|
704
|
+
.tab-column {
|
|
705
|
+
position: relative;
|
|
706
|
+
overflow: hidden;
|
|
707
|
+
z-index: 2;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.tab-column ixfx-timeline-track-tab {
|
|
711
|
+
position: absolute;
|
|
712
|
+
left: 0;
|
|
713
|
+
width: 100%;
|
|
714
|
+
box-sizing: border-box;
|
|
715
|
+
pointer-events: auto;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/* ── Vertical axis interaction zone ─────────────────────────────────── */
|
|
719
|
+
|
|
720
|
+
.v-axis-zone {
|
|
721
|
+
position: absolute;
|
|
722
|
+
top: 0;
|
|
723
|
+
left: 0;
|
|
724
|
+
width: var(--timeline-tab-width);
|
|
725
|
+
height: 100%;
|
|
726
|
+
cursor: ns-resize;
|
|
727
|
+
z-index: 3;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/* ── VCanvas ───────────────────────────────────────────────────────── */
|
|
731
|
+
|
|
732
|
+
.content-area x-vcanvas {
|
|
733
|
+
width: 100%;
|
|
734
|
+
height: 100%;
|
|
735
|
+
display: block;
|
|
736
|
+
--scrollbar-width: none;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/* ── Horizontal zoom ───────────────────────────────────────────────── */
|
|
740
|
+
|
|
741
|
+
.h-zoom {
|
|
742
|
+
padding: 4px 8px;
|
|
743
|
+
padding-left: var(--timeline-tab-width);
|
|
744
|
+
padding-right: 38px; /* align with v-zoom column (30px + 2×4px padding) */
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
.h-zoom ixfx-range-multi {
|
|
748
|
+
width: 100%;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/* ── Meta area ─────────────────────────────────────────────────────── */
|
|
752
|
+
|
|
753
|
+
.meta-resizer {
|
|
754
|
+
display: none;
|
|
755
|
+
height: 6px;
|
|
756
|
+
cursor: ns-resize;
|
|
757
|
+
background: var(--timeline-resizer-color);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.meta-resizer.visible {
|
|
761
|
+
display: block;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.meta-area {
|
|
765
|
+
display: none;
|
|
766
|
+
background: var(--timeline-meta-bg);
|
|
767
|
+
overflow: hidden;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.meta-area.visible {
|
|
771
|
+
display: block;
|
|
772
|
+
}
|
|
773
|
+
`;
|
|
774
|
+
//#endregion
|
|
775
|
+
//#region src/timeline/timeline-track-tabs.ts
|
|
776
|
+
/** Minimum track height in px */
|
|
777
|
+
const MIN_TRACK_HEIGHT = 24;
|
|
778
|
+
/** How close to the bottom edge the pointer must be (px) to trigger resize */
|
|
779
|
+
const EDGE_ZONE_PX = 6;
|
|
780
|
+
let TimelineTrackTabElement = class TimelineTrackTabElement extends LitElement {
|
|
781
|
+
constructor(..._args) {
|
|
782
|
+
super(..._args);
|
|
783
|
+
this.label = ``;
|
|
784
|
+
this.trackKey = ``;
|
|
785
|
+
this.selected = false;
|
|
786
|
+
this.reorderable = false;
|
|
787
|
+
this.resizable = false;
|
|
788
|
+
this.#dragging = false;
|
|
789
|
+
this.#startScreenY = 0;
|
|
790
|
+
this.#startHeight = 0;
|
|
791
|
+
this.#pointerId = void 0;
|
|
792
|
+
this.#onPointerMove = (e) => {
|
|
793
|
+
if (this.#dragging) {
|
|
794
|
+
const dy = e.screenY - this.#startScreenY;
|
|
795
|
+
const newHeight = Math.max(MIN_TRACK_HEIGHT, this.#startHeight + dy);
|
|
796
|
+
this.#fireResizeEvent(`track-resize-move`, newHeight);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (this.#isInResizeZone(e)) this.style.cursor = `ns-resize`;
|
|
800
|
+
else this.style.cursor = ``;
|
|
801
|
+
};
|
|
802
|
+
this.#onPointerDown = (e) => {
|
|
803
|
+
if (!this.#isInResizeZone(e)) return;
|
|
804
|
+
e.stopPropagation();
|
|
805
|
+
e.preventDefault();
|
|
806
|
+
this.#dragging = true;
|
|
807
|
+
this.#startScreenY = e.screenY;
|
|
808
|
+
this.#startHeight = this.offsetHeight;
|
|
809
|
+
this.#pointerId = e.pointerId;
|
|
810
|
+
this.setPointerCapture(e.pointerId);
|
|
811
|
+
};
|
|
812
|
+
this.#onPointerUp = (e) => {
|
|
813
|
+
if (!this.#dragging) return;
|
|
814
|
+
const dy = e.screenY - this.#startScreenY;
|
|
815
|
+
const finalHeight = Math.max(MIN_TRACK_HEIGHT, this.#startHeight + dy);
|
|
816
|
+
this.#fireResizeEvent(`track-resize`, finalHeight);
|
|
817
|
+
this.#resetDrag(e.pointerId);
|
|
818
|
+
};
|
|
819
|
+
this.#onPointerCancel = (_e) => {
|
|
820
|
+
if (!this.#dragging) return;
|
|
821
|
+
this.#resetDrag(this.#pointerId);
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
static {
|
|
825
|
+
this.styles = css`
|
|
826
|
+
:host {
|
|
827
|
+
display: flex;
|
|
828
|
+
align-items: center;
|
|
829
|
+
background: color-mix(in oklch, var(--timeline-tab-bg, #2a2a2a) 70%, transparent);
|
|
830
|
+
backdrop-filter: blur(8px);
|
|
831
|
+
-webkit-backdrop-filter: blur(8px);
|
|
832
|
+
color: var(--timeline-tab-text, #ccc);
|
|
833
|
+
font-size: 0.75rem;
|
|
834
|
+
overflow: hidden;
|
|
835
|
+
white-space: nowrap;
|
|
836
|
+
text-overflow: ellipsis;
|
|
837
|
+
cursor: pointer;
|
|
838
|
+
user-select: none;
|
|
839
|
+
border-bottom: 1px solid var(--timeline-grid-major-color, rgba(255,255,255,0.12));
|
|
840
|
+
box-sizing: border-box;
|
|
841
|
+
position: relative;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
:host([selected]) {
|
|
845
|
+
background: color-mix(in oklch, var(--timeline-tab-selected-bg, #3498db) 75%, transparent);
|
|
846
|
+
color: white;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
.handle {
|
|
850
|
+
display: none;
|
|
851
|
+
cursor: grab;
|
|
852
|
+
padding: 0 4px;
|
|
853
|
+
opacity: 0.5;
|
|
854
|
+
font-size: 0.65rem;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
:host([reorderable]) .handle {
|
|
858
|
+
display: block;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
.label {
|
|
862
|
+
flex: 1;
|
|
863
|
+
overflow: hidden;
|
|
864
|
+
text-overflow: ellipsis;
|
|
865
|
+
padding: 0 8px;
|
|
866
|
+
}
|
|
867
|
+
`;
|
|
868
|
+
}
|
|
869
|
+
#dragging;
|
|
870
|
+
#startScreenY;
|
|
871
|
+
#startHeight;
|
|
872
|
+
#pointerId;
|
|
873
|
+
connectedCallback() {
|
|
874
|
+
super.connectedCallback();
|
|
875
|
+
this.addEventListener(`pointermove`, this.#onPointerMove);
|
|
876
|
+
this.addEventListener(`pointerdown`, this.#onPointerDown);
|
|
877
|
+
this.addEventListener(`pointerup`, this.#onPointerUp);
|
|
878
|
+
this.addEventListener(`pointercancel`, this.#onPointerCancel);
|
|
879
|
+
this.addEventListener(`lostpointercapture`, this.#onPointerCancel);
|
|
880
|
+
}
|
|
881
|
+
disconnectedCallback() {
|
|
882
|
+
super.disconnectedCallback();
|
|
883
|
+
this.removeEventListener(`pointermove`, this.#onPointerMove);
|
|
884
|
+
this.removeEventListener(`pointerdown`, this.#onPointerDown);
|
|
885
|
+
this.removeEventListener(`pointerup`, this.#onPointerUp);
|
|
886
|
+
this.removeEventListener(`pointercancel`, this.#onPointerCancel);
|
|
887
|
+
this.removeEventListener(`lostpointercapture`, this.#onPointerCancel);
|
|
888
|
+
this.#dragging = false;
|
|
889
|
+
}
|
|
890
|
+
render() {
|
|
891
|
+
return html`
|
|
892
|
+
<span class="handle">\u2630</span>
|
|
893
|
+
<span class="label">${this.label}</span>
|
|
894
|
+
`;
|
|
895
|
+
}
|
|
896
|
+
/** Is the pointer within the bottom edge zone? */
|
|
897
|
+
#isInResizeZone(e) {
|
|
898
|
+
if (!this.resizable) return false;
|
|
899
|
+
const rect = this.getBoundingClientRect();
|
|
900
|
+
return e.clientY >= rect.bottom - EDGE_ZONE_PX;
|
|
901
|
+
}
|
|
902
|
+
#fireResizeEvent(eventName, height) {
|
|
903
|
+
this.dispatchEvent(new CustomEvent(eventName, {
|
|
904
|
+
detail: {
|
|
905
|
+
trackKey: this.trackKey,
|
|
906
|
+
height
|
|
907
|
+
},
|
|
908
|
+
bubbles: true,
|
|
909
|
+
composed: true
|
|
910
|
+
}));
|
|
911
|
+
}
|
|
912
|
+
#onPointerMove;
|
|
913
|
+
#onPointerDown;
|
|
914
|
+
#onPointerUp;
|
|
915
|
+
#onPointerCancel;
|
|
916
|
+
#resetDrag(pointerId) {
|
|
917
|
+
this.#dragging = false;
|
|
918
|
+
this.#startScreenY = 0;
|
|
919
|
+
this.#startHeight = 0;
|
|
920
|
+
if (pointerId !== void 0) try {
|
|
921
|
+
this.releasePointerCapture(pointerId);
|
|
922
|
+
} catch {}
|
|
923
|
+
this.#pointerId = void 0;
|
|
924
|
+
this.style.cursor = ``;
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
__decorate([property({ type: String })], TimelineTrackTabElement.prototype, "label", void 0);
|
|
928
|
+
__decorate([property({
|
|
929
|
+
type: String,
|
|
930
|
+
attribute: `track-key`
|
|
931
|
+
})], TimelineTrackTabElement.prototype, "trackKey", void 0);
|
|
932
|
+
__decorate([property({
|
|
933
|
+
type: Boolean,
|
|
934
|
+
reflect: true
|
|
935
|
+
})], TimelineTrackTabElement.prototype, "selected", void 0);
|
|
936
|
+
__decorate([property({
|
|
937
|
+
type: Boolean,
|
|
938
|
+
reflect: true
|
|
939
|
+
})], TimelineTrackTabElement.prototype, "reorderable", void 0);
|
|
940
|
+
__decorate([property({
|
|
941
|
+
type: Boolean,
|
|
942
|
+
reflect: true
|
|
943
|
+
})], TimelineTrackTabElement.prototype, "resizable", void 0);
|
|
944
|
+
TimelineTrackTabElement = __decorate([customElement(`ixfx-timeline-track-tab`)], TimelineTrackTabElement);
|
|
945
|
+
//#endregion
|
|
946
|
+
//#region src/timeline/timeline-element.ts
|
|
947
|
+
/** Reasonable maximum ppu to prevent infinite zoom-in */
|
|
948
|
+
const MAX_PPU = 1e4;
|
|
949
|
+
/** Minimum rendered track height (~1.5em) at max zoom-out */
|
|
950
|
+
const MIN_TRACK_RENDER_HEIGHT = 24;
|
|
951
|
+
let IxfxTimelineElement = class IxfxTimelineElement extends LitElement {
|
|
952
|
+
constructor(..._args) {
|
|
953
|
+
super(..._args);
|
|
954
|
+
this.tracks = [];
|
|
955
|
+
this.events = [];
|
|
956
|
+
this.min = 0;
|
|
957
|
+
this.max = 1e3;
|
|
958
|
+
this.trackHeight = `fixed`;
|
|
959
|
+
this.userScale = `normal`;
|
|
960
|
+
this.reorderable = false;
|
|
961
|
+
this.metaEnabled = false;
|
|
962
|
+
this.altMajorBg = false;
|
|
963
|
+
this.defaultTrackHeight = 48;
|
|
964
|
+
this.gridMode = `adaptive`;
|
|
965
|
+
this.minorStep = 10;
|
|
966
|
+
this.onDragging = null;
|
|
967
|
+
this.formatLabel = String;
|
|
968
|
+
this._ppu = 1;
|
|
969
|
+
this._viewRange = {
|
|
970
|
+
min: 0,
|
|
971
|
+
max: 4e3
|
|
972
|
+
};
|
|
973
|
+
this._vViewRange = {
|
|
974
|
+
min: 0,
|
|
975
|
+
max: 1
|
|
976
|
+
};
|
|
977
|
+
this._vScale = 1;
|
|
978
|
+
this._selectedIds = /* @__PURE__ */ new Set();
|
|
979
|
+
this._tickledId = void 0;
|
|
980
|
+
this._drag = { kind: `none` };
|
|
981
|
+
this._metaHeight = 120;
|
|
982
|
+
this._trackHeightOverrides = /* @__PURE__ */ new Map();
|
|
983
|
+
this._trackLayout = [];
|
|
984
|
+
this._internalEvents = [];
|
|
985
|
+
this._internalTracks = [];
|
|
986
|
+
this.#vcanvas = null;
|
|
987
|
+
this.#cssVarsCache = {};
|
|
988
|
+
this.#cleanupInteractions = null;
|
|
989
|
+
this.#axisHeight = 24;
|
|
990
|
+
this.#prevTickledId = void 0;
|
|
991
|
+
this.#resizeObserver = null;
|
|
992
|
+
this.#pendingVerticalZoomToFit = false;
|
|
993
|
+
this.#hasInitializedZoom = false;
|
|
994
|
+
this.#onResize = (_entries) => {
|
|
995
|
+
if (!this.#hasInitializedZoom) {
|
|
996
|
+
this.#hasInitializedZoom = true;
|
|
997
|
+
this.#initZoomToFit();
|
|
998
|
+
this.#initVerticalZoomToFit();
|
|
999
|
+
this.#recomputeTrackLayout();
|
|
1000
|
+
} else {
|
|
1001
|
+
this._viewRange = this.#deriveViewRange(this._viewRange.min, this._ppu);
|
|
1002
|
+
this.#deriveVerticalZoom(this._vViewRange.min, this._vViewRange.max);
|
|
1003
|
+
}
|
|
1004
|
+
this.#syncVCanvas();
|
|
1005
|
+
this.#invalidateCanvas();
|
|
1006
|
+
};
|
|
1007
|
+
this.#drawCallback = (ctx, region, _signal) => {
|
|
1008
|
+
const ppu = this._ppu;
|
|
1009
|
+
const logicalRegion = {
|
|
1010
|
+
x: region.x / ppu + this.min,
|
|
1011
|
+
y: region.y,
|
|
1012
|
+
width: region.width / ppu,
|
|
1013
|
+
height: region.height
|
|
1014
|
+
};
|
|
1015
|
+
this.#cssVarsCache = resolveCssVars(this);
|
|
1016
|
+
const grid = resolveGrid(this._viewRange, this.userScale, this.#getViewportWidth(), this.gridMode, this.minorStep, ppu);
|
|
1017
|
+
const trackInfos = this._internalTracks.map((t) => ({
|
|
1018
|
+
key: t.key,
|
|
1019
|
+
color: t.color
|
|
1020
|
+
}));
|
|
1021
|
+
drawTrackBackgrounds(ctx, logicalRegion, this._trackLayout, trackInfos, this.#cssVarsCache, ppu);
|
|
1022
|
+
drawMajorMinorGrid(ctx, logicalRegion, grid, this.altMajorBg, this.#cssVarsCache, ppu);
|
|
1023
|
+
drawEventRects(ctx, logicalRegion, this._internalEvents, this._trackLayout, this._selectedIds, this._tickledId, this._drag, this.#cssVarsCache, ppu);
|
|
1024
|
+
drawMarquee(ctx, this._drag, ppu);
|
|
1025
|
+
const scrollTop = this.#vcanvas?.logicalScrollTop ?? 0;
|
|
1026
|
+
drawAxis(ctx, logicalRegion, grid, this.#axisHeight, this.formatLabel, this.#cssVarsCache, ppu, scrollTop);
|
|
1027
|
+
};
|
|
1028
|
+
this.#onScrollVirtual = (e) => {
|
|
1029
|
+
const detail = e.detail;
|
|
1030
|
+
if (detail.userGesture === false) return;
|
|
1031
|
+
const ppu = this._ppu;
|
|
1032
|
+
if (ppu <= 0) return;
|
|
1033
|
+
const viewMin = detail.logicalScrollLeft / ppu + this.min;
|
|
1034
|
+
this._viewRange = this.#deriveViewRange(viewMin, ppu);
|
|
1035
|
+
if (this._vScale > 0) {
|
|
1036
|
+
const span = this._vViewRange.max - this._vViewRange.min;
|
|
1037
|
+
const newVMin = detail.logicalScrollTop / this._vScale;
|
|
1038
|
+
this.#deriveVerticalZoom(newVMin, newVMin + span);
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
this.#onHZoomInput = (e) => {
|
|
1042
|
+
const detail = e.detail;
|
|
1043
|
+
const span = detail.b - detail.a;
|
|
1044
|
+
const vpWidth = this.#getViewportWidth();
|
|
1045
|
+
if (span > 0) this._ppu = Math.max(this.#minPpu(), Math.min(vpWidth / span, MAX_PPU));
|
|
1046
|
+
this._viewRange = this.#deriveViewRange(detail.a, this._ppu);
|
|
1047
|
+
this.#syncVCanvas();
|
|
1048
|
+
};
|
|
1049
|
+
this.#onVZoomInput = (e) => {
|
|
1050
|
+
const detail = e.detail;
|
|
1051
|
+
this.#deriveVerticalZoom(detail.a, detail.b);
|
|
1052
|
+
};
|
|
1053
|
+
this.#onVAxisPointerDown = (e) => {
|
|
1054
|
+
const zone = e.currentTarget;
|
|
1055
|
+
zone.setPointerCapture(e.pointerId);
|
|
1056
|
+
e.stopPropagation();
|
|
1057
|
+
e.preventDefault();
|
|
1058
|
+
const rect = zone.getBoundingClientRect();
|
|
1059
|
+
const clickFrac = rect.height > 0 ? (e.clientY - rect.top) / rect.height : .5;
|
|
1060
|
+
const span = this._vViewRange.max - this._vViewRange.min;
|
|
1061
|
+
const anchorBaseY = this._vViewRange.min + clickFrac * span;
|
|
1062
|
+
this._drag = {
|
|
1063
|
+
kind: `v-axis-zoom`,
|
|
1064
|
+
anchorBaseY,
|
|
1065
|
+
startScreenX: e.screenX,
|
|
1066
|
+
startScreenY: e.screenY,
|
|
1067
|
+
startVMin: this._vViewRange.min,
|
|
1068
|
+
startVMax: this._vViewRange.max,
|
|
1069
|
+
startVScale: this._vScale
|
|
1070
|
+
};
|
|
1071
|
+
};
|
|
1072
|
+
this.#onVAxisPointerMove = (e) => {
|
|
1073
|
+
if (this._drag.kind !== `v-axis-zoom`) return;
|
|
1074
|
+
const drag = this._drag;
|
|
1075
|
+
const rawDx = e.screenX - drag.startScreenX;
|
|
1076
|
+
const rawDy = e.screenY - drag.startScreenY;
|
|
1077
|
+
if (Math.abs(rawDx) + Math.abs(rawDy) < 5) return;
|
|
1078
|
+
const dx = applyDeadzone(rawDx, 4);
|
|
1079
|
+
const dy = applyDeadzone(rawDy, 4);
|
|
1080
|
+
const result = computeVerticalAxisZoom(drag.startVMin, drag.startVMax, drag.startVScale, drag.anchorBaseY, dx, dy);
|
|
1081
|
+
this.setVerticalZoomPan(result.vMin, result.vMax);
|
|
1082
|
+
};
|
|
1083
|
+
this.#onVAxisPointerUp = (e) => {
|
|
1084
|
+
if (this._drag.kind !== `v-axis-zoom`) return;
|
|
1085
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
1086
|
+
this._drag = { kind: `none` };
|
|
1087
|
+
};
|
|
1088
|
+
this.#onTrackResizeMove = (e) => {
|
|
1089
|
+
const { trackKey, height } = e.detail;
|
|
1090
|
+
this.#applyTrackResize(trackKey, height);
|
|
1091
|
+
};
|
|
1092
|
+
this.#onTrackResizeEnd = (e) => {
|
|
1093
|
+
const { trackKey, height } = e.detail;
|
|
1094
|
+
this.#applyTrackResize(trackKey, height);
|
|
1095
|
+
this.dispatchEvent(new CustomEvent(`track-resize`, {
|
|
1096
|
+
detail: {
|
|
1097
|
+
trackKey,
|
|
1098
|
+
height
|
|
1099
|
+
},
|
|
1100
|
+
bubbles: true,
|
|
1101
|
+
composed: true
|
|
1102
|
+
}));
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
static {
|
|
1106
|
+
this.styles = [timelineStyles];
|
|
1107
|
+
}
|
|
1108
|
+
#vcanvas;
|
|
1109
|
+
#cssVarsCache;
|
|
1110
|
+
#cleanupInteractions;
|
|
1111
|
+
#axisHeight;
|
|
1112
|
+
#prevTickledId;
|
|
1113
|
+
#resizeObserver;
|
|
1114
|
+
#pendingVerticalZoomToFit;
|
|
1115
|
+
#hasInitializedZoom;
|
|
1116
|
+
/** Pixels per logical unit — the zoom level */
|
|
1117
|
+
get pxPerUnit() {
|
|
1118
|
+
return this._ppu;
|
|
1119
|
+
}
|
|
1120
|
+
/** Height of the axis header band in pixels */
|
|
1121
|
+
get axisHeight() {
|
|
1122
|
+
return this.#axisHeight;
|
|
1123
|
+
}
|
|
1124
|
+
/** Width of the track tab area in pixels (from --timeline-tab-width CSS var) */
|
|
1125
|
+
get tabWidth() {
|
|
1126
|
+
return this.#tabWidth;
|
|
1127
|
+
}
|
|
1128
|
+
#getViewportWidth() {
|
|
1129
|
+
return this.#vcanvas?.viewportWidth ?? this.#vcanvas?.clientWidth ?? 800;
|
|
1130
|
+
}
|
|
1131
|
+
/** Minimum ppu: fits the full total range into the viewport */
|
|
1132
|
+
#minPpu() {
|
|
1133
|
+
const totalSpan = this.max - this.min;
|
|
1134
|
+
if (totalSpan <= 0) return 1;
|
|
1135
|
+
return this.#getViewportWidth() / totalSpan;
|
|
1136
|
+
}
|
|
1137
|
+
/** Available vertical pixels for track content (excludes axis) */
|
|
1138
|
+
#getViewportHeight() {
|
|
1139
|
+
return this.#vcanvas?.viewportHeight ?? this.#vcanvas?.clientHeight ?? 300;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Total base height: sum of all visible track base heights (before vScale).
|
|
1143
|
+
* This is the "total range" for the vertical slider.
|
|
1144
|
+
*/
|
|
1145
|
+
#getTotalBaseHeight() {
|
|
1146
|
+
let total = 0;
|
|
1147
|
+
for (const track of this._internalTracks) {
|
|
1148
|
+
if (track.hidden) continue;
|
|
1149
|
+
const override = this._trackHeightOverrides.get(track.key);
|
|
1150
|
+
total += override ?? track.height ?? this.defaultTrackHeight;
|
|
1151
|
+
}
|
|
1152
|
+
return total || 1;
|
|
1153
|
+
}
|
|
1154
|
+
/** Smallest base height among visible tracks (before vScale) */
|
|
1155
|
+
#getSmallestBaseTrackHeight() {
|
|
1156
|
+
let smallest = Infinity;
|
|
1157
|
+
for (const track of this._internalTracks) {
|
|
1158
|
+
if (track.hidden) continue;
|
|
1159
|
+
const h = this._trackHeightOverrides.get(track.key) ?? track.height ?? this.defaultTrackHeight;
|
|
1160
|
+
if (h < smallest) smallest = h;
|
|
1161
|
+
}
|
|
1162
|
+
return smallest === Infinity ? this.defaultTrackHeight : smallest;
|
|
1163
|
+
}
|
|
1164
|
+
/** Largest base height among visible tracks (before vScale) */
|
|
1165
|
+
#getLargestBaseTrackHeight() {
|
|
1166
|
+
let largest = 0;
|
|
1167
|
+
for (const track of this._internalTracks) {
|
|
1168
|
+
if (track.hidden) continue;
|
|
1169
|
+
const h = this._trackHeightOverrides.get(track.key) ?? track.height ?? this.defaultTrackHeight;
|
|
1170
|
+
if (h > largest) largest = h;
|
|
1171
|
+
}
|
|
1172
|
+
return largest || this.defaultTrackHeight;
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Derive vScale and vViewRange from a given vertical view range.
|
|
1176
|
+
* viewportHeight / span = scale factor applied to base track heights.
|
|
1177
|
+
* The axis header takes up fixed space, so available height for tracks is reduced.
|
|
1178
|
+
*/
|
|
1179
|
+
#deriveVerticalZoom(vMin, vMax) {
|
|
1180
|
+
const totalBase = this.#getTotalBaseHeight();
|
|
1181
|
+
const vpHeight = this.#getViewportHeight() - this.#axisHeight;
|
|
1182
|
+
let a = vMin;
|
|
1183
|
+
let b = vMax;
|
|
1184
|
+
const largestBase = this.#getLargestBaseTrackHeight();
|
|
1185
|
+
const minSpan = Math.max(largestBase, totalBase * .05);
|
|
1186
|
+
const smallestBase = this.#getSmallestBaseTrackHeight();
|
|
1187
|
+
const maxSpanFromMinHeight = vpHeight > 0 ? vpHeight * smallestBase / MIN_TRACK_RENDER_HEIGHT : totalBase;
|
|
1188
|
+
const maxSpan = Math.min(totalBase, maxSpanFromMinHeight);
|
|
1189
|
+
let span = b - a;
|
|
1190
|
+
if (span < minSpan) {
|
|
1191
|
+
const center = (a + b) / 2;
|
|
1192
|
+
a = center - minSpan / 2;
|
|
1193
|
+
b = center + minSpan / 2;
|
|
1194
|
+
span = minSpan;
|
|
1195
|
+
}
|
|
1196
|
+
if (span > maxSpan) {
|
|
1197
|
+
const center = (a + b) / 2;
|
|
1198
|
+
a = center - maxSpan / 2;
|
|
1199
|
+
b = center + maxSpan / 2;
|
|
1200
|
+
span = maxSpan;
|
|
1201
|
+
}
|
|
1202
|
+
if (b > totalBase) {
|
|
1203
|
+
b = totalBase;
|
|
1204
|
+
a = Math.max(0, b - span);
|
|
1205
|
+
}
|
|
1206
|
+
if (a < 0) {
|
|
1207
|
+
a = 0;
|
|
1208
|
+
b = Math.min(totalBase, a + span);
|
|
1209
|
+
}
|
|
1210
|
+
this._vViewRange = {
|
|
1211
|
+
min: a,
|
|
1212
|
+
max: b
|
|
1213
|
+
};
|
|
1214
|
+
this._vScale = vpHeight > 0 ? vpHeight / (b - a) : 1;
|
|
1215
|
+
}
|
|
1216
|
+
/** Reset vertical zoom to fit all tracks */
|
|
1217
|
+
#initVerticalZoomToFit() {
|
|
1218
|
+
const totalBase = this.#getTotalBaseHeight();
|
|
1219
|
+
this.#deriveVerticalZoom(0, totalBase);
|
|
1220
|
+
}
|
|
1221
|
+
/** Pixel offset to scroll the content area */
|
|
1222
|
+
get #vScrollOffset() {
|
|
1223
|
+
return this._vViewRange.min * this._vScale;
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Derive viewRange from ppu + viewMin, clamped to [min, max].
|
|
1227
|
+
* Returns a new ViewRange.
|
|
1228
|
+
*/
|
|
1229
|
+
#deriveViewRange(viewMin, ppu) {
|
|
1230
|
+
const vpWidth = this.#getViewportWidth();
|
|
1231
|
+
const viewSpan = ppu > 0 ? vpWidth / ppu : this.max - this.min;
|
|
1232
|
+
let newMin = viewMin;
|
|
1233
|
+
let newMax = newMin + viewSpan;
|
|
1234
|
+
if (newMax > this.max) {
|
|
1235
|
+
newMax = this.max;
|
|
1236
|
+
newMin = Math.max(this.min, newMax - viewSpan);
|
|
1237
|
+
}
|
|
1238
|
+
if (newMin < this.min) {
|
|
1239
|
+
newMin = this.min;
|
|
1240
|
+
newMax = Math.min(this.max, newMin + viewSpan);
|
|
1241
|
+
}
|
|
1242
|
+
return {
|
|
1243
|
+
min: newMin,
|
|
1244
|
+
max: newMax
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
/** Set the zoom level (pixels per logical unit) */
|
|
1248
|
+
setPpu(ppu) {
|
|
1249
|
+
this._ppu = Math.max(this.#minPpu(), Math.min(ppu, MAX_PPU));
|
|
1250
|
+
this._viewRange = this.#deriveViewRange(this._viewRange.min, this._ppu);
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Set the view range. ppu is back-computed from the span.
|
|
1254
|
+
* Use for programmatic view changes (e.g. TimelineController).
|
|
1255
|
+
*/
|
|
1256
|
+
setViewRange(range) {
|
|
1257
|
+
const vpWidth = this.#getViewportWidth();
|
|
1258
|
+
const span = range.max - range.min;
|
|
1259
|
+
if (span > 0) this._ppu = Math.max(this.#minPpu(), Math.min(vpWidth / span, MAX_PPU));
|
|
1260
|
+
this._viewRange = this.#deriveViewRange(range.min, this._ppu);
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Set zoom + pan atomically. Used by axis-zoom interaction.
|
|
1264
|
+
* viewMin is the left edge in timeline units; ppu is the new zoom level.
|
|
1265
|
+
*/
|
|
1266
|
+
setZoomPan(viewMin, ppu) {
|
|
1267
|
+
this._ppu = Math.max(this.#minPpu(), Math.min(ppu, MAX_PPU));
|
|
1268
|
+
this._viewRange = this.#deriveViewRange(viewMin, this._ppu);
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Set vertical zoom + pan atomically. Used by v-axis-zoom interaction.
|
|
1272
|
+
* vMin/vMax define the vertical view range in base-height units.
|
|
1273
|
+
*/
|
|
1274
|
+
setVerticalZoomPan(vMin, vMax) {
|
|
1275
|
+
this.#deriveVerticalZoom(vMin, vMax);
|
|
1276
|
+
}
|
|
1277
|
+
selectAll() {
|
|
1278
|
+
this._selectedIds = new Set(this._internalEvents.map((e) => e.id));
|
|
1279
|
+
this.#fireSelectionChange();
|
|
1280
|
+
this.#invalidateCanvas();
|
|
1281
|
+
}
|
|
1282
|
+
deselectAll() {
|
|
1283
|
+
this._selectedIds = /* @__PURE__ */ new Set();
|
|
1284
|
+
this.#fireSelectionChange();
|
|
1285
|
+
this.#invalidateCanvas();
|
|
1286
|
+
}
|
|
1287
|
+
getTrackEvents(trackKey) {
|
|
1288
|
+
return this._internalEvents.filter((e) => e.trackKey === trackKey);
|
|
1289
|
+
}
|
|
1290
|
+
get selectedIds() {
|
|
1291
|
+
return [...this._selectedIds];
|
|
1292
|
+
}
|
|
1293
|
+
set selectedIds(ids) {
|
|
1294
|
+
this._selectedIds = new Set(ids);
|
|
1295
|
+
this.#fireSelectionChange();
|
|
1296
|
+
this.#invalidateCanvas();
|
|
1297
|
+
}
|
|
1298
|
+
selectTrack(key, shiftKey = false) {
|
|
1299
|
+
this.dispatchEvent(new CustomEvent(`track-select`, {
|
|
1300
|
+
detail: {
|
|
1301
|
+
trackKey: key,
|
|
1302
|
+
shiftKey
|
|
1303
|
+
},
|
|
1304
|
+
bubbles: true,
|
|
1305
|
+
composed: true
|
|
1306
|
+
}));
|
|
1307
|
+
}
|
|
1308
|
+
/** Resets all track height overrides to the default height and resets vertical zoom. */
|
|
1309
|
+
resetTrackHeights() {
|
|
1310
|
+
this._trackHeightOverrides = /* @__PURE__ */ new Map();
|
|
1311
|
+
this.#pendingVerticalZoomToFit = true;
|
|
1312
|
+
}
|
|
1313
|
+
static {
|
|
1314
|
+
this.defaultDragSnapping = () => ({});
|
|
1315
|
+
}
|
|
1316
|
+
static {
|
|
1317
|
+
this.gridSnapping = (detail) => {
|
|
1318
|
+
const { gridResolution, currentStart, currentEnd } = detail;
|
|
1319
|
+
const { minorStep } = gridResolution;
|
|
1320
|
+
return {
|
|
1321
|
+
start: Math.round(currentStart / minorStep) * minorStep,
|
|
1322
|
+
end: Math.round(currentEnd / minorStep) * minorStep
|
|
1323
|
+
};
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Called by timeline-interactions during drag.
|
|
1328
|
+
* Applies onDragging callback if set, then fires 'dragging' event.
|
|
1329
|
+
* Returns constrained values.
|
|
1330
|
+
*/
|
|
1331
|
+
processDrag(eventId, rawLogicalX, rawY, currentStart, currentEnd, currentTrackKey, originTrackKey, gridResolution) {
|
|
1332
|
+
let constrainedStart = currentStart;
|
|
1333
|
+
let constrainedEnd = currentEnd;
|
|
1334
|
+
let constrainedTrackKey = currentTrackKey;
|
|
1335
|
+
if (this.onDragging) {
|
|
1336
|
+
const result = this.onDragging({
|
|
1337
|
+
eventId,
|
|
1338
|
+
rawLogicalX,
|
|
1339
|
+
rawY,
|
|
1340
|
+
currentStart,
|
|
1341
|
+
currentEnd,
|
|
1342
|
+
currentTrackKey,
|
|
1343
|
+
gridResolution,
|
|
1344
|
+
originTrackKey
|
|
1345
|
+
});
|
|
1346
|
+
constrainedStart = result.start ?? currentStart;
|
|
1347
|
+
constrainedEnd = result.end ?? currentEnd;
|
|
1348
|
+
constrainedTrackKey = result.trackKey ?? currentTrackKey;
|
|
1349
|
+
}
|
|
1350
|
+
this.dispatchEvent(new CustomEvent(`dragging`, {
|
|
1351
|
+
detail: {
|
|
1352
|
+
eventId,
|
|
1353
|
+
start: constrainedStart,
|
|
1354
|
+
end: constrainedEnd,
|
|
1355
|
+
trackKey: constrainedTrackKey,
|
|
1356
|
+
rawLogicalX,
|
|
1357
|
+
rawY,
|
|
1358
|
+
originTrackKey
|
|
1359
|
+
},
|
|
1360
|
+
bubbles: true,
|
|
1361
|
+
composed: true
|
|
1362
|
+
}));
|
|
1363
|
+
return {
|
|
1364
|
+
start: constrainedStart,
|
|
1365
|
+
end: constrainedEnd,
|
|
1366
|
+
trackKey: constrainedTrackKey
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
/** Returns the vcanvas element (for timeline-interactions) */
|
|
1370
|
+
getVCanvas() {
|
|
1371
|
+
return this.#vcanvas;
|
|
1372
|
+
}
|
|
1373
|
+
/** Returns the currently hovered (tickled) event, or null if none. */
|
|
1374
|
+
get tickledEvent() {
|
|
1375
|
+
if (this._tickledId === void 0) return null;
|
|
1376
|
+
return this._internalEvents.find((e) => e.id === this._tickledId) ?? null;
|
|
1377
|
+
}
|
|
1378
|
+
firstUpdated() {
|
|
1379
|
+
this.#vcanvas = this.shadowRoot?.querySelector(`x-vcanvas`);
|
|
1380
|
+
if (this.#vcanvas) {
|
|
1381
|
+
this.#vcanvas.drawCallback = this.#drawCallback;
|
|
1382
|
+
this.#vcanvas.addEventListener(`scroll-virtual`, this.#onScrollVirtual);
|
|
1383
|
+
this.#cleanupInteractions = attachTimelineInteractions(this, this.#vcanvas);
|
|
1384
|
+
this.#resizeObserver = new ResizeObserver(this.#onResize);
|
|
1385
|
+
this.#resizeObserver.observe(this.#vcanvas);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
disconnectedCallback() {
|
|
1389
|
+
super.disconnectedCallback();
|
|
1390
|
+
this.#cleanupInteractions?.();
|
|
1391
|
+
this.#cleanupInteractions = null;
|
|
1392
|
+
this.#resizeObserver?.disconnect();
|
|
1393
|
+
this.#resizeObserver = null;
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* willUpdate runs before render — safe to set @state() properties here
|
|
1397
|
+
* without triggering an additional update cycle. All state derivation
|
|
1398
|
+
* (internal copies, track layout, vertical zoom) happens here.
|
|
1399
|
+
*/
|
|
1400
|
+
willUpdate(changedProps) {
|
|
1401
|
+
let needsLayoutRecompute = false;
|
|
1402
|
+
let needsVerticalZoomReset = false;
|
|
1403
|
+
let needsHorizontalZoomToFit = false;
|
|
1404
|
+
if (changedProps.has(`tracks`)) {
|
|
1405
|
+
this._internalTracks = [...this.tracks];
|
|
1406
|
+
needsLayoutRecompute = true;
|
|
1407
|
+
needsVerticalZoomReset = true;
|
|
1408
|
+
}
|
|
1409
|
+
if (changedProps.has(`events`)) this._internalEvents = [...this.events];
|
|
1410
|
+
if (changedProps.has(`trackHeight`)) {
|
|
1411
|
+
if (this.trackHeight !== `free` && this._trackHeightOverrides.size > 0) this._trackHeightOverrides = /* @__PURE__ */ new Map();
|
|
1412
|
+
needsLayoutRecompute = true;
|
|
1413
|
+
needsVerticalZoomReset = true;
|
|
1414
|
+
}
|
|
1415
|
+
if (changedProps.has(`_vScale`) || changedProps.has(`defaultTrackHeight`) || changedProps.has(`_trackHeightOverrides`)) needsLayoutRecompute = true;
|
|
1416
|
+
if (changedProps.has(`min`) || changedProps.has(`max`)) needsHorizontalZoomToFit = true;
|
|
1417
|
+
if (this.#pendingVerticalZoomToFit) {
|
|
1418
|
+
this.#pendingVerticalZoomToFit = false;
|
|
1419
|
+
needsVerticalZoomReset = true;
|
|
1420
|
+
}
|
|
1421
|
+
if (needsHorizontalZoomToFit) this.#initZoomToFit();
|
|
1422
|
+
if (needsVerticalZoomReset) this.#initVerticalZoomToFit();
|
|
1423
|
+
if (needsLayoutRecompute) this.#recomputeTrackLayout();
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* updated runs after render — for DOM side-effects only (syncing vcanvas,
|
|
1427
|
+
* scrolling, invalidating canvas, dispatching events).
|
|
1428
|
+
*/
|
|
1429
|
+
updated(changedProps) {
|
|
1430
|
+
if (changedProps.has(`_ppu`) || changedProps.has(`_viewRange`) || changedProps.has(`min`) || changedProps.has(`max`)) {
|
|
1431
|
+
this.#syncVCanvas();
|
|
1432
|
+
this.#invalidateCanvas();
|
|
1433
|
+
}
|
|
1434
|
+
if (changedProps.has(`events`) || changedProps.has(`userScale`) || changedProps.has(`altMajorBg`) || changedProps.has(`gridMode`) || changedProps.has(`minorStep`)) this.#invalidateCanvas();
|
|
1435
|
+
if (changedProps.has(`_trackLayout`)) {
|
|
1436
|
+
this.#syncVCanvasVertical();
|
|
1437
|
+
this.#positionTabs();
|
|
1438
|
+
this.#invalidateCanvas();
|
|
1439
|
+
}
|
|
1440
|
+
if (changedProps.has(`_vViewRange`) || changedProps.has(`_vScale`)) {
|
|
1441
|
+
this.#syncVerticalScroll();
|
|
1442
|
+
this.#invalidateCanvas();
|
|
1443
|
+
}
|
|
1444
|
+
if (this._tickledId !== this.#prevTickledId) {
|
|
1445
|
+
this.#prevTickledId = this._tickledId;
|
|
1446
|
+
this.dispatchEvent(new CustomEvent(`tickle-change`, {
|
|
1447
|
+
detail: { event: this.tickledEvent },
|
|
1448
|
+
bubbles: true,
|
|
1449
|
+
composed: true
|
|
1450
|
+
}));
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
render() {
|
|
1454
|
+
const totalBaseHeight = this.#getTotalBaseHeight();
|
|
1455
|
+
return html`
|
|
1456
|
+
<div class="content-wrapper">
|
|
1457
|
+
<div class="content-area">
|
|
1458
|
+
<div class="v-axis-zone"
|
|
1459
|
+
@pointerdown=${this.#onVAxisPointerDown}
|
|
1460
|
+
@pointermove=${this.#onVAxisPointerMove}
|
|
1461
|
+
@pointerup=${this.#onVAxisPointerUp}
|
|
1462
|
+
@pointercancel=${this.#onVAxisPointerUp}
|
|
1463
|
+
></div>
|
|
1464
|
+
<div class="tab-column">
|
|
1465
|
+
${this._internalTracks.filter((t) => !t.hidden).map((t) => {
|
|
1466
|
+
if (!this._trackLayout.find((l) => l.trackKey === t.key)) return nothing;
|
|
1467
|
+
return html`
|
|
1468
|
+
<ixfx-timeline-track-tab
|
|
1469
|
+
.label=${t.label}
|
|
1470
|
+
track-key="${t.key}"
|
|
1471
|
+
?resizable=${this.trackHeight !== `none`}
|
|
1472
|
+
?reorderable=${this.reorderable}
|
|
1473
|
+
@click=${(e) => this.selectTrack(t.key, e.shiftKey)}
|
|
1474
|
+
@track-resize-move=${this.#onTrackResizeMove}
|
|
1475
|
+
@track-resize=${this.#onTrackResizeEnd}
|
|
1476
|
+
></ixfx-timeline-track-tab>
|
|
1477
|
+
`;
|
|
1478
|
+
})}
|
|
1479
|
+
</div>
|
|
1480
|
+
<x-vcanvas></x-vcanvas>
|
|
1481
|
+
</div>
|
|
1482
|
+
<div class="v-zoom">
|
|
1483
|
+
<ixfx-range-multi
|
|
1484
|
+
orientation="vertical"
|
|
1485
|
+
min="0"
|
|
1486
|
+
max="${totalBaseHeight}"
|
|
1487
|
+
a="${this._vViewRange.min}"
|
|
1488
|
+
b="${this._vViewRange.max}"
|
|
1489
|
+
step="any"
|
|
1490
|
+
crossover="stop"
|
|
1491
|
+
resize-mode="bipolar-flex"
|
|
1492
|
+
@input=${this.#onVZoomInput}
|
|
1493
|
+
></ixfx-range-multi>
|
|
1494
|
+
</div>
|
|
1495
|
+
</div>
|
|
1496
|
+
|
|
1497
|
+
<div class="h-zoom">
|
|
1498
|
+
<ixfx-range-multi
|
|
1499
|
+
min="${this.min}"
|
|
1500
|
+
max="${this.max}"
|
|
1501
|
+
a="${this._viewRange.min}"
|
|
1502
|
+
b="${this._viewRange.max}"
|
|
1503
|
+
crossover="stop"
|
|
1504
|
+
resize-mode="bipolar-flex"
|
|
1505
|
+
@input=${this.#onHZoomInput}
|
|
1506
|
+
></ixfx-range-multi>
|
|
1507
|
+
</div>
|
|
1508
|
+
|
|
1509
|
+
<div class="meta-resizer ${this.metaEnabled ? `visible` : ``}"></div>
|
|
1510
|
+
<div class="meta-area ${this.metaEnabled ? `visible` : ``}" style="height:${this._metaHeight}px"></div>
|
|
1511
|
+
`;
|
|
1512
|
+
}
|
|
1513
|
+
/** Compute initial ppu to fit 80% of the range, then derive viewRange */
|
|
1514
|
+
#initZoomToFit() {
|
|
1515
|
+
const vpWidth = this.#getViewportWidth();
|
|
1516
|
+
const totalSpan = this.max - this.min;
|
|
1517
|
+
const zoomFactor = .8;
|
|
1518
|
+
this._ppu = totalSpan > 0 ? vpWidth / (totalSpan * zoomFactor) : 1;
|
|
1519
|
+
this._viewRange = {
|
|
1520
|
+
min: this.min,
|
|
1521
|
+
max: this.min + totalSpan * zoomFactor
|
|
1522
|
+
};
|
|
1523
|
+
this.#syncVCanvas();
|
|
1524
|
+
}
|
|
1525
|
+
#syncVCanvas() {
|
|
1526
|
+
if (!this.#vcanvas) return;
|
|
1527
|
+
const totalSpan = this.max - this.min;
|
|
1528
|
+
this.#vcanvas.logicalWidth = totalSpan * this._ppu;
|
|
1529
|
+
this.#syncVCanvasVertical();
|
|
1530
|
+
this.#syncVCanvasScroll();
|
|
1531
|
+
}
|
|
1532
|
+
/** Sync logicalHeight and logicalScrollTop to the vcanvas */
|
|
1533
|
+
#syncVCanvasVertical() {
|
|
1534
|
+
if (!this.#vcanvas) return;
|
|
1535
|
+
const totalHeight = this._trackLayout.reduce((sum, tl) => sum + tl.height, 0);
|
|
1536
|
+
const canvasHeight = Math.max(totalHeight, 40) + this.#axisHeight;
|
|
1537
|
+
this.#vcanvas.logicalHeight = canvasHeight;
|
|
1538
|
+
this.#vcanvas.logicalScrollTop = this.#vScrollOffset;
|
|
1539
|
+
}
|
|
1540
|
+
#syncVCanvasScroll() {
|
|
1541
|
+
if (!this.#vcanvas) return;
|
|
1542
|
+
this.#vcanvas.logicalScrollLeft = (this._viewRange.min - this.min) * this._ppu;
|
|
1543
|
+
}
|
|
1544
|
+
#invalidateCanvas() {
|
|
1545
|
+
this.#vcanvas?.invalidateAll();
|
|
1546
|
+
}
|
|
1547
|
+
/** Called by ResizeObserver when vcanvas/content-area changes size */
|
|
1548
|
+
#onResize;
|
|
1549
|
+
#drawCallback;
|
|
1550
|
+
#onScrollVirtual;
|
|
1551
|
+
#recomputeTrackLayout() {
|
|
1552
|
+
const layout = [];
|
|
1553
|
+
let y = this.#axisHeight;
|
|
1554
|
+
for (const track of this._internalTracks) {
|
|
1555
|
+
if (track.hidden) continue;
|
|
1556
|
+
const baseHeight = this._trackHeightOverrides.get(track.key) ?? track.height ?? this.defaultTrackHeight;
|
|
1557
|
+
const height = Math.round(baseHeight * this._vScale);
|
|
1558
|
+
layout.push({
|
|
1559
|
+
trackKey: track.key,
|
|
1560
|
+
y,
|
|
1561
|
+
height
|
|
1562
|
+
});
|
|
1563
|
+
y += height;
|
|
1564
|
+
}
|
|
1565
|
+
this._trackLayout = layout;
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Range slider handler.
|
|
1569
|
+
* Slider controls zoom (via span) + pan (via position).
|
|
1570
|
+
* Derive ppu from the span the slider produces.
|
|
1571
|
+
*/
|
|
1572
|
+
#onHZoomInput;
|
|
1573
|
+
/**
|
|
1574
|
+
* Vertical range slider handler.
|
|
1575
|
+
* Slider controls vertical zoom (via span) + vertical pan (via position).
|
|
1576
|
+
* Derive _vScale from the span the slider produces.
|
|
1577
|
+
*/
|
|
1578
|
+
#onVZoomInput;
|
|
1579
|
+
#onVAxisPointerDown;
|
|
1580
|
+
#onVAxisPointerMove;
|
|
1581
|
+
#onVAxisPointerUp;
|
|
1582
|
+
/** Sync vertical scroll position to vcanvas logicalScrollTop */
|
|
1583
|
+
#syncVerticalScroll() {
|
|
1584
|
+
this.#syncVCanvasVertical();
|
|
1585
|
+
this.#positionTabs();
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Position track tabs in the .tab-column via absolute CSS positioning.
|
|
1589
|
+
* Applies a vertical translateY to offset for the current vertical scroll.
|
|
1590
|
+
*/
|
|
1591
|
+
#positionTabs() {
|
|
1592
|
+
const tabColumn = this.shadowRoot?.querySelector(`.tab-column`);
|
|
1593
|
+
if (!tabColumn) return;
|
|
1594
|
+
const scrollTop = this.#vcanvas?.logicalScrollTop ?? 0;
|
|
1595
|
+
tabColumn.style.transform = `translateY(${-scrollTop}px)`;
|
|
1596
|
+
for (const layout of this._trackLayout) {
|
|
1597
|
+
const tab = tabColumn.querySelector(`ixfx-timeline-track-tab[track-key="${layout.trackKey}"]`);
|
|
1598
|
+
if (!tab) continue;
|
|
1599
|
+
tab.style.top = `${layout.y}px`;
|
|
1600
|
+
tab.style.height = `${layout.height}px`;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
/** Read the tab width from the CSS custom property */
|
|
1604
|
+
get #tabWidth() {
|
|
1605
|
+
const raw = getComputedStyle(this).getPropertyValue(`--timeline-tab-width`).trim();
|
|
1606
|
+
return Number.parseInt(raw, 10) || 120;
|
|
1607
|
+
}
|
|
1608
|
+
/** Live preview during drag — update heights but don't fire public event */
|
|
1609
|
+
#onTrackResizeMove;
|
|
1610
|
+
/** Final resize on pointerup — apply height and fire public event */
|
|
1611
|
+
#onTrackResizeEnd;
|
|
1612
|
+
/**
|
|
1613
|
+
* Apply track height based on the current `trackHeight` mode.
|
|
1614
|
+
* - `fixed`: sets `defaultTrackHeight` (all tracks change uniformly)
|
|
1615
|
+
* - `free`: sets per-track override in `_trackHeightOverrides`
|
|
1616
|
+
* - `none`: should never be called (resize is disabled)
|
|
1617
|
+
*/
|
|
1618
|
+
#applyTrackResize(trackKey, height) {
|
|
1619
|
+
if (this.trackHeight === `fixed`) this.defaultTrackHeight = height;
|
|
1620
|
+
else if (this.trackHeight === `free`) {
|
|
1621
|
+
const next = new Map(this._trackHeightOverrides);
|
|
1622
|
+
next.set(trackKey, height);
|
|
1623
|
+
this._trackHeightOverrides = next;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
#fireSelectionChange() {
|
|
1627
|
+
this.dispatchEvent(new CustomEvent(`selection-change`, {
|
|
1628
|
+
detail: { selectedIds: [...this._selectedIds] },
|
|
1629
|
+
bubbles: true,
|
|
1630
|
+
composed: true
|
|
1631
|
+
}));
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
__decorate([property({ type: Array })], IxfxTimelineElement.prototype, "tracks", void 0);
|
|
1635
|
+
__decorate([property({ type: Array })], IxfxTimelineElement.prototype, "events", void 0);
|
|
1636
|
+
__decorate([property({ type: Number })], IxfxTimelineElement.prototype, "min", void 0);
|
|
1637
|
+
__decorate([property({ type: Number })], IxfxTimelineElement.prototype, "max", void 0);
|
|
1638
|
+
__decorate([property({
|
|
1639
|
+
type: String,
|
|
1640
|
+
attribute: `track-height`
|
|
1641
|
+
})], IxfxTimelineElement.prototype, "trackHeight", void 0);
|
|
1642
|
+
__decorate([property({
|
|
1643
|
+
type: String,
|
|
1644
|
+
attribute: `user-scale`
|
|
1645
|
+
})], IxfxTimelineElement.prototype, "userScale", void 0);
|
|
1646
|
+
__decorate([property({ type: Boolean })], IxfxTimelineElement.prototype, "reorderable", void 0);
|
|
1647
|
+
__decorate([property({
|
|
1648
|
+
type: Boolean,
|
|
1649
|
+
attribute: `meta`
|
|
1650
|
+
})], IxfxTimelineElement.prototype, "metaEnabled", void 0);
|
|
1651
|
+
__decorate([property({
|
|
1652
|
+
type: Boolean,
|
|
1653
|
+
attribute: `alt-major-bg`
|
|
1654
|
+
})], IxfxTimelineElement.prototype, "altMajorBg", void 0);
|
|
1655
|
+
__decorate([property({
|
|
1656
|
+
type: Number,
|
|
1657
|
+
attribute: `default-track-height`
|
|
1658
|
+
})], IxfxTimelineElement.prototype, "defaultTrackHeight", void 0);
|
|
1659
|
+
__decorate([property({
|
|
1660
|
+
type: String,
|
|
1661
|
+
attribute: `grid-mode`
|
|
1662
|
+
})], IxfxTimelineElement.prototype, "gridMode", void 0);
|
|
1663
|
+
__decorate([property({
|
|
1664
|
+
type: Number,
|
|
1665
|
+
attribute: `minor-step`
|
|
1666
|
+
})], IxfxTimelineElement.prototype, "minorStep", void 0);
|
|
1667
|
+
__decorate([property({ type: Object })], IxfxTimelineElement.prototype, "onDragging", void 0);
|
|
1668
|
+
__decorate([state()], IxfxTimelineElement.prototype, "_ppu", void 0);
|
|
1669
|
+
__decorate([state()], IxfxTimelineElement.prototype, "_viewRange", void 0);
|
|
1670
|
+
__decorate([state()], IxfxTimelineElement.prototype, "_vViewRange", void 0);
|
|
1671
|
+
__decorate([state()], IxfxTimelineElement.prototype, "_vScale", void 0);
|
|
1672
|
+
__decorate([state()], IxfxTimelineElement.prototype, "_selectedIds", void 0);
|
|
1673
|
+
__decorate([state()], IxfxTimelineElement.prototype, "_tickledId", void 0);
|
|
1674
|
+
__decorate([state()], IxfxTimelineElement.prototype, "_drag", void 0);
|
|
1675
|
+
__decorate([state()], IxfxTimelineElement.prototype, "_metaHeight", void 0);
|
|
1676
|
+
__decorate([state()], IxfxTimelineElement.prototype, "_trackHeightOverrides", void 0);
|
|
1677
|
+
__decorate([state()], IxfxTimelineElement.prototype, "_trackLayout", void 0);
|
|
1678
|
+
__decorate([state()], IxfxTimelineElement.prototype, "_internalEvents", void 0);
|
|
1679
|
+
__decorate([state()], IxfxTimelineElement.prototype, "_internalTracks", void 0);
|
|
1680
|
+
IxfxTimelineElement = __decorate([customElement(`ixfx-timeline`)], IxfxTimelineElement);
|
|
1681
|
+
//#endregion
|
|
1682
|
+
//#region src/timeline/timeline-controller.ts
|
|
1683
|
+
/**
|
|
1684
|
+
* External adapter class that can configure a timeline element
|
|
1685
|
+
* with musical time, custom label formatting, etc.
|
|
1686
|
+
*
|
|
1687
|
+
* The controller has no import of any timeline element type.
|
|
1688
|
+
* Users create a controller, pass it the component instance,
|
|
1689
|
+
* and the controller calls the component's public API.
|
|
1690
|
+
*/
|
|
1691
|
+
var TimelineController = class {
|
|
1692
|
+
#host;
|
|
1693
|
+
#config;
|
|
1694
|
+
#bpm = 120;
|
|
1695
|
+
#timeSignatureNumerator = 4;
|
|
1696
|
+
#timeSignatureDenominator = 4;
|
|
1697
|
+
constructor(timeline, config) {
|
|
1698
|
+
this.#host = timeline;
|
|
1699
|
+
this.#config = config;
|
|
1700
|
+
timeline.min = config.logicalMin;
|
|
1701
|
+
timeline.max = config.logicalMax;
|
|
1702
|
+
if (config.labelFormatter) timeline.formatLabel = config.labelFormatter;
|
|
1703
|
+
timeline.addEventListener(`track-select`, this.#onTrackSelect);
|
|
1704
|
+
}
|
|
1705
|
+
/** Update the tempo (BPM) for musical time calculations. */
|
|
1706
|
+
setTempo(bpm) {
|
|
1707
|
+
this.#bpm = bpm;
|
|
1708
|
+
this.#updateFormatLabel();
|
|
1709
|
+
}
|
|
1710
|
+
/** Update the time signature for musical time calculations. */
|
|
1711
|
+
setTimeSignature(numerator, denominator) {
|
|
1712
|
+
this.#timeSignatureNumerator = numerator;
|
|
1713
|
+
this.#timeSignatureDenominator = denominator;
|
|
1714
|
+
this.#updateFormatLabel();
|
|
1715
|
+
}
|
|
1716
|
+
/** Set the visible view range on the timeline. */
|
|
1717
|
+
setViewRange(range) {
|
|
1718
|
+
this.#host.setViewRange(range);
|
|
1719
|
+
}
|
|
1720
|
+
/** Clean up any resources held by the controller. */
|
|
1721
|
+
dispose() {
|
|
1722
|
+
this.#host.removeEventListener(`track-select`, this.#onTrackSelect);
|
|
1723
|
+
}
|
|
1724
|
+
#onTrackSelect = (e) => {
|
|
1725
|
+
const detail = e.detail;
|
|
1726
|
+
const trackEventIds = this.#host.getTrackEvents(detail.trackKey).map((e) => e.id);
|
|
1727
|
+
if (detail.shiftKey) {
|
|
1728
|
+
const current = new Set(this.#host.selectedIds);
|
|
1729
|
+
for (const id of trackEventIds) if (current.has(id)) current.delete(id);
|
|
1730
|
+
else current.add(id);
|
|
1731
|
+
this.#host.selectedIds = [...current];
|
|
1732
|
+
} else this.#host.selectedIds = trackEventIds;
|
|
1733
|
+
};
|
|
1734
|
+
#updateFormatLabel() {
|
|
1735
|
+
if (this.#config.labelFormatter) return;
|
|
1736
|
+
this.#bpm;
|
|
1737
|
+
const num = this.#timeSignatureNumerator;
|
|
1738
|
+
this.#host.formatLabel = (v) => {
|
|
1739
|
+
const beatsPerBar = num;
|
|
1740
|
+
const totalBeats = v;
|
|
1741
|
+
return `${Math.floor(totalBeats / beatsPerBar) + 1}.${Math.floor(totalBeats % beatsPerBar) + 1}`;
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1745
|
+
//#endregion
|
|
1746
|
+
export { attachTimelineInteractions as a, timelineStyles as i, IxfxTimelineElement as n, resolveCssVars as o, TimelineTrackTabElement as r, resolveGrid as s, TimelineController as t };
|
|
1747
|
+
|
|
1748
|
+
//# sourceMappingURL=timeline-DBWJsSjd.js.map
|