@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
@@ -24,13 +24,17 @@ import {
24
24
  EFCaptionsActiveWord,
25
25
  } from "../elements/EFCaptions.js";
26
26
  import { EFImage } from "../elements/EFImage.js";
27
- import type { TemporalMixinInterface } from "../elements/EFTemporal.js";
27
+ import {
28
+ isEFTemporal,
29
+ type TemporalMixinInterface,
30
+ } from "../elements/EFTemporal.js";
28
31
  import { EFTimegroup } from "../elements/EFTimegroup.js";
29
32
  import { EFVideo } from "../elements/EFVideo.js";
30
33
  import { EFWaveform } from "../elements/EFWaveform.js";
34
+ import { TargetController } from "../elements/TargetController.js";
31
35
  import { TimegroupController } from "../elements/TimegroupController.js";
32
36
  import { msToTimeCode } from "../msToTimeCode.js";
33
- import { targetTimegroupContext } from "./ContextMixin.ts";
37
+ import { targetTemporalContext } from "./ContextMixin.ts";
34
38
  import type { EFPreview } from "./EFPreview.js";
35
39
  import type { EFWorkbench } from "./EFWorkbench.js";
36
40
  import { type FocusContext, focusContext } from "./focusContext.js";
@@ -142,9 +146,17 @@ class FilmstripItem extends TWMixin(LitElement) {
142
146
  return renderFilmstripChildren(
143
147
  Array.from(this.element.children),
144
148
  this.pixelsPerMs,
149
+ this.hideSelectors,
150
+ this.showSelectors,
145
151
  );
146
152
  }
147
153
 
154
+ @property({ type: Array, attribute: false })
155
+ hideSelectors?: string[];
156
+
157
+ @property({ type: Array, attribute: false })
158
+ showSelectors?: string[];
159
+
148
160
  contents() {
149
161
  return html``;
150
162
  }
@@ -295,7 +307,12 @@ export class EFCaptionsFilmstrip extends FilmstripItem {
295
307
 
296
308
  renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {
297
309
  // Also render normal DOM children (like ef-captions-active-word elements)
298
- return super.renderChildren();
310
+ return renderFilmstripChildren(
311
+ Array.from(this.element.children),
312
+ this.pixelsPerMs,
313
+ this.hideSelectors,
314
+ this.showSelectors,
315
+ );
299
316
  }
300
317
  }
301
318
 
@@ -545,6 +562,8 @@ export class EFTimegroupFilmstrip extends FilmstripItem {
545
562
  ${renderFilmstripChildren(
546
563
  Array.from(this.element.children || []),
547
564
  this.pixelsPerMs,
565
+ this.hideSelectors,
566
+ this.showSelectors,
548
567
  )}
549
568
  </div>
550
569
  `;
@@ -559,6 +578,8 @@ export class EFHTMLFilmstrip extends FilmstripItem {
559
578
  ${renderFilmstripChildren(
560
579
  Array.from(this.element.children || []),
561
580
  this.pixelsPerMs,
581
+ this.hideSelectors,
582
+ this.showSelectors,
562
583
  )}
563
584
  `;
564
585
  }
@@ -578,6 +599,12 @@ class EFHierarchyItem<
578
599
  @consume({ context: focusedElementContext, subscribe: true })
579
600
  focusedElement?: HTMLElement | null;
580
601
 
602
+ @property({ type: Array, attribute: false })
603
+ hideSelectors?: string[];
604
+
605
+ @property({ type: Array, attribute: false })
606
+ showSelectors?: string[];
607
+
581
608
  get icon(): TemplateResult<1> | string {
582
609
  return "📼";
583
610
  }
@@ -620,7 +647,11 @@ class EFHierarchyItem<
620
647
  }
621
648
 
622
649
  renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {
623
- return renderHierarchyChildren(Array.from(this.element.children));
650
+ return renderHierarchyChildren(
651
+ Array.from(this.element.children),
652
+ this.hideSelectors,
653
+ this.showSelectors,
654
+ );
624
655
  }
625
656
  }
626
657
 
@@ -700,50 +731,108 @@ class EFHTMLHierarchyItem extends EFHierarchyItem {
700
731
  }
701
732
  }
702
733
 
