@editframe/elements 0.26.4-beta.0 → 0.30.0-beta.14

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.
Files changed (62) hide show
  1. package/dist/elements/EFSourceMixin.js +1 -1
  2. package/dist/elements/EFSourceMixin.js.map +1 -1
  3. package/dist/elements/EFSurface.d.ts +4 -4
  4. package/dist/elements/EFText.d.ts +52 -0
  5. package/dist/elements/EFText.js +319 -0
  6. package/dist/elements/EFText.js.map +1 -0
  7. package/dist/elements/EFTextSegment.d.ts +30 -0
  8. package/dist/elements/EFTextSegment.js +94 -0
  9. package/dist/elements/EFTextSegment.js.map +1 -0
  10. package/dist/elements/EFThumbnailStrip.d.ts +4 -4
  11. package/dist/elements/EFWaveform.d.ts +4 -4
  12. package/dist/elements/FetchMixin.js +22 -7
  13. package/dist/elements/FetchMixin.js.map +1 -1
  14. package/dist/elements/easingUtils.js +62 -0
  15. package/dist/elements/easingUtils.js.map +1 -0
  16. package/dist/elements/updateAnimations.js +57 -10
  17. package/dist/elements/updateAnimations.js.map +1 -1
  18. package/dist/gui/ContextMixin.js +11 -2
  19. package/dist/gui/ContextMixin.js.map +1 -1
  20. package/dist/gui/EFConfiguration.d.ts +4 -4
  21. package/dist/gui/EFControls.d.ts +2 -2
  22. package/dist/gui/EFDial.d.ts +4 -4
  23. package/dist/gui/EFDial.js +4 -2
  24. package/dist/gui/EFDial.js.map +1 -1
  25. package/dist/gui/EFFilmstrip.d.ts +32 -6
  26. package/dist/gui/EFFilmstrip.js +314 -50
  27. package/dist/gui/EFFilmstrip.js.map +1 -1
  28. package/dist/gui/EFFitScale.js +39 -15
  29. package/dist/gui/EFFitScale.js.map +1 -1
  30. package/dist/gui/EFFocusOverlay.d.ts +4 -4
  31. package/dist/gui/EFPause.d.ts +4 -4
  32. package/dist/gui/EFPlay.d.ts +4 -4
  33. package/dist/gui/EFPreview.d.ts +4 -4
  34. package/dist/gui/EFPreview.js +2 -2
  35. package/dist/gui/EFPreview.js.map +1 -1
  36. package/dist/gui/EFResizableBox.d.ts +4 -4
  37. package/dist/gui/EFResizableBox.js +6 -3
  38. package/dist/gui/EFResizableBox.js.map +1 -1
  39. package/dist/gui/EFScrubber.d.ts +8 -5
  40. package/dist/gui/EFScrubber.js +64 -12
  41. package/dist/gui/EFScrubber.js.map +1 -1
  42. package/dist/gui/EFTimeDisplay.d.ts +4 -4
  43. package/dist/gui/EFToggleLoop.d.ts +4 -4
  44. package/dist/gui/EFTogglePlay.d.ts +4 -4
  45. package/dist/gui/EFWorkbench.d.ts +4 -4
  46. package/dist/gui/EFWorkbench.js +16 -3
  47. package/dist/gui/EFWorkbench.js.map +1 -1
  48. package/dist/gui/TWMixin.js +1 -1
  49. package/dist/gui/TWMixin.js.map +1 -1
  50. package/dist/index.d.ts +3 -1
  51. package/dist/index.js +3 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/style.css +7 -120
  54. package/package.json +3 -3
  55. package/scripts/build-css.js +2 -6
  56. package/test/constants.ts +8 -0
  57. package/test/recordReplayProxyPlugin.js +76 -10
  58. package/test/setup.ts +32 -0
  59. package/test/useMSW.ts +3 -0
  60. package/test/useTranscodeMSW.ts +191 -0
  61. package/tsdown.config.ts +6 -4
  62. package/types.json +1 -1
@@ -12,6 +12,8 @@ import { EFImage } from "../elements/EFImage.js";
12
12
  import { EFAudio } from "../elements/EFAudio.js";
13
13
  import { EFVideo } from "../elements/EFVideo.js";
14
14
  import { EFCaptions, EFCaptionsActiveWord } from "../elements/EFCaptions.js";
15
+ import { EFText } from "../elements/EFText.js";
16
+ import { EFTextSegment } from "../elements/EFTextSegment.js";
15
17
  import { EFWaveform } from "../elements/EFWaveform.js";
16
18
  import { msToTimeCode } from "../msToTimeCode.js";
17
19
  import { consume } from "@lit/context";
