@editframe/elements 0.15.0-beta.9 → 0.16.0-beta.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.
Files changed (52) hide show
  1. package/dist/EF_FRAMEGEN.d.ts +14 -10
  2. package/dist/EF_FRAMEGEN.js +17 -28
  3. package/dist/elements/EFCaptions.js +0 -7
  4. package/dist/elements/EFImage.js +0 -4
  5. package/dist/elements/EFMedia.d.ts +13 -8
  6. package/dist/elements/EFMedia.js +164 -146
  7. package/dist/elements/EFSourceMixin.js +2 -1
  8. package/dist/elements/EFTemporal.browsertest.d.ts +4 -3
  9. package/dist/elements/EFTemporal.d.ts +14 -11
  10. package/dist/elements/EFTemporal.js +63 -87
  11. package/dist/elements/EFTimegroup.d.ts +2 -4
  12. package/dist/elements/EFTimegroup.js +15 -103
  13. package/dist/elements/EFVideo.js +3 -1
  14. package/dist/elements/EFWaveform.d.ts +1 -1
  15. package/dist/elements/EFWaveform.js +11 -28
  16. package/dist/elements/durationConverter.d.ts +8 -8
  17. package/dist/elements/durationConverter.js +2 -2
  18. package/dist/elements/updateAnimations.d.ts +9 -0
  19. package/dist/elements/updateAnimations.js +62 -0
  20. package/dist/getRenderInfo.d.ts +51 -0
  21. package/dist/getRenderInfo.js +72 -0
  22. package/dist/gui/EFFilmstrip.js +7 -16
  23. package/dist/gui/EFFitScale.d.ts +27 -0
  24. package/dist/gui/EFFitScale.js +138 -0
  25. package/dist/gui/EFWorkbench.d.ts +2 -5
  26. package/dist/gui/EFWorkbench.js +13 -56
  27. package/dist/gui/TWMixin.css.js +1 -1
  28. package/dist/gui/TWMixin.js +14 -2
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.js +6 -1
  31. package/dist/style.css +3 -3
  32. package/package.json +4 -3
  33. package/src/elements/EFCaptions.browsertest.ts +2 -2
  34. package/src/elements/EFCaptions.ts +0 -7
  35. package/src/elements/EFImage.browsertest.ts +2 -2
  36. package/src/elements/EFImage.ts +0 -4
  37. package/src/elements/EFMedia.browsertest.ts +14 -14
  38. package/src/elements/EFMedia.ts +220 -182
  39. package/src/elements/EFSourceMixin.ts +4 -4
  40. package/src/elements/EFTemporal.browsertest.ts +64 -31
  41. package/src/elements/EFTemporal.ts +99 -119
  42. package/src/elements/EFTimegroup.ts +15 -133
  43. package/src/elements/EFVideo.ts +3 -1
  44. package/src/elements/EFWaveform.ts +10 -44
  45. package/src/elements/durationConverter.ts +9 -4
  46. package/src/elements/updateAnimations.ts +88 -0
  47. package/src/gui/ContextMixin.ts +0 -3
  48. package/src/gui/EFFilmstrip.ts +7 -16
  49. package/src/gui/EFFitScale.ts +152 -0
  50. package/src/gui/EFWorkbench.ts +18 -65
  51. package/src/gui/TWMixin.ts +19 -2
  52. package/types.json +1 -1
@@ -3,76 +3,109 @@ import { customElement } from "lit/decorators/custom-element.js";
3
3
  import { describe, expect, test } from "vitest";
4
4
  import { EFTemporal } from "./EFTemporal.js";
5
5
 
6
- @customElement("test-temporal")
7
- class TestTemporal extends EFTemporal(LitElement) {}
6
+ @customElement("ten-seconds")
7
+ class TenSeconds extends EFTemporal(LitElement) {
8
+ get intrinsicDurationMs() {
9
+ return 10_000;
10
+ }
11
+ }
8
12
 
9
13
  declare global {
10
14
  interface HTMLElementTagNameMap {
11
- "test-temporal": TestTemporal;
15
+ "ten-seconds": TenSeconds;
12
16
  }
13
17
  }
14
18
 