734
+ const shouldRenderElement = (
735
+ element: Element,
736
+ hideSelectors?: string[],
737
+ showSelectors?: string[],
738
+ ): boolean => {
739
+ if (element instanceof HTMLElement && element.dataset?.efHidden) {
740
+ return false;
741
+ }
742
+
743
+ // If show selectors are provided (allowlist mode), only render if matches
744
+ if (showSelectors && showSelectors.length > 0) {
745
+ return showSelectors.some((selector) => {
746
+ try {
747
+ return element.matches(selector);
748
+ } catch {
749
+ return false;
750
+ }
751
+ });
752
+ }
753
+
754
+ // If hide selectors are provided, don't render if matches
755
+ if (hideSelectors && hideSelectors.length > 0) {
756
+ return !hideSelectors.some((selector) => {
757
+ try {
758
+ return element.matches(selector);
759
+ } catch {
760
+ return false;
761
+ }
762
+ });
763
+ }
764
+
765
+ // No filters, render everything
766
+ return true;
767
+ };
768
+
703
769
  const renderHierarchyChildren = (
704
770
  children: Element[],
771
+ hideSelectors?: string[],
772
+ showSelectors?: string[],
773
+ skipRootFiltering = false,
705
774
  ): Array<TemplateResult<1> | typeof nothing> => {
706
775
  return children.map((child) => {
707
- if (child instanceof HTMLElement && child.dataset?.efHidden) {
776
+ if (
777
+ !skipRootFiltering &&
778
+ !shouldRenderElement(child, hideSelectors, showSelectors)
779
+ ) {
708
780
  return nothing;
709
781
  }
782
+
710
783
  if (child instanceof EFTimegroup) {
711
784
  return html`<ef-timegroup-hierarchy-item
712
785
  .element=${child}
786
+ .hideSelectors=${hideSelectors}
787
+ .showSelectors=${showSelectors}
713
788
  ></ef-timegroup-hierarchy-item>`;
714
789
  }
715
790
  if (child instanceof EFImage) {
716
791
  return html`<ef-image-hierarchy-item
717
792
  .element=${child}
793
+ .hideSelectors=${hideSelectors}
794
+ .showSelectors=${showSelectors}
718
795
  ></ef-image-hierarchy-item>`;
719
796
  }
720
797
  if (child instanceof EFAudio) {
721
798
  return html`<ef-audio-hierarchy-item
722
799
  .element=${child}
800
+ .hideSelectors=${hideSelectors}
801
+ .showSelectors=${showSelectors}
723
802
  ></ef-audio-hierarchy-item>`;
724
803
  }
725
804
  if (child instanceof EFVideo) {
726
805
  return html`<ef-video-hierarchy-item
727
806
  .element=${child}
807
+ .hideSelectors=${hideSelectors}
808
+ .showSelectors=${showSelectors}
728
809
  ></ef-video-hierarchy-item>`;
729
810
  }
730
811
  if (child instanceof EFCaptions) {
731
812
  return html`<ef-captions-hierarchy-item
732
813
  .element=${child}
814
+ .hideSelectors=${hideSelectors}
815
+ .showSelectors=${showSelectors}
733
816
  ></ef-captions-hierarchy-item>`;
734
817
  }
735
818
  if (child instanceof EFCaptionsActiveWord) {
736
819
  return html`<ef-captions-active-word-hierarchy-item
737
820
  .element=${child}
821
+ .hideSelectors=${hideSelectors}
822
+ .showSelectors=${showSelectors}
738
823
  ></ef-captions-active-word-hierarchy-item>`;
739
824
  }
740
825
  if (child instanceof EFWaveform) {
741
826
  return html`<ef-waveform-hierarchy-item
742
827
  .element=${child}
828
+ .hideSelectors=${hideSelectors}
829
+ .showSelectors=${showSelectors}
743
830
  ></ef-waveform-hierarchy-item>`;
744
831
  }
745
832
  return html`<ef-html-hierarchy-item
746
833
  .element=${child}
834
+ .hideSelectors=${hideSelectors}
835
+ .showSelectors=${showSelectors}
747
836
  ></ef-html-hierarchy-item>`;
748
837
  });
749
838
  };
@@ -751,15 +840,24 @@ const renderHierarchyChildren = (
751
840
  const renderFilmstripChildren = (
752
841
  children: Element[],
753
842
  pixelsPerMs: number,
843
+ hideSelectors?: string[],
844
+ showSelectors?: string[],
845
+ skipRootFiltering = false,
754
846
  ): Array<TemplateResult<1> | typeof nothing> => {
755
847
  return children.map((child) => {
756
- if (child instanceof HTMLElement && child.dataset?.efHidden) {
848
+ if (
849
+ !skipRootFiltering &&
850
+ !shouldRenderElement(child, hideSelectors, showSelectors)
851
+ ) {
757
852
  return nothing;
758
853
  }
854
+
759
855
  if (child instanceof EFTimegroup) {
760
856
  return html`<ef-timegroup-filmstrip
761
857
  .element=${child}
762
858
  .pixelsPerMs=${pixelsPerMs}
859
+ .hideSelectors=${hideSelectors}
860
+ .showSelectors=${showSelectors}
763
861
  >
764
862
  </ef-timegroup-filmstrip>`;
765
863
  }
@@ -767,59 +865,79 @@ const renderFilmstripChildren = (
767
865
  return html`<ef-image-filmstrip
768
866
  .element=${child}
769
867
  .pixelsPerMs=${pixelsPerMs}
868
+ .hideSelectors=${hideSelectors}
869
+ .showSelectors=${showSelectors}
770
870
  ></ef-image-filmstrip>`;
771
871
  }
772
872
  if (child instanceof EFAudio) {
773
873
  return html`<ef-audio-filmstrip
774
874
  .element=${child}
775
875
  .pixelsPerMs=${pixelsPerMs}
876
+ .hideSelectors=${hideSelectors}
877
+ .showSelectors=${showSelectors}
776
878
  ></ef-audio-filmstrip>`;
777
879
  }
778
880
  if (child instanceof EFVideo) {
779
881
  return html`<ef-video-filmstrip
780
882
  .element=${child}
781
883
  .pixelsPerMs=${pixelsPerMs}
884
+ .hideSelectors=${hideSelectors}
885
+ .showSelectors=${showSelectors}
782
886
  ></ef-video-filmstrip>`;
783
887
  }
784
888
  if (child instanceof EFCaptions) {
785
889
  return html`<ef-captions-filmstrip
786
890
  .element=${child}
787
891
  .pixelsPerMs=${pixelsPerMs}
892
+ .hideSelectors=${hideSelectors}
893
+ .showSelectors=${showSelectors}
788
894
  ></ef-captions-filmstrip>`;
789
895
  }
790
896
  if (child instanceof EFCaptionsActiveWord) {
791
897
  return html`<ef-captions-active-word-filmstrip
792
898
  .element=${child}
793
899
  .pixelsPerMs=${pixelsPerMs}
900
+ .hideSelectors=${hideSelectors}
901
+ .showSelectors=${showSelectors}
794
902
  ></ef-captions-active-word-filmstrip>`;
795
903
  }
796
904
  if (child.tagName === "EF-CAPTIONS-SEGMENT") {
797
905
  return html`<ef-captions-segment-filmstrip
798
906
  .element=${child}
799
907
  .pixelsPerMs=${pixelsPerMs}
908
+ .hideSelectors=${hideSelectors}
909
+ .showSelectors=${showSelectors}
800
910
  ></ef-captions-segment-filmstrip>`;
801
911
  }
802
912
  if (child.tagName === "EF-CAPTIONS-BEFORE-ACTIVE-WORD") {
803
913
  return html`<ef-captions-before-word-filmstrip
804
914
  .element=${child}
805
915
  .pixelsPerMs=${pixelsPerMs}
916
+ .hideSelectors=${hideSelectors}
917
+ .showSelectors=${showSelectors}
806
918
  ></ef-captions-before-word-filmstrip>`;
807
919
  }
808
920
  if (child.tagName === "EF-CAPTIONS-AFTER-ACTIVE-WORD") {
809
921
  return html`<ef-captions-after-word-filmstrip
810
922
  .element=${child}
811
923
  .pixelsPerMs=${pixelsPerMs}
924
+ .hideSelectors=${hideSelectors}
925
+ .showSelectors=${showSelectors}
812
926
  ></ef-captions-after-word-filmstrip>`;
813
927
  }
814
928
  if (child instanceof EFWaveform) {
815
929
  return html`<ef-waveform-filmstrip
816
930
  .element=${child}
817
931
  .pixelsPerMs=${pixelsPerMs}
932
+ .hideSelectors=${hideSelectors}
933
+ .showSelectors=${showSelectors}
818
934
  ></ef-waveform-filmstrip>`;
819
935
  }
820
936
  return html`<ef-html-filmstrip
821
937
  .element=${child}
822
938
  .pixelsPerMs=${pixelsPerMs}
939
+ .hideSelectors=${hideSelectors}
940
+ .showSelectors=${showSelectors}
823
941
  ></ef-html-filmstrip>`;
824
942
  });
825
943
  };
@@ -839,6 +957,28 @@ export class EFFilmstrip extends TWMixin(LitElement) {
839
957
  @property({ type: Number })
840
958
  pixelsPerMs = 0.04;
841
959
 
960
+ @property({ type: String })
961
+ hide = "";
962
+
963
+ @property({ type: String })
964
+ show = "";
965
+
966
+ get hideSelectors(): string[] | undefined {
967
+ if (!this.hide) return undefined;
968
+ return this.hide
969
+ .split(",")
970
+ .map((s) => s.trim())
971
+ .filter((s) => s.length > 0);
972
+ }
973
+
974
+ get showSelectors(): string[] | undefined {
975
+ if (!this.show) return undefined;
976
+ return this.show
977
+ .split(",")
978
+ .map((s) => s.trim())
979
+ .filter((s) => s.length > 0);
980
+ }
981
+
842
982
  @state()
843
983
  scrubbing = false;
844
984
 
@@ -873,6 +1013,10 @@ export class EFFilmstrip extends TWMixin(LitElement) {
873
1013
  window.addEventListener("keypress", this.#handleKeyPress);
874
1014
 
875
1015
  this.resizeObserver.observe(this);
1016
+
1017
+ if (this.target) {
1018
+ this.#targetController = new TargetController(this);
1019
+ }
876
1020
  }
877
1021
 
878
1022
  disconnectedCallback(): void {
@@ -882,7 +1026,7 @@ export class EFFilmstrip extends TWMixin(LitElement) {
882
1026
  }
883
1027
 
884
1028
  updatePixelsPerMs() {
885
- const target = this.targetTimegroup;
1029
+ const target = this.targetTemporal;
886
1030
  const gutter = this.gutterRef.value;
887
1031
  if (target && gutter && gutter.clientWidth > 0) {
888
1032
  this.pixelsPerMs = gutter.clientWidth / (target.durationMs || 1);
@@ -893,9 +1037,12 @@ export class EFFilmstrip extends TWMixin(LitElement) {
893
1037
  if (this.timegroupController) {
894
1038
  this.timegroupController.remove();
895
1039
  }
896
- const target = this.targetTimegroup;
1040
+ const target = this.targetTemporal;
897
1041
  if (target) {
898
- this.timegroupController = new TimegroupController(target, this);
1042
+ this.timegroupController = new TimegroupController(
1043
+ target as EFTimegroup,
1044
+ this,
1045
+ );
899
1046
  // Set the current time to the last saved time to avoid a cycle
900
1047
  // where the filmstrip clobbers the time loaded from localStorage
901
1048
  this.currentTimeMs = target.currentTimeMs;
@@ -975,16 +1122,16 @@ export class EFFilmstrip extends TWMixin(LitElement) {
975
1122
  return;
976
1123
  }
977
1124
  const rect = gutter.getBoundingClientRect();
978
- if (this.targetTimegroup) {
1125
+ if (this.targetTemporal) {
979
1126
  const layerX = e.pageX - rect.left + gutter.scrollLeft;
980
1127
  const scrubTimeMs = layerX / this.pixelsPerMs;
981
- this.targetTimegroup.currentTimeMs = scrubTimeMs;
1128
+ this.targetTemporal.currentTimeMs = scrubTimeMs;
982
1129
  }
983
1130
  }
984
1131
 
985
1132
  @eventOptions({ passive: false })
986
1133
  scrollScrub(e: WheelEvent) {
987
- if (this.targetTimegroup && this.gutter && !this.playing) {
1134
+ if (this.targetTemporal && this.gutter && !this.playing) {
988
1135
  if (e.deltaX !== 0) {
989
1136
  e.preventDefault(); // Prevent default side scroll behavior only
990
1137
  }
@@ -1010,7 +1157,7 @@ export class EFFilmstrip extends TWMixin(LitElement) {
1010
1157
 
1011
1158
  if (this) {
1012
1159
  this.gutter.scrollBy(e.deltaX, e.deltaY);
1013
- this.targetTimegroup.currentTimeMs += e.deltaX / this.pixelsPerMs;
1160
+ this.targetTemporal.currentTimeMs += e.deltaX / this.pixelsPerMs;
1014
1161
  }
1015
1162
  }
1016
1163
  }
@@ -1024,7 +1171,7 @@ export class EFFilmstrip extends TWMixin(LitElement) {
1024
1171
  }
1025
1172
 
1026
1173
  render() {
1027
- const target = this.targetTimegroup;
1174
+ const target = this.targetTemporal;
1028
1175
 
1029
1176
  return html` <div
1030
1177
  class="grid h-full bg-slate-100"
@@ -1068,7 +1215,12 @@ export class EFFilmstrip extends TWMixin(LitElement) {
1068
1215
  ${ref(this.hierarchyRef)}
1069
1216
  @scroll=${this.syncHierarchyScroll}
1070
1217
  >
1071
- ${renderHierarchyChildren(target ? [target] : [])}
1218
+ ${renderHierarchyChildren(
1219
+ target ? ([target] as unknown as Element[]) : [],
1220
+ this.hideSelectors,
1221
+ this.showSelectors,
1222
+ true,
1223
+ )}
1072
1224
  </div>
1073
1225
  <div
1074
1226
  class="flex h-full w-full cursor-crosshair overflow-auto bg-slate-200 pt-[8px]"
@@ -1092,19 +1244,25 @@ export class EFFilmstrip extends TWMixin(LitElement) {
1092
1244
  ${ref(this.playheadRef)}
1093
1245
  ></div>
1094
1246
 
1095
- ${renderFilmstripChildren(target ? [target] : [], this.pixelsPerMs)}
1247
+ ${renderFilmstripChildren(
1248
+ target ? ([target] as unknown as Element[]) : [],
1249
+ this.pixelsPerMs,
1250
+ this.hideSelectors,
1251
+ this.showSelectors,
1252
+ true,
1253
+ )}
1096
1254
  </div>
1097
1255
  </div>
1098
1256
  </div>`;
1099
1257
  }
1100
1258
 
1101
1259
  updated(changes: PropertyValueMap<any> | Map<PropertyKey, unknown>) {
1102
- if (!this.targetTimegroup) {
1260
+ if (!this.targetTemporal) {
1103
1261
  return;
1104
1262
  }
1105
1263
  if (changes.has("currentTimeMs")) {
1106
- if (this.targetTimegroup.currentTimeMs !== this.currentTimeMs) {
1107
- this.targetTimegroup.currentTimeMs = this.currentTimeMs;
1264
+ if (this.targetTemporal.currentTimeMs !== this.currentTimeMs) {
1265
+ this.targetTemporal.currentTimeMs = this.currentTimeMs;
1108
1266
  }
1109
1267
  }
1110
1268
  }
@@ -1114,18 +1272,50 @@ export class EFFilmstrip extends TWMixin(LitElement) {
1114
1272
  }
1115
1273
 
1116
1274
  @property({ type: String })
1117
- target?: string;
1275
+ target = "";
1276
+
1277
+ @state()
1278
+ targetElement: Element | null = null;
1118
1279
 
1119
- @consume({ context: targetTimegroupContext, subscribe: true })
1280
+ #targetController?: TargetController;
1281
+ #lastTargetTemporal?: TemporalMixinInterface | null;
1282
+
1283
+ @consume({ context: targetTemporalContext, subscribe: true })
1120
1284
  @state()
1121
- targetTimegroup?: EFTimegroup | null;
1285
+ private _contextProvidedTemporal?: TemporalMixinInterface | null;
1286
+
1287
+ get targetTemporal(): TemporalMixinInterface | null {
1288
+ const fromTarget =
1289
+ this.targetElement && isEFTemporal(this.targetElement)
1290
+ ? (this.targetElement as TemporalMixinInterface & HTMLElement)
1291
+ : null;
1292
+ const fromContext = this._contextProvidedTemporal;
1293
+
1294
+ if (fromTarget && fromContext && fromTarget !== fromContext) {
1295
+ console.warn(
1296
+ "EFFilmstrip: Both target attribute and parent context found. Using target attribute.",
1297
+ { target: this.target, fromTarget, fromContext },
1298
+ );
1299
+ }
1300
+
1301
+ return fromTarget ?? fromContext ?? null;
1302
+ }
1122
1303
 
1123
1304
  protected willUpdate(
1124
1305
  changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
1125
1306
  ) {
1126
- if (changedProperties.has("targetTimegroup")) {
1307
+ if (changedProperties.has("target")) {
1308
+ if (this.target && !this.#targetController) {
1309
+ this.#targetController = new TargetController(this);
1310
+ }
1311
+ }
1312
+
1313
+ const currentTargetTemporal = this.targetTemporal;
1314
+ if (this.#lastTargetTemporal !== currentTargetTemporal) {
1127
1315
  this.#bindToTargetTimegroup();
1316
+ this.#lastTargetTemporal = currentTargetTemporal;
1128
1317
  }
1318
+
1129
1319
  if (this.autoScale) {
1130
1320
  this.updatePixelsPerMs();
1131
1321
  }
@@ -0,0 +1,202 @@
1
+ import { html, render } from "lit";
2
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
3
+
4
+ import "../elements/EFTimegroup.js";
5
+ import "../elements/EFVideo.js";
6
+ import "./EFConfiguration.js";
7
+ import "./EFPause.js";
8
+ import "./EFPreview.js";
9
+
10
+ describe("EFPause", () => {
11
+ beforeEach(() => {
12
+ while (document.body.children.length) {
13
+ document.body.children[0]?.remove();
14
+ }
15
+ });
16
+
17
+ afterEach(() => {
18
+ const elements = document.querySelectorAll("ef-pause");
19
+ for (const element of elements) {
20
+ element.remove();
21
+ }
22
+ });
23
+
24
+ test("should be defined", () => {
25
+ const element = document.createElement("ef-pause");
26
+ expect(element).toBeDefined();
27
+ expect(element.tagName).toBe("EF-PAUSE");
28
+ });
29
+
30
+ test("should be hidden when not playing", async () => {
31
+ const container = document.createElement("div");
32
+ render(
33
+ html`
34
+ <ef-configuration api-host="http://localhost:63315" signing-url="">
35
+ <ef-preview id="test-preview">
36
+ <ef-video src="bars-n-tone.mp4"></ef-video>
37
+ <ef-pause>Pause Button</ef-pause>
38
+ </ef-preview>
39
+ </ef-configuration>
40
+ `,
41
+ container,
42
+ );
43
+ document.body.appendChild(container);
44
+
45
+ const pause = container.querySelector("ef-pause") as any;
46
+ const preview = container.querySelector("ef-preview") as any;
47
+ const video = container.querySelector("ef-video") as any;
48
+
49
+ await pause.updateComplete;
50
+ await preview.updateComplete;
51
+ await video.updateComplete;
52
+
53
+ await video.mediaEngineTask.run();
54
+ await new Promise((resolve) => setTimeout(resolve, 100));
55
+ await pause.updateComplete;
56
+
57
+ expect(pause.playing).toBe(false);
58
+ expect(getComputedStyle(pause).display).toBe("none");
59
+
60
+ container.remove();
61
+ });
62
+
63
+ test("should be visible when playing", async () => {
64
+ const container = document.createElement("div");
65
+ render(
66
+ html`
67
+ <ef-configuration api-host="http://localhost:63315" signing-url="">
68
+ <ef-preview id="test-preview">
69
+ <ef-video src="bars-n-tone.mp4"></ef-video>
70
+ <ef-pause>Pause Button</ef-pause>
71
+ </ef-preview>
72
+ </ef-configuration>
73
+ `,
74
+ container,
75
+ );
76
+ document.body.appendChild(container);
77
+
78
+ const pause = container.querySelector("ef-pause") as any;
79
+ const preview = container.querySelector("ef-preview") as any;
80
+ const video = container.querySelector("ef-video") as any;
81
+
82
+ await pause.updateComplete;
83
+ await preview.updateComplete;
84
+ await video.updateComplete;
85
+
86
+ await video.mediaEngineTask.run();
87
+ await new Promise((resolve) => setTimeout(resolve, 100));
88
+ await pause.updateComplete;
89
+
90
+ expect(getComputedStyle(pause).display).toBe("none");
91
+
92
+ // Manually trigger playing state change to test visibility mechanism
93
+ pause.playing = true;
94
+ await pause.updateComplete;
95
+
96
+ expect(getComputedStyle(pause).display).not.toBe("none");
97
+
98
+ container.remove();
99
+ });
100
+
101
+ test("should call pause() when clicked", async () => {
102
+ const container = document.createElement("div");
103
+ render(
104
+ html`
105
+ <ef-configuration api-host="http://localhost:63315" signing-url="">
106
+ <ef-preview id="test-preview">
107
+ <ef-video src="bars-n-tone.mp4"></ef-video>
108
+ <ef-pause>Pause Button</ef-pause>
109
+ </ef-preview>
110
+ </ef-configuration>
111
+ `,
112
+ container,
113
+ );
114
+ document.body.appendChild(container);
115
+
116
+ const pause = container.querySelector("ef-pause") as any;
117
+ const preview = container.querySelector("ef-preview") as any;
118
+ const video = container.querySelector("ef-video") as any;
119
+
120
+ await pause.updateComplete;
121
+ await preview.updateComplete;
122
+ await video.updateComplete;
123
+
124
+ await video.mediaEngineTask.run();
125
+ await new Promise((resolve) => setTimeout(resolve, 100));
126
+ await pause.updateComplete;
127
+
128
+ const pauseSpy = vi.spyOn(video.playbackController, "pause");
129
+
130
+ pause.click();
131
+
132
+ expect(pauseSpy).toHaveBeenCalledTimes(1);
133
+
134
+ pauseSpy.mockRestore();
135
+ container.remove();
136
+ }, 1000);
137
+
138
+ test("should pass through children with default slot", async () => {
139
+ const container = document.createElement("div");
140
+ render(
141
+ html`
142
+ <ef-configuration api-host="http://localhost:63315" signing-url="">
143
+ <ef-preview id="test-preview">
144
+ <ef-video src="bars-n-tone.mp4"></ef-video>
145
+ <ef-pause><span id="pause-content">⏸ Pause</span></ef-pause>
146
+ </ef-preview>
147
+ </ef-configuration>
148
+ `,
149
+ container,
150
+ );
151
+ document.body.appendChild(container);
152
+
153
+ const pause = container.querySelector("ef-pause") as any;
154
+ const content = container.querySelector("#pause-content");
155
+
156
+ await pause.updateComplete;
157
+
158
+ expect(content).toBeDefined();
159
+ expect(content?.textContent).toBe("⏸ Pause");
160
+
161
+ container.remove();
162
+ });
163
+
164
+ test("should work with target attribute", async () => {
165
+ const container = document.createElement("div");
166
+ render(
167
+ html`
168
+ <ef-configuration api-host="http://localhost:63315" signing-url="">
169
+ <ef-preview id="test-preview">
170
+ <ef-video src="bars-n-tone.mp4"></ef-video>
171
+ </ef-preview>
172
+ <ef-pause target="test-preview">Pause Button</ef-pause>
173
+ </ef-configuration>
174
+ `,
175
+ container,
176
+ );
177
+ document.body.appendChild(container);
178
+
179
+ const pause = container.querySelector("ef-pause") as any;
180
+ const preview = container.querySelector("ef-preview") as any;
181
+ const video = container.querySelector("ef-video") as any;
182
+
183
+ await pause.updateComplete;
184
+ await preview.updateComplete;
185
+ await video.updateComplete;
186
+
187
+ await video.mediaEngineTask.run();
188
+ await new Promise((resolve) => setTimeout(resolve, 100));
189
+ await pause.updateComplete;
190
+
191
+ expect(pause.efContext).toBe(preview);
192
+
193
+ const pauseSpy = vi.spyOn(video.playbackController, "pause");
194
+
195
+ pause.click();
196
+
197
+ expect(pauseSpy).toHaveBeenCalledTimes(1);
198
+
199
+ pauseSpy.mockRestore();
200
+ container.remove();
201
+ }, 1000);
202
+ });