@editframe/elements 0.19.2-beta.0 → 0.19.4-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.
@@ -33,12 +33,7 @@ export declare class EFTimegroup extends EFTimegroup_base {
33
33
  getPendingFrameTasks(signal?: AbortSignal): Promise<Task<readonly unknown[], unknown>[]>;
34
34
  waitForNestedUpdates(signal?: AbortSignal): Promise<void>;
35
35
  waitForFrameTasks(): Promise<void>;
36
- /**
37
- * Wait for all media elements to load their initial segments.
38
- * Ideally we would only need the extracted index json data, but
39
- * that caused issues with constructing audio data. We had negative durations
40
- * in calculations and it was not clear why.
41
- */
36
+ mediaDurationsPromise: Promise<void> | undefined;
42
37
  waitForMediaDurations(): Promise<void>;
43
38
  get childTemporals(): import('./EFTemporal.js').TemporalMixinInterface[];
44
39
  get contextProvider(): import('../gui/ContextMixin.js').ContextMixinInterface | null;
@@ -32,6 +32,7 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
32
32
  this._mode = "contain";
33
33
  this._overlapMs = 0;
34
34
  this.fit = "none";
35
+ this.mediaDurationsPromise = void 0;
35
36
  this.frameTask = new Task(this, {
36
37
  autoRun: false,
37
38
  args: () => [this.ownCurrentTimeMs, this.currentTimeMs],
@@ -90,17 +91,16 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
90
91
  #pendingSeekTime;
91
92
  #processingPendingSeek = false;
92
93
  set currentTime(time) {
94
+ time = Math.max(0, time);
93
95
  if (!this.isRootTimegroup) return;
94
96
  if (Number.isNaN(time)) return;
95
97
  if (time === this.#currentTime && !this.#processingPendingSeek) return;
96
98
  if (this.#pendingSeekTime === time) return;
97
99
  if (this.#seekInProgress) {
98
- console.trace("pending seek to", time);
99
100
  this.#pendingSeekTime = time;
100
101
  this.#currentTime = time;
101
102
  return;
102
103
  }
103
- console.trace("seeking to", time);
104
104
  this.#currentTime = time;
105
105
  this.#seekInProgress = true;
106
106
  this.seekTask.run().finally(() => {
@@ -161,9 +161,12 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
161
161
  }
162
162
  connectedCallback() {
163
163
  super.connectedCallback();
164
- if (this.id) this.waitForMediaDurations().then(() => {
165
- const maybeLoadedTime = this.maybeLoadTimeFromLocalStorage();
166
- if (maybeLoadedTime !== void 0) this.currentTime = maybeLoadedTime;
164
+ this.waitForMediaDurations().then(() => {
165
+ if (this.id) {
166
+ const maybeLoadedTime = this.maybeLoadTimeFromLocalStorage();
167
+ if (maybeLoadedTime !== void 0) this.currentTime = maybeLoadedTime;
168
+ }
169
+ if (EF_INTERACTIVE && this.seekTask.status === TaskStatus.INITIAL) this.seekTask.run();
167
170
  });
168
171
  if (this.parentTimegroup) new TimegroupController(this.parentTimegroup, this);
169
172
  if (this.shouldWrapWithWorkbench()) this.wrapWithWorkbench();
@@ -256,13 +259,17 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
256
259
  return element.frameTask.run();
257
260
  }));
258
261
  }
262
+ async waitForMediaDurations() {
263
+ if (!this.mediaDurationsPromise) this.mediaDurationsPromise = this.#waitForMediaDurations();
264
+ return this.mediaDurationsPromise;
265
+ }
259
266
  /**
260
267
  * Wait for all media elements to load their initial segments.
261
268
  * Ideally we would only need the extracted index json data, but
262
269
  * that caused issues with constructing audio data. We had negative durations
263
270
  * in calculations and it was not clear why.
264
271
  */
265
- async waitForMediaDurations() {
272
+ async #waitForMediaDurations() {
266
273
  await this.updateComplete;
267
274
  const mediaElements = deepGetMediaElements(this);
268
275
  await Promise.all(mediaElements.map((m) => m.mediaEngineTask.value ? Promise.resolve() : m.mediaEngineTask.run()));
@@ -1,5 +1,5 @@
1
1
  import { deepGetTemporalElements, isEFTemporal } from "./EFTemporal.js";
2
- const ANIMATION_PRECISION_OFFSET = Number.EPSILON;
2
+ const ANIMATION_PRECISION_OFFSET = .001;
3
3
  const DEFAULT_ANIMATION_ITERATIONS = 1;
4
4
  const PROGRESS_PROPERTY = "--ef-progress";
5
5
  const DURATION_PROPERTY = "--ef-duration";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@editframe/elements",
3
- "version": "0.19.2-beta.0",
3
+ "version": "0.19.4-beta.0",
4
4
  "description": "",
5
5
  "exports": {
6
6
  ".": {
@@ -27,7 +27,7 @@
27
27
  "license": "UNLICENSED",
28
28
  "dependencies": {
29
29
  "@bramus/style-observer": "^1.3.0",
30
- "@editframe/assets": "0.19.2-beta.0",
30
+ "@editframe/assets": "0.19.4-beta.0",
31
31
  "@lit/context": "^1.1.2",
32
32
  "@lit/task": "^1.0.1",
33
33
  "d3": "^7.9.0",
@@ -106,6 +106,7 @@ export class EFTimegroup extends EFTemporal(LitElement) {
106
106
 
107
107
  @property({ type: Number, attribute: "currenttime" })
108
108
  set currentTime(time: number) {
109
+ time = Math.max(0, time);
109
110
  if (!this.isRootTimegroup) {
110
111
  return;
111
112
  }
@@ -120,12 +121,10 @@ export class EFTimegroup extends EFTemporal(LitElement) {
120
121
  }
121
122
 
122
123
  if (this.#seekInProgress) {
123
- console.trace("pending seek to", time);
124
124
  this.#pendingSeekTime = time;
125
125
  this.#currentTime = time;
126
126
  return;
127
127
  }
128
- console.trace("seeking to", time);
129
128
 
130
129
  this.#currentTime = time;
131
130
  // This will be set to false in the seekTask
@@ -212,14 +211,17 @@ export class EFTimegroup extends EFTemporal(LitElement) {
212
211
 
213
212
  connectedCallback() {
214
213
  super.connectedCallback();
215
- if (this.id) {
216
- this.waitForMediaDurations().then(() => {
214
+ this.waitForMediaDurations().then(() => {
215
+ if (this.id) {
217
216
  const maybeLoadedTime = this.maybeLoadTimeFromLocalStorage();
218
217
  if (maybeLoadedTime !== undefined) {
219
218
  this.currentTime = maybeLoadedTime;
220
219
  }
221
- });
222
- }
220
+ }
221
+ if (EF_INTERACTIVE && this.seekTask.status === TaskStatus.INITIAL) {
222
+ this.seekTask.run();
223
+ }
224
+ });
223
225
 
224
226
  if (this.parentTimegroup) {
225
227
  new TimegroupController(this.parentTimegroup, this);
@@ -376,13 +378,22 @@ export class EFTimegroup extends EFTemporal(LitElement) {
376
378
  );
377
379
  }
378
380
 
381
+ mediaDurationsPromise: Promise<void> | undefined = undefined;
382
+
383
+ async waitForMediaDurations() {
384
+ if (!this.mediaDurationsPromise) {
385
+ this.mediaDurationsPromise = this.#waitForMediaDurations();
386
+ }
387
+ return this.mediaDurationsPromise;
388
+ }
389
+
379
390
  /**
380
391
  * Wait for all media elements to load their initial segments.
381
392
  * Ideally we would only need the extracted index json data, but
382
393
  * that caused issues with constructing audio data. We had negative durations
383
394
  * in calculations and it was not clear why.
384
395
  */
385
- async waitForMediaDurations() {
396
+ async #waitForMediaDurations() {
386
397
  // We must await updateComplete to ensure all media elements inside this are connected
387
398
  // and will match deepGetMediaElements
388
399
  await this.updateComplete;
@@ -7,8 +7,6 @@ import {
7
7
 
8
8
  import "./EFTimegroup.js";
9
9
 
10
- // Import the constant for tests that need to check animation precision
11
-
12
10
  beforeEach(() => {
13
11
  // Clean up DOM
14
12
  while (document.body.children.length) {
@@ -415,6 +413,35 @@ describe("Timeline Element Synchronizer", () => {
415
413
 
416
414
  assert.equal(animation.playState, "paused");
417
415
  });
416
+
417
+ test("keeps completed animations available for scrubbing", async () => {
418
+ // Create a timegroup with 10s duration
419
+ const timegroup = document.createElement("ef-timegroup") as EFTimegroup;
420
+ timegroup.setAttribute("mode", "fixed");
421
+ timegroup.setAttribute("duration", "10000ms");
422
+ document.body.appendChild(timegroup);
423
+
424
+ // Create a child element with a 5s animation
425
+ const child = document.createElement("div");
426
+ timegroup.appendChild(child);
427
+
428
+ child.animate([{ opacity: 0 }, { opacity: 1 }], {
429
+ duration: 5000, // 5s animation
430
+ iterations: 1,
431
+ delay: 0,
432
+ });
433
+ timegroup.currentTime = 6;
434
+ await timegroup.seekTask.run();
435
+
436
+ // Animation should still be available even though timeline (6s) > animation duration (5s)
437
+ // This prevents animations from being removed, enabling scrubbing backwards
438
+ const animations = timegroup.getAnimations({ subtree: true });
439
+ assert.equal(
440
+ animations.length,
441
+ 1,
442
+ "REGRESSION TEST: Animation should remain available for scrubbing. This would fail with Number.EPSILON due to insufficient precision offset.",
443
+ );
444
+ });
418
445
  });
419
446
 
420
447
  describe("edge cases", () => {
@@ -8,7 +8,7 @@ import {
8
8
  export type AnimatableElement = TemporalMixinInterface & HTMLElement;
9
9
 
10
10
  // Constants
11
- const ANIMATION_PRECISION_OFFSET = Number.EPSILON;
11
+ const ANIMATION_PRECISION_OFFSET = 0.001;
12
12
  const DEFAULT_ANIMATION_ITERATIONS = 1;
13
13
  const PROGRESS_PROPERTY = "--ef-progress";
14
14
  const DURATION_PROPERTY = "--ef-duration";