15
19
  describe("sourcein and sourceout", () => {
16
20
  test("sourcein and sourceout are parsed correctly", () => {
17
- const element = document.createElement("test-temporal");
21
+ const element = document.createElement("ten-seconds");
18
22
  element.setAttribute("sourcein", "1s");
19
23
  element.setAttribute("sourceout", "5s");
20
24
  expect(element.sourceInMs).toBe(1_000);
21
25
  expect(element.sourceOutMs).toBe(5_000);
26
+ expect(element.durationMs).toBe(4_000);
22
27
  });
23
28
 
24
- test("sourcein and sourceout can be set directly on the element", () => {
25
- const element = document.createElement("test-temporal");
26
- element.sourcein = "1s";
27
- element.sourceout = "5s";
28
- expect(element.sourceInMs).toBe(1_000);
29
- expect(element.sourceOutMs).toBe(5_000);
29
+ describe("only srcin is set", () => {
30
+ test("duration is calculated", () => {
31
+ const element = document.createElement("ten-seconds");
32
+ element.sourceInMs = 1_000;
33
+ expect(element.durationMs).toBe(9_000);
34
+ });
35
+ });
36
+
37
+ describe("only srcout is set", () => {
38
+ test("duration is calculated", () => {
39
+ const element = document.createElement("ten-seconds");
40
+ element.sourceOutMs = 5_000;
41
+ expect(element.durationMs).toBe(5_000);
42
+ });
30
43
  });
31
44
 
32
- test("sourcein and sourceout are reflected correctly", () => {
33
- const element = document.createElement("test-temporal");
34
- element.sourceInMs = 1_000;
35
- element.sourceOutMs = 5_000;
36
- expect(element.getAttribute("sourcein")).toBe("1s");
37
- expect(element.getAttribute("sourceout")).toBe("5s");
45
+ describe("srcout is before srcin", () => {
46
+ test("duration is zero", () => {
47
+ const element = document.createElement("ten-seconds");
48
+ element.sourceInMs = 5_000;
49
+ element.sourceOutMs = 1_000;
50
+ console.log(element.sourceInMs, element.sourceOutMs, element.durationMs);
51
+ expect(element.durationMs).toBe(0);
52
+ });
53
+ });
54
+
55
+ describe("srcin is negative", () => {
56
+ test("srcin is normalized to 0 ", () => {
57
+ const element = document.createElement("ten-seconds");
58
+ element.sourceInMs = -1_000;
59
+ expect(element.sourceInMs).toBe(0);
60
+ expect(element.durationMs).toBe(10_000);
61
+ });
62
+ });
63
+
64
+ describe("srcout is beyond the intrinsic duration", () => {
65
+ test("srcout is normalized to the intrinsic duration", () => {
66
+ const element = document.createElement("ten-seconds");
67
+ element.sourceOutMs = 15_000;
68
+ expect(element.sourceOutMs).toBe(10_000);
69
+ });
38
70
  });
39
71
  });
40
72
 
41
73
  describe("trimstart and trimend", () => {
42
74
  test("trimstart and trimend attributes are parsed correctly", () => {
43
- const element = document.createElement("test-temporal");
75
+ const element = document.createElement("ten-seconds");
44
76
  element.setAttribute("trimstart", "1s");
45
77
  element.setAttribute("trimend", "5s");
46
78
  expect(element.trimStartMs).toBe(1_000);
47
79
  expect(element.trimEndMs).toBe(5_000);
80
+ expect(element.durationMs).toBe(4_000);
48
81
  });
49
82
 
50
- test("trimstart and trimend properties are reflected correctly", () => {
51
- const element = document.createElement("test-temporal");
52
- element.trimStartMs = 1_000;
53
- element.trimEndMs = 5_000;
54
- expect(element.getAttribute("trimstart")).toBe("1s");
55
- expect(element.getAttribute("trimend")).toBe("5s");
83
+ describe("trimstart is beyond the intrinsic duration", () => {
84
+ test("trimstart is normalized to the intrinsic duration", () => {
85
+ const element = document.createElement("ten-seconds");
86
+ element.trimStartMs = 15_000;
87
+ expect(element.trimStartMs).toBe(10_000);
88
+ });
56
89
  });
57
90
 
58
- test("trimstart and trimend can be set directly on the element", () => {
59
- const element = document.createElement("test-temporal");
60
- element.trimstart = "1s";
61
- element.trimend = "5s";
62
- expect(element.trimStartMs).toBe(1_000);
63
- expect(element.trimEndMs).toBe(5_000);
91
+ describe("trimend is beyond the intrinsic duration", () => {
92
+ test("trimend is normalized to the intrinsic duration", () => {
93
+ const element = document.createElement("ten-seconds");
94
+ element.trimEndMs = 15_000;
95
+ expect(element.trimEndMs).toBe(10_000);
96
+ });
64
97
  });
65
98
  });
66
99
 
67
100
  describe("duration", () => {
68
101
  test("duration is parsed correctly", () => {
69
- const element = document.createElement("test-temporal");
102
+ const element = document.createElement("ten-seconds");
70
103
  element.setAttribute("duration", "10s");
71
104
  expect(element.durationMs).toBe(10_000);
72
105
  });
73
106
 
74
107
  test("duration can be set directly on the element", () => {
75
- const element = document.createElement("test-temporal");
108
+ const element = document.createElement("ten-seconds");
76
109
  element.duration = "10s";
77
110
  expect(element.durationMs).toBe(10_000);
78
111
  });
@@ -18,6 +18,8 @@ export declare class TemporalMixinInterface {
18
18
  */
19
19
  get hasExplicitDuration(): boolean;
20
20
 
21
+ get sourceStartMs(): number;
22
+
21
23
  /**
22
24
  * Used to trim the start of the media.
23
25
  *
@@ -27,7 +29,7 @@ export declare class TemporalMixinInterface {
27
29
  *
28
30
  * @domAttribute "trimstart"
29
31
  */
30
- get trimStartMs(): number;
32
+ get trimStartMs(): number | undefined;
31
33
 
32
34
  /**
33
35
  * Used to trim the end of the media.
@@ -40,10 +42,10 @@ export declare class TemporalMixinInterface {
40
42
  */
41
43
  get trimEndMs(): number;
42
44
 
43
- set trimStartMs(value: number);
44
- set trimEndMs(value: number);
45
- set trimstart(value: string);
46
- set trimend(value: string);
45
+ set trimStartMs(value: number | undefined);
46
+ set trimEndMs(value: number | undefined);
47
+ set trimstart(value: string | undefined);
48
+ set trimend(value: string | undefined);
47
49
 
48
50
  /**
49
51
  * The source in time of the element.
@@ -64,7 +66,7 @@ export declare class TemporalMixinInterface {
64
66
  *
65
67
  * @domAttribute "sourcein"
66
68
  */
67
- get sourceInMs(): number;
69
+ get sourceInMs(): number | undefined;
68
70
 
69
71
  /**
70
72
  * The source out time of the element.
@@ -87,18 +89,22 @@ export declare class TemporalMixinInterface {
87
89
  *
88
90
  * @domAttribute "sourceout"
89
91
  */
90
- get sourceOutMs(): number;
92
+ get sourceOutMs(): number | undefined;
91
93
 
92
- set sourceInMs(value: number);
93
- set sourceOutMs(value: number);
94
- set sourcein(value: string);
95
- set sourceout(value: string);
94
+ set sourceInMs(value: number | undefined);
95
+ set sourceOutMs(value: number | undefined);
96
+ set sourcein(value: string | undefined);
97
+ set sourceout(value: string | undefined);
96
98
 
97
99
  /**
98
100
  * @domAttribute "duration"
99
101
  */
100
102
  get durationMs(): number;
101
103
 
104
+ get explicitDurationMs(): number | undefined;
105
+
106
+ get intrinsicDurationMs(): number | undefined;
107
+
102
108
  /**
103
109
  * The start time of the element within its root timegroup in milliseconds.
104
110
  *
@@ -338,109 +344,85 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
338
344
  }
339
345
  }
340
346
 
341
- private _trimStartMs = 0;
342
347
  @property({
343
348
  type: Number,
344
349
  attribute: "trimstart",
345
350
  converter: durationConverter,
346
351
  })
347
- public get trimStartMs(): number {
348
- return this._trimStartMs;
349
- }
350
- public set trimStartMs(value: number) {
351
- if (this._trimStartMs === value) {
352
- return;
352
+ _trimStartMs: number | undefined = undefined;
353
+
354
+ get trimStartMs() {
355
+ if (this._trimStartMs === undefined) {
356
+ return undefined;
353
357
  }
354
- this._trimStartMs = value;
355
- this.setAttribute(
356
- "trimstart",
357
- durationConverter.toAttribute(value / 1000),
358
+ return Math.min(
359
+ Math.max(this._trimStartMs, 0),
360
+ this.intrinsicDurationMs ?? 0,
358
361
  );
359
362
  }
360
- set trimstart(value: string | undefined) {
361
- if (value !== undefined) {
362
- this.setAttribute("trimstart", value);
363
- } else {
364
- this.removeAttribute("trimstart");
365
- }
363
+
364
+ set trimStartMs(value: number | undefined) {
365
+ this._trimStartMs = value;
366
366
  }
367
367
 
368
- private _trimEndMs = 0;
369
368
  @property({
370
369
  type: Number,
371
370
  attribute: "trimend",
372
371
  converter: durationConverter,
373
372
  })
374
- public get trimEndMs(): number {
375
- return this._trimEndMs;
376
- }
377
- public set trimEndMs(value: number) {
378
- if (this._trimEndMs === value) {
379
- return;
373
+ _trimEndMs: number | undefined = undefined;
374
+
375
+ get trimEndMs() {
376
+ if (this._trimEndMs === undefined) {
377
+ return undefined;
380
378
  }
381
- this._trimEndMs = value;
382
- this.setAttribute("trimend", durationConverter.toAttribute(value / 1000));
379
+ return Math.min(this._trimEndMs, this.intrinsicDurationMs ?? 0);
383
380
  }
384
- set trimend(value: string | undefined) {
385
- if (value !== undefined) {
386
- this.setAttribute("trimend", value);
387
- } else {
388
- this.removeAttribute("trimend");
389
- }
381
+
382
+ set trimEndMs(value: number | undefined) {
383
+ this._trimEndMs = value;
390
384
  }
391
385
 
392
- private _sourceInMs: number | undefined;
393
386
  @property({
394
387
  type: Number,
395
388
  attribute: "sourcein",
396
389
  converter: durationConverter,
397
- reflect: true,
398
390
  })
399
- get sourceInMs(): number | undefined {
400
- return this._sourceInMs;
391
+ _sourceInMs: number | undefined = undefined;
392
+
393
+ get sourceInMs() {
394
+ if (this._sourceInMs === undefined) {
395
+ return undefined;
396
+ }
397
+ return Math.max(this._sourceInMs, 0);
401
398
  }
399
+
402
400
  set sourceInMs(value: number | undefined) {
403
401
  this._sourceInMs = value;
404
- value !== undefined
405
- ? this.setAttribute(
406
- "sourcein",
407
- durationConverter.toAttribute(value / 1000),
408
- )
409
- : this.removeAttribute("sourcein");
410
- }
411
- set sourcein(value: string | undefined) {
412
- if (value !== undefined) {
413
- this.setAttribute("sourcein", value);
414
- } else {
415
- this.removeAttribute("sourcein");
416
- }
417
402
  }
418
403
 
419
- private _sourceOutMs: number | undefined;
420
404
  @property({
421
405
  type: Number,
422
406
  attribute: "sourceout",
423
407
  converter: durationConverter,
424
- reflect: true,
425
408
  })
426
- get sourceOutMs(): number | undefined {
427
- return this._sourceOutMs;
409
+ _sourceOutMs: number | undefined = undefined;
410
+
411
+ get sourceOutMs() {
412
+ if (this._sourceOutMs === undefined) {
413
+ return undefined;
414
+ }
415
+ if (
416
+ this.intrinsicDurationMs &&
417
+ this._sourceOutMs > this.intrinsicDurationMs
418
+ ) {
419
+ return this.intrinsicDurationMs;
420
+ }
421
+ return Math.max(this._sourceOutMs, 0);
428
422
  }
423
+
429
424
  set sourceOutMs(value: number | undefined) {
430
425
  this._sourceOutMs = value;
431
- value !== undefined
432
- ? this.setAttribute(
433
- "sourceout",
434
- durationConverter.toAttribute(value / 1000),
435
- )
436
- : this.removeAttribute("sourceout");
437
- }
438
- set sourceout(value: string | undefined) {
439
- if (value !== undefined) {
440
- this.setAttribute("sourceout", value);
441
- } else {
442
- this.removeAttribute("sourceout");
443
- }
444
426
  }
445
427
 
446
428
  @property({
@@ -469,28 +451,51 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
469
451
  return this._durationMs !== undefined;
470
452
  }
471
453
 
454
+ get explicitDurationMs() {
455
+ if (this.hasExplicitDuration) {
456
+ return this._durationMs;
457
+ }
458
+ return undefined;
459
+ }
460
+
472
461
  get hasOwnDuration() {
473
462
  return false;
474
463
  }
475
464
 
476
- // Defining this as a getter to a private property allows us to
477
- // override it classes that include this mixin.
465
+ get intrinsicDurationMs() {
466
+ return undefined;
467
+ }
468
+
478
469
  get durationMs() {
479
- if (this.sourceInMs) {
480
- return (
481
- this._durationMs ||
482
- this.parentTimegroup?.durationMs ||
483
- 0 - this.sourceInMs
484
- );
470
+ if (this.intrinsicDurationMs === undefined) {
471
+ return this._durationMs || this.parentTimegroup?.durationMs || 0;
485
472
  }
486
- if (this.sourceOutMs) {
487
- return (
488
- this._durationMs ||
489
- this.parentTimegroup?.durationMs ||
490
- 0 - this.sourceOutMs
491
- );
473
+
474
+ if (this.trimStartMs || this.trimEndMs) {
475
+ const trimmedDurationMs =
476
+ this.intrinsicDurationMs -
477
+ (this.trimStartMs ?? 0) -
478
+ (this.trimEndMs ?? 0);
479
+ if (trimmedDurationMs < 0) {
480
+ return 0;
481
+ }
482
+ return trimmedDurationMs;
483
+ }
484
+
485
+ if (this.sourceInMs || this.sourceOutMs) {
486
+ const sourceInMs = this.sourceInMs ?? 0;
487
+ const sourceOutMs = this.sourceOutMs ?? this.intrinsicDurationMs;
488
+ if (sourceInMs >= sourceOutMs) {
489
+ return 0;
490
+ }
491
+ return sourceOutMs - sourceInMs;
492
492
  }
493
- return this._durationMs || this.parentTimegroup?.durationMs || 0;
493
+
494
+ return this.intrinsicDurationMs;
495
+ }
496
+
497
+ get sourceStartMs() {
498
+ return this.trimStartMs ?? this.sourceInMs ?? 0;
494
499
  }
495
500
 
496
501
  get offsetMs() {
@@ -586,33 +591,8 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
586
591
  * for mapping to internal media time codes for audio/video elements.
587
592
  */
588
593
  get currentSourceTimeMs() {
589
- if (this.rootTimegroup) {
590
- if (this.sourceInMs && this.sourceOutMs) {
591
- return Math.min(
592
- Math.max(
593
- 0,
594
- this.rootTimegroup.currentTimeMs -
595
- this.startTimeMs +
596
- this.trimStartMs +
597
- this.sourceInMs,
598
- ),
599
- this.durationMs +
600
- Math.abs(this.startOffsetMs) +
601
- this.trimStartMs +
602
- this.sourceInMs,
603
- );
604
- }
605
- return Math.min(
606
- Math.max(
607
- 0,
608
- this.rootTimegroup.currentTimeMs -
609
- this.startTimeMs +
610
- this.trimStartMs,
611
- ),
612
- this.durationMs + Math.abs(this.startOffsetMs) + this.trimStartMs,
613
- );
614
- }
615
- return 0;
594
+ const leadingTrimMs = this.sourceInMs || this.trimStartMs || 0;
595
+ return this.ownCurrentTimeMs + leadingTrimMs;
616
596
  }
617
597
 
618
598
  frameTask = new Task(this, {
@@ -9,12 +9,12 @@ import { isContextMixin } from "../gui/ContextMixin.js";
9
9
  import { deepGetMediaElements } from "./EFMedia.js";
10
10
  import {
11
11
  EFTemporal,
12
- isEFTemporal,
13
12
  shallowGetTemporalElements,
14
13
  timegroupContext,
15
14
  } from "./EFTemporal.js";
16
15
  import { TimegroupController } from "./TimegroupController.js";
17
16
  import { durationConverter } from "./durationConverter.js";
17
+ import { updateAnimations } from "./updateAnimations.ts";
18
18
 
19
19
  const log = debug("ef:elements:EFTimegroup");
20
20
 
@@ -40,7 +40,8 @@ export class EFTimegroup extends EFTemporal(LitElement) {
40
40
  width: 100%;
41
41
  height: 100%;
42
42
  position: absolute;
43
- transform-origin: center center;
43
+ top: 0;
44
+ left: 0;
44
45
  }
45
46
  `;
46
47
 
@@ -121,16 +122,9 @@ export class EFTimegroup extends EFTemporal(LitElement) {
121
122
  this.wrapWithWorkbench();
122
123
  }
123
124
 
124
- // Create resize observer to handle scaling
125
- this.#resizeObserver = new ResizeObserver(() => this.updateScale());
126
- if (this.parentElement) {
127
- this.#resizeObserver.observe(this.parentElement);
128
- }
129
- this.updateScale();
130
-
131
- // Initialize animations when component is first connected
132
- // Regrettably, this doesn't work without the requestAnimationFrame
133
- requestAnimationFrame(() => this.updateAnimations());
125
+ requestAnimationFrame(() => {
126
+ this.updateAnimations();
127
+ });
134
128
  }
135
129
 
136
130
  disconnectedCallback() {
@@ -138,53 +132,6 @@ export class EFTimegroup extends EFTemporal(LitElement) {
138
132
  this.#resizeObserver?.disconnect();
139
133
  }
140
134
 
141
- private get displayedParent(): Element | null {
142
- let displayedParent = this.parentElement;
143
- while (
144
- displayedParent &&
145
- getComputedStyle(displayedParent).display === "contents"
146
- ) {
147
- displayedParent = displayedParent.parentElement;
148
- }
149
- return displayedParent;
150
- }
151
-
152
- private updateScale() {
153
- if (this.fit === "none") return;
154
-
155
- const displayedParent = this.displayedParent;
156
- if (!displayedParent) return;
157
-
158
- // Get the natural size of the content
159
- const contentWidth = this.clientWidth;
160
- const contentHeight = this.clientHeight;
161
-
162
- // Get the available space from displayed parent
163
- const containerWidth = displayedParent.clientWidth;
164
- const containerHeight = displayedParent.clientHeight;
165
-
166
- // Calculate scale ratios
167
- const widthRatio = containerWidth / contentWidth;
168
- const heightRatio = containerHeight / contentHeight;
169
-
170
- let scale: number;
171
- if (this.fit === "contain") {
172
- // Use height ratio for contain mode to ensure it fits vertically
173
- scale = heightRatio;
174
-
175
- // If width would overflow after scaling, use width ratio instead
176
- if (contentWidth * scale > containerWidth) {
177
- scale = widthRatio;
178
- }
179
- } else {
180
- // cover
181
- scale = Math.max(widthRatio, heightRatio);
182
- }
183
-
184
- // Apply transform with fixed center origin
185
- this.style.transform = `scale(${scale})`;
186
- }
187
-
188
135
  get storageKey() {
189
136
  if (!this.id) {
190
137
  throw new Error("Timegroup must have an id to use localStorage.");
@@ -192,6 +139,13 @@ export class EFTimegroup extends EFTemporal(LitElement) {
192
139
  return `ef-timegroup-${this.id}`;
193
140
  }
194
141
 
142
+ get intrinsicDurationMs() {
143
+ if (this.hasExplicitDuration) {
144
+ return this.explicitDurationMs;
145
+ }
146
+ return undefined;
147
+ }
148
+
195
149
  get durationMs() {
196
150
  switch (this.mode) {
197
151
  case "fixed":
@@ -209,7 +163,7 @@ export class EFTimegroup extends EFTemporal(LitElement) {
209
163
  case "contain": {
210
164
  let maxDuration = 0;
211
165
  for (const node of this.childTemporals) {
212
- if (node.hasOwnDuration) {
166
+ if (node.intrinsicDurationMs !== undefined) {
213
167
  maxDuration = Math.max(maxDuration, node.durationMs);
214
168
  }
215
169
  }
@@ -250,75 +204,7 @@ export class EFTimegroup extends EFTemporal(LitElement) {
250
204
  }
251
205
 
252
206
  private updateAnimations() {
253
- this.style.setProperty(
254
- "--ef-progress",
255
- `${Math.max(0, Math.min(1, this.currentTimeMs / this.durationMs)) * 100}%`,
256
- );
257
- const timelineTimeMs = (this.rootTimegroup ?? this).currentTimeMs;
258
- if (this.startTimeMs > timelineTimeMs || this.endTimeMs < timelineTimeMs) {
259
- this.style.display = "none";
260
- return;
261
- }
262
- this.style.display = "";
263
- const animations = this.getAnimations({ subtree: true });
264
- this.style.setProperty("--ef-duration", `${this.durationMs}ms`);
265
- this.style.setProperty(
266
- "--ef-transition-duration",
267
- `${this.parentTimegroup?.overlapMs ?? 0}ms`,
268
- );
269
- this.style.setProperty(
270
- "--ef-transition-out-start",
271
- `${this.durationMs - (this.parentTimegroup?.overlapMs ?? 0)}ms`,
272
- );
273
-
274
- for (const animation of animations) {
275
- if (animation.playState === "running") {
276
- animation.pause();
277
- }
278
- const effect = animation.effect;
279
- if (!(effect && effect instanceof KeyframeEffect)) {
280
- return;
281
- }
282
- const target = effect.target;
283
- // TODO: better generalize work avoidance for temporal elements
284
- if (!target) {
285
- return;
286
- }
287
- if (target.closest("ef-timegroup") !== this) {
288
- return;
289
- }
290
-
291
- // Important to avoid going to the end of the animation
292
- // or it will reset awkwardly.
293
- if (isEFTemporal(target)) {
294
- const timing = effect.getTiming();
295
- const duration = Number(timing.duration) ?? 0;
296
- const delay = Number(timing.delay);
297
- const newTime = Math.floor(
298
- Math.min(target.ownCurrentTimeMs, duration - 1 + delay),
299
- );
300
- if (Number.isNaN(newTime)) {
301
- return;
302
- }
303
- animation.currentTime = newTime;
304
- } else if (target) {
305
- const nearestTimegroup = target.closest("ef-timegroup");
306
- if (!nearestTimegroup) {
307
- return;
308
- }
309
- const timing = effect.getTiming();
310
- const duration = Number(timing.duration) ?? 0;
311
- const delay = Number(timing.delay);
312
- const newTime = Math.floor(
313
- Math.min(nearestTimegroup.ownCurrentTimeMs, duration - 1 + delay),
314
- );
315
-
316
- if (Number.isNaN(newTime)) {
317
- return;
318
- }
319
- animation.currentTime = newTime;
320
- }
321
- }
207
+ updateAnimations(this);
322
208
  }
323
209
 
324
210
  get contextProvider() {
@@ -366,10 +252,6 @@ export class EFTimegroup extends EFTemporal(LitElement) {
366
252
  workbench.append(filmstrip);
367
253
  }
368
254
 
369
- get hasOwnDuration() {
370
- return true;
371
- }
372
-
373
255
  get efElements() {
374
256
  return Array.from(
375
257
  this.querySelectorAll(
@@ -20,7 +20,9 @@ export class EFVideo extends TWMixin(EFMedia) {
20
20
  ];
21
21
  canvasRef = createRef<HTMLCanvasElement>();
22
22
  render() {
23
- return html` <canvas ${ref(this.canvasRef)}></canvas>`;
23
+ return html`
24
+ <canvas ${ref(this.canvasRef)}></canvas>
25
+ `;
24
26
  }
25
27
 
26
28
  get canvasElement() {