@editframe/elements 0.21.0-beta.0 → 0.23.6-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 (142) hide show
  1. package/dist/EF_FRAMEGEN.js +2 -3
  2. package/dist/attachContextRoot.d.ts +1 -0
  3. package/dist/attachContextRoot.js +9 -0
  4. package/dist/elements/ContextProxiesController.d.ts +1 -2
  5. package/dist/elements/EFAudio.js +2 -2
  6. package/dist/elements/EFCaptions.d.ts +1 -3
  7. package/dist/elements/EFCaptions.js +59 -51
  8. package/dist/elements/EFImage.js +2 -2
  9. package/dist/elements/EFMedia/AssetIdMediaEngine.js +1 -2
  10. package/dist/elements/EFMedia/AssetMediaEngine.js +1 -3
  11. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +1 -1
  12. package/dist/elements/EFMedia/BufferedSeekingInput.js +2 -4
  13. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +4 -7
  14. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +1 -2
  15. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +5 -9
  16. package/dist/elements/EFMedia/shared/BufferUtils.js +1 -3
  17. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +4 -7
  18. package/dist/elements/EFMedia.d.ts +19 -0
  19. package/dist/elements/EFMedia.js +19 -2
  20. package/dist/elements/EFSourceMixin.js +1 -1
  21. package/dist/elements/EFSurface.js +1 -1
  22. package/dist/elements/EFTemporal.browsertest.d.ts +11 -0
  23. package/dist/elements/EFTemporal.d.ts +10 -0
  24. package/dist/elements/EFTemporal.js +82 -5
  25. package/dist/elements/EFThumbnailStrip.js +9 -16
  26. package/dist/elements/EFTimegroup.browsertest.d.ts +3 -3
  27. package/dist/elements/EFTimegroup.d.ts +35 -14
  28. package/dist/elements/EFTimegroup.js +72 -120
  29. package/dist/elements/EFVideo.d.ts +10 -0
  30. package/dist/elements/EFVideo.js +15 -2
  31. package/dist/elements/EFWaveform.js +10 -18
  32. package/dist/elements/SampleBuffer.js +1 -2
  33. package/dist/elements/TargetController.js +2 -2
  34. package/dist/elements/renderTemporalAudio.d.ts +10 -0
  35. package/dist/elements/renderTemporalAudio.js +35 -0
  36. package/dist/elements/updateAnimations.js +7 -10
  37. package/dist/gui/ContextMixin.d.ts +5 -5
  38. package/dist/gui/ContextMixin.js +151 -117
  39. package/dist/gui/Controllable.browsertest.d.ts +0 -0
  40. package/dist/gui/Controllable.d.ts +15 -0
  41. package/dist/gui/Controllable.js +9 -0
  42. package/dist/gui/EFConfiguration.js +1 -1
  43. package/dist/gui/EFControls.browsertest.d.ts +11 -0
  44. package/dist/gui/EFControls.d.ts +18 -4
  45. package/dist/gui/EFControls.js +67 -25
  46. package/dist/gui/EFDial.browsertest.d.ts +0 -0
  47. package/dist/gui/EFDial.d.ts +18 -0
  48. package/dist/gui/EFDial.js +141 -0
  49. package/dist/gui/EFFilmstrip.browsertest.d.ts +11 -0
  50. package/dist/gui/EFFilmstrip.d.ts +12 -2
  51. package/dist/gui/EFFilmstrip.js +140 -34
  52. package/dist/gui/EFFitScale.js +2 -4
  53. package/dist/gui/EFFocusOverlay.js +1 -1
  54. package/dist/gui/EFPause.browsertest.d.ts +0 -0
  55. package/dist/gui/EFPause.d.ts +23 -0
  56. package/dist/gui/EFPause.js +59 -0
  57. package/dist/gui/EFPlay.browsertest.d.ts +0 -0
  58. package/dist/gui/EFPlay.d.ts +23 -0
  59. package/dist/gui/EFPlay.js +59 -0
  60. package/dist/gui/EFPreview.d.ts +4 -0
  61. package/dist/gui/EFPreview.js +15 -6
  62. package/dist/gui/EFResizableBox.browsertest.d.ts +0 -0
  63. package/dist/gui/EFResizableBox.d.ts +34 -0
  64. package/dist/gui/EFResizableBox.js +547 -0
  65. package/dist/gui/EFScrubber.d.ts +9 -3
  66. package/dist/gui/EFScrubber.js +7 -7
  67. package/dist/gui/EFTimeDisplay.d.ts +7 -1
  68. package/dist/gui/EFTimeDisplay.js +5 -5
  69. package/dist/gui/EFToggleLoop.d.ts +9 -3
  70. package/dist/gui/EFToggleLoop.js +6 -4
  71. package/dist/gui/EFTogglePlay.d.ts +12 -4
  72. package/dist/gui/EFTogglePlay.js +24 -19
  73. package/dist/gui/EFWorkbench.js +1 -1
  74. package/dist/gui/PlaybackController.d.ts +67 -0
  75. package/dist/gui/PlaybackController.js +310 -0
  76. package/dist/gui/TWMixin.js +1 -1
  77. package/dist/gui/TargetOrContextMixin.d.ts +10 -0
  78. package/dist/gui/TargetOrContextMixin.js +98 -0
  79. package/dist/gui/efContext.d.ts +2 -2
  80. package/dist/index.d.ts +4 -0
  81. package/dist/index.js +5 -1
  82. package/dist/otel/setupBrowserTracing.d.ts +1 -1
  83. package/dist/otel/setupBrowserTracing.js +6 -4
  84. package/dist/otel/tracingHelpers.js +1 -2
  85. package/dist/style.css +1 -1
  86. package/package.json +5 -5
  87. package/src/elements/ContextProxiesController.ts +10 -10
  88. package/src/elements/EFAudio.ts +1 -0
  89. package/src/elements/EFCaptions.browsertest.ts +128 -58
  90. package/src/elements/EFCaptions.ts +60 -34
  91. package/src/elements/EFImage.browsertest.ts +1 -2
  92. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +3 -0
  93. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +1 -1
  94. package/src/elements/EFMedia.browsertest.ts +8 -15
  95. package/src/elements/EFMedia.ts +38 -7
  96. package/src/elements/EFSurface.browsertest.ts +2 -6
  97. package/src/elements/EFSurface.ts +1 -0
  98. package/src/elements/EFTemporal.browsertest.ts +58 -1
  99. package/src/elements/EFTemporal.ts +140 -4
  100. package/src/elements/EFThumbnailStrip.browsertest.ts +2 -8
  101. package/src/elements/EFThumbnailStrip.ts +1 -0
  102. package/src/elements/EFTimegroup.browsertest.ts +6 -7
  103. package/src/elements/EFTimegroup.ts +162 -244
  104. package/src/elements/EFVideo.browsertest.ts +143 -47
  105. package/src/elements/EFVideo.ts +26 -0
  106. package/src/elements/FetchContext.browsertest.ts +7 -2
  107. package/src/elements/TargetController.browsertest.ts +1 -0
  108. package/src/elements/TargetController.ts +1 -0
  109. package/src/elements/renderTemporalAudio.ts +108 -0
  110. package/src/elements/updateAnimations.browsertest.ts +181 -6
  111. package/src/elements/updateAnimations.ts +6 -6
  112. package/src/gui/ContextMixin.browsertest.ts +274 -27
  113. package/src/gui/ContextMixin.ts +230 -175
  114. package/src/gui/Controllable.browsertest.ts +258 -0
  115. package/src/gui/Controllable.ts +41 -0
  116. package/src/gui/EFControls.browsertest.ts +294 -80
  117. package/src/gui/EFControls.ts +139 -28
  118. package/src/gui/EFDial.browsertest.ts +84 -0
  119. package/src/gui/EFDial.ts +172 -0
  120. package/src/gui/EFFilmstrip.browsertest.ts +712 -0
  121. package/src/gui/EFFilmstrip.ts +213 -23
  122. package/src/gui/EFPause.browsertest.ts +202 -0
  123. package/src/gui/EFPause.ts +73 -0
  124. package/src/gui/EFPlay.browsertest.ts +202 -0
  125. package/src/gui/EFPlay.ts +73 -0
  126. package/src/gui/EFPreview.ts +20 -5
  127. package/src/gui/EFResizableBox.browsertest.ts +79 -0
  128. package/src/gui/EFResizableBox.ts +898 -0
  129. package/src/gui/EFScrubber.ts +7 -5
  130. package/src/gui/EFTimeDisplay.browsertest.ts +19 -19
  131. package/src/gui/EFTimeDisplay.ts +3 -1
  132. package/src/gui/EFToggleLoop.ts +6 -5
  133. package/src/gui/EFTogglePlay.ts +30 -23
  134. package/src/gui/PlaybackController.ts +522 -0
  135. package/src/gui/TWMixin.css +3 -0
  136. package/src/gui/TargetOrContextMixin.ts +185 -0
  137. package/src/gui/efContext.ts +2 -2
  138. package/src/otel/setupBrowserTracing.ts +17 -12
  139. package/test/cache-integration-verification.browsertest.ts +1 -1
  140. package/types.json +1 -1
  141. package/dist/elements/ContextProxiesController.js +0 -49
  142. /package/dist/_virtual/{_@oxc-project_runtime@0.93.0 → _@oxc-project_runtime@0.94.0}/helpers/decorate.js +0 -0
