@andymcloid/trakk 1.0.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/LICENSE +21 -0
- package/README.md +211 -0
- package/dist/trakk.css +1 -0
- package/dist/trakk.css.map +1 -0
- package/dist/trakk.esm.js +2 -0
- package/dist/trakk.esm.js.map +1 -0
- package/dist/trakk.js +2 -0
- package/dist/trakk.js.map +1 -0
- package/package.json +63 -0
- package/src/trakk-engine.js +276 -0
- package/src/trakk.css +377 -0
- package/src/trakk.js +1783 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/logo.svg" alt="trakk" width="320">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>Lightweight timeline editor web component</strong><br>
|
|
7
|
+
Zero dependencies. Works everywhere.
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="#installation">Installation</a> •
|
|
12
|
+
<a href="#usage">Usage</a> •
|
|
13
|
+
<a href="#api">API</a> •
|
|
14
|
+
<a href="#events">Events</a> •
|
|
15
|
+
<a href="#demo">Demo</a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img src="assets/demo.gif" alt="trakk demo" width="700">
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## What is it?
|
|
25
|
+
|
|
26
|
+
A timeline editor for building animation tools, video editors, audio sequencers, or anything that needs time-based sequencing. Built as a native Web Component with zero dependencies.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @andymcloid/trakk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or use directly via CDN:
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<script type="module" src="https://unpkg.com/@andymcloid/trakk/dist/trakk.esm.js"></script>
|
|
38
|
+
<link rel="stylesheet" href="https://unpkg.com/@andymcloid/trakk/dist/trakk.css">
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<trakk-editor id="timeline"></trakk-editor>
|
|
45
|
+
|
|
46
|
+
<script type="module">
|
|
47
|
+
import { Trakk } from '@andymcloid/trakk';
|
|
48
|
+
import '@andymcloid/trakk/css';
|
|
49
|
+
|
|
50
|
+
const timeline = document.getElementById('timeline');
|
|
51
|
+
|
|
52
|
+
timeline.setData([
|
|
53
|
+
{
|
|
54
|
+
id: 'track-1',
|
|
55
|
+
name: 'Audio',
|
|
56
|
+
blocks: [
|
|
57
|
+
{ id: 'block-1', name: 'Intro', start: 0, end: 5 },
|
|
58
|
+
{ id: 'block-2', name: 'Main', start: 6, end: 15 }
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'track-2',
|
|
63
|
+
name: 'Video',
|
|
64
|
+
blocks: [
|
|
65
|
+
{ id: 'block-3', name: 'Scene 1', start: 0, end: 10 }
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
// Play/pause
|
|
71
|
+
timeline.play({ autoEnd: true });
|
|
72
|
+
timeline.pause();
|
|
73
|
+
|
|
74
|
+
// Listen for changes
|
|
75
|
+
timeline.addEventListener('change', (e) => {
|
|
76
|
+
console.log('Updated:', e.detail.tracks);
|
|
77
|
+
});
|
|
78
|
+
</script>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
|
|
83
|
+
### Methods
|
|
84
|
+
|
|
85
|
+
| Method | Description |
|
|
86
|
+
|--------|-------------|
|
|
87
|
+
| `setData(tracks)` | Set timeline data |
|
|
88
|
+
| `play(options?)` | Start playback. Options: `{ autoEnd: boolean, toTime: number }` |
|
|
89
|
+
| `pause()` | Pause playback |
|
|
90
|
+
| `setTime(time)` | Set current time position |
|
|
91
|
+
| `getTime()` | Get current time |
|
|
92
|
+
| `getTotalTime()` | Get total duration (end of last block) |
|
|
93
|
+
| `setConfig(config)` | Update configuration |
|
|
94
|
+
| `saveToLocalStorage(key?)` | Save to localStorage |
|
|
95
|
+
| `loadFromLocalStorage(key?)` | Load from localStorage |
|
|
96
|
+
|
|
97
|
+
### Configuration
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
timeline.setConfig({
|
|
101
|
+
scale: 1, // Seconds per scale unit
|
|
102
|
+
scaleWidth: 160, // Pixels per scale unit
|
|
103
|
+
scaleCount: 20, // Number of scale units
|
|
104
|
+
startLeft: 100, // Left margin (label column width)
|
|
105
|
+
rowHeight: 32, // Track height in pixels
|
|
106
|
+
autoScroll: true, // Auto-scroll during playback
|
|
107
|
+
hideCursor: false, // Hide playhead cursor
|
|
108
|
+
disableDrag: false // Disable all dragging
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Data Structure
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
interface Track {
|
|
116
|
+
id: string;
|
|
117
|
+
name?: string;
|
|
118
|
+
locked?: boolean; // Prevent editing
|
|
119
|
+
blocks: Block[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface Block {
|
|
123
|
+
id: string;
|
|
124
|
+
name?: string;
|
|
125
|
+
start: number; // Start time in seconds
|
|
126
|
+
end: number; // End time in seconds
|
|
127
|
+
locked?: boolean; // Prevent editing this block
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Events
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
// Data changed (drag, resize, create, delete)
|
|
135
|
+
timeline.addEventListener('change', (e) => {
|
|
136
|
+
console.log(e.detail.tracks);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// New block created via drag
|
|
140
|
+
timeline.addEventListener('itemcreated', (e) => {
|
|
141
|
+
console.log(e.detail.item, e.detail.row);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Block deleted
|
|
145
|
+
timeline.addEventListener('blockdeleted', (e) => {
|
|
146
|
+
console.log(e.detail.block, e.detail.track);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Track deleted
|
|
150
|
+
timeline.addEventListener('trackdeleted', (e) => {
|
|
151
|
+
console.log(e.detail.track);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Track/block renamed (double-click to edit)
|
|
155
|
+
timeline.addEventListener('trackrenamed', (e) => {
|
|
156
|
+
console.log(e.detail.track, e.detail.name);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
timeline.addEventListener('blockrenamed', (e) => {
|
|
160
|
+
console.log(e.detail.block, e.detail.name);
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Engine Events
|
|
165
|
+
|
|
166
|
+
Access the playback engine directly:
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
timeline.engine.on('play', () => console.log('Playing'));
|
|
170
|
+
timeline.engine.on('paused', () => console.log('Paused'));
|
|
171
|
+
timeline.engine.on('ended', () => console.log('Ended'));
|
|
172
|
+
|
|
173
|
+
timeline.engine.on('setTimeByTick', ({ time }) => {
|
|
174
|
+
// Called every frame during playback
|
|
175
|
+
console.log('Current time:', time);
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Callbacks
|
|
180
|
+
|
|
181
|
+
For advanced control over interactions:
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
timeline.setCallbacks({
|
|
185
|
+
// Custom rendering
|
|
186
|
+
getActionRender: (block, track) => `<b>${block.name}</b>`,
|
|
187
|
+
getScaleRender: (time) => `${time.toFixed(1)}s`,
|
|
188
|
+
|
|
189
|
+
// Interaction hooks (return false to cancel)
|
|
190
|
+
onActionMoving: ({ action, start, end }) => {
|
|
191
|
+
if (start < 0) return false; // Prevent moving before 0
|
|
192
|
+
},
|
|
193
|
+
onActionResizing: ({ action, start, end }) => {
|
|
194
|
+
if (end - start < 0.5) return false; // Minimum 0.5s duration
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
// Click handlers
|
|
198
|
+
onClickAction: (e, { action, row, time }) => {},
|
|
199
|
+
onDoubleClickAction: (e, { action, row }) => {},
|
|
200
|
+
onClickRow: (e, { row, time }) => {},
|
|
201
|
+
onClickTimeArea: (e, { time }) => {}
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Demo
|
|
206
|
+
|
|
207
|
+
Open `demo.html` in a browser or check out the [live demo](https://sidcom-ab.github.io/trakk).
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
MIT
|
package/dist/trakk.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.timeline-editor{height:100%;width:100%;min-height:200px;position:relative;font-size:12px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;background-color:#191b1d;display:flex;flex-direction:column;overflow:hidden;color:#fff;box-sizing:border-box;user-select:none}.timeline-editor-time-area{position:relative;height:32px;flex:0 0 auto;background-color:#191b1d;overflow:hidden;z-index:150}.timeline-editor-time-area-wrapper{position:relative;height:100%}.timeline-editor-time-area-interact{position:absolute;cursor:pointer;left:0;top:0;height:100%;display:flex;align-items:flex-end}.timeline-editor-time-unit{border-right:1px solid rgba(255,255,255,.15);position:relative;box-sizing:border-box;height:6px;bottom:0;flex-shrink:0}.timeline-editor-time-unit-big{height:12px;border-right:1px solid rgba(255,255,255,.3)}.timeline-editor-time-unit-scale{color:rgba(255,255,255,.6);position:absolute;right:0;top:0;transform:translate(50%,-100%);padding-bottom:4px;font-size:11px}.timeline-editor-edit-area{flex:1 1 0;margin-top:0;overflow:auto;position:relative;min-height:0;min-width:0;height:100%;width:0;scrollbar-color:rgba(255,255,255,0.3) rgba(0,0,0,0.2);scrollbar-width:auto}.timeline-editor-rows{position:relative;z-index:1}.timeline-editor-edit-area::-webkit-scrollbar{width:14px;height:14px}.timeline-editor-edit-area::-webkit-scrollbar-track{background-color:rgba(0,0,0,.2);border-radius:10px}.timeline-editor-edit-area::-webkit-scrollbar-thumb{background-color:rgba(255,255,255,.3);border-radius:10px}.timeline-editor-edit-area::-webkit-scrollbar-thumb:hover{background-color:rgba(255,255,255,.45)}.timeline-editor-edit-area::-webkit-scrollbar-corner{background-color:rgba(0,0,0,.2)}.timeline-editor-edit-row{--timeline-scale-width:160px;background-repeat:no-repeat,repeat;background-image:linear-gradient(#191b1d,#191b1d),linear-gradient(90deg,rgba(255,255,255,.08) 1px,transparent 0);background-size:var(--timeline-scale-width) 100%;display:flex;flex-direction:row;box-sizing:border-box;position:relative;border-bottom:1px solid rgba(255,255,255,.1);min-height:32px}.timeline-editor-edit-row:first-child{border-top:1px solid rgba(255,255,255,.1)}.timeline-editor-edit-row::before{content:'';position:absolute;top:0;left:0;right:0;bottom:0;background-color:transparent;pointer-events:none;transition:background-color .1s}.timeline-editor-edit-row:hover::before{background-color:rgba(255,255,255,.02)}.timeline-editor-row-label{position:absolute;left:0;top:0;height:100%;display:flex;align-items:center;padding:0 8px;color:rgba(255,255,255,.7);font-size:12px;font-weight:500;background-color:#191b1d;border-right:1px solid rgba(255,255,255,.1);z-index:2;pointer-events:auto}.timeline-editor-row-label:hover{color:rgba(255,255,255,.9)}.timeline-editor-row-label-locked::after{content:'';margin-left:6px;width:10px;height:10px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='rgba(255,255,255,0.4)'%3E%3Cpath d='M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z'/%3E%3C/svg%3E");background-size:contain;background-repeat:no-repeat;flex-shrink:0}.timeline-editor-row-label-input{user-select:text}.timeline-editor-row-delete{position:absolute;right:4px;top:50%;transform:translateY(-50%);width:16px;height:16px;cursor:pointer;opacity:0;transition:opacity .15s;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='rgba(255,255,255,0.5)'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E");background-size:contain;background-repeat:no-repeat}.timeline-editor-label-row:hover .timeline-editor-row-delete,.timeline-editor-row-label:hover .timeline-editor-row-delete{opacity:1}.timeline-editor-label-row:hover .timeline-editor-lock-icon{opacity:0}.timeline-editor-row-delete:hover{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='rgba(255,100,100,0.9)'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E")}.timeline-editor-action-locked{opacity:.7}.timeline-editor-action-delete{position:absolute;right:14px;top:50%;transform:translateY(-50%);width:12px;height:12px;cursor:pointer;opacity:0;transition:opacity .15s;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='rgba(255,255,255,0.5)'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E");background-size:contain;background-repeat:no-repeat;z-index:2}.timeline-editor-action:hover .timeline-editor-action-delete{opacity:1}.timeline-editor-action-delete:hover{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='rgba(255,100,100,0.9)'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E")}.timeline-editor-action{position:absolute;left:0;top:4px;background-color:#2f3134;border-radius:4px;cursor:move;user-select:none;height:calc(100% - 8px);display:flex;align-items:center;justify-content:center;border:1px solid rgba(255,255,255,.1);box-sizing:border-box;transition:background-color .2s}.timeline-editor-action:hover{background-color:#3a3d40}.timeline-editor-action.selected{background-color:#4a7ba7;border-color:#5297ff}.timeline-editor-action-left-stretch,.timeline-editor-action-right-stretch{position:absolute;top:0;width:10px;border-radius:4px;height:100%;overflow:hidden;cursor:ew-resize;z-index:1}.timeline-editor-action-left-stretch::after,.timeline-editor-action-right-stretch::after{content:"";position:absolute;top:0;bottom:0;margin:auto;border-radius:4px;border-top:14px solid transparent;border-bottom:14px solid transparent}.timeline-editor-action-left-stretch{left:0}.timeline-editor-action-left-stretch::after{left:0;border-left:7px solid rgba(255,255,255,.1);border-right:7px solid transparent}.timeline-editor-action-right-stretch{right:0}.timeline-editor-action-right-stretch::after{right:0;border-right:7px solid rgba(255,255,255,.1);border-left:7px solid transparent}.timeline-editor-cursor{cursor:ew-resize;position:absolute;top:32px;height:calc(100% - 32px);box-sizing:border-box;border-left:1px solid #5297ff;border-right:1px solid #5297ff;transform:translateX(-50%);pointer-events:none;z-index:100;will-change:left}.timeline-editor-cursor-top{position:absolute;top:-1px;left:50%;transform:translateX(-50%);width:8px;height:12px;pointer-events:none}.timeline-editor-cursor-top::before{content:'';position:absolute;top:0;left:0;width:8px;height:12px;background:#5297ff;clip-path:polygon(0 0,100% 0,100% 60%,50% 100%,0 60%)}.timeline-editor-cursor-area{width:16px;height:100%;cursor:ew-resize;position:absolute;top:0;left:50%;transform:translateX(-50%);pointer-events:all}.timeline-editor-disable .timeline-editor-action{cursor:not-allowed;opacity:.6}.timeline-editor-disable .timeline-editor-cursor{cursor:not-allowed}.timeline-editor-action-content{padding:0 12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;width:100%;text-align:center}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["$stdin"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
class t{constructor(){this.events={}}on(t,e){this.events[t]||(this.events[t]=[]),this.events[t].push(e)}off(t,e){this.events[t]&&(this.events[t]=this.events[t].filter(t=>t!==e))}emit(t,e){return!this.events[t]||(this.events[t].forEach(t=>t(e)),!0)}}class e extends t{constructor(){super(),this._timerId=null,this._playRate=1,this._currentTime=0,this._playState="paused",this._prev=0,this._effectMap={},this._actionMap={},this._actionSortIds=[],this._next=0,this._activeActionIds=[]}get isPlaying(){return"playing"===this._playState}get isPaused(){return"paused"===this._playState}set effects(t){this._effectMap=t}set data(t){this.isPlaying&&this.pause(),this._dealData(t),this._dealClear(),this._dealEnter(this._currentTime)}setPlayRate(t){return t<=0?(console.error("Error: rate cannot be less than 0!"),!1):(this._playRate=t,this.emit("afterSetPlayRate",{rate:t,engine:this}),!0)}getPlayRate(){return this._playRate}reRender(){this.isPlaying||this._tickAction(this._currentTime)}setTime(t,e=!1){return this._currentTime=t,this._next=0,this._dealLeave(t),this._dealEnter(t),e?this.emit("setTimeByTick",{time:t,engine:this}):this.emit("afterSetTime",{time:t,engine:this}),!0}getTime(){return this._currentTime}play({toTime:t,autoEnd:e}={}){const i=this.getTime();return!(this.isPlaying||t&&t<=i)&&(this._playState="playing",this._startOrStop("start"),this.emit("play",{engine:this}),this._timerId=requestAnimationFrame(i=>{this._prev=i,this._tick({now:i,autoEnd:e,to:t})}),!0)}pause(){this.isPlaying&&(this._playState="paused",this._startOrStop("stop"),this.emit("paused",{engine:this})),this._timerId&&cancelAnimationFrame(this._timerId)}_end(){this.pause(),this.emit("ended",{engine:this})}_startOrStop(t){for(let e=0;e<this._activeActionIds.length;e++){const i=this._activeActionIds[e],s=this._actionMap[i],n=this._effectMap[s?.effectId];"start"===t?n?.source?.start?.({action:s,effect:n,engine:this,isPlaying:this.isPlaying,time:this.getTime()}):"stop"===t&&n?.source?.stop?.({action:s,effect:n,engine:this,isPlaying:this.isPlaying,time:this.getTime()})}}_tick(t){if(this.isPaused)return;const{now:e,autoEnd:i,to:s}=t;let n=this.getTime()+Math.min(1e3,e-this._prev)/1e3*this._playRate;this._prev=e,s&&s<=n&&(n=s),this.setTime(n,!0),this._tickAction(n),!s&&i&&this._next>=this._actionSortIds.length&&0===this._activeActionIds.length||s&&s<=n?this._end():this.isPaused||(this._timerId=requestAnimationFrame(t=>{this._tick({now:t,autoEnd:i,to:s})}))}_tickAction(t){this._dealEnter(t),this._dealLeave(t);const e=this._activeActionIds.length;for(let i=0;i<e;i++){const e=this._activeActionIds[i],s=this._actionMap[e],n=this._effectMap[s.effectId];n?.source?.update&&n.source.update({time:t,action:s,isPlaying:this.isPlaying,effect:n,engine:this})}}_dealClear(){for(;this._activeActionIds.length;){const t=this._activeActionIds.shift(),e=this._actionMap[t],i=this._effectMap[e?.effectId];i?.source?.leave&&i.source.leave({action:e,effect:i,engine:this,isPlaying:this.isPlaying,time:this.getTime()})}this._next=0}_dealEnter(t){for(;this._actionSortIds[this._next];){const e=this._actionSortIds[this._next],i=this._actionMap[e];if(!i.disable){if(i.start>t)break;if(i.end>t&&!this._activeActionIds.includes(e)){const s=this._effectMap[i.effectId];s?.source?.enter&&s.source.enter({action:i,effect:s,isPlaying:this.isPlaying,time:t,engine:this}),this._activeActionIds.push(e)}}this._next++}}_dealLeave(t){let e=0;for(;this._activeActionIds[e];){const i=this._activeActionIds[e],s=this._actionMap[i];if(s.start>t||s.end<t){const i=this._effectMap[s.effectId];i?.source?.leave&&i.source.leave({action:s,effect:i,isPlaying:this.isPlaying,time:t,engine:this}),this._activeActionIds.splice(e,1);continue}e++}}_dealData(t){const e=[];t.forEach(t=>{const i=t.blocks||t.items||t.actions||[];e.push(...i)});const i=e.sort((t,e)=>t.start-e.start),s={},n=[];i.forEach(t=>{n.push(t.id),s[t.id]={...t}}),this._actionMap=s,this._actionSortIds=n}}const i=(t,{startLeft:e,scale:i,scaleWidth:s})=>t/i*s+e,s=(t,{startLeft:e,scale:i,scaleWidth:s})=>(t-e)/s*i;class n extends HTMLElement{constructor(){super(),this.config={scale:1,scaleWidth:160,scaleCount:20,scaleSplitCount:10,startLeft:120,contentPadding:0,minScaleCount:10,maxScaleCount:1/0,rowHeight:32,autoScroll:!0,hideCursor:!1,disableDrag:!1,gridSnap:!0,grid:1},this.callbacks={onActionMoveStart:null,onActionMoving:null,onActionMoveEnd:null,onActionResizeStart:null,onActionResizing:null,onActionResizeEnd:null,onClickRow:null,onClickAction:null,onDoubleClickRow:null,onDoubleClickAction:null,onContextMenuRow:null,onContextMenuAction:null,onCursorDragStart:null,onCursorDrag:null,onCursorDragEnd:null,onClickTimeArea:null,getActionRender:null,getScaleRender:null},this.tracks=[],this.cursorTime=0,this.isPlaying=!1,this._scrollX=0,this._scrollY=0,this.timeAreaEl=null,this.timeAreaWrapperEl=null,this.editAreaEl=null,this.labelColumnEl=null,this.labelInnerEl=null,this.cursorEl=null,this.engine=new e,this.dragState={isDragging:!1,isActuallyDragging:!1,type:null,action:null,row:null,rowIndex:null,startX:0,startY:0,currentLeft:0,currentWidth:0,deltaX:0,totalDeltaX:0}}connectedCallback(){this.className="timeline-editor",this.render(),this._setupEngineListeners(),this._setupResizeObserver()}disconnectedCallback(){this.engine.pause(),this._cleanup()}_cleanup(){document.removeEventListener("mousemove",this._boundHandleMouseMove),document.removeEventListener("mouseup",this._boundHandleMouseUp),this._resizeObserver&&this._resizeObserver.disconnect()}_setupResizeObserver(){this._resizeObserver=new ResizeObserver(t=>{for(let e of t)this.editAreaEl&&this._updateCursorPosition()}),this._resizeObserver.observe(this)}setData(t){this.tracks=t||[],this.engine.data=this.tracks,this.render()}setConfig(t){Object.assign(this.config,t),this.render()}setCallbacks(t){Object.assign(this.callbacks,t)}on(t,e){this.callbacks.hasOwnProperty(t)&&(this.callbacks[t]=e)}getData(){return{tracks:this.tracks}}exportJSON(){return JSON.stringify(this.getData(),null,2)}importJSON(t){try{const e=JSON.parse(t),i=e.tracks||e.editorData||[];return this.setData(i),!0}catch(t){return console.error("Failed to import timeline data:",t),!1}}saveToLocalStorage(t="timeline-data"){try{return localStorage.setItem(t,this.exportJSON()),!0}catch(t){return console.error("Failed to save to localStorage:",t),!1}}loadFromLocalStorage(t="timeline-data"){try{const e=localStorage.getItem(t);return!!e&&this.importJSON(e)}catch(t){return console.error("Failed to load from localStorage:",t),!1}}setTime(t){this.cursorTime=Math.max(0,t),this.engine.setTime(this.cursorTime),this._updateCursorPosition()}getTime(){return this.engine.getTime()}getTotalTime(){let t=0;for(const e of this.tracks){const i=e.blocks||e.items||e.actions||[];for(const e of i)e.end>t&&(t=e.end)}return t}play(t={}){return this.engine.play(t)}pause(){this.engine.pause()}_syncEngineData(){this.engine.data=this.tracks}_emitChange(){this._syncEngineData(),this.dispatchEvent(new CustomEvent("change",{detail:{tracks:this.tracks}}))}_expandTimelineIfNeeded(t){const e=Math.ceil(t/this.config.scale)+2;if(e>this.config.scaleCount){const t=Math.min(e,this.config.maxScaleCount);if(t>this.config.scaleCount)return this.config.scaleCount=t,this._updateTimelineWidth(),!0}return!1}_updateTimelineWidth(){const t=this.config.contentPadding,e=this.config.scaleCount*this.config.scaleWidth+t,i=this.editAreaEl?.querySelector(".timeline-editor-rows");i&&(i.style.width=`${e}px`,i.style.minWidth=`${e}px`);const s=this.editAreaEl?.querySelectorAll(".timeline-editor-edit-row");s&&s.forEach(t=>{t.style.width=`${e}px`}),this._rerenderTimeArea()}_rerenderTimeArea(){if(!this.timeAreaEl)return;const t=this._createTimeArea();this.timeAreaEl.replaceWith(t),this.timeAreaEl=t,this._syncTimeAreaScroll()}_setupEngineListeners(){this.engine.on("play",()=>{this.isPlaying=!0,this.classList.add("timeline-editor-playing"),this._scrollToCursorImmediate()}),this.engine.on("paused",()=>{this.isPlaying=!1,this.classList.remove("timeline-editor-playing")}),this.engine.on("setTimeByTick",({time:t})=>{this.cursorTime=t,this._updateCursorPosition(!0)}),this.engine.on("afterSetTime",({time:t})=>{this.cursorTime=t,this._updateCursorPosition(!1)})}render(){this.innerHTML="",this.timeAreaEl=this._createTimeArea(),this.appendChild(this.timeAreaEl),this.config.hideCursor||(this.cursorEl=this._createCursor(),this.appendChild(this.cursorEl)),this.style.setProperty("--timeline-start-left",`${this.config.startLeft}px`),this.style.setProperty("--timeline-content-padding",`${this.config.contentPadding}px`),this.style.setProperty("--timeline-scale-width",`${this.config.scaleWidth}px`);const t=document.createElement("div");t.className="timeline-editor-spacer",t.style.cssText="\n height: 10px;\n width: calc(var(--timeline-start-left) - 4px);\n background-color: #191b1d;\n flex-shrink: 0;\n position: relative;\n z-index: 101;\n ",this.appendChild(t);const e=document.createElement("div");e.className="timeline-editor-content",e.style.cssText="\n display: flex;\n flex: 1 1 0;\n overflow: hidden;\n position: relative;\n min-height: 0;\n min-width: 0;\n height: 0;\n ",this.labelColumnEl=this._createLabelColumn(),e.appendChild(this.labelColumnEl),this.editAreaEl=this._createEditArea(),e.appendChild(this.editAreaEl),this.appendChild(e),this._scrollX>0&&(this.editAreaEl.scrollLeft=this._scrollX,this._syncTimeAreaScroll()),this._scrollY>0&&(this.editAreaEl.scrollTop=this._scrollY,this._syncLabelColumnScroll()),this._updateCursorPosition()}_createTimeArea(){const t=document.createElement("div");t.className="timeline-editor-time-area";const e=this.config.contentPadding,i=document.createElement("div");i.className="timeline-editor-time-area-wrapper";const n=this.config.scaleCount*this.config.scaleWidth+this.config.startLeft+e;i.style.width=`${n}px`,i.style.height="100%",i.style.position="relative";const a=document.createElement("div");a.className="timeline-editor-time-area-interact",a.style.width=`${n}px`;const r=this.config.scaleCount*this.config.scaleSplitCount,o=this.config.scaleWidth/this.config.scaleSplitCount;for(let t=0;t<=r;t++){const i=document.createElement("div"),s=t%this.config.scaleSplitCount===0;if(i.className="timeline-editor-time-unit "+(s?"timeline-editor-time-unit-big":""),i.style.width=`${o}px`,0===t&&(i.style.marginLeft=this.config.startLeft+e-o+1+"px"),s){const e=document.createElement("div");e.className="timeline-editor-time-unit-scale";const s=t/this.config.scaleSplitCount*this.config.scale;if(this.callbacks.getScaleRender){const t=this.callbacks.getScaleRender(s);"string"==typeof t?e.innerHTML=t:t instanceof HTMLElement&&(e.innerHTML="",e.appendChild(t))}else e.textContent=0===t?"0s":`${s.toFixed(1)}s`;i.appendChild(e)}a.appendChild(i)}return i.appendChild(a),t.appendChild(i),this.timeAreaWrapperEl=i,t.addEventListener("click",i=>{if(this.isPlaying)return;const n=t.getBoundingClientRect(),a=i.clientX-n.left+this._scrollX-e,r=s(a,this.config);if(this.callbacks.onClickTimeArea){if(!1===this.callbacks.onClickTimeArea(i,{time:r}))return}this.setTime(Math.max(0,r))}),t}_createLabelColumn(){const t=document.createElement("div");t.className="timeline-editor-label-column",t.style.cssText=`\n width: ${this.config.startLeft}px;\n flex-shrink: 0;\n overflow: hidden;\n background-color: #191b1d;\n border-right: 1px solid rgba(255, 255, 255, 0.1);\n z-index: 200;\n position: relative;\n `;const e=document.createElement("div");return e.className="timeline-editor-label-inner",this.labelInnerEl=e,this.tracks.forEach((t,i)=>{const s=this._createLabelRow(t,i);e.appendChild(s)}),t.appendChild(e),t}_createLabelRow(t,e){const i=document.createElement("div");i.className="timeline-editor-label-row",i.style.cssText=`\n height: ${t.rowHeight||this.config.rowHeight}px;\n display: flex;\n align-items: center;\n padding: 0 8px;\n color: rgba(255, 255, 255, 0.7);\n font-size: 12px;\n font-weight: 500;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n box-sizing: border-box;\n position: relative;\n `,0===e&&(i.style.borderTop="1px solid rgba(255, 255, 255, 0.1)"),i.dataset.rowId=t.id,i.dataset.rowIndex=e;const s=document.createElement("span");if(s.className="timeline-editor-label-text",s.textContent=t.name||"",s.style.cssText="\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n ",i.appendChild(s),t.locked||(i.style.cursor="text",i.addEventListener("dblclick",e=>{e.stopPropagation(),this._startLabelEdit(i,s,t)})),t.locked){const t=document.createElement("span");t.className="timeline-editor-lock-icon",t.style.cssText="\n width: 10px;\n height: 10px;\n margin-left: 6px;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='rgba(255,255,255,0.4)'%3E%3Cpath d='M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z'/%3E%3C/svg%3E\");\n background-size: contain;\n background-repeat: no-repeat;\n flex-shrink: 0;\n ",i.appendChild(t)}if(!t.noDelete){const e=document.createElement("div");e.className="timeline-editor-row-delete",e.addEventListener("click",e=>{e.stopPropagation(),this._deleteTrack(t)}),i.appendChild(e)}return i}_createEditArea(){const t=document.createElement("div");t.className="timeline-editor-edit-area";const e=this.config.contentPadding,i=this.config.scaleCount*this.config.scaleWidth+e,s=document.createElement("div");return s.className="timeline-editor-rows",s.style.position="relative",s.style.width=`${i}px`,s.style.minWidth=`${i}px`,this.tracks.forEach((t,n)=>{const a=this._createRow(t,n,i,e);s.appendChild(a)}),t.appendChild(s),t.addEventListener("scroll",()=>{this._scrollX=t.scrollLeft,this._scrollY=t.scrollTop,this._syncTimeAreaScroll(),this._syncLabelColumnScroll(),this._updateCursorPosition()}),t}_syncLabelColumnScroll(){this.labelInnerEl&&(this.labelInnerEl.style.transform=`translateY(-${this._scrollY}px)`)}_createRow(t,e,i,n){const a=document.createElement("div");a.className="timeline-editor-edit-row",a.style.height=`${t.rowHeight||this.config.rowHeight}px`,a.style.width=`${i}px`,a.style.backgroundPosition=`${n}px 0`,a.dataset.rowId=t.id,a.dataset.rowIndex=e,a.addEventListener("click",e=>{if(e.target===a&&(this._cancelActiveEditing(),this.callbacks.onClickRow)){const i=e.currentTarget.getBoundingClientRect(),a=e.clientX-i.left+this._scrollX-n+this.config.startLeft,r=s(a,this.config);this.callbacks.onClickRow(e,{row:t,time:r})}}),a.addEventListener("dblclick",e=>{if(e.target===a&&this.callbacks.onDoubleClickRow){const i=e.currentTarget.getBoundingClientRect(),a=e.clientX-i.left+this._scrollX-n+this.config.startLeft,r=s(a,this.config);this.callbacks.onDoubleClickRow(e,{row:t,time:r})}}),a.addEventListener("contextmenu",e=>{if(e.target===a&&this.callbacks.onContextMenuRow){e.preventDefault();const i=e.currentTarget.getBoundingClientRect(),a=e.clientX-i.left+this._scrollX-n+this.config.startLeft,r=s(a,this.config);this.callbacks.onContextMenuRow(e,{row:t,time:r})}}),a.addEventListener("mousedown",i=>{i.target!==a&&i.target!==a.querySelector(".timeline-editor-row-label")||this._handleRowDragStart(i,t,e)});return(t.blocks||t.items||t.actions||[]).forEach(i=>{const s=this._createAction(i,t,e);a.appendChild(s)}),a}_startLabelEdit(t,e,i){if(t.querySelector("input"))return;const s=i.name||"",n=document.createElement("input");n.type="text",n.value=s,n.className="timeline-editor-row-label-input",n.style.cssText="\n flex: 1;\n min-width: 0;\n background: transparent;\n border: none;\n border-bottom: 1px solid rgba(255,255,255,0.3);\n color: inherit;\n font: inherit;\n outline: none;\n padding: 0;\n margin: 0;\n user-select: text;\n box-sizing: border-box;\n ";let a=!1;n.addEventListener("blur",()=>{if(a)return;a=!0;const t=n.value.trim();i.name=t,e.textContent=t,e.style.display="",n.remove(),this._emitChange(),this.dispatchEvent(new CustomEvent("trackrenamed",{detail:{track:i,name:t}}))}),n.addEventListener("keydown",t=>{"Enter"===t.key?(t.preventDefault(),n.blur()):"Escape"===t.key&&(n.value=s,n.blur())}),e.style.display="none",t.insertBefore(n,e),n.focus(),n.select()}_deleteTrack(t){const e=this.tracks.indexOf(t);e>-1&&this.tracks.splice(e,1),this.render(),this._emitChange(),this.dispatchEvent(new CustomEvent("trackdeleted",{detail:{track:t}}))}_deleteBlock(t,e){const i=e.blocks||e.items||e.actions||[],s=i.indexOf(t);s>-1&&i.splice(s,1),this.render(),this._emitChange(),this.dispatchEvent(new CustomEvent("blockdeleted",{detail:{block:t,track:e}}))}_startBlockNameEdit(t,e,i){const s=t.querySelector(".timeline-editor-action-content");if(!s||s.querySelector("input"))return;const n=e.name||"",a=document.createElement("input");a.type="text",a.value=n,a.className="timeline-editor-block-name-input",a.style.cssText="\n width: 100%;\n background: transparent;\n border: none;\n border-bottom: 1px solid rgba(255,255,255,0.3);\n color: inherit;\n font: inherit;\n outline: none;\n padding: 0;\n text-align: center;\n user-select: text;\n ";let r=!1;a.addEventListener("blur",()=>{if(r)return;r=!0;const t=a.value.trim();if(e.name=t,a.remove(),this.callbacks.getActionRender){const t=this.callbacks.getActionRender(e,i);"string"==typeof t&&(s.innerHTML=t)}else s.textContent=t;this._emitChange(),this.dispatchEvent(new CustomEvent("blockrenamed",{detail:{block:e,track:i,name:t}}))}),a.addEventListener("keydown",t=>{"Enter"===t.key?(t.preventDefault(),a.blur()):"Escape"===t.key&&(a.value=n,a.blur()),t.stopPropagation()}),a.addEventListener("mousedown",t=>t.stopPropagation()),a.addEventListener("click",t=>t.stopPropagation()),s.innerHTML="",s.appendChild(a),a.focus(),a.select()}_cancelActiveEditing(){const t=this.querySelector(".timeline-editor-block-name-input");t&&t.blur();const e=this.querySelector(".timeline-editor-row-label-input");e&&e.blur()}_handleRowDragStart(t,e,i){if(this.isPlaying||this.config.disableDrag||e.locked)return;if(0!==t.button)return;t.preventDefault();const n=this.editAreaEl.getBoundingClientRect(),a=this.config.contentPadding,r=t.clientX-n.left+this._scrollX-a+this.config.startLeft,o=s(r,this.config);this.dragState.isDragging=!0,this.dragState.isActuallyDragging=!1,this.dragState.type="item-create",this.dragState.row=e,this.dragState.rowIndex=i,this.dragState.startX=t.clientX,this.dragState.startTime=o,this.dragState.totalDeltaX=0,this.dragState.newItem=null,this.dragState.newItemEl=null,this._boundHandleMouseMove=this._handleMouseMove.bind(this),this._boundHandleMouseUp=this._handleMouseUp.bind(this),document.addEventListener("mousemove",this._boundHandleMouseMove),document.addEventListener("mouseup",this._boundHandleMouseUp)}_createAction(t,e,n){const a=document.createElement("div");a.className="timeline-editor-action",t.selected&&a.classList.add("selected");const r=this.config.contentPadding,o=i(t.start,this.config)-this.config.startLeft+r,l=i(t.end,this.config)-this.config.startLeft+r-o;a.style.left=`${o}px`,a.style.width=`${l}px`,a.dataset.actionId=t.id,a.dataset.rowIndex=n;const c=document.createElement("div");if(c.className="timeline-editor-action-content",this.callbacks.getActionRender){const i=this.callbacks.getActionRender(t,e);"string"==typeof i?c.innerHTML=i:i instanceof HTMLElement&&(c.innerHTML="",c.appendChild(i))}else c.textContent=t.name||"";if(a.appendChild(c),!1!==t.flexible&&!e.locked){const i=document.createElement("div");i.className="timeline-editor-action-left-stretch",a.appendChild(i);const s=document.createElement("div");s.className="timeline-editor-action-right-stretch",a.appendChild(s),i.addEventListener("mousedown",i=>this._handleResizeStart(i,t,e,n,"left")),s.addEventListener("mousedown",i=>this._handleResizeStart(i,t,e,n,"right"))}if(!1===t.movable||e.locked||a.addEventListener("mousedown",i=>{i.target.classList.contains("timeline-editor-action-left-stretch")||i.target.classList.contains("timeline-editor-action-right-stretch")||this._handleMoveStart(i,t,e,n)}),e.locked&&(a.classList.add("timeline-editor-action-locked"),a.style.cursor="default"),!t.noDelete&&!e.locked){const i=document.createElement("div");i.className="timeline-editor-action-delete",i.addEventListener("click",i=>{i.stopPropagation(),this._deleteBlock(t,e)}),i.addEventListener("mousedown",t=>{t.stopPropagation()}),a.appendChild(i)}return a.addEventListener("click",i=>{if(this.callbacks.onClickAction){const n=this.editAreaEl.getBoundingClientRect(),a=i.clientX-n.left+this._scrollX-r+this.config.startLeft,o=s(a,this.config);this.callbacks.onClickAction(i,{action:t,row:e,time:o})}}),a.addEventListener("dblclick",i=>{if(i.stopPropagation(),this.callbacks.onDoubleClickAction){const n=this.editAreaEl.getBoundingClientRect(),a=i.clientX-n.left+this._scrollX-r+this.config.startLeft,o=s(a,this.config);if(!1===this.callbacks.onDoubleClickAction(i,{action:t,row:e,time:o}))return}e.locked||this._startBlockNameEdit(a,t,e)}),a.addEventListener("contextmenu",i=>{if(this.callbacks.onContextMenuAction){i.preventDefault();const n=this.editAreaEl.getBoundingClientRect(),a=i.clientX-n.left+this._scrollX-r+this.config.startLeft,o=s(a,this.config);this.callbacks.onContextMenuAction(i,{action:t,row:e,time:o})}}),a}_createCursor(){const t=document.createElement("div");t.className="timeline-editor-cursor";const e=document.createElement("div");e.className="timeline-editor-cursor-top",t.appendChild(e);const i=document.createElement("div");return i.className="timeline-editor-cursor-area",t.appendChild(i),i.addEventListener("mousedown",t=>this._handleCursorDragStart(t)),t}_updateCursorPosition(t=!1){if(!this.cursorEl)return;const e=this.config.contentPadding,s=i(this.cursorTime,this.config)+e;this.cursorEl.style.left=s-this._scrollX+"px",t&&this.config.autoScroll&&this.editAreaEl&&this._autoScrollToCursor(s)}_scrollToCursorImmediate(){if(!this.editAreaEl||!this.config.autoScroll)return;const t=this.config.contentPadding,e=i(this.cursorTime,this.config)+t,s=this.editAreaEl.clientWidth,n=e-this.config.startLeft,a=this._scrollX,r=this._scrollX+s;if(n<a+50||n>r-50){const t=Math.max(0,n-s/3);this.editAreaEl.scrollLeft=t}}_autoScrollToCursor(t){const e=this.editAreaEl.clientWidth,i=t-this.config.startLeft,s=this._scrollX,n=this._scrollX+e;let a=null;if(i<s+50?a=Math.max(0,i-50):i>n-50&&(a=i-e+50),null!==a){const t=a-this._scrollX,e=Math.sign(t)*Math.min(Math.abs(t),8);this.editAreaEl.scrollLeft=this._scrollX+e}}_syncTimeAreaScroll(){this.timeAreaWrapperEl||(this.timeAreaWrapperEl=this.timeAreaEl?.querySelector(".timeline-editor-time-area-wrapper")),this.timeAreaWrapperEl&&(this.timeAreaWrapperEl.style.transform=`translateX(-${this._scrollX}px)`)}_handleCursorDragStart(t){if(!this.isPlaying&&!this.config.disableDrag){if(t.preventDefault(),t.stopPropagation(),this.callbacks.onCursorDragStart){if(!1===this.callbacks.onCursorDragStart(t,{time:this.cursorTime}))return}this.dragState.isDragging=!0,this.dragState.type="cursor",this.dragState.startX=t.clientX,this._boundHandleMouseMove=this._handleMouseMove.bind(this),this._boundHandleMouseUp=this._handleMouseUp.bind(this),document.addEventListener("mousemove",this._boundHandleMouseMove),document.addEventListener("mouseup",this._boundHandleMouseUp)}}_handleMoveStart(t,e,s,n){this.isPlaying||this.config.disableDrag||(t.preventDefault(),t.stopPropagation(),this._cancelActiveEditing(),this.dragState.isDragging=!0,this.dragState.isActuallyDragging=!1,this.dragState.type="action-move",this.dragState.action=e,this.dragState.row=s,this.dragState.rowIndex=n,this.dragState.startX=t.clientX,this.dragState.deltaX=0,this.dragState.totalDeltaX=0,this.dragState.currentLeft=i(e.start,this.config),this.dragState.currentWidth=i(e.end,this.config)-this.dragState.currentLeft,this._boundHandleMouseMove=this._handleMouseMove.bind(this),this._boundHandleMouseUp=this._handleMouseUp.bind(this),document.addEventListener("mousemove",this._boundHandleMouseMove),document.addEventListener("mouseup",this._boundHandleMouseUp))}_handleResizeStart(t,e,s,n,a){this.isPlaying||this.config.disableDrag||(t.preventDefault(),t.stopPropagation(),this._cancelActiveEditing(),this.dragState.isDragging=!0,this.dragState.isActuallyDragging=!1,this.dragState.type=`action-resize-${a}`,this.dragState.action=e,this.dragState.row=s,this.dragState.rowIndex=n,this.dragState.startX=t.clientX,this.dragState.deltaX=0,this.dragState.totalDeltaX=0,this.dragState.currentLeft=i(e.start,this.config),this.dragState.currentWidth=i(e.end,this.config)-this.dragState.currentLeft,this._boundHandleMouseMove=this._handleMouseMove.bind(this),this._boundHandleMouseUp=this._handleMouseUp.bind(this),document.addEventListener("mousemove",this._boundHandleMouseMove),document.addEventListener("mouseup",this._boundHandleMouseUp))}_handleMouseMove(t){if(this.dragState.isDragging)if("cursor"===this.dragState.type)this._handleCursorDrag(t);else if("action-move"===this.dragState.type){const e=t.clientX-this.dragState.startX;if(this.dragState.startX=t.clientX,this.dragState.deltaX+=e,this.dragState.totalDeltaX+=Math.abs(e),!this.dragState.isActuallyDragging&&this.dragState.totalDeltaX>3&&(this.dragState.isActuallyDragging=!0,this.callbacks.onActionMoveStart)){if(!1===this.callbacks.onActionMoveStart({action:this.dragState.action,row:this.dragState.row}))return void this._cancelDrag()}this.dragState.isActuallyDragging&&this._handleActionMove()}else if("action-resize-left"===this.dragState.type){const e=t.clientX-this.dragState.startX;if(this.dragState.startX=t.clientX,this.dragState.deltaX+=e,this.dragState.totalDeltaX+=Math.abs(e),!this.dragState.isActuallyDragging&&this.dragState.totalDeltaX>3&&(this.dragState.isActuallyDragging=!0,this.callbacks.onActionResizeStart)){if(!1===this.callbacks.onActionResizeStart({action:this.dragState.action,row:this.dragState.row,direction:"left"}))return void this._cancelDrag()}this.dragState.isActuallyDragging&&this._handleActionResizeLeft()}else if("action-resize-right"===this.dragState.type){const e=t.clientX-this.dragState.startX;if(this.dragState.startX=t.clientX,this.dragState.deltaX+=e,this.dragState.totalDeltaX+=Math.abs(e),!this.dragState.isActuallyDragging&&this.dragState.totalDeltaX>3&&(this.dragState.isActuallyDragging=!0,this.callbacks.onActionResizeStart)){if(!1===this.callbacks.onActionResizeStart({action:this.dragState.action,row:this.dragState.row,direction:"right"}))return void this._cancelDrag()}this.dragState.isActuallyDragging&&this._handleActionResizeRight()}else if("item-create"===this.dragState.type){const e=t.clientX-this.dragState.startX;this.dragState.totalDeltaX+=Math.abs(e-(this.dragState.lastDx||0)),this.dragState.lastDx=e,!this.dragState.isActuallyDragging&&this.dragState.totalDeltaX>3&&(this.dragState.isActuallyDragging=!0,this._createNewItemFromDrag()),this.dragState.isActuallyDragging&&this.dragState.newItem&&this._updateNewItemFromDrag(t)}}_createNewItemFromDrag(){const t=this.dragState.row,e=this.dragState.rowIndex,i=Math.max(0,this.dragState.startTime),s={id:`block-${Date.now()}`,name:"",start:i,end:i+.1,flexible:!0,movable:!0,metadata:{}};t.blocks||(t.blocks=[]),t.blocks.push(s),this.dragState.newItem=s;const n=this.editAreaEl.querySelector(`[data-row-index="${e}"]`);if(n){const i=this._createAction(s,t,e);i.classList.add("creating"),n.appendChild(i),this.dragState.newItemEl=i}}_updateNewItemFromDrag(t){const e=this.dragState.newItem;if(!e)return;const n=this.editAreaEl.getBoundingClientRect(),a=this.config.contentPadding,r=t.clientX-n.left+this._scrollX-a+this.config.startLeft,o=s(r,this.config),l=this.dragState.startTime;if(o>l?(e.start=Math.max(0,l),e.end=o):(e.start=Math.max(0,o),e.end=l),this._expandTimelineIfNeeded(e.end),this.dragState.newItemEl){const t=this.config.contentPadding,s=i(e.start,this.config)-this.config.startLeft+t,n=i(e.end,this.config)-this.config.startLeft+t-s;this.dragState.newItemEl.style.left=`${s}px`,this.dragState.newItemEl.style.width=`${Math.max(10,n)}px`}}_handleMouseUp(t){if(!this.dragState.isDragging)return;const e=this.dragState.type,i=this.dragState.action,s=this.dragState.row;if(this.dragState.isActuallyDragging){if("cursor"===e&&this.callbacks.onCursorDragEnd)this.callbacks.onCursorDragEnd(t,{time:this.cursorTime});else if("action-move"===e&&this.callbacks.onActionMoveEnd)this.callbacks.onActionMoveEnd({action:i,row:s});else if("action-resize-left"!==e&&"action-resize-right"!==e||!this.callbacks.onActionResizeEnd){if("item-create"===e&&this.dragState.newItem){const t=this.dragState.newItem;this.dragState.newItemEl&&this.dragState.newItemEl.classList.remove("creating"),this._emitChange(),this.dispatchEvent(new CustomEvent("itemcreated",{detail:{item:t,row:s}}))}}else this.callbacks.onActionResizeEnd({action:i,row:s});e&&e.startsWith("action-")&&this._emitChange()}else if("item-create"===e&&this.dragState.newItem){const t=this.dragState.row,e=this.dragState.newItem,i=t.blocks||t.items||t.actions||[],s=i.indexOf(e);s>-1&&i.splice(s,1),this.dragState.newItemEl&&this.dragState.newItemEl.remove()}this.dragState.isDragging=!1,this.dragState.isActuallyDragging=!1,this.dragState.type=null,this.dragState.action=null,this.dragState.row=null,this.dragState.totalDeltaX=0,this.dragState.newItem=null,this.dragState.newItemEl=null,this.dragState.lastDx=0,document.removeEventListener("mousemove",this._boundHandleMouseMove),document.removeEventListener("mouseup",this._boundHandleMouseUp)}_cancelDrag(){this.dragState.isDragging=!1,this.dragState.isActuallyDragging=!1,this.dragState.type=null,this.dragState.action=null,this.dragState.row=null,this.dragState.totalDeltaX=0,this.dragState.newItem=null,this.dragState.newItemEl=null,this.dragState.lastDx=0,document.removeEventListener("mousemove",this._boundHandleMouseMove),document.removeEventListener("mouseup",this._boundHandleMouseUp)}_handleCursorDrag(t){if(!this.editAreaEl)return;const e=this.editAreaEl.getBoundingClientRect(),i=this.config.contentPadding,n=t.clientX-e.left+this._scrollX-i+this.config.startLeft,a=Math.max(0,s(n,this.config));if(this.callbacks.onCursorDrag){if(!1===this.callbacks.onCursorDrag(t,{time:a}))return}this.cursorTime=a,this.engine.setTime(a),this._updateCursorPosition(!0)}_handleActionMove(){const t=this.dragState.action,e=this.dragState.row,i=this.config.gridSnap?this.config.scaleWidth/10:1;if(Math.abs(this.dragState.deltaX)>=i){const n=parseInt(this.dragState.deltaX/i);let a=this.dragState.currentLeft+n*i;if(this.config.gridSnap){0!==(a-this.config.startLeft)%i&&(a=this.config.startLeft+i*Math.round((a-this.config.startLeft)/i))}a=Math.max(this.config.startLeft,a),this.dragState.currentLeft=a,this.dragState.deltaX=this.dragState.deltaX%i;const r=s(a,this.config),o=t.end-t.start;if(this.callbacks.onActionMoving){if(!1===this.callbacks.onActionMoving({action:t,row:e,start:r,end:r+o}))return}t.start=r,t.end=r+o,this._expandTimelineIfNeeded(t.end),this._updateActionElement(t,this.dragState.rowIndex)}}_handleActionResizeLeft(){const t=this.dragState.action,e=this.dragState.row,i=this.config.gridSnap?this.config.scaleWidth/10:1;if(Math.abs(this.dragState.deltaX)>=i){const n=parseInt(this.dragState.deltaX/i);let a=this.dragState.currentLeft+n*i;if(this.config.gridSnap){0!==(a-this.config.startLeft)%i&&(a=this.config.startLeft+i*Math.round((a-this.config.startLeft)/i))}const r=this.dragState.currentLeft+this.dragState.currentWidth;a=Math.max(this.config.startLeft,a);const o=10;a=Math.min(a,r-o);const l=r-a;this.dragState.currentLeft=a,this.dragState.currentWidth=l,this.dragState.deltaX=this.dragState.deltaX%i;const c=s(a,this.config);if(this.callbacks.onActionResizing){if(!1===this.callbacks.onActionResizing({action:t,row:e,start:Math.max(0,c),end:t.end}))return}t.start=Math.max(0,c),this._updateActionElement(t,this.dragState.rowIndex)}}_handleActionResizeRight(){const t=this.dragState.action,e=this.dragState.row,i=this.config.gridSnap?this.config.scaleWidth/10:1;if(Math.abs(this.dragState.deltaX)>=i){const n=parseInt(this.dragState.deltaX/i);let a=this.dragState.currentWidth+n*i;const r=this.dragState.currentLeft+a;if(this.config.gridSnap){if(0!==(r-this.config.startLeft)%i){a=this.config.startLeft+i*Math.round((r-this.config.startLeft)/i)-this.dragState.currentLeft}}const o=10;a=Math.max(o,a),this.dragState.currentWidth=a,this.dragState.deltaX=this.dragState.deltaX%i;const l=this.dragState.currentLeft+a,c=s(l,this.config);if(this.callbacks.onActionResizing){if(!1===this.callbacks.onActionResizing({action:t,row:e,start:t.start,end:Math.max(t.start+.1,c)}))return}t.end=Math.max(t.start+.1,c),this._expandTimelineIfNeeded(t.end),this._updateActionElement(t,this.dragState.rowIndex)}}_updateActionElement(t,e){const s=this.editAreaEl.querySelector(`[data-row-index="${e}"]`);if(!s)return;const n=s.querySelector(`[data-action-id="${t.id}"]`);if(!n)return;const a=this.config.contentPadding,r=i(t.start,this.config)-this.config.startLeft+a,o=i(t.end,this.config)-this.config.startLeft+a-r;n.style.left=`${r}px`,n.style.width=`${o}px`}}customElements.get("trakk-editor")||customElements.define("trakk-editor",n);export{n as Trakk};
|
|
2
|
+
//# sourceMappingURL=trakk.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trakk.esm.js","sources":["../src/trakk-engine.js","../src/trakk.js"],"sourcesContent":["/**\n * Event Emitter for Trakk Engine\n */\nclass EventEmitter {\n constructor() {\n this.events = {};\n }\n\n on(event, callback) {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n this.events[event].push(callback);\n }\n\n off(event, callback) {\n if (!this.events[event]) return;\n this.events[event] = this.events[event].filter(cb => cb !== callback);\n }\n\n emit(event, data) {\n if (!this.events[event]) return true;\n this.events[event].forEach(callback => callback(data));\n return true;\n }\n}\n\n/**\n * Trakk Engine - Core animation timeline player\n * Can run independently from the editor\n */\nexport class TrakkEngine extends EventEmitter {\n constructor() {\n super();\n\n this._timerId = null;\n this._playRate = 1;\n this._currentTime = 0;\n this._playState = 'paused';\n this._prev = 0;\n\n this._effectMap = {};\n this._actionMap = {};\n this._actionSortIds = [];\n\n this._next = 0;\n this._activeActionIds = [];\n }\n\n get isPlaying() {\n return this._playState === 'playing';\n }\n\n get isPaused() {\n return this._playState === 'paused';\n }\n\n set effects(effects) {\n this._effectMap = effects;\n }\n\n set data(data) {\n if (this.isPlaying) this.pause();\n this._dealData(data);\n this._dealClear();\n this._dealEnter(this._currentTime);\n }\n\n /**\n * Set playback rate\n */\n setPlayRate(rate) {\n if (rate <= 0) {\n console.error('Error: rate cannot be less than 0!');\n return false;\n }\n this._playRate = rate;\n this.emit('afterSetPlayRate', { rate, engine: this });\n return true;\n }\n\n getPlayRate() {\n return this._playRate;\n }\n\n /**\n * Re-render current time\n */\n reRender() {\n if (this.isPlaying) return;\n this._tickAction(this._currentTime);\n }\n\n /**\n * Set playback time\n */\n setTime(time, isTick = false) {\n this._currentTime = time;\n this._next = 0;\n this._dealLeave(time);\n this._dealEnter(time);\n\n if (isTick) {\n this.emit('setTimeByTick', { time, engine: this });\n } else {\n this.emit('afterSetTime', { time, engine: this });\n }\n return true;\n }\n\n getTime() {\n return this._currentTime;\n }\n\n /**\n * Play timeline\n */\n play({ toTime, autoEnd } = {}) {\n const currentTime = this.getTime();\n if (this.isPlaying || (toTime && toTime <= currentTime)) return false;\n\n this._playState = 'playing';\n this._startOrStop('start');\n this.emit('play', { engine: this });\n\n this._timerId = requestAnimationFrame((time) => {\n this._prev = time;\n this._tick({ now: time, autoEnd, to: toTime });\n });\n return true;\n }\n\n /**\n * Pause playback\n */\n pause() {\n if (this.isPlaying) {\n this._playState = 'paused';\n this._startOrStop('stop');\n this.emit('paused', { engine: this });\n }\n if (this._timerId) {\n cancelAnimationFrame(this._timerId);\n }\n }\n\n _end() {\n this.pause();\n this.emit('ended', { engine: this });\n }\n\n _startOrStop(type) {\n for (let i = 0; i < this._activeActionIds.length; i++) {\n const actionId = this._activeActionIds[i];\n const action = this._actionMap[actionId];\n const effect = this._effectMap[action?.effectId];\n\n if (type === 'start') {\n effect?.source?.start?.({ action, effect, engine: this, isPlaying: this.isPlaying, time: this.getTime() });\n } else if (type === 'stop') {\n effect?.source?.stop?.({ action, effect, engine: this, isPlaying: this.isPlaying, time: this.getTime() });\n }\n }\n }\n\n _tick(data) {\n if (this.isPaused) return;\n const { now, autoEnd, to } = data;\n\n let currentTime = this.getTime() + (Math.min(1000, now - this._prev) / 1000) * this._playRate;\n this._prev = now;\n\n if (to && to <= currentTime) currentTime = to;\n this.setTime(currentTime, true);\n\n this._tickAction(currentTime);\n\n if (!to && autoEnd && this._next >= this._actionSortIds.length && this._activeActionIds.length === 0) {\n this._end();\n return;\n }\n\n if (to && to <= currentTime) {\n this._end();\n return;\n }\n\n if (this.isPaused) return;\n this._timerId = requestAnimationFrame((time) => {\n this._tick({ now: time, autoEnd, to });\n });\n }\n\n _tickAction(time) {\n this._dealEnter(time);\n this._dealLeave(time);\n\n const length = this._activeActionIds.length;\n for (let i = 0; i < length; i++) {\n const actionId = this._activeActionIds[i];\n const action = this._actionMap[actionId];\n const effect = this._effectMap[action.effectId];\n if (effect?.source?.update) {\n effect.source.update({ time, action, isPlaying: this.isPlaying, effect, engine: this });\n }\n }\n }\n\n _dealClear() {\n while (this._activeActionIds.length) {\n const actionId = this._activeActionIds.shift();\n const action = this._actionMap[actionId];\n const effect = this._effectMap[action?.effectId];\n if (effect?.source?.leave) {\n effect.source.leave({ action, effect, engine: this, isPlaying: this.isPlaying, time: this.getTime() });\n }\n }\n this._next = 0;\n }\n\n _dealEnter(time) {\n while (this._actionSortIds[this._next]) {\n const actionId = this._actionSortIds[this._next];\n const action = this._actionMap[actionId];\n\n if (!action.disable) {\n if (action.start > time) break;\n if (action.end > time && !this._activeActionIds.includes(actionId)) {\n const effect = this._effectMap[action.effectId];\n if (effect?.source?.enter) {\n effect.source.enter({ action, effect, isPlaying: this.isPlaying, time, engine: this });\n }\n this._activeActionIds.push(actionId);\n }\n }\n this._next++;\n }\n }\n\n _dealLeave(time) {\n let i = 0;\n while (this._activeActionIds[i]) {\n const actionId = this._activeActionIds[i];\n const action = this._actionMap[actionId];\n\n if (action.start > time || action.end < time) {\n const effect = this._effectMap[action.effectId];\n if (effect?.source?.leave) {\n effect.source.leave({ action, effect, isPlaying: this.isPlaying, time, engine: this });\n }\n this._activeActionIds.splice(i, 1);\n continue;\n }\n i++;\n }\n }\n\n _dealData(data) {\n const actions = [];\n data.forEach(row => {\n // Support both new schema (blocks) and old schema (actions/items)\n const items = row.blocks || row.items || row.actions || [];\n actions.push(...items);\n });\n const sortActions = actions.sort((a, b) => a.start - b.start);\n const actionMap = {};\n const actionSortIds = [];\n\n sortActions.forEach(action => {\n actionSortIds.push(action.id);\n actionMap[action.id] = { ...action };\n });\n this._actionMap = actionMap;\n this._actionSortIds = actionSortIds;\n }\n}\n","import { TrakkEngine } from './trakk-engine.js';\n\n/**\n * Utility functions\n */\nconst parserTimeToPixel = (time, { startLeft, scale, scaleWidth }) => {\n return (time / scale) * scaleWidth + startLeft;\n};\n\nconst parserPixelToTime = (pixel, { startLeft, scale, scaleWidth }) => {\n return ((pixel - startLeft) / scaleWidth) * scale;\n};\n\n/**\n * Trakk - Timeline Editor Web Component\n */\nexport class Trakk extends HTMLElement {\n constructor() {\n super();\n\n // Default configuration\n this.config = {\n scale: 1,\n scaleWidth: 160,\n scaleCount: 20,\n scaleSplitCount: 10,\n startLeft: 120,\n contentPadding: 0,\n minScaleCount: 10,\n maxScaleCount: Infinity,\n rowHeight: 32,\n autoScroll: true,\n hideCursor: false,\n disableDrag: false,\n gridSnap: true,\n grid: 1\n };\n\n // Callback functions\n this.callbacks = {\n onActionMoveStart: null,\n onActionMoving: null,\n onActionMoveEnd: null,\n onActionResizeStart: null,\n onActionResizing: null,\n onActionResizeEnd: null,\n onClickRow: null,\n onClickAction: null,\n onDoubleClickRow: null,\n onDoubleClickAction: null,\n onContextMenuRow: null,\n onContextMenuAction: null,\n onCursorDragStart: null,\n onCursorDrag: null,\n onCursorDragEnd: null,\n onClickTimeArea: null,\n getActionRender: null,\n getScaleRender: null\n };\n\n // State\n this.tracks = [];\n this.cursorTime = 0;\n this.isPlaying = false;\n this._scrollX = 0;\n this._scrollY = 0;\n\n // DOM refs\n this.timeAreaEl = null;\n this.timeAreaWrapperEl = null;\n this.editAreaEl = null;\n this.labelColumnEl = null;\n this.labelInnerEl = null;\n this.cursorEl = null;\n\n // Engine\n this.engine = new TrakkEngine();\n\n // Drag state\n this.dragState = {\n isDragging: false,\n isActuallyDragging: false, // Only true after threshold\n type: null,\n action: null,\n row: null,\n rowIndex: null,\n startX: 0,\n startY: 0,\n currentLeft: 0,\n currentWidth: 0,\n deltaX: 0,\n totalDeltaX: 0 // Track total movement\n };\n\n }\n\n connectedCallback() {\n this.className = 'timeline-editor';\n this.render();\n this._setupEngineListeners();\n this._setupResizeObserver();\n }\n\n disconnectedCallback() {\n this.engine.pause();\n this._cleanup();\n }\n\n _cleanup() {\n document.removeEventListener('mousemove', this._boundHandleMouseMove);\n document.removeEventListener('mouseup', this._boundHandleMouseUp);\n if (this._resizeObserver) {\n this._resizeObserver.disconnect();\n }\n }\n\n _setupResizeObserver() {\n // Watch for container size changes\n this._resizeObserver = new ResizeObserver((entries) => {\n for (let entry of entries) {\n // Re-render on resize to update layout\n if (this.editAreaEl) {\n this._updateCursorPosition();\n }\n }\n });\n this._resizeObserver.observe(this);\n }\n\n /**\n * Set timeline data\n */\n setData(tracks) {\n this.tracks = tracks || [];\n this.engine.data = this.tracks;\n this.render();\n }\n\n /**\n * Update configuration\n */\n setConfig(newConfig) {\n Object.assign(this.config, newConfig);\n this.render();\n }\n\n /**\n * Set callback functions\n */\n setCallbacks(callbacks) {\n Object.assign(this.callbacks, callbacks);\n }\n\n /**\n * Set a single callback\n */\n on(event, callback) {\n if (this.callbacks.hasOwnProperty(event)) {\n this.callbacks[event] = callback;\n }\n }\n\n /**\n * Get current timeline data (for export)\n */\n getData() {\n return {\n tracks: this.tracks\n };\n }\n\n /**\n * Export timeline to JSON string\n */\n exportJSON() {\n return JSON.stringify(this.getData(), null, 2);\n }\n\n /**\n * Import timeline from JSON string\n */\n importJSON(jsonString) {\n try {\n const data = JSON.parse(jsonString);\n // Support both old (editorData) and new (tracks) formats\n const tracks = data.tracks || data.editorData || [];\n this.setData(tracks);\n return true;\n } catch (e) {\n console.error('Failed to import timeline data:', e);\n return false;\n }\n }\n\n /**\n * Save to localStorage\n */\n saveToLocalStorage(key = 'timeline-data') {\n try {\n localStorage.setItem(key, this.exportJSON());\n return true;\n } catch (e) {\n console.error('Failed to save to localStorage:', e);\n return false;\n }\n }\n\n /**\n * Load from localStorage\n */\n loadFromLocalStorage(key = 'timeline-data') {\n try {\n const data = localStorage.getItem(key);\n if (data) {\n return this.importJSON(data);\n }\n return false;\n } catch (e) {\n console.error('Failed to load from localStorage:', e);\n return false;\n }\n }\n\n /**\n * Set current time\n */\n setTime(time) {\n this.cursorTime = Math.max(0, time);\n this.engine.setTime(this.cursorTime);\n this._updateCursorPosition();\n }\n\n /**\n * Get current time\n */\n getTime() {\n return this.engine.getTime();\n }\n\n /**\n * Get total time (end time of the last block across all tracks)\n */\n getTotalTime() {\n let maxEnd = 0;\n for (const track of this.tracks) {\n const blocks = track.blocks || track.items || track.actions || [];\n for (const block of blocks) {\n if (block.end > maxEnd) {\n maxEnd = block.end;\n }\n }\n }\n return maxEnd;\n }\n\n /**\n * Play timeline\n */\n play(options = {}) {\n return this.engine.play(options);\n }\n\n /**\n * Pause timeline\n */\n pause() {\n this.engine.pause();\n }\n\n /**\n * Sync engine data with current tracks (call after modifying tracks)\n */\n _syncEngineData() {\n this.engine.data = this.tracks;\n }\n\n /**\n * Emit change event and sync engine\n */\n _emitChange() {\n this._syncEngineData();\n this.dispatchEvent(new CustomEvent('change', {\n detail: { tracks: this.tracks }\n }));\n }\n\n /**\n * Expand timeline if an action extends beyond current bounds\n * @param {number} endTime - The end time to check against\n * @returns {boolean} - Whether the timeline was expanded\n */\n _expandTimelineIfNeeded(endTime) {\n // Add 2 scale units of margin beyond the end time\n const marginScales = 2;\n const requiredScales = Math.ceil(endTime / this.config.scale) + marginScales;\n\n if (requiredScales > this.config.scaleCount) {\n // Respect maxScaleCount limit\n const newScaleCount = Math.min(requiredScales, this.config.maxScaleCount);\n if (newScaleCount > this.config.scaleCount) {\n this.config.scaleCount = newScaleCount;\n this._updateTimelineWidth();\n return true;\n }\n }\n return false;\n }\n\n /**\n * Update timeline width without full re-render (for dynamic expansion)\n */\n _updateTimelineWidth() {\n const contentPadding = this.config.contentPadding;\n const totalWidth = this.config.scaleCount * this.config.scaleWidth + contentPadding;\n\n // Update rows container width\n const rowsContainer = this.editAreaEl?.querySelector('.timeline-editor-rows');\n if (rowsContainer) {\n rowsContainer.style.width = `${totalWidth}px`;\n rowsContainer.style.minWidth = `${totalWidth}px`;\n }\n\n // Update each row width\n const rows = this.editAreaEl?.querySelectorAll('.timeline-editor-edit-row');\n if (rows) {\n rows.forEach(row => {\n row.style.width = `${totalWidth}px`;\n });\n }\n\n // Re-render time area to add new ticks\n this._rerenderTimeArea();\n }\n\n /**\n * Re-render time area (for dynamic expansion)\n */\n _rerenderTimeArea() {\n if (!this.timeAreaEl) return;\n\n // Create new time area content\n const newTimeArea = this._createTimeArea();\n\n // Replace old time area\n this.timeAreaEl.replaceWith(newTimeArea);\n this.timeAreaEl = newTimeArea;\n\n // Restore scroll sync\n this._syncTimeAreaScroll();\n }\n\n /**\n * Setup engine event listeners\n */\n _setupEngineListeners() {\n this.engine.on('play', () => {\n this.isPlaying = true;\n this.classList.add('timeline-editor-playing');\n // Jump to cursor immediately when play starts (not gradual scroll)\n this._scrollToCursorImmediate();\n });\n\n this.engine.on('paused', () => {\n this.isPlaying = false;\n this.classList.remove('timeline-editor-playing');\n });\n\n this.engine.on('setTimeByTick', ({ time }) => {\n this.cursorTime = time;\n this._updateCursorPosition(true); // Auto-scroll during playback\n });\n\n this.engine.on('afterSetTime', ({ time }) => {\n this.cursorTime = time;\n this._updateCursorPosition(false); // No auto-scroll for manual time set\n });\n }\n\n /**\n * Render the timeline editor\n */\n render() {\n this.innerHTML = '';\n\n // Time area (header row with ruler)\n this.timeAreaEl = this._createTimeArea();\n this.appendChild(this.timeAreaEl);\n\n // Cursor (created before content wrapper so it's behind label column)\n if (!this.config.hideCursor) {\n this.cursorEl = this._createCursor();\n this.appendChild(this.cursorEl);\n }\n\n // Set CSS custom properties for dynamic values\n this.style.setProperty('--timeline-start-left', `${this.config.startLeft}px`);\n this.style.setProperty('--timeline-content-padding', `${this.config.contentPadding}px`);\n this.style.setProperty('--timeline-scale-width', `${this.config.scaleWidth}px`);\n\n // Spacer div to cover cursor line between time area and content\n const spacer = document.createElement('div');\n spacer.className = 'timeline-editor-spacer';\n spacer.style.cssText = `\n height: 10px;\n width: calc(var(--timeline-start-left) - 4px);\n background-color: #191b1d;\n flex-shrink: 0;\n position: relative;\n z-index: 101;\n `;\n this.appendChild(spacer);\n\n // Main content wrapper (labels + edit area side by side)\n const contentWrapper = document.createElement('div');\n contentWrapper.className = 'timeline-editor-content';\n contentWrapper.style.cssText = `\n display: flex;\n flex: 1 1 0;\n overflow: hidden;\n position: relative;\n min-height: 0;\n min-width: 0;\n height: 0;\n `;\n\n // Frozen label column\n this.labelColumnEl = this._createLabelColumn();\n contentWrapper.appendChild(this.labelColumnEl);\n\n // Edit area (scrollable)\n this.editAreaEl = this._createEditArea();\n contentWrapper.appendChild(this.editAreaEl);\n\n this.appendChild(contentWrapper);\n\n // Restore scroll position and sync\n if (this._scrollX > 0) {\n this.editAreaEl.scrollLeft = this._scrollX;\n this._syncTimeAreaScroll();\n }\n if (this._scrollY > 0) {\n this.editAreaEl.scrollTop = this._scrollY;\n this._syncLabelColumnScroll();\n }\n this._updateCursorPosition();\n }\n\n /**\n * Create time area (ruler)\n */\n _createTimeArea() {\n const timeArea = document.createElement('div');\n timeArea.className = 'timeline-editor-time-area';\n\n // Content padding to push content away from label column edge\n const contentPadding = this.config.contentPadding;\n\n // Create a wrapper that will be scrolled\n const wrapper = document.createElement('div');\n wrapper.className = 'timeline-editor-time-area-wrapper';\n const totalWidth = this.config.scaleCount * this.config.scaleWidth + this.config.startLeft + contentPadding;\n wrapper.style.width = `${totalWidth}px`;\n wrapper.style.height = '100%';\n wrapper.style.position = 'relative';\n\n const interact = document.createElement('div');\n interact.className = 'timeline-editor-time-area-interact';\n interact.style.width = `${totalWidth}px`;\n\n // Calculate total number of tick marks including subdivisions\n const totalTicks = this.config.scaleCount * this.config.scaleSplitCount;\n const tickWidth = this.config.scaleWidth / this.config.scaleSplitCount;\n\n for (let i = 0; i <= totalTicks; i++) {\n const unit = document.createElement('div');\n const isBig = i % this.config.scaleSplitCount === 0;\n unit.className = `timeline-editor-time-unit ${isBig ? 'timeline-editor-time-unit-big' : ''}`;\n unit.style.width = `${tickWidth}px`;\n\n // Position first tick at startLeft + contentPadding to clear the blocker\n // The blocker covers the full startLeft area, so we add contentPadding to push \"0.0s\" label into view\n if (i === 0) {\n unit.style.marginLeft = `${this.config.startLeft + contentPadding - tickWidth + 1}px`;\n }\n\n if (isBig) {\n const scale = document.createElement('div');\n scale.className = 'timeline-editor-time-unit-scale';\n const scaleValue = (i / this.config.scaleSplitCount) * this.config.scale;\n\n // Use custom render if provided\n if (this.callbacks.getScaleRender) {\n const customContent = this.callbacks.getScaleRender(scaleValue);\n if (typeof customContent === 'string') {\n scale.innerHTML = customContent;\n } else if (customContent instanceof HTMLElement) {\n scale.innerHTML = '';\n scale.appendChild(customContent);\n }\n } else {\n // First tick shows \"0s\", others show decimal like \"1.0s\"\n scale.textContent = i === 0 ? '0s' : `${scaleValue.toFixed(1)}s`;\n }\n\n unit.appendChild(scale);\n }\n\n interact.appendChild(unit);\n }\n\n wrapper.appendChild(interact);\n timeArea.appendChild(wrapper);\n\n // Store wrapper reference for scroll sync\n this.timeAreaWrapperEl = wrapper;\n\n // Click handler for time area\n timeArea.addEventListener('click', (e) => {\n if (this.isPlaying) return;\n const rect = timeArea.getBoundingClientRect();\n // Account for contentPadding in time conversion\n const x = e.clientX - rect.left + this._scrollX - contentPadding;\n const time = parserPixelToTime(x, this.config);\n\n // Callback\n if (this.callbacks.onClickTimeArea) {\n const result = this.callbacks.onClickTimeArea(e, { time });\n if (result === false) return;\n }\n\n this.setTime(Math.max(0, time));\n });\n\n return timeArea;\n }\n\n /**\n * Create frozen label column\n */\n _createLabelColumn() {\n const labelColumn = document.createElement('div');\n labelColumn.className = 'timeline-editor-label-column';\n labelColumn.style.cssText = `\n width: ${this.config.startLeft}px;\n flex-shrink: 0;\n overflow: hidden;\n background-color: #191b1d;\n border-right: 1px solid rgba(255, 255, 255, 0.1);\n z-index: 200;\n position: relative;\n `;\n\n // Inner container that will be transformed for scroll sync\n const labelInner = document.createElement('div');\n labelInner.className = 'timeline-editor-label-inner';\n this.labelInnerEl = labelInner;\n\n this.tracks.forEach((row, rowIndex) => {\n const labelRow = this._createLabelRow(row, rowIndex);\n labelInner.appendChild(labelRow);\n });\n\n labelColumn.appendChild(labelInner);\n return labelColumn;\n }\n\n /**\n * Create a label row (for frozen column)\n */\n _createLabelRow(row, rowIndex) {\n const labelRow = document.createElement('div');\n labelRow.className = 'timeline-editor-label-row';\n labelRow.style.cssText = `\n height: ${row.rowHeight || this.config.rowHeight}px;\n display: flex;\n align-items: center;\n padding: 0 8px;\n color: rgba(255, 255, 255, 0.7);\n font-size: 12px;\n font-weight: 500;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n box-sizing: border-box;\n position: relative;\n `;\n\n if (rowIndex === 0) {\n labelRow.style.borderTop = '1px solid rgba(255, 255, 255, 0.1)';\n }\n\n labelRow.dataset.rowId = row.id;\n labelRow.dataset.rowIndex = rowIndex;\n\n // Label text\n const labelText = document.createElement('span');\n labelText.className = 'timeline-editor-label-text';\n labelText.textContent = row.name || '';\n labelText.style.cssText = `\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n `;\n labelRow.appendChild(labelText);\n\n // Make label editable on dblclick (unless locked)\n if (!row.locked) {\n labelRow.style.cursor = 'text';\n labelRow.addEventListener('dblclick', (e) => {\n e.stopPropagation();\n this._startLabelEdit(labelRow, labelText, row);\n });\n }\n\n // Show locked indicator\n if (row.locked) {\n const lockIcon = document.createElement('span');\n lockIcon.className = 'timeline-editor-lock-icon';\n lockIcon.style.cssText = `\n width: 10px;\n height: 10px;\n margin-left: 6px;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='rgba(255,255,255,0.4)'%3E%3Cpath d='M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z'/%3E%3C/svg%3E\");\n background-size: contain;\n background-repeat: no-repeat;\n flex-shrink: 0;\n `;\n labelRow.appendChild(lockIcon);\n }\n\n // Add delete button (unless noDelete is set)\n if (!row.noDelete) {\n const deleteBtn = document.createElement('div');\n deleteBtn.className = 'timeline-editor-row-delete';\n deleteBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this._deleteTrack(row);\n });\n labelRow.appendChild(deleteBtn);\n }\n\n return labelRow;\n }\n\n /**\n * Create edit area (rows and actions)\n */\n _createEditArea() {\n const editArea = document.createElement('div');\n editArea.className = 'timeline-editor-edit-area';\n\n // Extra padding to match the time area tick offset (content pushed away from label edge)\n const contentPadding = this.config.contentPadding;\n const totalWidth = this.config.scaleCount * this.config.scaleWidth + contentPadding;\n\n // Create rows container\n const rowsContainer = document.createElement('div');\n rowsContainer.className = 'timeline-editor-rows';\n rowsContainer.style.position = 'relative';\n rowsContainer.style.width = `${totalWidth}px`;\n rowsContainer.style.minWidth = `${totalWidth}px`;\n\n this.tracks.forEach((row, rowIndex) => {\n const rowEl = this._createRow(row, rowIndex, totalWidth, contentPadding);\n rowsContainer.appendChild(rowEl);\n });\n\n editArea.appendChild(rowsContainer);\n\n // Sync scroll with time area and label column\n editArea.addEventListener('scroll', () => {\n this._scrollX = editArea.scrollLeft;\n this._scrollY = editArea.scrollTop;\n this._syncTimeAreaScroll();\n this._syncLabelColumnScroll();\n this._updateCursorPosition();\n });\n\n return editArea;\n }\n\n /**\n * Sync label column scroll with edit area (vertical only)\n */\n _syncLabelColumnScroll() {\n if (!this.labelInnerEl) return;\n this.labelInnerEl.style.transform = `translateY(-${this._scrollY}px)`;\n }\n\n /**\n * Create a row (edit area only, no label - labels are in frozen column)\n */\n _createRow(row, rowIndex, totalWidth, contentPadding) {\n const rowEl = document.createElement('div');\n rowEl.className = 'timeline-editor-edit-row';\n rowEl.style.height = `${row.rowHeight || this.config.rowHeight}px`;\n rowEl.style.width = `${totalWidth}px`;\n\n // Offset background grid by contentPadding to align with time ruler (size comes from CSS variable)\n rowEl.style.backgroundPosition = `${contentPadding}px 0`;\n\n rowEl.dataset.rowId = row.id;\n rowEl.dataset.rowIndex = rowIndex;\n\n // Click handler\n rowEl.addEventListener('click', (e) => {\n // Only if clicking directly on the row (not on an action)\n if (e.target === rowEl) {\n // Cancel any active editing when clicking empty track area\n this._cancelActiveEditing();\n\n if (this.callbacks.onClickRow) {\n const rect = e.currentTarget.getBoundingClientRect();\n // Edit area starts at 0, account for contentPadding, add startLeft for correct time conversion\n const x = e.clientX - rect.left + this._scrollX - contentPadding + this.config.startLeft;\n const time = parserPixelToTime(x, this.config);\n this.callbacks.onClickRow(e, { row, time });\n }\n }\n });\n\n // Double-click handler\n rowEl.addEventListener('dblclick', (e) => {\n if (e.target === rowEl && this.callbacks.onDoubleClickRow) {\n const rect = e.currentTarget.getBoundingClientRect();\n // Edit area starts at 0, account for contentPadding, add startLeft for correct time conversion\n const x = e.clientX - rect.left + this._scrollX - contentPadding + this.config.startLeft;\n const time = parserPixelToTime(x, this.config);\n this.callbacks.onDoubleClickRow(e, { row, time });\n }\n });\n\n // Context menu handler\n rowEl.addEventListener('contextmenu', (e) => {\n if (e.target === rowEl && this.callbacks.onContextMenuRow) {\n e.preventDefault();\n const rect = e.currentTarget.getBoundingClientRect();\n // Edit area starts at 0, account for contentPadding, add startLeft for correct time conversion\n const x = e.clientX - rect.left + this._scrollX - contentPadding + this.config.startLeft;\n const time = parserPixelToTime(x, this.config);\n this.callbacks.onContextMenuRow(e, { row, time });\n }\n });\n\n // Add mousedown handler for creating new items via drag on empty space\n rowEl.addEventListener('mousedown', (e) => {\n // Only if clicking directly on the row (not on an action)\n if (e.target === rowEl || e.target === rowEl.querySelector('.timeline-editor-row-label')) {\n this._handleRowDragStart(e, row, rowIndex);\n }\n });\n\n // Create items for this row (fallback to actions for backward compatibility)\n const items = row.blocks || row.items || row.actions || [];\n items.forEach((item) => {\n const actionEl = this._createAction(item, row, rowIndex);\n rowEl.appendChild(actionEl);\n });\n\n return rowEl;\n }\n\n /**\n * Start editing a track label\n */\n _startLabelEdit(labelRow, labelText, row) {\n if (labelRow.querySelector('input')) return; // Already editing\n\n const currentName = row.name || '';\n const input = document.createElement('input');\n input.type = 'text';\n input.value = currentName;\n input.className = 'timeline-editor-row-label-input';\n input.style.cssText = `\n flex: 1;\n min-width: 0;\n background: transparent;\n border: none;\n border-bottom: 1px solid rgba(255,255,255,0.3);\n color: inherit;\n font: inherit;\n outline: none;\n padding: 0;\n margin: 0;\n user-select: text;\n box-sizing: border-box;\n `;\n\n let editFinished = false;\n const finishEdit = () => {\n // Prevent double execution\n if (editFinished) return;\n editFinished = true;\n\n const newName = input.value.trim();\n row.name = newName;\n labelText.textContent = newName;\n labelText.style.display = '';\n input.remove();\n\n // Emit change event\n this._emitChange();\n this.dispatchEvent(new CustomEvent('trackrenamed', {\n detail: { track: row, name: newName }\n }));\n };\n\n input.addEventListener('blur', finishEdit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n input.blur();\n } else if (e.key === 'Escape') {\n input.value = currentName;\n input.blur();\n }\n });\n\n labelText.style.display = 'none';\n labelRow.insertBefore(input, labelText);\n input.focus();\n input.select();\n }\n\n /**\n * Delete a track\n */\n _deleteTrack(row) {\n // Remove from tracks array\n const idx = this.tracks.indexOf(row);\n if (idx > -1) {\n this.tracks.splice(idx, 1);\n }\n\n // Re-render\n this.render();\n\n // Emit events\n this._emitChange();\n this.dispatchEvent(new CustomEvent('trackdeleted', {\n detail: { track: row }\n }));\n }\n\n /**\n * Delete a block from a track\n */\n _deleteBlock(block, row) {\n const blocks = row.blocks || row.items || row.actions || [];\n const idx = blocks.indexOf(block);\n if (idx > -1) {\n blocks.splice(idx, 1);\n }\n\n // Re-render\n this.render();\n\n // Emit events\n this._emitChange();\n this.dispatchEvent(new CustomEvent('blockdeleted', {\n detail: { block, track: row }\n }));\n }\n\n /**\n * Start editing a block name\n */\n _startBlockNameEdit(actionEl, block, row) {\n const content = actionEl.querySelector('.timeline-editor-action-content');\n if (!content || content.querySelector('input')) return; // Already editing\n\n const currentName = block.name || '';\n const input = document.createElement('input');\n input.type = 'text';\n input.value = currentName;\n input.className = 'timeline-editor-block-name-input';\n input.style.cssText = `\n width: 100%;\n background: transparent;\n border: none;\n border-bottom: 1px solid rgba(255,255,255,0.3);\n color: inherit;\n font: inherit;\n outline: none;\n padding: 0;\n text-align: center;\n user-select: text;\n `;\n\n let editFinished = false;\n const finishEdit = () => {\n // Prevent double execution\n if (editFinished) return;\n editFinished = true;\n\n const newName = input.value.trim();\n block.name = newName;\n\n // Remove input and restore content\n input.remove();\n\n // Update content display\n if (this.callbacks.getActionRender) {\n const customContent = this.callbacks.getActionRender(block, row);\n if (typeof customContent === 'string') {\n content.innerHTML = customContent;\n }\n } else {\n content.textContent = newName;\n }\n\n // Emit change event\n this._emitChange();\n this.dispatchEvent(new CustomEvent('blockrenamed', {\n detail: { block, track: row, name: newName }\n }));\n };\n\n input.addEventListener('blur', finishEdit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n input.blur();\n } else if (e.key === 'Escape') {\n input.value = currentName;\n input.blur();\n }\n e.stopPropagation();\n });\n input.addEventListener('mousedown', (e) => e.stopPropagation());\n input.addEventListener('click', (e) => e.stopPropagation());\n\n content.innerHTML = '';\n content.appendChild(input);\n input.focus();\n input.select();\n }\n\n /**\n * Cancel any active block name editing by blurring input fields\n */\n _cancelActiveEditing() {\n const activeInput = this.querySelector('.timeline-editor-block-name-input');\n if (activeInput) {\n activeInput.blur();\n }\n const activeLabelInput = this.querySelector('.timeline-editor-row-label-input');\n if (activeLabelInput) {\n activeLabelInput.blur();\n }\n }\n\n /**\n * Handle drag start on empty row space to create new item\n */\n _handleRowDragStart(e, row, rowIndex) {\n if (this.isPlaying || this.config.disableDrag || row.locked) return;\n\n // Only left mouse button\n if (e.button !== 0) return;\n\n e.preventDefault();\n\n // Use editAreaEl for consistent coordinate calculation (same as _updateNewItemFromDrag)\n const rect = this.editAreaEl.getBoundingClientRect();\n const contentPadding = this.config.contentPadding;\n // Edit area starts at 0, account for contentPadding, add startLeft for correct time conversion\n const x = e.clientX - rect.left + this._scrollX - contentPadding + this.config.startLeft;\n const startTime = parserPixelToTime(x, this.config);\n\n // Setup drag state for item creation\n this.dragState.isDragging = true;\n this.dragState.isActuallyDragging = false;\n this.dragState.type = 'item-create';\n this.dragState.row = row;\n this.dragState.rowIndex = rowIndex;\n this.dragState.startX = e.clientX;\n this.dragState.startTime = startTime;\n this.dragState.totalDeltaX = 0;\n this.dragState.newItem = null;\n this.dragState.newItemEl = null;\n\n this._boundHandleMouseMove = this._handleMouseMove.bind(this);\n this._boundHandleMouseUp = this._handleMouseUp.bind(this);\n\n document.addEventListener('mousemove', this._boundHandleMouseMove);\n document.addEventListener('mouseup', this._boundHandleMouseUp);\n }\n\n /**\n * Create an action element\n */\n _createAction(action, row, rowIndex) {\n const actionEl = document.createElement('div');\n actionEl.className = 'timeline-editor-action';\n if (action.selected) {\n actionEl.classList.add('selected');\n }\n\n // Content padding to match time ruler offset\n const contentPadding = this.config.contentPadding;\n // Edit area starts at 0 (label column is separate), so subtract startLeft, then add contentPadding\n const left = parserTimeToPixel(action.start, this.config) - this.config.startLeft + contentPadding;\n const width = parserTimeToPixel(action.end, this.config) - this.config.startLeft + contentPadding - left;\n\n actionEl.style.left = `${left}px`;\n actionEl.style.width = `${width}px`;\n actionEl.dataset.actionId = action.id;\n actionEl.dataset.rowIndex = rowIndex;\n\n // Content\n const content = document.createElement('div');\n content.className = 'timeline-editor-action-content';\n\n // Use custom render if provided\n if (this.callbacks.getActionRender) {\n const customContent = this.callbacks.getActionRender(action, row);\n if (typeof customContent === 'string') {\n content.innerHTML = customContent;\n } else if (customContent instanceof HTMLElement) {\n content.innerHTML = '';\n content.appendChild(customContent);\n }\n } else {\n // Display block name - only show if explicitly set (non-empty string)\n // Don't fall back to id as that creates ugly display\n content.textContent = action.name || '';\n }\n actionEl.appendChild(content);\n\n // Resize handles (only if not locked)\n if (action.flexible !== false && !row.locked) {\n const leftStretch = document.createElement('div');\n leftStretch.className = 'timeline-editor-action-left-stretch';\n actionEl.appendChild(leftStretch);\n\n const rightStretch = document.createElement('div');\n rightStretch.className = 'timeline-editor-action-right-stretch';\n actionEl.appendChild(rightStretch);\n\n leftStretch.addEventListener('mousedown', (e) => this._handleResizeStart(e, action, row, rowIndex, 'left'));\n rightStretch.addEventListener('mousedown', (e) => this._handleResizeStart(e, action, row, rowIndex, 'right'));\n }\n\n // Add drag listener for moving (only if not locked)\n if (action.movable !== false && !row.locked) {\n actionEl.addEventListener('mousedown', (e) => {\n // Ignore if clicking on resize handles\n if (e.target.classList.contains('timeline-editor-action-left-stretch') ||\n e.target.classList.contains('timeline-editor-action-right-stretch')) {\n return;\n }\n this._handleMoveStart(e, action, row, rowIndex);\n });\n }\n\n // Visual indicator for locked track\n if (row.locked) {\n actionEl.classList.add('timeline-editor-action-locked');\n actionEl.style.cursor = 'default';\n }\n\n // Add delete button for block (unless noDelete is set on block or track is locked)\n if (!action.noDelete && !row.locked) {\n const deleteBtn = document.createElement('div');\n deleteBtn.className = 'timeline-editor-action-delete';\n deleteBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this._deleteBlock(action, row);\n });\n deleteBtn.addEventListener('mousedown', (e) => {\n e.stopPropagation(); // Prevent drag start\n });\n actionEl.appendChild(deleteBtn);\n }\n\n // Click event\n actionEl.addEventListener('click', (e) => {\n if (this.callbacks.onClickAction) {\n const rect = this.editAreaEl.getBoundingClientRect();\n // Edit area starts at 0, account for contentPadding, add startLeft for correct time conversion\n const x = e.clientX - rect.left + this._scrollX - contentPadding + this.config.startLeft;\n const time = parserPixelToTime(x, this.config);\n this.callbacks.onClickAction(e, { action, row, time });\n }\n });\n\n // Double-click event - edit block name (unless locked)\n actionEl.addEventListener('dblclick', (e) => {\n e.stopPropagation();\n\n // Allow callback to handle or prevent\n if (this.callbacks.onDoubleClickAction) {\n const rect = this.editAreaEl.getBoundingClientRect();\n // Edit area starts at 0, account for contentPadding, add startLeft for correct time conversion\n const x = e.clientX - rect.left + this._scrollX - contentPadding + this.config.startLeft;\n const time = parserPixelToTime(x, this.config);\n const result = this.callbacks.onDoubleClickAction(e, { action, row, time });\n if (result === false) return;\n }\n\n // Start editing block name (unless track is locked)\n if (!row.locked) {\n this._startBlockNameEdit(actionEl, action, row);\n }\n });\n\n // Context menu event\n actionEl.addEventListener('contextmenu', (e) => {\n if (this.callbacks.onContextMenuAction) {\n e.preventDefault();\n const rect = this.editAreaEl.getBoundingClientRect();\n // Edit area starts at 0, account for contentPadding, add startLeft for correct time conversion\n const x = e.clientX - rect.left + this._scrollX - contentPadding + this.config.startLeft;\n const time = parserPixelToTime(x, this.config);\n this.callbacks.onContextMenuAction(e, { action, row, time });\n }\n });\n\n return actionEl;\n }\n\n /**\n * Create cursor\n */\n _createCursor() {\n const cursor = document.createElement('div');\n cursor.className = 'timeline-editor-cursor';\n\n const cursorTop = document.createElement('div');\n cursorTop.className = 'timeline-editor-cursor-top';\n cursor.appendChild(cursorTop);\n\n const cursorArea = document.createElement('div');\n cursorArea.className = 'timeline-editor-cursor-area';\n cursor.appendChild(cursorArea);\n\n // Cursor drag\n cursorArea.addEventListener('mousedown', (e) => this._handleCursorDragStart(e));\n\n return cursor;\n }\n\n /**\n * Update cursor position\n * @param {boolean} shouldAutoScroll - Whether to auto-scroll to keep cursor visible (only during playback/drag)\n */\n _updateCursorPosition(shouldAutoScroll = false) {\n if (!this.cursorEl) return;\n const contentPadding = this.config.contentPadding;\n // parserTimeToPixel includes startLeft, which now represents the label column width\n // The cursor is positioned relative to the whole timeline including label column\n // Add contentPadding to align with the offset content\n const left = parserTimeToPixel(this.cursorTime, this.config) + contentPadding;\n // Cursor position relative to viewport, accounting for scroll\n this.cursorEl.style.left = `${left - this._scrollX}px`;\n\n // Auto-scroll to keep cursor visible (only when explicitly requested, e.g. during playback)\n if (shouldAutoScroll && this.config.autoScroll && this.editAreaEl) {\n this._autoScrollToCursor(left);\n }\n }\n\n /**\n * Scroll to cursor immediately (no gradual scroll) - used when play starts\n */\n _scrollToCursorImmediate() {\n if (!this.editAreaEl || !this.config.autoScroll) return;\n\n const contentPadding = this.config.contentPadding;\n const cursorLeft = parserTimeToPixel(this.cursorTime, this.config) + contentPadding;\n const editAreaWidth = this.editAreaEl.clientWidth;\n const cursorInEditArea = cursorLeft - this.config.startLeft;\n\n const scrollMargin = 50;\n const visibleLeft = this._scrollX;\n const visibleRight = this._scrollX + editAreaWidth;\n\n // Only jump if cursor is outside visible area\n if (cursorInEditArea < visibleLeft + scrollMargin || cursorInEditArea > visibleRight - scrollMargin) {\n // Center cursor in view (or near left edge if at start)\n const targetScrollX = Math.max(0, cursorInEditArea - editAreaWidth / 3);\n this.editAreaEl.scrollLeft = targetScrollX;\n }\n }\n\n /**\n * Auto-scroll edit area to keep cursor visible\n */\n _autoScrollToCursor(cursorLeft) {\n const editAreaWidth = this.editAreaEl.clientWidth;\n // Cursor position in edit area coordinates (subtract startLeft since edit area doesn't include label column)\n const cursorInEditArea = cursorLeft - this.config.startLeft;\n\n // Define margin - scroll when cursor is within this distance from edge\n const scrollMargin = 50;\n // Maximum scroll step per update for smooth scrolling\n const maxScrollStep = 8;\n\n // Check if cursor is outside visible area\n const visibleLeft = this._scrollX;\n const visibleRight = this._scrollX + editAreaWidth;\n\n let targetScrollX = null;\n\n if (cursorInEditArea < visibleLeft + scrollMargin) {\n // Cursor is too far left - scroll left\n targetScrollX = Math.max(0, cursorInEditArea - scrollMargin);\n } else if (cursorInEditArea > visibleRight - scrollMargin) {\n // Cursor is too far right - scroll right\n targetScrollX = cursorInEditArea - editAreaWidth + scrollMargin;\n }\n\n if (targetScrollX !== null) {\n // Smooth scroll: move gradually towards target instead of jumping\n const delta = targetScrollX - this._scrollX;\n const step = Math.sign(delta) * Math.min(Math.abs(delta), maxScrollStep);\n this.editAreaEl.scrollLeft = this._scrollX + step;\n }\n }\n\n /**\n * Sync time area scroll with edit area\n */\n _syncTimeAreaScroll() {\n if (!this.timeAreaWrapperEl) {\n // Fallback: try to find wrapper if reference is lost\n this.timeAreaWrapperEl = this.timeAreaEl?.querySelector('.timeline-editor-time-area-wrapper');\n }\n if (!this.timeAreaWrapperEl) return;\n this.timeAreaWrapperEl.style.transform = `translateX(-${this._scrollX}px)`;\n }\n\n /**\n * Handle cursor drag start\n */\n _handleCursorDragStart(e) {\n if (this.isPlaying || this.config.disableDrag) return;\n e.preventDefault();\n e.stopPropagation();\n\n // Callback\n if (this.callbacks.onCursorDragStart) {\n const result = this.callbacks.onCursorDragStart(e, { time: this.cursorTime });\n if (result === false) return;\n }\n\n this.dragState.isDragging = true;\n this.dragState.type = 'cursor';\n this.dragState.startX = e.clientX;\n\n this._boundHandleMouseMove = this._handleMouseMove.bind(this);\n this._boundHandleMouseUp = this._handleMouseUp.bind(this);\n\n document.addEventListener('mousemove', this._boundHandleMouseMove);\n document.addEventListener('mouseup', this._boundHandleMouseUp);\n }\n\n /**\n * Handle action move start\n */\n _handleMoveStart(e, action, row, rowIndex) {\n if (this.isPlaying || this.config.disableDrag) return;\n e.preventDefault();\n e.stopPropagation();\n\n // Cancel any active editing before starting drag\n this._cancelActiveEditing();\n\n this.dragState.isDragging = true;\n this.dragState.isActuallyDragging = false;\n this.dragState.type = 'action-move';\n this.dragState.action = action;\n this.dragState.row = row;\n this.dragState.rowIndex = rowIndex;\n this.dragState.startX = e.clientX;\n this.dragState.deltaX = 0;\n this.dragState.totalDeltaX = 0;\n this.dragState.currentLeft = parserTimeToPixel(action.start, this.config);\n this.dragState.currentWidth = parserTimeToPixel(action.end, this.config) - this.dragState.currentLeft;\n\n this._boundHandleMouseMove = this._handleMouseMove.bind(this);\n this._boundHandleMouseUp = this._handleMouseUp.bind(this);\n\n document.addEventListener('mousemove', this._boundHandleMouseMove);\n document.addEventListener('mouseup', this._boundHandleMouseUp);\n }\n\n /**\n * Handle action resize start\n */\n _handleResizeStart(e, action, row, rowIndex, direction) {\n if (this.isPlaying || this.config.disableDrag) return;\n e.preventDefault();\n e.stopPropagation();\n\n // Cancel any active editing before starting resize\n this._cancelActiveEditing();\n\n this.dragState.isDragging = true;\n this.dragState.isActuallyDragging = false;\n this.dragState.type = `action-resize-${direction}`;\n this.dragState.action = action;\n this.dragState.row = row;\n this.dragState.rowIndex = rowIndex;\n this.dragState.startX = e.clientX;\n this.dragState.deltaX = 0;\n this.dragState.totalDeltaX = 0;\n this.dragState.currentLeft = parserTimeToPixel(action.start, this.config);\n this.dragState.currentWidth = parserTimeToPixel(action.end, this.config) - this.dragState.currentLeft;\n\n this._boundHandleMouseMove = this._handleMouseMove.bind(this);\n this._boundHandleMouseUp = this._handleMouseUp.bind(this);\n\n document.addEventListener('mousemove', this._boundHandleMouseMove);\n document.addEventListener('mouseup', this._boundHandleMouseUp);\n }\n\n /**\n * Handle mouse move (unified for all drag types)\n */\n _handleMouseMove(e) {\n if (!this.dragState.isDragging) return;\n\n if (this.dragState.type === 'cursor') {\n this._handleCursorDrag(e);\n } else if (this.dragState.type === 'action-move') {\n const dx = e.clientX - this.dragState.startX;\n this.dragState.startX = e.clientX;\n this.dragState.deltaX += dx;\n this.dragState.totalDeltaX += Math.abs(dx);\n\n // Only trigger callback after moving 3px (threshold)\n if (!this.dragState.isActuallyDragging && this.dragState.totalDeltaX > 3) {\n this.dragState.isActuallyDragging = true;\n\n // Trigger callback now that we're actually dragging\n if (this.callbacks.onActionMoveStart) {\n const result = this.callbacks.onActionMoveStart({\n action: this.dragState.action,\n row: this.dragState.row\n });\n if (result === false) {\n this._cancelDrag();\n return;\n }\n }\n }\n\n if (this.dragState.isActuallyDragging) {\n this._handleActionMove();\n }\n } else if (this.dragState.type === 'action-resize-left') {\n const dx = e.clientX - this.dragState.startX;\n this.dragState.startX = e.clientX;\n this.dragState.deltaX += dx;\n this.dragState.totalDeltaX += Math.abs(dx);\n\n // Only trigger callback after moving 3px\n if (!this.dragState.isActuallyDragging && this.dragState.totalDeltaX > 3) {\n this.dragState.isActuallyDragging = true;\n\n if (this.callbacks.onActionResizeStart) {\n const result = this.callbacks.onActionResizeStart({\n action: this.dragState.action,\n row: this.dragState.row,\n direction: 'left'\n });\n if (result === false) {\n this._cancelDrag();\n return;\n }\n }\n }\n\n if (this.dragState.isActuallyDragging) {\n this._handleActionResizeLeft();\n }\n } else if (this.dragState.type === 'action-resize-right') {\n const dx = e.clientX - this.dragState.startX;\n this.dragState.startX = e.clientX;\n this.dragState.deltaX += dx;\n this.dragState.totalDeltaX += Math.abs(dx);\n\n // Only trigger callback after moving 3px\n if (!this.dragState.isActuallyDragging && this.dragState.totalDeltaX > 3) {\n this.dragState.isActuallyDragging = true;\n\n if (this.callbacks.onActionResizeStart) {\n const result = this.callbacks.onActionResizeStart({\n action: this.dragState.action,\n row: this.dragState.row,\n direction: 'right'\n });\n if (result === false) {\n this._cancelDrag();\n return;\n }\n }\n }\n\n if (this.dragState.isActuallyDragging) {\n this._handleActionResizeRight();\n }\n } else if (this.dragState.type === 'item-create') {\n const dx = e.clientX - this.dragState.startX;\n this.dragState.totalDeltaX += Math.abs(dx - (this.dragState.lastDx || 0));\n this.dragState.lastDx = dx;\n\n // Only create item after moving 3px (threshold)\n if (!this.dragState.isActuallyDragging && this.dragState.totalDeltaX > 3) {\n this.dragState.isActuallyDragging = true;\n this._createNewItemFromDrag();\n }\n\n if (this.dragState.isActuallyDragging && this.dragState.newItem) {\n this._updateNewItemFromDrag(e);\n }\n }\n }\n\n /**\n * Create new block when drag threshold is reached\n */\n _createNewItemFromDrag() {\n const row = this.dragState.row;\n const rowIndex = this.dragState.rowIndex;\n const startTime = Math.max(0, this.dragState.startTime);\n\n // Create new block with minimal duration (will expand as user drags)\n const newBlock = {\n id: `block-${Date.now()}`,\n name: '',\n start: startTime,\n end: startTime + 0.1, // Minimal initial duration\n flexible: true,\n movable: true,\n metadata: {}\n };\n\n // Add to row data (ensure blocks array exists)\n if (!row.blocks) row.blocks = [];\n row.blocks.push(newBlock);\n this.dragState.newItem = newBlock;\n\n // Create and append the visual element\n const rowEl = this.editAreaEl.querySelector(`[data-row-index=\"${rowIndex}\"]`);\n if (rowEl) {\n const actionEl = this._createAction(newBlock, row, rowIndex);\n actionEl.classList.add('creating');\n rowEl.appendChild(actionEl);\n this.dragState.newItemEl = actionEl;\n }\n }\n\n /**\n * Update new item size as user drags\n */\n _updateNewItemFromDrag(e) {\n const newItem = this.dragState.newItem;\n if (!newItem) return;\n\n const rect = this.editAreaEl.getBoundingClientRect();\n const contentPadding = this.config.contentPadding;\n // Edit area starts at 0, account for contentPadding, add startLeft for correct time conversion\n const x = e.clientX - rect.left + this._scrollX - contentPadding + this.config.startLeft;\n const currentTime = parserPixelToTime(x, this.config);\n\n // Determine start and end based on drag direction\n const startTime = this.dragState.startTime;\n if (currentTime > startTime) {\n newItem.start = Math.max(0, startTime);\n newItem.end = currentTime;\n } else {\n newItem.start = Math.max(0, currentTime);\n newItem.end = startTime;\n }\n\n // Expand timeline if item extends beyond current bounds\n this._expandTimelineIfNeeded(newItem.end);\n\n // Update visual element (subtract startLeft since edit area starts at 0, add contentPadding)\n if (this.dragState.newItemEl) {\n const contentPadding = this.config.contentPadding;\n const left = parserTimeToPixel(newItem.start, this.config) - this.config.startLeft + contentPadding;\n const width = parserTimeToPixel(newItem.end, this.config) - this.config.startLeft + contentPadding - left;\n this.dragState.newItemEl.style.left = `${left}px`;\n this.dragState.newItemEl.style.width = `${Math.max(10, width)}px`;\n }\n }\n\n /**\n * Handle mouse up (unified)\n */\n _handleMouseUp(e) {\n if (!this.dragState.isDragging) return;\n\n const dragType = this.dragState.type;\n const action = this.dragState.action;\n const row = this.dragState.row;\n const wasActuallyDragging = this.dragState.isActuallyDragging;\n\n // Only trigger end callbacks if we actually dragged (past threshold)\n if (wasActuallyDragging) {\n // End callbacks\n if (dragType === 'cursor' && this.callbacks.onCursorDragEnd) {\n this.callbacks.onCursorDragEnd(e, { time: this.cursorTime });\n } else if (dragType === 'action-move' && this.callbacks.onActionMoveEnd) {\n this.callbacks.onActionMoveEnd({ action, row });\n } else if ((dragType === 'action-resize-left' || dragType === 'action-resize-right') && this.callbacks.onActionResizeEnd) {\n this.callbacks.onActionResizeEnd({ action, row });\n } else if (dragType === 'item-create' && this.dragState.newItem) {\n // Finalize item creation\n const newItem = this.dragState.newItem;\n if (this.dragState.newItemEl) {\n this.dragState.newItemEl.classList.remove('creating');\n }\n\n // Emit events\n this._emitChange();\n this.dispatchEvent(new CustomEvent('itemcreated', {\n detail: { item: newItem, row: row }\n }));\n }\n\n if (dragType && dragType.startsWith('action-')) {\n // Emit change event only if we actually moved/resized\n this._emitChange();\n }\n } else if (dragType === 'item-create' && this.dragState.newItem) {\n // User didn't drag enough - remove the item\n const row = this.dragState.row;\n const newItem = this.dragState.newItem;\n const items = row.blocks || row.items || row.actions || [];\n const idx = items.indexOf(newItem);\n if (idx > -1) {\n items.splice(idx, 1);\n }\n if (this.dragState.newItemEl) {\n this.dragState.newItemEl.remove();\n }\n }\n\n this.dragState.isDragging = false;\n this.dragState.isActuallyDragging = false;\n this.dragState.type = null;\n this.dragState.action = null;\n this.dragState.row = null;\n this.dragState.totalDeltaX = 0;\n this.dragState.newItem = null;\n this.dragState.newItemEl = null;\n this.dragState.lastDx = 0;\n\n document.removeEventListener('mousemove', this._boundHandleMouseMove);\n document.removeEventListener('mouseup', this._boundHandleMouseUp);\n }\n\n /**\n * Cancel drag operation\n */\n _cancelDrag() {\n this.dragState.isDragging = false;\n this.dragState.isActuallyDragging = false;\n this.dragState.type = null;\n this.dragState.action = null;\n this.dragState.row = null;\n this.dragState.totalDeltaX = 0;\n this.dragState.newItem = null;\n this.dragState.newItemEl = null;\n this.dragState.lastDx = 0;\n\n document.removeEventListener('mousemove', this._boundHandleMouseMove);\n document.removeEventListener('mouseup', this._boundHandleMouseUp);\n }\n\n /**\n * Handle cursor drag\n */\n _handleCursorDrag(e) {\n if (!this.editAreaEl) return;\n const rect = this.editAreaEl.getBoundingClientRect();\n const contentPadding = this.config.contentPadding;\n // Edit area starts at 0, account for contentPadding, add startLeft for correct time conversion\n const x = e.clientX - rect.left + this._scrollX - contentPadding + this.config.startLeft;\n const time = Math.max(0, parserPixelToTime(x, this.config));\n\n // Callback\n if (this.callbacks.onCursorDrag) {\n const result = this.callbacks.onCursorDrag(e, { time });\n if (result === false) return;\n }\n\n // Update time and cursor with auto-scroll during drag\n this.cursorTime = time;\n this.engine.setTime(time);\n this._updateCursorPosition(true);\n }\n\n /**\n * Handle action move\n */\n _handleActionMove() {\n const action = this.dragState.action;\n const row = this.dragState.row;\n const grid = this.config.gridSnap ? this.config.scaleWidth / 10 : 1;\n\n // Only apply when accumulated delta exceeds grid\n if (Math.abs(this.dragState.deltaX) >= grid) {\n const count = parseInt(this.dragState.deltaX / grid);\n let newLeft = this.dragState.currentLeft + count * grid;\n\n // Apply grid snapping\n if (this.config.gridSnap) {\n const gridOffset = (newLeft - this.config.startLeft) % grid;\n if (gridOffset !== 0) {\n newLeft = this.config.startLeft + grid * Math.round((newLeft - this.config.startLeft) / grid);\n }\n }\n\n // Bounds check\n newLeft = Math.max(this.config.startLeft, newLeft);\n\n // Update current position\n this.dragState.currentLeft = newLeft;\n this.dragState.deltaX = this.dragState.deltaX % grid;\n\n const startTime = parserPixelToTime(newLeft, this.config);\n const duration = action.end - action.start;\n\n // Callback\n if (this.callbacks.onActionMoving) {\n const result = this.callbacks.onActionMoving({\n action,\n row,\n start: startTime,\n end: startTime + duration\n });\n if (result === false) return;\n }\n\n action.start = startTime;\n action.end = startTime + duration;\n\n // Expand timeline if action extends beyond current bounds\n this._expandTimelineIfNeeded(action.end);\n\n this._updateActionElement(action, this.dragState.rowIndex);\n }\n }\n\n /**\n * Handle action resize left\n */\n _handleActionResizeLeft() {\n const action = this.dragState.action;\n const row = this.dragState.row;\n const grid = this.config.gridSnap ? this.config.scaleWidth / 10 : 1;\n\n // Only apply when accumulated delta exceeds grid\n if (Math.abs(this.dragState.deltaX) >= grid) {\n const count = parseInt(this.dragState.deltaX / grid);\n let newLeft = this.dragState.currentLeft + count * grid;\n\n // Apply grid snapping\n if (this.config.gridSnap) {\n const gridOffset = (newLeft - this.config.startLeft) % grid;\n if (gridOffset !== 0) {\n newLeft = this.config.startLeft + grid * Math.round((newLeft - this.config.startLeft) / grid);\n }\n }\n\n // Keep right edge fixed\n const rightEdge = this.dragState.currentLeft + this.dragState.currentWidth;\n\n // Minimum width and bounds\n newLeft = Math.max(this.config.startLeft, newLeft);\n const minWidth = 10;\n newLeft = Math.min(newLeft, rightEdge - minWidth);\n\n const newWidth = rightEdge - newLeft;\n\n // Update state\n this.dragState.currentLeft = newLeft;\n this.dragState.currentWidth = newWidth;\n this.dragState.deltaX = this.dragState.deltaX % grid;\n\n const startTime = parserPixelToTime(newLeft, this.config);\n\n // Callback\n if (this.callbacks.onActionResizing) {\n const result = this.callbacks.onActionResizing({\n action,\n row,\n start: Math.max(0, startTime),\n end: action.end\n });\n if (result === false) return;\n }\n\n action.start = Math.max(0, startTime);\n\n this._updateActionElement(action, this.dragState.rowIndex);\n }\n }\n\n /**\n * Handle action resize right\n */\n _handleActionResizeRight() {\n const action = this.dragState.action;\n const row = this.dragState.row;\n const grid = this.config.gridSnap ? this.config.scaleWidth / 10 : 1;\n\n // Only apply when accumulated delta exceeds grid\n if (Math.abs(this.dragState.deltaX) >= grid) {\n const count = parseInt(this.dragState.deltaX / grid);\n let newWidth = this.dragState.currentWidth + count * grid;\n\n // Apply grid snapping to right edge\n const rightPos = this.dragState.currentLeft + newWidth;\n if (this.config.gridSnap) {\n const gridOffset = (rightPos - this.config.startLeft) % grid;\n if (gridOffset !== 0) {\n const snappedRight = this.config.startLeft + grid * Math.round((rightPos - this.config.startLeft) / grid);\n newWidth = snappedRight - this.dragState.currentLeft;\n }\n }\n\n // Minimum width\n const minWidth = 10;\n newWidth = Math.max(minWidth, newWidth);\n\n // Update state\n this.dragState.currentWidth = newWidth;\n this.dragState.deltaX = this.dragState.deltaX % grid;\n\n const endPixel = this.dragState.currentLeft + newWidth;\n const endTime = parserPixelToTime(endPixel, this.config);\n\n // Callback\n if (this.callbacks.onActionResizing) {\n const result = this.callbacks.onActionResizing({\n action,\n row,\n start: action.start,\n end: Math.max(action.start + 0.1, endTime)\n });\n if (result === false) return;\n }\n\n action.end = Math.max(action.start + 0.1, endTime);\n\n // Expand timeline if action extends beyond current bounds\n this._expandTimelineIfNeeded(action.end);\n\n this._updateActionElement(action, this.dragState.rowIndex);\n }\n }\n\n /**\n * Update action element visually\n */\n _updateActionElement(action, rowIndex) {\n const rowEl = this.editAreaEl.querySelector(`[data-row-index=\"${rowIndex}\"]`);\n if (!rowEl) return;\n\n const actionEl = rowEl.querySelector(`[data-action-id=\"${action.id}\"]`);\n if (!actionEl) return;\n\n // Content padding to match time ruler offset\n const contentPadding = this.config.contentPadding;\n // Edit area starts at 0 (label column is separate), so subtract startLeft, then add contentPadding\n const left = parserTimeToPixel(action.start, this.config) - this.config.startLeft + contentPadding;\n const width = parserTimeToPixel(action.end, this.config) - this.config.startLeft + contentPadding - left;\n\n actionEl.style.left = `${left}px`;\n actionEl.style.width = `${width}px`;\n }\n}\n\n// Register the custom element\nif (!customElements.get('trakk-editor')) {\n customElements.define('trakk-editor', Trakk);\n}\n"],"names":["EventEmitter","constructor","this","events","on","event","callback","push","off","filter","cb","emit","data","forEach","TrakkEngine","super","_timerId","_playRate","_currentTime","_playState","_prev","_effectMap","_actionMap","_actionSortIds","_next","_activeActionIds","isPlaying","isPaused","effects","pause","_dealData","_dealClear","_dealEnter","setPlayRate","rate","console","error","engine","getPlayRate","reRender","_tickAction","setTime","time","isTick","_dealLeave","getTime","play","toTime","autoEnd","currentTime","_startOrStop","requestAnimationFrame","_tick","now","to","cancelAnimationFrame","_end","type","i","length","actionId","action","effect","effectId","source","start","stop","Math","min","update","shift","leave","disable","end","includes","enter","splice","actions","row","items","blocks","sortActions","sort","a","b","actionMap","actionSortIds","id","parserTimeToPixel","startLeft","scale","scaleWidth","parserPixelToTime","pixel","Trakk","HTMLElement","config","scaleCount","scaleSplitCount","contentPadding","minScaleCount","maxScaleCount","Infinity","rowHeight","autoScroll","hideCursor","disableDrag","gridSnap","grid","callbacks","onActionMoveStart","onActionMoving","onActionMoveEnd","onActionResizeStart","onActionResizing","onActionResizeEnd","onClickRow","onClickAction","onDoubleClickRow","onDoubleClickAction","onContextMenuRow","onContextMenuAction","onCursorDragStart","onCursorDrag","onCursorDragEnd","onClickTimeArea","getActionRender","getScaleRender","tracks","cursorTime","_scrollX","_scrollY","timeAreaEl","timeAreaWrapperEl","editAreaEl","labelColumnEl","labelInnerEl","cursorEl","dragState","isDragging","isActuallyDragging","rowIndex","startX","startY","currentLeft","currentWidth","deltaX","totalDeltaX","connectedCallback","className","render","_setupEngineListeners","_setupResizeObserver","disconnectedCallback","_cleanup","document","removeEventListener","_boundHandleMouseMove","_boundHandleMouseUp","_resizeObserver","disconnect","ResizeObserver","entries","entry","_updateCursorPosition","observe","setData","setConfig","newConfig","Object","assign","setCallbacks","hasOwnProperty","getData","exportJSON","JSON","stringify","importJSON","jsonString","parse","editorData","e","saveToLocalStorage","key","localStorage","setItem","loadFromLocalStorage","getItem","max","getTotalTime","maxEnd","track","block","options","_syncEngineData","_emitChange","dispatchEvent","CustomEvent","detail","_expandTimelineIfNeeded","endTime","requiredScales","ceil","newScaleCount","_updateTimelineWidth","totalWidth","rowsContainer","querySelector","style","width","minWidth","rows","querySelectorAll","_rerenderTimeArea","newTimeArea","_createTimeArea","replaceWith","_syncTimeAreaScroll","classList","add","_scrollToCursorImmediate","remove","innerHTML","appendChild","_createCursor","setProperty","spacer","createElement","cssText","contentWrapper","_createLabelColumn","_createEditArea","scrollLeft","scrollTop","_syncLabelColumnScroll","timeArea","wrapper","height","position","interact","totalTicks","tickWidth","unit","isBig","marginLeft","scaleValue","customContent","textContent","toFixed","addEventListener","rect","getBoundingClientRect","x","clientX","left","labelColumn","labelInner","labelRow","_createLabelRow","borderTop","dataset","rowId","labelText","name","locked","cursor","stopPropagation","_startLabelEdit","lockIcon","noDelete","deleteBtn","_deleteTrack","editArea","rowEl","_createRow","transform","backgroundPosition","target","_cancelActiveEditing","currentTarget","preventDefault","_handleRowDragStart","item","actionEl","_createAction","currentName","input","value","editFinished","newName","trim","display","blur","insertBefore","focus","select","idx","indexOf","_deleteBlock","_startBlockNameEdit","content","activeInput","activeLabelInput","button","startTime","newItem","newItemEl","_handleMouseMove","bind","_handleMouseUp","selected","flexible","leftStretch","rightStretch","_handleResizeStart","movable","contains","_handleMoveStart","cursorTop","cursorArea","_handleCursorDragStart","shouldAutoScroll","_autoScrollToCursor","cursorLeft","editAreaWidth","clientWidth","cursorInEditArea","visibleLeft","visibleRight","targetScrollX","delta","step","sign","abs","direction","_handleCursorDrag","dx","_cancelDrag","_handleActionMove","_handleActionResizeLeft","_handleActionResizeRight","lastDx","_createNewItemFromDrag","_updateNewItemFromDrag","newBlock","Date","metadata","dragType","startsWith","count","parseInt","newLeft","round","duration","_updateActionElement","rightEdge","newWidth","rightPos","endPixel","customElements","get","define"],"mappings":"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"}
|