@@ -75,7 +77,7 @@ var FilmstripItem = class extends TWMixin(LitElement) {
75
77
  render() {
76
78
  return html`<div style=${styleMap(this.gutterStyles)}>
77
79
  <div
78
- class="bg-slate-300"
80
+ style="background-color: var(--filmstrip-bg);"
79
81
  ?data-focused=${this.isFocused}
80
82
  @mouseenter=${() => {
81
83
  if (this.focusContext) this.focusContext.focusedElement = this.element;
@@ -86,8 +88,12 @@ var FilmstripItem = class extends TWMixin(LitElement) {
86
88
  >
87
89
  <div
88
90
  ?data-focused=${this.isFocused}
89
- class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border border-slate-500 bg-blue-200 text-sm data-[focused]:bg-slate-400"
90
- style=${styleMap(this.trimPortionStyles)}
91
+ class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border text-sm"
92
+ style=${styleMap({
93
+ ...this.trimPortionStyles,
94
+ backgroundColor: this.isFocused ? "var(--filmstrip-item-focused)" : "var(--filmstrip-item-bg)",
95
+ borderColor: "var(--filmstrip-border)"
96
+ })}
91
97
  >
92
98
  ${this.animations()}
93
99
  </div>
@@ -113,18 +119,22 @@ var FilmstripItem = class extends TWMixin(LitElement) {
113
119
  const properties = new Set(Object.keys(firstKeyframe));
114
120
  for (const key of CommonEffectKeys) properties.delete(key);
115
121
  return html`<div
116
- class="relative h-[5px] bg-blue-500 opacity-50"
122
+ class="relative h-[5px] opacity-50"
117
123
  label="animation"
118
124
  style=${styleMap({
119
125
  left: `${this.pixelsPerMs * start}px`,
120
- width: `${this.pixelsPerMs * Number(duration)}px`
126
+ width: `${this.pixelsPerMs * Number(duration)}px`,
127
+ backgroundColor: "var(--filmstrip-animation-bg)"
121
128
  })}
122
129
  >
123
130
  <!-- <div class="text-nowrap">${Array.from(properties).join(" ")}</div> -->
124
131
  ${effect.getKeyframes().map((keyframe) => {
125
132
  return html`<div
126
- class="absolute top-0 h-full w-1 bg-red-500"
127
- style=${styleMap({ left: `${this.pixelsPerMs * keyframe.computedOffset * Number(duration)}px` })}
133
+ class="absolute top-0 h-full w-1"
134
+ style=${styleMap({
135
+ left: `${this.pixelsPerMs * keyframe.computedOffset * Number(duration)}px`,
136
+ backgroundColor: "var(--filmstrip-keyframe-bg)"
137
+ })}
128
138
  ></div>`;
129
139
  })}
130
140
  </div>`;
@@ -176,7 +186,8 @@ let EFCaptionsFilmstrip = class EFCaptionsFilmstrip$1 extends FilmstripItem {
176
186
  const captionsData = this.element.unifiedCaptionsDataTask.value;
177
187
  return html`<div style=${styleMap(this.gutterStyles)}>
178
188
  <div
179
- class="bg-slate-300 relative"
189
+ class="relative"
190
+ style="background-color: var(--filmstrip-bg);"
180
191
  ?data-focused=${this.isFocused}
181
192
  @mouseenter=${() => {
182
193
  if (this.focusContext) this.focusContext.focusedElement = this.element;
@@ -187,8 +198,12 @@ let EFCaptionsFilmstrip = class EFCaptionsFilmstrip$1 extends FilmstripItem {
187
198
  >
188
199
  <div
189
200
  ?data-focused=${this.isFocused}
190
- class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border border-slate-500 bg-blue-200 text-sm data-[focused]:bg-slate-400 overflow-hidden"
191
- style=${styleMap(this.trimPortionStyles)}
201
+ class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border text-sm overflow-hidden"
202
+ style=${styleMap({
203
+ ...this.trimPortionStyles,
204
+ backgroundColor: this.isFocused ? "var(--filmstrip-item-focused)" : "var(--filmstrip-item-bg)",
205
+ borderColor: "var(--filmstrip-border)"
206
+ })}
192
207
  >
193
208
  📝 ${this.renderCaptionsData(captionsData)}
194
209
  </div>
@@ -203,12 +218,14 @@ let EFCaptionsFilmstrip = class EFCaptionsFilmstrip$1 extends FilmstripItem {
203
218
  return html`${captionsData.segments.map((segment) => {
204
219
  const isActive = captionsLocalTimeSec >= segment.start && captionsLocalTimeSec < segment.end;
205
220
  return html`<div
206
- class="absolute border border-slate-600 text-xs overflow-hidden flex items-center ${isActive ? "bg-green-200 border-green-500 font-bold z-[5]" : "bg-slate-100"}"
221
+ class="absolute border text-xs overflow-hidden flex items-center ${isActive ? "font-bold z-[5]" : ""}"
207
222
  style=${styleMap({
208
223
  left: `${this.pixelsPerMs * segment.start * 1e3}px`,
209
224
  width: `${this.pixelsPerMs * (segment.end - segment.start) * 1e3}px`,
210
225
  height: "100%",
211
- top: "0px"
226
+ top: "0px",
227
+ backgroundColor: isActive ? "var(--filmstrip-segment-bg)" : "var(--filmstrip-item-bg)",
228
+ borderColor: isActive ? "var(--filmstrip-segment-border)" : "var(--filmstrip-border)"
212
229
  })}
213
230
  title="Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)"
214
231
  >
@@ -234,26 +251,28 @@ let EFCaptionsActiveWordFilmstrip = class EFCaptionsActiveWordFilmstrip$1 extend
234
251
  const parentCaptions = this.element.closest("ef-captions");
235
252
  const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
236
253
  if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
237
- <div class="bg-slate-300 border border-slate-500 h-[1.1rem] mb-[1px] text-xs">
254
+ <div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
238
255
  🗣️ Active Word
239
256
  </div>
240
257
  </div>`;
241
258
  const captionsLocalTimeSec = ((parentCaptions.rootTimegroup?.currentTimeMs || 0) - parentCaptions.startTimeMs) / 1e3;
242
259
  return html`<div style=${styleMap(this.captionsTrackStyles)}>
243
- <div class="bg-slate-300 relative border border-slate-500 h-[1.1rem] mb-[1px] w-full">
260
+ <div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
244
261
  ${captionsData.word_segments.map((word) => {
245
262
  const isCurrentlyActive = captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end;
246
263
  return html`<div
247
- class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "bg-yellow-200 border-yellow-500 font-bold z-[5]" : "bg-blue-50 border-blue-200"}"
264
+ class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
248
265
  style=${styleMap({
249
266
  left: `${this.pixelsPerMs * word.start * 1e3}px`,
250
267
  width: `${this.pixelsPerMs * (word.end - word.start) * 1e3}px`,
251
268
  height: "100%",
252
- top: "0px"
269
+ top: "0px",
270
+ backgroundColor: isCurrentlyActive ? "var(--filmstrip-caption-bg)" : "var(--filmstrip-item-bg)",
271
+ borderColor: isCurrentlyActive ? "var(--filmstrip-caption-border)" : "var(--filmstrip-border)"
253
272
  })}
254
273
  title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
255
274
  >
256
- ${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap bg-yellow-200">${word.text.trim()}</span>` : ""}
275
+ ${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap" style="background-color: var(--filmstrip-caption-bg);">${word.text.trim()}</span>` : ""}
257
276
  </div>`;
258
277
  })}
259
278
  </div>
@@ -274,26 +293,28 @@ let EFCaptionsSegmentFilmstrip = class EFCaptionsSegmentFilmstrip$1 extends Film
274
293
  const parentCaptions = this.element.closest("ef-captions");
275
294
  const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
276
295
  if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
277
- <div class="bg-slate-300 border border-slate-500 h-[1.1rem] mb-[1px] text-xs">
296
+ <div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
278
297
  📄 Segment
279
298
  </div>
280
299
  </div>`;
281
300
  const captionsLocalTimeSec = ((parentCaptions.rootTimegroup?.currentTimeMs || 0) - parentCaptions.startTimeMs) / 1e3;
282
301
  return html`<div style=${styleMap(this.captionsTrackStyles)}>
283
- <div class="bg-slate-300 relative border border-slate-500 h-[1.1rem] mb-[1px] w-full">
302
+ <div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
284
303
  ${captionsData.segments.map((segment) => {
285
304
  const isCurrentlyActive = captionsLocalTimeSec >= segment.start && captionsLocalTimeSec < segment.end;
286
305
  return html`<div
287
- class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "bg-green-200 border-green-500 font-bold z-[5]" : "bg-green-50 border-green-200"}"
306
+ class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
288
307
  style=${styleMap({
289
308
  left: `${this.pixelsPerMs * segment.start * 1e3}px`,
290
309
  width: `${this.pixelsPerMs * (segment.end - segment.start) * 1e3}px`,
291
310
  height: "100%",
292
- top: "0px"
311
+ top: "0px",
312
+ backgroundColor: isCurrentlyActive ? "var(--filmstrip-segment-bg)" : "var(--filmstrip-item-bg)",
313
+ borderColor: isCurrentlyActive ? "var(--filmstrip-segment-border)" : "var(--filmstrip-border)"
293
314
  })}
294
315
  title="Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)"
295
316
  >
296
- ${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap bg-green-200">${segment.text}</span>` : ""}
317
+ ${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap" style="background-color: var(--filmstrip-segment-bg);">${segment.text}</span>` : ""}
297
318
  </div>`;
298
319
  })}
299
320
  </div>
@@ -314,21 +335,24 @@ let EFCaptionsBeforeWordFilmstrip = class EFCaptionsBeforeWordFilmstrip$1 extend
314
335
  const parentCaptions = this.element.closest("ef-captions");
315
336
  const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
316
337
  if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
317
- <div class="bg-slate-300 border border-slate-500 h-[1.1rem] mb-[1px] text-xs">
338
+ <div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
318
339
  ⬅️ Before
319
340
  </div>
320
341
  </div>`;
321
342
  const captionsLocalTimeSec = ((parentCaptions.rootTimegroup?.currentTimeMs || 0) - parentCaptions.startTimeMs) / 1e3;
322
343
  return html`<div style=${styleMap(this.captionsTrackStyles)}>
323
- <div class="bg-slate-300 relative border border-slate-500 h-[1.1rem] mb-[1px] w-full">
344
+ <div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
324
345
  ${captionsData.word_segments.map((word) => {
346
+ const isCurrentlyActive = captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end;
325
347
  return html`<div
326
- class="absolute border text-xs overflow-visible flex items-center ${captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end ? "bg-yellow-200 border-yellow-500 font-bold z-[5]" : "bg-purple-50 border-purple-200"}"
348
+ class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
327
349
  style=${styleMap({
328
350
  left: `${this.pixelsPerMs * word.start * 1e3}px`,
329
351
  width: `${this.pixelsPerMs * (word.end - word.start) * 1e3}px`,
330
352
  height: "100%",
331
- top: "0px"
353
+ top: "0px",
354
+ backgroundColor: isCurrentlyActive ? "var(--filmstrip-caption-bg)" : "var(--filmstrip-waveform-bg)",
355
+ borderColor: isCurrentlyActive ? "var(--filmstrip-caption-border)" : "var(--filmstrip-waveform-border)"
332
356
  })}
333
357
  title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
334
358
  >
@@ -353,21 +377,24 @@ let EFCaptionsAfterWordFilmstrip = class EFCaptionsAfterWordFilmstrip$1 extends
353
377
  const parentCaptions = this.element.closest("ef-captions");
354
378
  const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
355
379
  if (!captionsData) return html`<div style=${styleMap(this.captionsTrackStyles)}>
356
- <div class="bg-slate-300 border border-slate-500 h-[1.1rem] mb-[1px] text-xs">
380
+ <div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
357
381
  ➡️ After
358
382
  </div>
359
383
  </div>`;
360
384
  const captionsLocalTimeSec = ((parentCaptions.rootTimegroup?.currentTimeMs || 0) - parentCaptions.startTimeMs) / 1e3;
361
385
  return html`<div style=${styleMap(this.captionsTrackStyles)}>
362
- <div class="bg-slate-300 relative border border-slate-500 h-[1.1rem] mb-[1px] w-full">
386
+ <div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
363
387
  ${captionsData.word_segments.map((word) => {
388
+ const isCurrentlyActive = captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end;
364
389
  return html`<div
365
- class="absolute border text-xs overflow-visible flex items-center ${captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end ? "bg-yellow-200 border-yellow-500 font-bold z-[5]" : "bg-purple-50 border-purple-200"}"
390
+ class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
366
391
  style=${styleMap({
367
392
  left: `${this.pixelsPerMs * word.start * 1e3}px`,
368
393
  width: `${this.pixelsPerMs * (word.end - word.start) * 1e3}px`,
369
394
  height: "100%",
370
- top: "0px"
395
+ top: "0px",
396
+ backgroundColor: isCurrentlyActive ? "var(--filmstrip-caption-bg)" : "var(--filmstrip-waveform-bg)",
397
+ borderColor: isCurrentlyActive ? "var(--filmstrip-caption-border)" : "var(--filmstrip-waveform-border)"
371
398
  })}
372
399
  title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
373
400
  >
@@ -388,6 +415,104 @@ let EFWaveformFilmstrip = class EFWaveformFilmstrip$1 extends FilmstripItem {
388
415
  }
389
416
  };
390
417
  EFWaveformFilmstrip = __decorate([customElement("ef-waveform-filmstrip")], EFWaveformFilmstrip);
418
+ let EFTextFilmstrip = class EFTextFilmstrip$1 extends FilmstripItem {
419
+ render() {
420
+ const text = this.element;
421
+ const segments = Array.from(text.querySelectorAll("ef-text-segment"));
422
+ return html`<div style=${styleMap(this.gutterStyles)}>
423
+ <div
424
+ class="relative"
425
+ style="background-color: var(--filmstrip-bg);"
426
+ ?data-focused=${this.isFocused}
427
+ @mouseenter=${() => {
428
+ if (this.focusContext) this.focusContext.focusedElement = this.element;
429
+ }}
430
+ @mouseleave=${() => {
431
+ if (this.focusContext) this.focusContext.focusedElement = null;
432
+ }}
433
+ >
434
+ <div
435
+ ?data-focused=${this.isFocused}
436
+ class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border text-sm overflow-hidden"
437
+ style=${styleMap({
438
+ ...this.trimPortionStyles,
439
+ backgroundColor: this.isFocused ? "var(--filmstrip-item-focused)" : "var(--filmstrip-item-bg)",
440
+ borderColor: "var(--filmstrip-border)"
441
+ })}
442
+ >
443
+ 📄 ${this.renderTextSegments(segments)}
444
+ </div>
445
+ </div>
446
+ ${this.renderChildren()}
447
+ </div>`;
448
+ }
449
+ renderTextSegments(segments) {
450
+ if (segments.length === 0) return html``;
451
+ const text = this.element;
452
+ const textLocalTimeMs = (text.rootTimegroup?.currentTimeMs || 0) - text.startTimeMs;
453
+ return segments.map((segment) => {
454
+ const isActive = textLocalTimeMs >= segment.segmentStartMs && textLocalTimeMs < segment.segmentEndMs;
455
+ return html`<div
456
+ class="absolute border text-xs overflow-hidden flex items-center ${isActive ? "font-bold z-[5]" : ""}"
457
+ style=${styleMap({
458
+ left: `${this.pixelsPerMs * segment.segmentStartMs}px`,
459
+ width: `${this.pixelsPerMs * (segment.segmentEndMs - segment.segmentStartMs)}px`,
460
+ height: "100%",
461
+ top: "0px",
462
+ backgroundColor: isActive ? "var(--filmstrip-segment-bg)" : "var(--filmstrip-item-bg)",
463
+ borderColor: isActive ? "var(--filmstrip-segment-border)" : "var(--filmstrip-border)"
464
+ })}
465
+ title="Segment: '${segment.segmentText}' (${segment.segmentStartMs}ms - ${segment.segmentEndMs}ms)"
466
+ >
467
+ <span class="px-0.5 text-[8px] ${isActive ? "font-bold" : ""}">${segment.segmentText}</span>
468
+ </div>`;
469
+ });
470
+ }
471
+ renderChildren() {
472
+ return renderFilmstripChildren(Array.from(this.element.children), this.pixelsPerMs, this.hideSelectors, this.showSelectors);
473
+ }
474
+ };
475
+ EFTextFilmstrip = __decorate([customElement("ef-text-filmstrip")], EFTextFilmstrip);
476
+ let EFTextSegmentFilmstrip = class EFTextSegmentFilmstrip$1 extends FilmstripItem {
477
+ get textTrackStyles() {
478
+ const parentText = this.element.closest("ef-text");
479
+ return {
480
+ position: "relative",
481
+ left: `${this.pixelsPerMs * (parentText?.startTimeWithinParentMs || 0)}px`,
482
+ width: `${this.pixelsPerMs * (parentText?.durationMs || 0)}px`
483
+ };
484
+ }
485
+ render() {
486
+ const segment = this.element;
487
+ const parentText = segment.closest("ef-text");
488
+ if (!parentText) return html`<div style=${styleMap(this.textTrackStyles)}>
489
+ <div class="border h-[1.1rem] mb-[1px] text-xs" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
490
+ 📄 Text Segment
491
+ </div>
492
+ </div>`;
493
+ const textLocalTimeMs = (parentText.rootTimegroup?.currentTimeMs || 0) - parentText.startTimeMs;
494
+ const isCurrentlyActive = textLocalTimeMs >= segment.segmentStartMs && textLocalTimeMs < segment.segmentEndMs;
495
+ return html`<div style=${styleMap(this.textTrackStyles)}>
496
+ <div class="relative border h-[1.1rem] mb-[1px] w-full" style="background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);">
497
+ <div
498
+ class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "font-bold z-[5]" : ""}"
499
+ style=${styleMap({
500
+ left: `${this.pixelsPerMs * segment.segmentStartMs}px`,
501
+ width: `${this.pixelsPerMs * (segment.segmentEndMs - segment.segmentStartMs)}px`,
502
+ height: "100%",
503
+ top: "0px",
504
+ backgroundColor: isCurrentlyActive ? "var(--filmstrip-caption-bg)" : "var(--filmstrip-item-bg)",
505
+ borderColor: isCurrentlyActive ? "var(--filmstrip-caption-border)" : "var(--filmstrip-border)"
506
+ })}
507
+ title="Segment: '${segment.segmentText}' (${segment.segmentStartMs}ms - ${segment.segmentEndMs}ms)"
508
+ >
509
+ ${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap" style="background-color: var(--filmstrip-caption-bg);">${segment.segmentText}</span>` : ""}
510
+ </div>
511
+ </div>
512
+ </div>`;
513
+ }
514
+ };
515
+ EFTextSegmentFilmstrip = __decorate([customElement("ef-text-segment-filmstrip")], EFTextSegmentFilmstrip);
391
516
  let EFImageFilmstrip = class EFImageFilmstrip$1 extends FilmstripItem {
392
517
  contents() {
393
518
  return html` 🖼️ `;
@@ -431,9 +556,11 @@ let EFHierarchyItem = class EFHierarchyItem$1 extends TWMixin(LitElement) {
431
556
  return html`
432
557
  <div>
433
558
  <div
434
- class="peer
435
- flex h-[1.1rem] items-center overflow-hidden text-nowrap border border-slate-500
436
- bg-slate-200 pl-2 text-xs font-mono hover:bg-slate-400 data-[focused]:bg-slate-400"
559
+ class="peer flex h-[1.1rem] items-center overflow-hidden text-nowrap border pl-2 text-xs font-mono"
560
+ style=${styleMap({
561
+ backgroundColor: this.isFocused ? "var(--filmstrip-timegroup-focused)" : "var(--filmstrip-timegroup-bg)",
562
+ borderColor: "var(--filmstrip-border)"
563
+ })}
437
564
  ?data-focused=${this.isFocused}
438
565
  @mouseenter=${() => {
439
566
  if (this.focusContext) this.focusContext.focusedElement = this.element;
@@ -445,7 +572,8 @@ let EFHierarchyItem = class EFHierarchyItem$1 extends TWMixin(LitElement) {
445
572
  ${this.icon} ${this.displayLabel()}
446
573
  </div>
447
574
  <div
448
- class="p-[1px] pb-0 pl-2 pr-0 peer-hover:bg-slate-300 peer-data-[focused]:bg-slate-300 peer-hover:border-slate-400 peer-data-[focused]:border-slate-400""
575
+ class="p-[1px] pb-0 pl-2 pr-0 peer-hover-container peer-data-container"
576
+ style=${styleMap({ backgroundColor: this.isFocused ? "var(--filmstrip-bg)" : "transparent" })}
449
577
  >
450
578
  ${this.renderChildren()}
451
579
  </div>
@@ -512,6 +640,18 @@ let EFCaptionsActiveWordHierarchyItem = class EFCaptionsActiveWordHierarchyItem$
512
640
  }
513
641
  };
514
642
  EFCaptionsActiveWordHierarchyItem = __decorate([customElement("ef-captions-active-word-hierarchy-item")], EFCaptionsActiveWordHierarchyItem);
643
+ let EFTextHierarchyItem = class EFTextHierarchyItem$1 extends EFHierarchyItem {
644
+ get icon() {
645
+ return "📄 Text";
646
+ }
647
+ };
648
+ EFTextHierarchyItem = __decorate([customElement("ef-text-hierarchy-item")], EFTextHierarchyItem);
649
+ let EFTextSegmentHierarchyItem = class EFTextSegmentHierarchyItem$1 extends EFHierarchyItem {
650
+ get icon() {
651
+ return "📄 Segment";
652
+ }
653
+ };
654
+ EFTextSegmentHierarchyItem = __decorate([customElement("ef-text-segment-hierarchy-item")], EFTextSegmentHierarchyItem);
515
655
  let EFWaveformHierarchyItem = class EFWaveformHierarchyItem$1 extends EFHierarchyItem {
516
656
  get icon() {
517
657
  return "🌊";
@@ -587,6 +727,16 @@ const renderHierarchyChildren = (children, hideSelectors, showSelectors, skipRoo
587
727
  .hideSelectors=${hideSelectors}
588
728
  .showSelectors=${showSelectors}
589
729
  ></ef-captions-active-word-hierarchy-item>`;
730
+ if (child instanceof EFText) return html`<ef-text-hierarchy-item
731
+ .element=${child}
732
+ .hideSelectors=${hideSelectors}
733
+ .showSelectors=${showSelectors}
734
+ ></ef-text-hierarchy-item>`;
735
+ if (child instanceof EFTextSegment) return html`<ef-text-segment-hierarchy-item
736
+ .element=${child}
737
+ .hideSelectors=${hideSelectors}
738
+ .showSelectors=${showSelectors}
739
+ ></ef-text-segment-hierarchy-item>`;
590
740
  if (child instanceof EFWaveform) return html`<ef-waveform-hierarchy-item
591
741
  .element=${child}
592
742
  .hideSelectors=${hideSelectors}
@@ -639,6 +789,18 @@ const renderFilmstripChildren = (children, pixelsPerMs, hideSelectors, showSelec
639
789
  .hideSelectors=${hideSelectors}
640
790
  .showSelectors=${showSelectors}
641
791
  ></ef-captions-active-word-filmstrip>`;
792
+ if (child instanceof EFText) return html`<ef-text-filmstrip
793
+ .element=${child}
794
+ .pixelsPerMs=${pixelsPerMs}
795
+ .hideSelectors=${hideSelectors}
796
+ .showSelectors=${showSelectors}
797
+ ></ef-text-filmstrip>`;
798
+ if (child instanceof EFTextSegment) return html`<ef-text-segment-filmstrip
799
+ .element=${child}
800
+ .pixelsPerMs=${pixelsPerMs}
801
+ .hideSelectors=${hideSelectors}
802
+ .showSelectors=${showSelectors}
803
+ ></ef-text-segment-filmstrip>`;
642
804
  if (child.tagName === "EF-CAPTIONS-SEGMENT") return html`<ef-captions-segment-filmstrip
643
805
  .element=${child}
644
806
  .pixelsPerMs=${pixelsPerMs}
@@ -678,6 +840,7 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
678
840
  this.hide = "";
679
841
  this.show = "";
680
842
  this.scrubbing = false;
843
+ this.capturedPointerId = null;
681
844
  this.timelineScrolltop = 0;
682
845
  this.currentTimeMs = 0;
683
846
  this.autoScale = false;
@@ -697,6 +860,48 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
697
860
  overflow: hidden;
698
861
  width: 100%;
699
862
  height: 100%;
863
+
864
+ /* Light mode colors */
865
+ --filmstrip-bg: rgb(203 213 225); /* slate-300 */
866
+ --filmstrip-border: rgb(100 116 139); /* slate-500 */
867
+ --filmstrip-item-bg: rgb(191 219 254); /* blue-200 */
868
+ --filmstrip-item-focused: rgb(148 163 184); /* slate-400 */
869
+ --filmstrip-animation-bg: rgb(59 130 246); /* blue-500 */
870
+ --filmstrip-keyframe-bg: rgb(239 68 68); /* red-500 */
871
+ --filmstrip-caption-bg: rgb(254 240 138); /* yellow-200 */
872
+ --filmstrip-caption-border: rgb(234 179 8); /* yellow-500 */
873
+ --filmstrip-segment-bg: rgb(187 247 208); /* green-200 */
874
+ --filmstrip-segment-border: rgb(34 197 94); /* green-500 */
875
+ --filmstrip-waveform-bg: rgb(250 245 255); /* purple-50 */
876
+ --filmstrip-waveform-border: rgb(233 213 255); /* purple-200 */
877
+ --filmstrip-timegroup-bg: rgb(226 232 240); /* slate-200 */
878
+ --filmstrip-timegroup-hover: rgb(148 163 184); /* slate-400 */
879
+ --filmstrip-timegroup-focused: rgb(148 163 184); /* slate-400 */
880
+ --filmstrip-gutter-bg: rgb(241 245 249); /* slate-100 */
881
+ --filmstrip-timeline-bg: rgb(226 232 240); /* slate-200 */
882
+ --filmstrip-playhead: rgb(185 28 28); /* red-700 */
883
+ }
884
+
885
+ :host(.dark), :host-context(.dark) {
886
+ /* Dark mode colors */
887
+ --filmstrip-bg: rgb(71 85 105); /* slate-600 */
888
+ --filmstrip-border: rgb(148 163 184); /* slate-400 */
889
+ --filmstrip-item-bg: rgb(30 64 175); /* blue-800 */
890
+ --filmstrip-item-focused: rgb(100 116 139); /* slate-500 */
891
+ --filmstrip-animation-bg: rgb(96 165 250); /* blue-400 */
892
+ --filmstrip-keyframe-bg: rgb(248 113 113); /* red-400 */
893
+ --filmstrip-caption-bg: rgb(133 77 14); /* yellow-800 */
894
+ --filmstrip-caption-border: rgb(250 204 21); /* yellow-400 */
895
+ --filmstrip-segment-bg: rgb(22 101 52); /* green-800 */
896
+ --filmstrip-segment-border: rgb(74 222 128); /* green-400 */
897
+ --filmstrip-waveform-bg: rgb(88 28 135); /* purple-900 */
898
+ --filmstrip-waveform-border: rgb(126 34 206); /* purple-700 */
899
+ --filmstrip-timegroup-bg: rgb(51 65 85); /* slate-700 */
900
+ --filmstrip-timegroup-hover: rgb(100 116 139); /* slate-500 */
901
+ --filmstrip-timegroup-focused: rgb(100 116 139); /* slate-500 */
902
+ --filmstrip-gutter-bg: rgb(30 41 59); /* slate-800 */
903
+ --filmstrip-timeline-bg: rgb(51 65 85); /* slate-700 */
904
+ --filmstrip-playhead: rgb(239 68 68); /* red-500 */
700
905
  }
701
906
  `];
702
907
  }
@@ -756,17 +961,61 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
756
961
  scrub(e) {
757
962
  if (this.playing) return;
758
963
  if (!this.scrubbing) return;
964
+ if (this.capturedPointerId !== null && e.pointerId !== this.capturedPointerId) return;
965
+ e.preventDefault();
966
+ e.stopPropagation();
759
967
  this.applyScrub(e);
760
968
  }
761
969
  startScrub(e) {
762
970
  e.preventDefault();
971
+ e.stopPropagation();
763
972
  this.scrubbing = true;
973
+ this.capturedPointerId = e.pointerId;
974
+ const target = e.currentTarget;
975
+ if (target) try {
976
+ target.setPointerCapture(e.pointerId);
977
+ } catch (err) {
978
+ console.warn("Failed to set pointer capture:", err);
979
+ }
764
980
  queueMicrotask(() => {
765
981
  this.applyScrub(e);
766
982
  });
767
- addEventListener("mouseup", () => {
768
- this.scrubbing = false;
769
- }, { once: true });
983
+ const handlePointerUp = (upEvent) => {
984
+ if (upEvent.pointerId === this.capturedPointerId) {
985
+ upEvent.preventDefault();
986
+ upEvent.stopPropagation();
987
+ if (target) try {
988
+ target.releasePointerCapture(upEvent.pointerId);
989
+ } catch (_err) {}
990
+ this.capturedPointerId = null;
991
+ this.scrubbing = false;
992
+ removeEventListener("pointerup", handlePointerUp);
993
+ }
994
+ };
995
+ const handlePointerCancel = (cancelEvent) => {
996
+ if (cancelEvent.pointerId === this.capturedPointerId) {
997
+ if (target) try {
998
+ target.releasePointerCapture(cancelEvent.pointerId);
999
+ } catch (_err) {}
1000
+ this.capturedPointerId = null;
1001
+ this.scrubbing = false;
1002
+ removeEventListener("pointercancel", handlePointerCancel);
1003
+ }
1004
+ };
1005
+ addEventListener("pointerup", handlePointerUp, {
1006
+ once: true,
1007
+ passive: false
1008
+ });
1009
+ addEventListener("pointercancel", handlePointerCancel, {
1010
+ once: true,
1011
+ passive: false
1012
+ });
1013
+ }
1014
+ handleContextMenu(e) {
1015
+ if (this.scrubbing) {
1016
+ e.preventDefault();
1017
+ e.stopPropagation();
1018
+ }
770
1019
  }
771
1020
  applyScrub(e) {
772
1021
  const gutter = this.shadowRoot?.querySelector("#gutter");
@@ -800,14 +1049,16 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
800
1049
  render() {
801
1050
  const target = this.targetTemporal;
802
1051
  return html` <div
803
- class="grid h-full bg-slate-100"
1052
+ class="grid h-full"
804
1053
  style=${styleMap({
805
1054
  gridTemplateColumns: "200px 1fr",
806
- gridTemplateRows: "1.5rem 1fr"
1055
+ gridTemplateRows: "1.5rem 1fr",
1056
+ backgroundColor: "var(--filmstrip-gutter-bg)"
807
1057
  })}
808
1058
  >
809
1059
  <div
810
- class="z-20 col-span-2 border-b-slate-600 bg-slate-100 shadow shadow-slate-300"
1060
+ class="z-20 col-span-2 shadow"
1061
+ style="background-color: var(--filmstrip-gutter-bg); border-bottom-color: var(--filmstrip-border);"
811
1062
  >
812
1063
  ${!this.autoScale ? html`<input
813
1064
  type="range"
@@ -833,30 +1084,33 @@ let EFFilmstrip = class EFFilmstrip$1 extends TWMixin(LitElement) {
833
1084
  <ef-toggle-loop><button>${this.loop ? "🔁" : html`<span class="opacity-50 line-through">🔁</span>`}</button></ef-toggle-loop>
834
1085
  </div>
835
1086
  <div
836
- class="z-10 pl-1 pr-1 pt-[8px] shadow shadow-slate-600 overflow-auto"
1087
+ class="z-10 pl-1 pr-1 pt-[8px] shadow overflow-auto"
837
1088
  ${ref(this.hierarchyRef)}
838
1089
  @scroll=${this.syncHierarchyScroll}
839
1090
  >
840
1091
  ${renderHierarchyChildren(target ? [target] : [], this.hideSelectors, this.showSelectors, true)}
841
1092
  </div>
842
1093
  <div
843
- class="flex h-full w-full cursor-crosshair overflow-auto bg-slate-200 pt-[8px]"
1094
+ class="flex h-full w-full cursor-crosshair overflow-auto pt-[8px] touch-pan-x"
1095
+ style="background-color: var(--filmstrip-timeline-bg);"
844
1096
  id="gutter"
845
1097
  ${ref(this.gutterRef)}
846
1098
  @scroll=${this.syncGutterScroll}
847
1099
  @wheel=${this.scrollScrub}
848
1100
  >
849
1101
  <div
850
- class="relative h-full w-full"
851
- style="width: ${this.pixelsPerMs * (target?.durationMs ?? 0)}px;"
852
- @mousemove=${this.scrub}
853
- @mousedown=${this.startScrub}
1102
+ class="relative h-full w-full touch-none"
1103
+ style="width: ${this.pixelsPerMs * (target?.durationMs ?? 0)}px; touch-action: none; user-select: none;"
1104
+ @pointermove=${this.scrub}
1105
+ @pointerdown=${this.startScrub}
1106
+ @contextmenu=${this.handleContextMenu}
854
1107
  >
855
1108
  <div
856
- class="border-red pointer-events-none absolute z-[20] h-full w-[2px] border-r-2 border-red-700"
1109
+ class="border-red pointer-events-none absolute z-[20] h-full w-[2px] border-r-2"
857
1110
  style=${styleMap({
858
1111
  left: `${this.pixelsPerMs * this.currentTimeMs}px`,
859
- top: `${this.timelineScrolltop}px`
1112
+ top: `${this.timelineScrolltop}px`,
1113
+ borderColor: "var(--filmstrip-playhead)"
860
1114
  })}
861
1115
  ${ref(this.playheadRef)}
862
1116
  ></div>
@@ -921,8 +1175,18 @@ __decorate([property({
921
1175
  })], EFFilmstrip.prototype, "autoScale", void 0);
922
1176
  __decorate([eventOptions({ passive: false })], EFFilmstrip.prototype, "syncGutterScroll", null);
923
1177
  __decorate([eventOptions({ passive: false })], EFFilmstrip.prototype, "syncHierarchyScroll", null);
924
- __decorate([eventOptions({ capture: false })], EFFilmstrip.prototype, "scrub", null);
925
- __decorate([eventOptions({ capture: false })], EFFilmstrip.prototype, "startScrub", null);
1178
+ __decorate([eventOptions({
1179
+ capture: false,
1180
+ passive: false
1181
+ })], EFFilmstrip.prototype, "scrub", null);
1182
+ __decorate([eventOptions({
1183
+ capture: false,
1184
+ passive: false
1185
+ })], EFFilmstrip.prototype, "startScrub", null);
1186
+ __decorate([eventOptions({
1187
+ passive: false,
1188
+ capture: false
1189
+ })], EFFilmstrip.prototype, "handleContextMenu", null);
926
1190
  __decorate([eventOptions({ passive: false })], EFFilmstrip.prototype, "scrollScrub", null);
927
1191
  __decorate([property({ type: String })], EFFilmstrip.prototype, "target", void 0);
928
1192
  __decorate([state()], EFFilmstrip.prototype, "targetElement", void 0);