@@ -12,11 +12,10 @@ beforeEach(() => {
12
12
  describe("EFCaptions", () => {
13
13
  describe("when rendering", () => {
14
14
  beforeEach(() => {
15
- // @ts-ignore
15
+ // @ts-expect-error
16
16
  window.FRAMEGEN_BRIDGE = true;
17
17
  });
18
18
  afterEach(() => {
19
- // @ts-ignore
20
19
  delete window.FRAMEGEN_BRIDGE;
21
20
  });
22
21
  test("captionsPath uses http:// protocol", () => {
@@ -84,7 +83,7 @@ describe("EFCaptions", () => {
84
83
  captions.captionsSrc = "test-captions-simple.json";
85
84
  document.body.appendChild(captions);
86
85
 
87
- await captions.updateComplete;
86
+ await captions.frameTask.taskComplete;
88
87
  // @ts-expect-error accessing private property for testing
89
88
  const captionsTask = captions.customCaptionsDataTask;
90
89
 
@@ -123,7 +122,7 @@ describe("EFCaptions", () => {
123
122
  captions.captionsScript = scriptId;
124
123
  document.body.appendChild(captions);
125
124
 
126
- await captions.updateComplete;
125
+ await captions.frameTask.taskComplete;
127
126
 
128
127
  // @ts-expect-error accessing private property for testing
129
128
  const captionsTask = captions.customCaptionsDataTask;
@@ -160,7 +159,7 @@ describe("EFCaptions", () => {
160
159
  captions.captionsData = testData;
161
160
  document.body.appendChild(captions);
162
161
 
163
- await captions.updateComplete;
162
+ await captions.frameTask.taskComplete;
164
163
  await captions.unifiedCaptionsDataTask.taskComplete;
165
164
 
166
165
  expect(captions.unifiedCaptionsDataTask.value).toEqual(testData);
@@ -205,7 +204,7 @@ describe("EFCaptions", () => {
205
204
  captions.captionsData = directData;
206
205
  document.body.appendChild(captions);
207
206
 
208
- await captions.updateComplete;
207
+ await captions.frameTask.taskComplete;
209
208
  await captions.unifiedCaptionsDataTask.taskComplete;
210
209
 
211
210
  // Should use direct property data, not script or file
@@ -227,7 +226,7 @@ describe("EFCaptions", () => {
227
226
  captions.captionsSrc = "nonexistent-file.json";
228
227
  document.body.appendChild(captions);
229
228
 
230
- await captions.updateComplete;
229
+ await captions.frameTask.taskComplete;
231
230
  await captions.unifiedCaptionsDataTask.taskComplete;
232
231
 
233
232
  // @ts-expect-error accessing private property for testing
@@ -256,7 +255,7 @@ describe("EFCaptions", () => {
256
255
  captions.captionsScript = scriptId;
257
256
  document.body.appendChild(captions);
258
257
 
259
- await captions.updateComplete;
258
+ await captions.frameTask.taskComplete;
260
259
 
261
260
  // @ts-expect-error accessing private property for testing
262
261
  const captionsTask = captions.customCaptionsDataTask;
@@ -290,6 +289,8 @@ describe("EFCaptions", () => {
290
289
  // Test at t=0 (first segment)
291
290
  timegroup.currentTimeMs = 0;
292
291
  await timegroup.seekTask.taskComplete;
292
+ await captions.frameTask.taskComplete;
293
+ await segmentContainer.updateComplete;
293
294
  expect(segmentContainer.segmentText).toBe("First test segment");
294
295
  expect(segmentContainer.segmentStartMs).toBe(0);
295
296
  expect(segmentContainer.segmentEndMs).toBe(3000);
@@ -297,6 +298,8 @@ describe("EFCaptions", () => {
297
298
  // Test at t=4000ms (second segment)
298
299
  timegroup.currentTimeMs = 4000;
299
300
  await timegroup.seekTask.taskComplete;
301
+ await captions.frameTask.taskComplete;
302
+ await segmentContainer.updateComplete;
300
303
  expect(segmentContainer.segmentText).toBe("Second test segment");
301
304
  expect(segmentContainer.segmentStartMs).toBe(3000);
302
305
  expect(segmentContainer.segmentEndMs).toBe(6000);
@@ -304,6 +307,8 @@ describe("EFCaptions", () => {
304
307
  // Test at t=7500ms (third segment)
305
308
  timegroup.currentTimeMs = 7500;
306
309
  await timegroup.seekTask.taskComplete;
310
+ await captions.frameTask.taskComplete;
311
+ await segmentContainer.updateComplete;
307
312
 
308
313
  expect(segmentContainer.segmentText).toBe("Third test segment");
309
314
  expect(segmentContainer.segmentStartMs).toBe(6000);
@@ -334,6 +339,8 @@ describe("EFCaptions", () => {
334
339
  // Test at t=0.3s (should be "First")
335
340
  timegroup.currentTimeMs = 300;
336
341
  await timegroup.seekTask.taskComplete;
342
+ await captions.frameTask.taskComplete;
343
+ await wordContainer.updateComplete;
337
344
  expect(wordContainer.wordText).toBe("First");
338
345
  expect(wordContainer.wordStartMs).toBe(0);
339
346
  expect(wordContainer.wordEndMs).toBe(600);
@@ -341,6 +348,8 @@ describe("EFCaptions", () => {
341
348
  // Test at t=0.9s (should be " test")
342
349
  timegroup.currentTimeMs = 900;
343
350
  await timegroup.seekTask.taskComplete;
351
+ await captions.frameTask.taskComplete;
352
+ await wordContainer.updateComplete;
344
353
  expect(wordContainer.wordText).toBe(" test");
345
354
  expect(wordContainer.wordStartMs).toBe(600);
346
355
  expect(wordContainer.wordEndMs).toBe(1200);
@@ -348,6 +357,8 @@ describe("EFCaptions", () => {
348
357
  // Test at t=1.8s (should be " segment")
349
358
  timegroup.currentTimeMs = 1800;
350
359
  await timegroup.seekTask.taskComplete;
360
+ await captions.frameTask.taskComplete;
361
+ await wordContainer.updateComplete;
351
362
  expect(wordContainer.wordText).toBe(" segment");
352
363
  expect(wordContainer.wordStartMs).toBe(1200);
353
364
  expect(wordContainer.wordEndMs).toBe(3000);
@@ -378,6 +389,7 @@ describe("EFCaptions", () => {
378
389
  captions.appendChild(afterContainer);
379
390
  timegroup.appendChild(captions);
380
391
  document.body.appendChild(timegroup);
392
+ await timegroup.waitForMediaDurations();
381
393
 
382
394
  // @ts-expect-error accessing private property for testing
383
395
  const captionsTask = captions.customCaptionsDataTask;
@@ -386,6 +398,8 @@ describe("EFCaptions", () => {
386
398
  // Test at t=1.0s (active word: "longer", context should be available)
387
399
  timegroup.currentTimeMs = 1000;
388
400
  await timegroup.seekTask.taskComplete;
401
+ await captions.frameTask.taskComplete;
402
+ await activeContainer.updateComplete;
389
403
 
390
404
  expect(activeContainer.wordText).toBe(" longer");
391
405
  expect(beforeContainer.segmentText).toBe("This is a");
@@ -451,8 +465,8 @@ describe("EFCaptions", () => {
451
465
  });
452
466
  });
453
467
 
454
- describe("display modes", () => {
455
- test("segment mode shows segment text", async () => {
468
+ describe("child element types", () => {
469
+ test("segment containers show segment text", async () => {
456
470
  const id = v4();
457
471
  const timegroup = document.createElement("ef-timegroup");
458
472
  const target = document.createElement("ef-video");
@@ -462,7 +476,6 @@ describe("EFCaptions", () => {
462
476
 
463
477
  const captions = document.createElement("ef-captions");
464
478
  captions.setAttribute("target", id);
465
- captions.displayMode = "segment";
466
479
  captions.captionsSrc = "test-captions-simple.json";
467
480
 
468
481
  const segmentContainer = document.createElement("ef-captions-segment");
@@ -476,10 +489,11 @@ describe("EFCaptions", () => {
476
489
 
477
490
  timegroup.currentTimeMs = 1500;
478
491
  await timegroup.seekTask.taskComplete;
492
+ await captions.frameTask.taskComplete;
479
493
  expect(segmentContainer.segmentText).toBe("First test segment");
480
494
  });
481
495
 
482
- test("word mode shows active word", async () => {
496
+ test("word containers show active word", async () => {
483
497
  const id = v4();
484
498
  const timegroup = document.createElement("ef-timegroup");
485
499
  const target = document.createElement("ef-video");
@@ -489,7 +503,6 @@ describe("EFCaptions", () => {
489
503
 
490
504
  const captions = document.createElement("ef-captions");
491
505
  captions.setAttribute("target", id);
492
- captions.displayMode = "word";
493
506
  captions.captionsSrc = "test-captions-simple.json";
494
507
 
495
508
  const wordContainer = document.createElement("ef-captions-active-word");
@@ -497,16 +510,19 @@ describe("EFCaptions", () => {
497
510
  timegroup.appendChild(captions);
498
511
  document.body.appendChild(timegroup);
499
512
 
513
+ await timegroup.waitForMediaDurations();
514
+
500
515
  // @ts-expect-error accessing private property for testing
501
516
  const captionsTask = captions.customCaptionsDataTask;
502
517
  await captionsTask.taskComplete;
503
518
 
504
519
  timegroup.currentTimeMs = 900;
505
520
  await timegroup.seekTask.taskComplete;
521
+ await captions.frameTask.taskComplete;
506
522
  expect(wordContainer.wordText).toBe(" test");
507
523
  });
508
524
 
509
- test("context mode shows before/active/after words", async () => {
525
+ test("context containers show before/active/after words", async () => {
510
526
  const id = v4();
511
527
  const timegroup = document.createElement("ef-timegroup");
512
528
  const target = document.createElement("ef-video");
@@ -516,7 +532,6 @@ describe("EFCaptions", () => {
516
532
 
517
533
  const captions = document.createElement("ef-captions");
518
534
  captions.setAttribute("target", id);
519
- captions.displayMode = "context";
520
535
  captions.captionsSrc = "test-captions-complex.json";
521
536
 
522
537
  const beforeContainer = document.createElement(
@@ -532,6 +547,7 @@ describe("EFCaptions", () => {
532
547
  captions.appendChild(afterContainer);
533
548
  timegroup.appendChild(captions);
534
549
  document.body.appendChild(timegroup);
550
+ await timegroup.waitForMediaDurations();
535
551
 
536
552
  // @ts-expect-error accessing private property for testing
537
553
  const captionsTask = captions.customCaptionsDataTask;
@@ -540,6 +556,7 @@ describe("EFCaptions", () => {
540
556
  // Test middle of first segment
541
557
  timegroup.currentTimeMs = 2400; // during "multiple"
542
558
  await timegroup.seekTask.taskComplete;
559
+ await captions.frameTask.taskComplete;
543
560
 
544
561
  expect(activeContainer.wordText).toBe(" multiple");
545
562
  expect(beforeContainer.segmentText).toBeTruthy();
@@ -577,6 +594,7 @@ describe("EFCaptions", () => {
577
594
  // Test first word "First" (0-0.6s)
578
595
  timegroup.currentTimeMs = 300;
579
596
  await timegroup.seekTask.taskComplete;
597
+ await captions.frameTask.taskComplete;
580
598
 
581
599
  expect(wordContainer.startTimeMs).toBe(0);
582
600
  expect(wordContainer.durationMs).toBe(600);
@@ -584,6 +602,7 @@ describe("EFCaptions", () => {
584
602
  // Test second word " test" (0.6-1.2s)
585
603
  timegroup.currentTimeMs = 900;
586
604
  await timegroup.seekTask.taskComplete;
605
+ await captions.frameTask.taskComplete;
587
606
 
588
607
  expect(wordContainer.startTimeMs).toBe(600);
589
608
  expect(wordContainer.durationMs).toBe(600);
@@ -613,6 +632,7 @@ describe("EFCaptions", () => {
613
632
  // Test first segment (0-3s)
614
633
  timegroup.currentTimeMs = 1500;
615
634
  await timegroup.seekTask.taskComplete;
635
+ await captions.frameTask.taskComplete;
616
636
 
617
637
  expect(segmentContainer.startTimeMs).toBe(0);
618
638
  expect(segmentContainer.durationMs).toBe(3000);
@@ -620,6 +640,7 @@ describe("EFCaptions", () => {
620
640
  // Test second segment (3-6s)
621
641
  timegroup.currentTimeMs = 4500;
622
642
  await timegroup.seekTask.taskComplete;
643
+ await captions.frameTask.taskComplete;
623
644
 
624
645
  expect(segmentContainer.startTimeMs).toBe(3000);
625
646
  expect(segmentContainer.durationMs).toBe(3000);
@@ -650,6 +671,7 @@ describe("EFCaptions", () => {
650
671
  captions.appendChild(afterContainer);
651
672
  timegroup.appendChild(captions);
652
673
  document.body.appendChild(timegroup);
674
+ await timegroup.waitForMediaDurations();
653
675
 
654
676
  // @ts-expect-error accessing private property for testing
655
677
  const captionsTask = captions.customCaptionsDataTask;
@@ -658,6 +680,7 @@ describe("EFCaptions", () => {
658
680
  // Test during "longer" word (0.8-1.3s) in first segment (0-4s)
659
681
  timegroup.currentTimeMs = 1000;
660
682
  await timegroup.seekTask.taskComplete;
683
+ await captions.frameTask.taskComplete;
661
684
 
662
685
  // Active word timing
663
686
  expect(activeContainer.startTimeMs).toBe(800);
@@ -705,6 +728,7 @@ describe("EFCaptions", () => {
705
728
  for (const step of timingSteps) {
706
729
  timegroup.currentTimeMs = step.time;
707
730
  await timegroup.seekTask.taskComplete;
731
+ await captions.frameTask.taskComplete;
708
732
 
709
733
  expect(wordContainer.startTimeMs).toBe(step.expectedStart);
710
734
  expect(wordContainer.durationMs).toBe(step.expectedDuration);
@@ -850,23 +874,23 @@ describe("EFCaptions", () => {
850
874
  const captionsTask = captions.customCaptionsDataTask;
851
875
  await captionsTask.taskComplete;
852
876
 
853
- // Test at exact boundary 2.6s - should show " test" (the starting word)
854
- timegroup.currentTimeMs = 2600;
855
- await timegroup.seekTask.taskComplete;
856
- await captions.updateComplete;
857
- await wordContainer.updateComplete;
858
-
859
- console.log(`At 2600ms: wordText="${wordContainer.wordText}"`);
860
- expect(wordContainer.wordText).toBe(" test");
861
-
862
877
  // Test just before boundary 2.599s - should show " timing"
863
878
  timegroup.currentTimeMs = 2599;
864
879
  await timegroup.seekTask.taskComplete;
865
- await captions.updateComplete;
880
+ await captions.frameTask.taskComplete;
866
881
  await wordContainer.updateComplete;
867
882
 
868
883
  console.log(`At 2599ms: wordText="${wordContainer.wordText}"`);
869
884
  expect(wordContainer.wordText).toBe(" timing");
885
+
886
+ // Test at exact boundary 2.6s - should show " test" (the starting word)
887
+ timegroup.currentTimeMs = 2600;
888
+ await timegroup.seekTask.taskComplete;
889
+ await captions.frameTask.taskComplete;
890
+ await wordContainer.updateComplete;
891
+
892
+ console.log(`At 2600ms: wordText="${wordContainer.wordText}"`);
893
+ expect(wordContainer.wordText).toBe(" test");
870
894
  });
871
895
 
872
896
  test("handles demo captions data boundary correctly", async () => {
@@ -892,26 +916,28 @@ describe("EFCaptions", () => {
892
916
  const captionsTask = captions.customCaptionsDataTask;
893
917
  await captionsTask.taskComplete;
894
918
 
895
- // Test at exact boundary 2.6s from user's example
896
- timegroup.currentTimeMs = 2600;
919
+ // Test just before boundary
920
+ timegroup.currentTimeMs = 2590;
897
921
  await timegroup.seekTask.taskComplete;
922
+ await captions.frameTask.taskComplete;
898
923
  await wordContainer.updateComplete;
899
924
 
900
925
  console.log(
901
- `Demo case - At 2600ms: wordText="${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
926
+ `Demo case - At 2590ms: wordText="${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
902
927
  );
903
- expect(wordContainer.wordText).toBe(" demo!");
904
- expect(wordContainer.hidden).toBe(false);
928
+ expect(wordContainer.wordText).toBe(" captions");
905
929
 
906
- // Test just before boundary
907
- timegroup.currentTimeMs = 2590;
930
+ // Test at exact boundary 2.6s from user's example
931
+ timegroup.currentTimeMs = 2600;
908
932
  await timegroup.seekTask.taskComplete;
933
+ await captions.frameTask.taskComplete;
909
934
  await wordContainer.updateComplete;
910
935
 
911
936
  console.log(
912
- `Demo case - At 2590ms: wordText="${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
937
+ `Demo case - At 2600ms: wordText="${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
913
938
  );
914
- expect(wordContainer.wordText).toBe(" captions");
939
+ expect(wordContainer.wordText).toBe(" demo!");
940
+ expect(wordContainer.hidden).toBe(false);
915
941
  });
916
942
 
917
943
  test("standalone captions sync with timegroup (demo structure)", async () => {
@@ -960,7 +986,8 @@ describe("EFCaptions", () => {
960
986
  // Test the problematic timing
961
987
  rootTimegroup.currentTimeMs = 2600;
962
988
  await rootTimegroup.seekTask.taskComplete;
963
- await captions.updateComplete;
989
+ await captions.frameTask.taskComplete;
990
+ await wordContainer.updateComplete;
964
991
 
965
992
  console.log(
966
993
  `After seek: rootTimegroup.currentTimeMs=${rootTimegroup.currentTimeMs}, captions.ownCurrentTimeMs=${captions.ownCurrentTimeMs}`,
@@ -1012,7 +1039,10 @@ describe("EFCaptions", () => {
1012
1039
  // Test during " custom" word (1.2-1.8s) - should have before/after context
1013
1040
  timegroup.currentTimeMs = 1500;
1014
1041
  await timegroup.seekTask.taskComplete;
1015
- await captions.updateComplete;
1042
+ await captions.frameTask.taskComplete;
1043
+ await activeContainer.updateComplete;
1044
+ await beforeContainer.updateComplete;
1045
+ await afterContainer.updateComplete;
1016
1046
 
1017
1047
  console.log("Context test - At 1500ms:");
1018
1048
  console.log(` activeWord: "${activeContainer.wordText}"`);
@@ -1083,7 +1113,7 @@ describe("EFCaptions", () => {
1083
1113
  // Test during " custom" word (1.2-1.8s) - within first segment (0-4s)
1084
1114
  timegroup.currentTimeMs = 1500;
1085
1115
  await timegroup.seekTask.taskComplete;
1086
- await captions.updateComplete;
1116
+ await captions.frameTask.taskComplete;
1087
1117
 
1088
1118
  console.log("Demo debug - At 1500ms:");
1089
1119
  console.log(
@@ -1102,7 +1132,7 @@ describe("EFCaptions", () => {
1102
1132
  // Try different timing in second segment
1103
1133
  timegroup.currentTimeMs = 5000; // During "demonstrates" in second segment
1104
1134
  await timegroup.seekTask.taskComplete;
1105
- await captions.updateComplete;
1135
+ await captions.frameTask.taskComplete;
1106
1136
 
1107
1137
  console.log("Demo debug - At 5000ms (second segment):");
1108
1138
  console.log(` activeWord: "${activeContainer.wordText}"`);
@@ -1158,7 +1188,10 @@ describe("EFCaptions", () => {
1158
1188
  // Test during " test" word (2.5-3.5s)
1159
1189
  timegroup.currentTimeMs = 3000;
1160
1190
  await timegroup.seekTask.taskComplete;
1161
- await captions.updateComplete;
1191
+ await captions.frameTask.taskComplete;
1192
+ await activeContainer.updateComplete;
1193
+ await beforeContainer.updateComplete;
1194
+ await afterContainer.updateComplete;
1162
1195
 
1163
1196
  console.log("Timing sync test - At 3000ms:");
1164
1197
  console.log(
@@ -1278,7 +1311,13 @@ describe("EFCaptions", () => {
1278
1311
  for (const test of testWords) {
1279
1312
  timegroup.currentTimeMs = test.time;
1280
1313
  await timegroup.seekTask.taskComplete;
1281
- await captions.updateComplete;
1314
+ await captions.frameTask.taskComplete;
1315
+ await activeContainer.updateComplete;
1316
+ await beforeContainer.updateComplete;
1317
+ await afterContainer.updateComplete;
1318
+
1319
+ // Wait for browser to paint before measuring
1320
+ await new Promise((resolve) => requestAnimationFrame(resolve));
1282
1321
 
1283
1322
  const measurement = measureElements();
1284
1323
  measurements.push({
@@ -1304,19 +1343,35 @@ describe("EFCaptions", () => {
1304
1343
  }
1305
1344
 
1306
1345
  // Check if total width stays consistent
1307
- const firstTotal = measurements[0]?.measurements.totalContentWidth;
1308
- const allTotalsConsistent = measurements.every(
1309
- (m) => Math.abs(m.measurements.totalContentWidth - firstTotal) < 2, // Allow 1-2px tolerance
1346
+ // const firstTotal = measurements[0]?.measurements.totalContentWidth;
1347
+ // const allTotalsConsistent = measurements.every(
1348
+ // (m) => Math.abs(m.measurements.totalContentWidth - firstTotal) < 2, // Allow 1-2px tolerance
1349
+ // );
1350
+ //
1351
+ // if (!allTotalsConsistent) {
1352
+ // console.log("Width inconsistency detected:");
1353
+ // measurements.forEach((m) => {
1354
+ // console.log(` ${m.word}: ${m.measurements.totalContentWidth}px`);
1355
+ // });
1356
+ // }
1357
+ //
1358
+ // expect(allTotalsConsistent).toBe(true);
1359
+
1360
+ // Check if the overall container width stays consistent (the real measure of stability)
1361
+ const firstContainerWidth = measurements[0]?.measurements.captions.width;
1362
+ const allContainerWidthsConsistent = measurements.every(
1363
+ (m) =>
1364
+ Math.abs(m.measurements.captions.width - firstContainerWidth) < 0.1,
1310
1365
  );
1311
1366
 
1312
- if (!allTotalsConsistent) {
1313
- console.log("Width inconsistency detected:");
1367
+ if (!allContainerWidthsConsistent) {
1368
+ console.log("Container width inconsistency detected:");
1314
1369
  measurements.forEach((m) => {
1315
- console.log(` ${m.word}: ${m.measurements.totalContentWidth}px`);
1370
+ console.log(` ${m.word}: ${m.measurements.captions.width}px`);
1316
1371
  });
1317
1372
  }
1318
1373
 
1319
- expect(allTotalsConsistent).toBe(true);
1374
+ expect(allContainerWidthsConsistent).toBe(true);
1320
1375
  });
1321
1376
 
1322
1377
  test("measures font weight differences causing layout shifts", async () => {
@@ -1368,7 +1423,8 @@ describe("EFCaptions", () => {
1368
1423
  // Test with consistent font weight
1369
1424
  timegroup.currentTimeMs = 300;
1370
1425
  await timegroup.seekTask.taskComplete;
1371
- await captions.updateComplete;
1426
+ await captions.frameTask.taskComplete;
1427
+ await activeContainer.updateComplete;
1372
1428
 
1373
1429
  const welcomeRect = activeContainer.getBoundingClientRect();
1374
1430
  console.log(
@@ -1377,7 +1433,8 @@ describe("EFCaptions", () => {
1377
1433
 
1378
1434
  timegroup.currentTimeMs = 750;
1379
1435
  await timegroup.seekTask.taskComplete;
1380
- await captions.updateComplete;
1436
+ await captions.frameTask.taskComplete;
1437
+ await activeContainer.updateComplete;
1381
1438
 
1382
1439
  const toRect = activeContainer.getBoundingClientRect();
1383
1440
  console.log(`" to" with consistent bold: width=${toRect.width}px`);
@@ -1390,7 +1447,10 @@ describe("EFCaptions", () => {
1390
1447
 
1391
1448
  timegroup.currentTimeMs = 1050;
1392
1449
  await timegroup.seekTask.taskComplete;
1393
- await captions.updateComplete;
1450
+ await captions.frameTask.taskComplete;
1451
+ await activeContainer.updateComplete;
1452
+ await beforeContainer.updateComplete;
1453
+ await afterContainer.updateComplete;
1394
1454
 
1395
1455
  const totalWidth2 =
1396
1456
  beforeContainer.getBoundingClientRect().width +
@@ -1448,7 +1508,7 @@ describe("EFCaptions", () => {
1448
1508
  // Measure widths with natural component behavior
1449
1509
  timegroup.currentTimeMs = 500;
1450
1510
  await timegroup.seekTask.taskComplete;
1451
- await captions.updateComplete;
1511
+ await captions.frameTask.taskComplete;
1452
1512
 
1453
1513
  const naturalRect1 = captions.getBoundingClientRect();
1454
1514
  console.log(
@@ -1457,7 +1517,7 @@ describe("EFCaptions", () => {
1457
1517
 
1458
1518
  timegroup.currentTimeMs = 1500;
1459
1519
  await timegroup.seekTask.taskComplete;
1460
- await captions.updateComplete;
1520
+ await captions.frameTask.taskComplete;
1461
1521
 
1462
1522
  const naturalRect2 = captions.getBoundingClientRect();
1463
1523
  console.log(`Natural captions width at " flow": ${naturalRect2.width}px`);
@@ -1516,7 +1576,7 @@ describe("EFCaptions", () => {
1516
1576
  for (const test of positionTests) {
1517
1577
  timegroup.currentTimeMs = test.time;
1518
1578
  await timegroup.seekTask.taskComplete;
1519
- await captions.updateComplete;
1579
+ await captions.frameTask.taskComplete;
1520
1580
 
1521
1581
  const beforeRect = beforeContainer.getBoundingClientRect();
1522
1582
  const activeRect = activeContainer.getBoundingClientRect();
@@ -1602,7 +1662,8 @@ describe("EFCaptions", () => {
1602
1662
 
1603
1663
  timegroup.currentTimeMs = test.time;
1604
1664
  await timegroup.seekTask.taskComplete;
1605
- await captions.updateComplete;
1665
+ await captions.frameTask.taskComplete;
1666
+ await activeContainer.updateComplete;
1606
1667
 
1607
1668
  // Check that element is visible and has animation properties
1608
1669
  const computedStyle = getComputedStyle(activeContainer);
@@ -1627,7 +1688,7 @@ describe("EFCaptions", () => {
1627
1688
  // Test that element is hidden when outside time range
1628
1689
  timegroup.currentTimeMs = 3500; // After all words
1629
1690
  await timegroup.seekTask.taskComplete;
1630
- await captions.updateComplete;
1691
+ await captions.frameTask.taskComplete;
1631
1692
 
1632
1693
  console.log("\nAfter time range (3500ms):");
1633
1694
  console.log(` Element hidden: ${activeContainer.hidden}`);
@@ -1728,6 +1789,7 @@ describe("EFCaptions", () => {
1728
1789
  // Set timeline to be in second timegroup (7s = 2s into second captions)
1729
1790
  sequence.currentTimeMs = 7000;
1730
1791
  await sequence.seekTask.taskComplete;
1792
+ await captions2.frameTask.taskComplete;
1731
1793
 
1732
1794
  console.log(
1733
1795
  `Timeline at 7000ms - Second captions word: "${word2.wordText}", hidden: ${word2.hidden}`,
@@ -1770,7 +1832,8 @@ describe("EFCaptions", () => {
1770
1832
  // Test at 2.5s - should show " text" word normally
1771
1833
  timegroup.currentTimeMs = 2500;
1772
1834
  await timegroup.seekTask.taskComplete;
1773
- await captions.updateComplete;
1835
+ await captions.frameTask.taskComplete;
1836
+ await wordContainer.updateComplete;
1774
1837
 
1775
1838
  expect(wordContainer.wordText).toBe(" text");
1776
1839
  expect(wordContainer.hidden).toBe(false);
@@ -1778,7 +1841,10 @@ describe("EFCaptions", () => {
1778
1841
  // Test at 4s - after all words finished but segment still active
1779
1842
  timegroup.currentTimeMs = 4000;
1780
1843
  await timegroup.seekTask.taskComplete;
1781
- await captions.updateComplete;
1844
+ await captions.frameTask.taskComplete;
1845
+ await wordContainer.updateComplete;
1846
+ await beforeContainer.updateComplete;
1847
+ await segmentContainer.updateComplete;
1782
1848
 
1783
1849
  console.log(
1784
1850
  ` Active word: "${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
@@ -1849,7 +1915,11 @@ describe("EFCaptions", () => {
1849
1915
  // Test at 1s - in segment but before first word starts
1850
1916
  timegroup.currentTimeMs = 1000;
1851
1917
  await timegroup.seekTask.taskComplete;
1852
- await captions.updateComplete;
1918
+ await captions.frameTask.taskComplete;
1919
+ await wordContainer.updateComplete;
1920
+ await beforeContainer.updateComplete;
1921
+ await afterContainer.updateComplete;
1922
+ await segmentContainer.updateComplete;
1853
1923
 
1854
1924
  console.log(
1855
1925
  ` Active word: "${wordContainer.wordText}", hidden=${wordContainer.hidden}`,