@editframe/elements 0.45.2 → 0.45.3
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.
- package/dist/DelayedLoadingState.js.map +1 -1
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_RENDERING.js.map +1 -1
- package/dist/canvas/EFCanvas.js +3 -3
- package/dist/canvas/EFCanvas.js.map +1 -1
- package/dist/canvas/EFCanvasItem.js.map +1 -1
- package/dist/canvas/api/CanvasAPI.js.map +1 -1
- package/dist/canvas/getElementBounds.js.map +1 -1
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
- package/dist/canvas/overlays/overlayState.js.map +1 -1
- package/dist/canvas/selection/SelectionController.js +25 -23
- package/dist/canvas/selection/SelectionController.js.map +1 -1
- package/dist/canvas/selection/SelectionModel.js.map +1 -1
- package/dist/canvas/selection/selectionContext.js.map +1 -1
- package/dist/elements/ContainerInfo.js.map +1 -1
- package/dist/elements/CrossUpdateController.js.map +1 -1
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.js +1 -1
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
- package/dist/elements/EFMedia/CachedFetcher.js.map +1 -1
- package/dist/elements/EFMedia/MediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/SegmentIndex.js.map +1 -1
- package/dist/elements/EFMedia/SegmentTransport.js.map +1 -1
- package/dist/elements/EFMedia/TimingModel.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/GlobalInputCache.js.map +1 -1
- package/dist/elements/EFMedia/shared/PrecisionUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.js +9 -8
- package/dist/elements/EFPanZoom.js.map +1 -1
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +4 -4
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.d.ts +4 -4
- package/dist/elements/EFTimegroup.js +7 -8
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +4 -4
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +4 -4
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.js.map +1 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/SampleBuffer.js.map +1 -1
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/cloneFactoryRegistry.js.map +1 -1
- package/dist/elements/durationConverter.js.map +1 -1
- package/dist/elements/easingUtils.js.map +1 -1
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/setupTemporalHierarchy.js.map +1 -1
- package/dist/elements/updateAnimations.js +1 -1
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.js.map +1 -1
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +4 -4
- package/dist/gui/EFActiveRootTemporal.js.map +1 -1
- package/dist/gui/EFConfiguration.d.ts +4 -4
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFFilmstrip.d.ts +4 -4
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFOverlayItem.d.ts +4 -4
- package/dist/gui/EFOverlayItem.js.map +1 -1
- package/dist/gui/EFOverlayLayer.d.ts +4 -4
- package/dist/gui/EFOverlayLayer.js.map +1 -1
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFPreview.d.ts +4 -4
- package/dist/gui/EFPreview.js.map +1 -1
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +4 -4
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +4 -4
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +4 -4
- package/dist/gui/EFTimelineRuler.js.map +1 -1
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFTogglePlay.js.map +1 -1
- package/dist/gui/EFTransformHandles.js.map +1 -1
- package/dist/gui/EFWorkbench.d.ts +4 -4
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.js.map +1 -1
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/TWMixin2.js.map +1 -1
- package/dist/gui/TargetOrContextMixin.js.map +1 -1
- package/dist/gui/currentTimeContext.js.map +1 -1
- package/dist/gui/efContext.js.map +1 -1
- package/dist/gui/fetchContext.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchy.d.ts +4 -4
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +2 -2
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -1
- package/dist/gui/hierarchy/hierarchyContext.js.map +1 -1
- package/dist/gui/panZoomTransformContext.js.map +1 -1
- package/dist/gui/previewSettingsContext.js.map +1 -1
- package/dist/gui/theme.js.map +1 -1
- package/dist/gui/timeline/EFTimeline.d.ts +2 -2
- package/dist/gui/timeline/EFTimeline.js +0 -1
- package/dist/gui/timeline/EFTimeline.js.map +1 -1
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -1
- package/dist/gui/timeline/TrimHandles.d.ts +4 -4
- package/dist/gui/timeline/TrimHandles.js.map +1 -1
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -1
- package/dist/gui/timeline/timelineStateContext.js.map +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -1
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -1
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
- package/dist/gui/transformCalculations.js.map +1 -1
- package/dist/gui/transformUtils.js.map +1 -1
- package/dist/gui/tree/EFTree.d.ts +4 -4
- package/dist/gui/tree/EFTree.js.map +1 -1
- package/dist/gui/tree/EFTreeItem.d.ts +4 -4
- package/dist/gui/tree/EFTreeItem.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/otel/BridgeSpanExporter.js.map +1 -1
- package/dist/otel/setupBrowserTracing.js.map +1 -1
- package/dist/otel/tracingHelpers.js.map +1 -1
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
- package/dist/preview/FrameController.js.map +1 -1
- package/dist/preview/QualityUpgradeScheduler.js.map +1 -1
- package/dist/preview/RenderContext.js.map +1 -1
- package/dist/preview/RenderProfiler.js.map +1 -1
- package/dist/preview/RenderStats.js.map +1 -1
- package/dist/preview/encoding/canvasEncoder.js.map +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
- package/dist/preview/previewSettings.js.map +1 -1
- package/dist/preview/previewTypes.js.map +1 -1
- package/dist/preview/renderElementToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.js +2 -44
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToVideo.js +2 -2
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/renderVideoToVideo.js +2 -2
- package/dist/preview/renderVideoToVideo.js.map +1 -1
- package/dist/preview/renderers.js.map +1 -1
- package/dist/preview/rendering/ScaleConfig.js.map +1 -1
- package/dist/preview/rendering/loadImage.js.map +1 -1
- package/dist/preview/rendering/renderToImageNative.js.map +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
- package/dist/preview/statsTrackingStrategy.js.map +1 -1
- package/dist/preview/workers/WorkerPool.js.map +1 -1
- package/dist/render/EFRenderAPI.js.map +1 -1
- package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
- package/dist/utils/LRUCache.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PanZoomTransform } from "../elements/EFPanZoom.js";
|
|
2
2
|
import { EFOverlayItem } from "./EFOverlayItem.js";
|
|
3
|
-
import * as
|
|
3
|
+
import * as lit31 from "lit";
|
|
4
4
|
import { LitElement } from "lit";
|
|
5
|
-
import * as
|
|
5
|
+
import * as lit_html29 from "lit-html";
|
|
6
6
|
|
|
7
7
|
//#region src/gui/EFOverlayLayer.d.ts
|
|
8
8
|
|
|
@@ -26,7 +26,7 @@ import * as lit_html30 from "lit-html";
|
|
|
26
26
|
* 2. EFOverlayItem can use this rect for coordinate calculations
|
|
27
27
|
*/
|
|
28
28
|
declare class EFOverlayLayer extends LitElement {
|
|
29
|
-
static styles:
|
|
29
|
+
static styles: lit31.CSSResult[];
|
|
30
30
|
panZoomTransformFromContext?: PanZoomTransform;
|
|
31
31
|
/**
|
|
32
32
|
* Pan/zoom transform as fallback for when context or sibling PanZoom is not available.
|
|
@@ -58,7 +58,7 @@ declare class EFOverlayLayer extends LitElement {
|
|
|
58
58
|
connectedCallback(): void;
|
|
59
59
|
disconnectedCallback(): void;
|
|
60
60
|
updated(): void;
|
|
61
|
-
render():
|
|
61
|
+
render(): lit_html29.TemplateResult<1>;
|
|
62
62
|
}
|
|
63
63
|
declare global {
|
|
64
64
|
interface HTMLElementTagNameMap {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFOverlayLayer.js","names":["EFOverlayLayer"],"sources":["../../src/gui/EFOverlayLayer.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { panZoomTransformContext } from \"./panZoomTransformContext.js\";\nimport type { PanZoomTransform } from \"../elements/EFPanZoom.js\";\nimport type { EFOverlayItem } from \"./EFOverlayItem.js\";\n\n/**\n * Overlay layer container component.\n *\n * SIMPLE ARCHITECTURE:\n * - Runs a SINGLE RAF loop that updates everything every frame\n * - No change detection, no optimization, no delays\n * - Just: read transform → apply it → update all items → next frame\n *\n * Core responsibilities:\n * - Consumes PanZoom transform from context (if PanZoom is ancestor)\n * - Accepts transform via props as fallback (for sibling PanZoom)\n * - Applies translate-only transform (no scale) directly to host element\n * - Manages registered child EFOverlayItem components\n * - Updates all overlays every animation frame\n *\n * The transform is applied directly to the host element so that:\n * 1. getBoundingClientRect() returns the transformed position\n * 2. EFOverlayItem can use this rect for coordinate calculations\n */\n@customElement(\"ef-overlay-layer\")\nexport class EFOverlayLayer extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n position: absolute;\n inset: 0;\n pointer-events: none;\n transform-origin: 0 0;\n }\n `,\n ];\n\n @consume({ context: panZoomTransformContext, subscribe: true })\n panZoomTransformFromContext?: PanZoomTransform;\n\n /**\n * Pan/zoom transform as fallback for when context or sibling PanZoom is not available.\n * Primarily used for testing and standalone usage.\n */\n @property({ type: Object })\n panZoomTransform?: PanZoomTransform;\n\n // Track registered overlay items for coordinated updates\n private registeredItems = new Set<EFOverlayItem>();\n private rafId: number | null = null;\n\n /**\n * Register an overlay item for coordinated updates.\n * Called by EFOverlayItem in connectedCallback.\n */\n registerOverlayItem(item: EFOverlayItem) {\n this.registeredItems.add(item);\n }\n\n /**\n * Unregister an overlay item.\n * Called by EFOverlayItem in disconnectedCallback.\n */\n unregisterOverlayItem(item: EFOverlayItem) {\n this.registeredItems.delete(item);\n }\n\n /**\n * Single source of truth for reading the current transform.\n * Priority: context > sibling DOM read > prop > default\n */\n private readTransform(): PanZoomTransform {\n // 1. Context (synchronous, preferred)\n if (this.panZoomTransformFromContext) {\n return this.panZoomTransformFromContext;\n }\n\n // 2. Read directly from sibling PanZoom element\n const panZoomElement = this.parentElement?.querySelector(\
|
|
1
|
+
{"version":3,"file":"EFOverlayLayer.js","names":["EFOverlayLayer"],"sources":["../../src/gui/EFOverlayLayer.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { panZoomTransformContext } from \"./panZoomTransformContext.js\";\nimport type { PanZoomTransform } from \"../elements/EFPanZoom.js\";\nimport type { EFOverlayItem } from \"./EFOverlayItem.js\";\n\n/**\n * Overlay layer container component.\n *\n * SIMPLE ARCHITECTURE:\n * - Runs a SINGLE RAF loop that updates everything every frame\n * - No change detection, no optimization, no delays\n * - Just: read transform → apply it → update all items → next frame\n *\n * Core responsibilities:\n * - Consumes PanZoom transform from context (if PanZoom is ancestor)\n * - Accepts transform via props as fallback (for sibling PanZoom)\n * - Applies translate-only transform (no scale) directly to host element\n * - Manages registered child EFOverlayItem components\n * - Updates all overlays every animation frame\n *\n * The transform is applied directly to the host element so that:\n * 1. getBoundingClientRect() returns the transformed position\n * 2. EFOverlayItem can use this rect for coordinate calculations\n */\n@customElement(\"ef-overlay-layer\")\nexport class EFOverlayLayer extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n position: absolute;\n inset: 0;\n pointer-events: none;\n transform-origin: 0 0;\n }\n `,\n ];\n\n @consume({ context: panZoomTransformContext, subscribe: true })\n panZoomTransformFromContext?: PanZoomTransform;\n\n /**\n * Pan/zoom transform as fallback for when context or sibling PanZoom is not available.\n * Primarily used for testing and standalone usage.\n */\n @property({ type: Object })\n panZoomTransform?: PanZoomTransform;\n\n // Track registered overlay items for coordinated updates\n private registeredItems = new Set<EFOverlayItem>();\n private rafId: number | null = null;\n\n /**\n * Register an overlay item for coordinated updates.\n * Called by EFOverlayItem in connectedCallback.\n */\n registerOverlayItem(item: EFOverlayItem) {\n this.registeredItems.add(item);\n }\n\n /**\n * Unregister an overlay item.\n * Called by EFOverlayItem in disconnectedCallback.\n */\n unregisterOverlayItem(item: EFOverlayItem) {\n this.registeredItems.delete(item);\n }\n\n /**\n * Single source of truth for reading the current transform.\n * Priority: context > sibling DOM read > prop > default\n */\n private readTransform(): PanZoomTransform {\n // 1. Context (synchronous, preferred)\n if (this.panZoomTransformFromContext) {\n return this.panZoomTransformFromContext;\n }\n\n // 2. Read directly from sibling PanZoom element\n const panZoomElement = this.parentElement?.querySelector(\"ef-pan-zoom\") as any;\n if (panZoomElement && typeof panZoomElement.x === \"number\") {\n const contentWrapper = panZoomElement.shadowRoot?.querySelector(\".content-wrapper\");\n const computedTransform = contentWrapper && window.getComputedStyle(contentWrapper).transform;\n\n // Parse scale from matrix(scaleX, skewY, skewX, scaleY, tx, ty)\n const matrixMatch = computedTransform?.match(/matrix\\(([^)]+)\\)/);\n const scale = matrixMatch\n ? parseFloat(matrixMatch[1].split(\",\")[0].trim())\n : (panZoomElement.scale ?? 1);\n\n return { x: panZoomElement.x ?? 0, y: panZoomElement.y ?? 0, scale };\n }\n\n // 3. Prop (for testing)\n if (this.panZoomTransform) {\n return this.panZoomTransform;\n }\n\n // 4. Default\n return { x: 0, y: 0, scale: 1 };\n }\n\n /**\n * Simple RAF loop: Update everything on every frame.\n */\n private startLoop() {\n const update = () => {\n const transform = this.readTransform();\n\n // Apply transform\n // If we're a child of panzoom (receiving context), we're inside the scaled content-wrapper\n // which already applies translate(x, y) scale(s). We should NOT apply our own translate\n // because the parent transform already handles the pan. Our getBoundingClientRect() will\n // naturally include the parent's transform.\n // If we're a sibling of panzoom, we need to apply the translate ourselves to match the pan.\n if (this.panZoomTransformFromContext) {\n // Child of panzoom - don't apply any transform, parent handles it\n this.style.transform = \"none\";\n } else {\n // Sibling of panzoom - apply translate directly to match panzoom's pan\n this.style.transform = `translate(${transform.x}px, ${transform.y}px)`;\n }\n\n // Update all overlay items\n for (const item of this.registeredItems) {\n item.updatePosition();\n }\n\n // Schedule next frame\n this.rafId = requestAnimationFrame(update);\n };\n\n this.rafId = requestAnimationFrame(update);\n }\n\n private stopLoop() {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n connectedCallback() {\n super.connectedCallback();\n this.startLoop();\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.stopLoop();\n }\n\n updated() {\n // Transform changes are handled by RAF loop\n }\n\n render() {\n // Simple slot - transform is applied to host element, not a wrapper\n return html`<slot></slot>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-overlay-layer\": EFOverlayLayer;\n }\n}\n"],"mappings":";;;;;;;AA2BO,2BAAMA,yBAAuB,WAAW;;;yCAwBnB,IAAI,KAAoB;eACnB;;;gBAxBf,CACd,GAAG;;;;;;;;MASJ;;;;;;CAoBD,oBAAoB,MAAqB;AACvC,OAAK,gBAAgB,IAAI,KAAK;;;;;;CAOhC,sBAAsB,MAAqB;AACzC,OAAK,gBAAgB,OAAO,KAAK;;;;;;CAOnC,AAAQ,gBAAkC;AAExC,MAAI,KAAK,4BACP,QAAO,KAAK;EAId,MAAM,iBAAiB,KAAK,eAAe,cAAc,cAAc;AACvE,MAAI,kBAAkB,OAAO,eAAe,MAAM,UAAU;GAC1D,MAAM,iBAAiB,eAAe,YAAY,cAAc,mBAAmB;GAInF,MAAM,eAHoB,kBAAkB,OAAO,iBAAiB,eAAe,CAAC,YAG7C,MAAM,oBAAoB;GACjE,MAAM,QAAQ,cACV,WAAW,YAAY,GAAG,MAAM,IAAI,CAAC,GAAG,MAAM,CAAC,GAC9C,eAAe,SAAS;AAE7B,UAAO;IAAE,GAAG,eAAe,KAAK;IAAG,GAAG,eAAe,KAAK;IAAG;IAAO;;AAItE,MAAI,KAAK,iBACP,QAAO,KAAK;AAId,SAAO;GAAE,GAAG;GAAG,GAAG;GAAG,OAAO;GAAG;;;;;CAMjC,AAAQ,YAAY;EAClB,MAAM,eAAe;GACnB,MAAM,YAAY,KAAK,eAAe;AAQtC,OAAI,KAAK,4BAEP,MAAK,MAAM,YAAY;OAGvB,MAAK,MAAM,YAAY,aAAa,UAAU,EAAE,MAAM,UAAU,EAAE;AAIpE,QAAK,MAAM,QAAQ,KAAK,gBACtB,MAAK,gBAAgB;AAIvB,QAAK,QAAQ,sBAAsB,OAAO;;AAG5C,OAAK,QAAQ,sBAAsB,OAAO;;CAG5C,AAAQ,WAAW;AACjB,MAAI,KAAK,UAAU,MAAM;AACvB,wBAAqB,KAAK,MAAM;AAChC,QAAK,QAAQ;;;CAIjB,oBAAoB;AAClB,QAAM,mBAAmB;AACzB,OAAK,WAAW;;CAGlB,uBAAuB;AACrB,QAAM,sBAAsB;AAC5B,OAAK,UAAU;;CAGjB,UAAU;CAIV,SAAS;AAEP,SAAO,IAAI;;;YAxHZ,QAAQ;CAAE,SAAS;CAAyB,WAAW;CAAM,CAAC;YAO9D,SAAS,EAAE,MAAM,QAAQ,CAAC;6BArB5B,cAAc,mBAAmB"}
|
package/dist/gui/EFPause.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit17 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html16 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFPause.d.ts
|
|
7
7
|
declare const EFPause_base: (new (...args: any[]) => {
|
|
@@ -10,13 +10,13 @@ declare const EFPause_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFPause extends EFPause_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit17.CSSResult[];
|
|
14
14
|
playing: boolean;
|
|
15
15
|
get efContext(): ControllableInterface | null;
|
|
16
16
|
connectedCallback(): void;
|
|
17
17
|
disconnectedCallback(): void;
|
|
18
18
|
updated(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
19
|
-
render():
|
|
19
|
+
render(): lit_html16.TemplateResult<1>;
|
|
20
20
|
handleClick: () => void;
|
|
21
21
|
}
|
|
22
22
|
declare global {
|
package/dist/gui/EFPlay.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit16 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html15 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFPlay.d.ts
|
|
7
7
|
declare const EFPlay_base: (new (...args: any[]) => {
|
|
@@ -10,13 +10,13 @@ declare const EFPlay_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFPlay extends EFPlay_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit16.CSSResult[];
|
|
14
14
|
playing: boolean;
|
|
15
15
|
get efContext(): ControllableInterface | null;
|
|
16
16
|
connectedCallback(): void;
|
|
17
17
|
disconnectedCallback(): void;
|
|
18
18
|
updated(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
19
|
-
render():
|
|
19
|
+
render(): lit_html15.TemplateResult<1>;
|
|
20
20
|
handleClick: () => void;
|
|
21
21
|
}
|
|
22
22
|
declare global {
|
package/dist/gui/EFPreview.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { ContextMixinInterface } from "./ContextMixin.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit9 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html9 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFPreview.d.ts
|
|
7
7
|
declare const EFPreview_base: (new (...args: any[]) => ContextMixinInterface) & typeof LitElement;
|
|
8
8
|
declare class EFPreview extends EFPreview_base {
|
|
9
|
-
static styles:
|
|
9
|
+
static styles: lit9.CSSResult[];
|
|
10
10
|
focusedElement?: HTMLElement;
|
|
11
11
|
/**
|
|
12
12
|
* Find the closest temporal element (timegroup, video, audio, etc.)
|
|
13
13
|
*/
|
|
14
14
|
private findClosestTemporal;
|
|
15
15
|
constructor();
|
|
16
|
-
render():
|
|
16
|
+
render(): lit_html9.TemplateResult<1>;
|
|
17
17
|
}
|
|
18
18
|
declare global {
|
|
19
19
|
interface HTMLElementTagNameMap {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFPreview.js","names":["EFPreview"],"sources":["../../src/gui/EFPreview.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { isEFTemporal } from \"../elements/EFTemporal.js\";\nimport { EFTargetable } from \"../elements/TargetController.js\";\nimport { ContextMixin } from \"./ContextMixin.js\";\nimport { focusedElementContext } from \"./focusedElementContext.js\";\nimport { TWMixin } from \"./TWMixin.js\";\n\n@customElement(\"ef-preview\")\nexport class EFPreview extends EFTargetable(ContextMixin(TWMixin(LitElement))) {\n static styles = [\n css`\n :host {\n position: relative;\n display: block;\n cursor: crosshair;\n }\n `,\n ];\n\n @provide({ context: focusedElementContext })\n focusedElement?: HTMLElement;\n\n /**\n * Find the closest temporal element (timegroup, video, audio, etc.)\n */\n private findClosestTemporal(element: HTMLElement | null): HTMLElement | null {\n let current = element;\n while (current && current !== this) {\n if (isEFTemporal(current)) {\n return current;\n }\n current = current.parentElement;\n }\n return null;\n }\n\n constructor() {\n super();\n this.addEventListener(\"pointerover\", (e) => {\n const target = e.target as HTMLElement;\n const temporal = this.findClosestTemporal(target);\n if (target !== this && temporal) {\n this.focusedElement = target;\n }\n });\n this.addEventListener(\"pointerout\", (e) => {\n const relatedTarget = e.relatedTarget as HTMLElement;\n const targetingTemporal = this.findClosestTemporal(relatedTarget);\n // Clear focus if:\n // 1. Moving outside the preview entirely, or\n // 2. Moving to the preview itself, or\n // 3. Moving to an element that's not within a temporal\n if (
|
|
1
|
+
{"version":3,"file":"EFPreview.js","names":["EFPreview"],"sources":["../../src/gui/EFPreview.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { isEFTemporal } from \"../elements/EFTemporal.js\";\nimport { EFTargetable } from \"../elements/TargetController.js\";\nimport { ContextMixin } from \"./ContextMixin.js\";\nimport { focusedElementContext } from \"./focusedElementContext.js\";\nimport { TWMixin } from \"./TWMixin.js\";\n\n@customElement(\"ef-preview\")\nexport class EFPreview extends EFTargetable(ContextMixin(TWMixin(LitElement))) {\n static styles = [\n css`\n :host {\n position: relative;\n display: block;\n cursor: crosshair;\n }\n `,\n ];\n\n @provide({ context: focusedElementContext })\n focusedElement?: HTMLElement;\n\n /**\n * Find the closest temporal element (timegroup, video, audio, etc.)\n */\n private findClosestTemporal(element: HTMLElement | null): HTMLElement | null {\n let current = element;\n while (current && current !== this) {\n if (isEFTemporal(current)) {\n return current;\n }\n current = current.parentElement;\n }\n return null;\n }\n\n constructor() {\n super();\n this.addEventListener(\"pointerover\", (e) => {\n const target = e.target as HTMLElement;\n const temporal = this.findClosestTemporal(target);\n if (target !== this && temporal) {\n this.focusedElement = target;\n }\n });\n this.addEventListener(\"pointerout\", (e) => {\n const relatedTarget = e.relatedTarget as HTMLElement;\n const targetingTemporal = this.findClosestTemporal(relatedTarget);\n // Clear focus if:\n // 1. Moving outside the preview entirely, or\n // 2. Moving to the preview itself, or\n // 3. Moving to an element that's not within a temporal\n if (!this.contains(relatedTarget) || relatedTarget === this || !targetingTemporal) {\n this.focusedElement = undefined;\n }\n });\n }\n\n render() {\n return html`<slot></slot>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-preview\": EFPreview;\n }\n}\n"],"mappings":";;;;;;;;;;;AAUO,sBAAMA,oBAAkB,aAAa,aAAa,QAAQ,WAAW,CAAC,CAAC,CAAC;;gBAC7D,CACd,GAAG;;;;;;MAOJ;;;;;CAQD,AAAQ,oBAAoB,SAAiD;EAC3E,IAAI,UAAU;AACd,SAAO,WAAW,YAAY,MAAM;AAClC,OAAI,aAAa,QAAQ,CACvB,QAAO;AAET,aAAU,QAAQ;;AAEpB,SAAO;;CAGT,cAAc;AACZ,SAAO;AACP,OAAK,iBAAiB,gBAAgB,MAAM;GAC1C,MAAM,SAAS,EAAE;GACjB,MAAM,WAAW,KAAK,oBAAoB,OAAO;AACjD,OAAI,WAAW,QAAQ,SACrB,MAAK,iBAAiB;IAExB;AACF,OAAK,iBAAiB,eAAe,MAAM;GACzC,MAAM,gBAAgB,EAAE;GACxB,MAAM,oBAAoB,KAAK,oBAAoB,cAAc;AAKjE,OAAI,CAAC,KAAK,SAAS,cAAc,IAAI,kBAAkB,QAAQ,CAAC,kBAC9D,MAAK,iBAAiB;IAExB;;CAGJ,SAAS;AACP,SAAO,IAAI;;;YAxCZ,QAAQ,EAAE,SAAS,uBAAuB,CAAC;wBAZ7C,cAAc,aAAa"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFResizableBox.js","names":["EFResizableBox"],"sources":["../../src/gui/EFResizableBox.ts"],"sourcesContent":["import { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport { getCornerPoint, getOppositeCorner } from \"./transformUtils.js\";\n\nconst DEFAULT_MIN_SIZE = 10;\n\nexport interface BoxBounds {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\ntype ResizeHandle = \"nw\" | \"n\" | \"ne\" | \"e\" | \"se\" | \"s\" | \"sw\" | \"w\";\n\n@customElement(\"ef-resizable-box\")\nexport class EFResizableBox extends LitElement {\n @property({ type: Object })\n bounds: BoxBounds = { x: 0, y: 0, width: 100, height: 100 };\n\n @state()\n private containerWidth = 0;\n\n @state()\n private containerHeight = 0;\n\n @property({ type: Number })\n minSize = DEFAULT_MIN_SIZE;\n\n @state()\n private isDragging = false;\n\n @state()\n private isResizing: ResizeHandle | null = null;\n\n private dragStart: { x: number; y: number } | null = null;\n private dragStartPosition: { x: number; y: number } = { x: 0, y: 0 };\n private resizeStartCorner: { x: number; y: number } | null = null;\n private resizeStartSize: { width: number; height: number } | null = null;\n private resizeStartPosition: { x: number; y: number } | null = null;\n\n static styles = css`\n .box {\n position: absolute;\n border: 2px solid var(--ef-resizable-box-border-color, var(--ef-color-primary));\n background-color: var(--ef-resizable-box-bg-color, color-mix(in srgb, var(--ef-color-primary) 20%, transparent));\n cursor: grab;\n }\n .box.dragging {\n border-color: var(--ef-resizable-box-dragging-border-color, var(--ef-color-primary));\n background-color: var(--ef-resizable-box-dragging-bg-color, color-mix(in srgb, var(--ef-color-primary) 30%, transparent));\n }\n .handle {\n position: absolute;\n background-color: var(--ef-resizable-box-handle-color, var(--ef-color-primary));\n touch-action: none;\n }\n .handle.nw { top: -4px; left: -4px; width: 8px; height: 8px; cursor: nwse-resize; }\n .handle.ne { top: -4px; right: -4px; width: 8px; height: 8px; cursor: nesw-resize; }\n .handle.sw { bottom: -4px; left: -4px; width: 8px; height: 8px; cursor: nesw-resize; }\n .handle.se { bottom: -4px; right: -4px; width: 8px; height: 8px; cursor: nwse-resize; }\n .handle.n { top: -4px; left: 4px; right: 4px; height: 8px; cursor: ns-resize; }\n .handle.e { top: 4px; bottom: 4px; right: -4px; width: 8px; cursor: ew-resize; }\n .handle.s { bottom: -4px; left: 4px; right: 4px; height: 8px; cursor: ns-resize; }\n .handle.w { top: 4px; bottom: 4px; left: -4px; width: 8px; cursor: ew-resize; }\n `;\n\n private resizeObserver?: ResizeObserver;\n\n connectedCallback() {\n super.connectedCallback();\n if (this.offsetParent) {\n this.containerWidth = this.offsetParent.clientWidth;\n this.containerHeight = this.offsetParent.clientHeight;\n }\n this.resizeObserver = new ResizeObserver(() => {\n if (this.offsetParent) {\n this.containerWidth = this.offsetParent.clientWidth;\n this.containerHeight = this.offsetParent.clientHeight;\n }\n });\n if (this.offsetParent) {\n this.resizeObserver.observe(this.offsetParent);\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.resizeObserver?.disconnect();\n this.cleanup();\n }\n\n private handlePointerDown = (\n e: PointerEvent,\n mode: \"move\" | \"resize\",\n handle?: ResizeHandle,\n ) => {\n e.preventDefault();\n e.stopPropagation();\n this.isDragging = true;\n this.isResizing = mode === \"resize\" ? handle || null : null;\n\n this.dragStart = { x: e.clientX, y: e.clientY };\n this.dragStartPosition = { x: this.bounds.x, y: this.bounds.y };\n\n if (mode === \"resize\" && handle) {\n const oppositeCorner = getOppositeCorner(handle);\n const rotationRadians = 0;\n const initialCorner = getCornerPoint(\n this.bounds.x,\n this.bounds.y,\n this.bounds.width,\n this.bounds.height,\n rotationRadians,\n oppositeCorner.x,\n oppositeCorner.y,\n );\n this.resizeStartCorner = initialCorner;\n this.resizeStartSize = {\n width: this.bounds.width,\n height: this.bounds.height,\n };\n this.resizeStartPosition = { x: this.bounds.x, y: this.bounds.y };\n }\n\n document.addEventListener(\"pointermove\", this.handlePointerMove, {\n passive: false,\n });\n document.addEventListener(\"pointerup\", this.handlePointerUp, {\n passive: false,\n });\n };\n\n private handlePointerMove = (e: PointerEvent) => {\n if (!this.isDragging || !this.dragStart) return;\n\n e.preventDefault();\n\n const deltaX = e.clientX - this.dragStart.x;\n const deltaY = e.clientY - this.dragStart.y;\n\n if (\n this.isResizing &&\n this.resizeStartCorner &&\n this.resizeStartSize &&\n this.resizeStartPosition\n ) {\n const oppositeCorner = getOppositeCorner(this.isResizing);\n const rotationRadians = 0;\n\n let newWidth = this.resizeStartSize.width;\n let newHeight = this.resizeStartSize.height;\n\n if (this.isResizing.includes(\"e\")) {\n newWidth = this.resizeStartSize.width + deltaX;\n } else if (this.isResizing.includes(\"w\")) {\n newWidth = this.resizeStartSize.width - deltaX;\n }\n\n if (this.isResizing.includes(\"s\")) {\n newHeight = this.resizeStartSize.height + deltaY;\n } else if (this.isResizing.includes(\"n\")) {\n newHeight = this.resizeStartSize.height - deltaY;\n }\n\n newWidth = Math.max(\n this.minSize,\n Math.min(this.containerWidth - this.bounds.x, newWidth),\n );\n newHeight = Math.max(\n this.minSize,\n Math.min(this.containerHeight - this.bounds.y, newHeight),\n );\n\n const newOppositeCorner = getCornerPoint(\n this.resizeStartPosition.x,\n this.resizeStartPosition.y,\n newWidth,\n newHeight,\n rotationRadians,\n oppositeCorner.x,\n oppositeCorner.y,\n );\n\n const offsetX = this.resizeStartCorner.x - newOppositeCorner.x;\n const offsetY = this.resizeStartCorner.y - newOppositeCorner.y;\n\n const newX = Math.max(\n 0,\n Math.min(\n this.containerWidth - newWidth,\n this.resizeStartPosition.x + offsetX,\n ),\n );\n const newY = Math.max(\n 0,\n Math.min(\n this.containerHeight - newHeight,\n this.resizeStartPosition.y + offsetY,\n ),\n );\n\n this.bounds = {\n x: newX,\n y: newY,\n width: newWidth,\n height: newHeight,\n };\n } else {\n const constrainedX = Math.max(\n 0,\n Math.min(\n this.containerWidth - this.bounds.width,\n this.dragStartPosition.x + deltaX,\n ),\n );\n const constrainedY = Math.max(\n 0,\n Math.min(\n this.containerHeight - this.bounds.height,\n this.dragStartPosition.y + deltaY,\n ),\n );\n\n this.bounds = {\n ...this.bounds,\n x: constrainedX,\n y: constrainedY,\n };\n }\n\n this.dispatchBoundsChange();\n };\n\n private handlePointerUp = (e: PointerEvent) => {\n e.preventDefault();\n this.cleanup();\n };\n\n private cleanup() {\n this.isDragging = false;\n this.isResizing = null;\n this.dragStart = null;\n this.dragStartPosition = { x: 0, y: 0 };\n this.resizeStartCorner = null;\n this.resizeStartSize = null;\n this.resizeStartPosition = null;\n document.removeEventListener(\"pointermove\", this.handlePointerMove);\n document.removeEventListener(\"pointerup\", this.handlePointerUp);\n }\n\n private dispatchBoundsChange() {\n this.dispatchEvent(\n new CustomEvent(\"bounds-change\", {\n detail: { bounds: this.bounds },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n render() {\n const boxStyles = {\n left: `${this.bounds.x}px`,\n top: `${this.bounds.y}px`,\n width: `${this.bounds.width}px`,\n height: `${this.bounds.height}px`,\n };\n\n const handles: ResizeHandle[] = [\n \"nw\",\n \"n\",\n \"ne\",\n \"e\",\n \"se\",\n \"s\",\n \"sw\",\n \"w\",\n ];\n\n return html`\n <div\n class=\"box ${this.isDragging ? \"dragging\" : \"\"}\"\n style=${styleMap(boxStyles)}\n @pointerdown=${(e: PointerEvent) => this.handlePointerDown(e, \"move\")}\n >\n ${handles.map(\n (handle) => html`\n <div\n class=\"handle ${handle}\"\n @pointerdown=${(e: PointerEvent) => {\n e.stopPropagation();\n this.handlePointerDown(e, \"resize\", handle);\n }}\n ></div>\n `,\n )}\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFResizableBox.js","names":["EFResizableBox"],"sources":["../../src/gui/EFResizableBox.ts"],"sourcesContent":["import { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport { getCornerPoint, getOppositeCorner } from \"./transformUtils.js\";\n\nconst DEFAULT_MIN_SIZE = 10;\n\nexport interface BoxBounds {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\ntype ResizeHandle = \"nw\" | \"n\" | \"ne\" | \"e\" | \"se\" | \"s\" | \"sw\" | \"w\";\n\n@customElement(\"ef-resizable-box\")\nexport class EFResizableBox extends LitElement {\n @property({ type: Object })\n bounds: BoxBounds = { x: 0, y: 0, width: 100, height: 100 };\n\n @state()\n private containerWidth = 0;\n\n @state()\n private containerHeight = 0;\n\n @property({ type: Number })\n minSize = DEFAULT_MIN_SIZE;\n\n @state()\n private isDragging = false;\n\n @state()\n private isResizing: ResizeHandle | null = null;\n\n private dragStart: { x: number; y: number } | null = null;\n private dragStartPosition: { x: number; y: number } = { x: 0, y: 0 };\n private resizeStartCorner: { x: number; y: number } | null = null;\n private resizeStartSize: { width: number; height: number } | null = null;\n private resizeStartPosition: { x: number; y: number } | null = null;\n\n static styles = css`\n .box {\n position: absolute;\n border: 2px solid var(--ef-resizable-box-border-color, var(--ef-color-primary));\n background-color: var(--ef-resizable-box-bg-color, color-mix(in srgb, var(--ef-color-primary) 20%, transparent));\n cursor: grab;\n }\n .box.dragging {\n border-color: var(--ef-resizable-box-dragging-border-color, var(--ef-color-primary));\n background-color: var(--ef-resizable-box-dragging-bg-color, color-mix(in srgb, var(--ef-color-primary) 30%, transparent));\n }\n .handle {\n position: absolute;\n background-color: var(--ef-resizable-box-handle-color, var(--ef-color-primary));\n touch-action: none;\n }\n .handle.nw { top: -4px; left: -4px; width: 8px; height: 8px; cursor: nwse-resize; }\n .handle.ne { top: -4px; right: -4px; width: 8px; height: 8px; cursor: nesw-resize; }\n .handle.sw { bottom: -4px; left: -4px; width: 8px; height: 8px; cursor: nesw-resize; }\n .handle.se { bottom: -4px; right: -4px; width: 8px; height: 8px; cursor: nwse-resize; }\n .handle.n { top: -4px; left: 4px; right: 4px; height: 8px; cursor: ns-resize; }\n .handle.e { top: 4px; bottom: 4px; right: -4px; width: 8px; cursor: ew-resize; }\n .handle.s { bottom: -4px; left: 4px; right: 4px; height: 8px; cursor: ns-resize; }\n .handle.w { top: 4px; bottom: 4px; left: -4px; width: 8px; cursor: ew-resize; }\n `;\n\n private resizeObserver?: ResizeObserver;\n\n connectedCallback() {\n super.connectedCallback();\n if (this.offsetParent) {\n this.containerWidth = this.offsetParent.clientWidth;\n this.containerHeight = this.offsetParent.clientHeight;\n }\n this.resizeObserver = new ResizeObserver(() => {\n if (this.offsetParent) {\n this.containerWidth = this.offsetParent.clientWidth;\n this.containerHeight = this.offsetParent.clientHeight;\n }\n });\n if (this.offsetParent) {\n this.resizeObserver.observe(this.offsetParent);\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.resizeObserver?.disconnect();\n this.cleanup();\n }\n\n private handlePointerDown = (e: PointerEvent, mode: \"move\" | \"resize\", handle?: ResizeHandle) => {\n e.preventDefault();\n e.stopPropagation();\n this.isDragging = true;\n this.isResizing = mode === \"resize\" ? handle || null : null;\n\n this.dragStart = { x: e.clientX, y: e.clientY };\n this.dragStartPosition = { x: this.bounds.x, y: this.bounds.y };\n\n if (mode === \"resize\" && handle) {\n const oppositeCorner = getOppositeCorner(handle);\n const rotationRadians = 0;\n const initialCorner = getCornerPoint(\n this.bounds.x,\n this.bounds.y,\n this.bounds.width,\n this.bounds.height,\n rotationRadians,\n oppositeCorner.x,\n oppositeCorner.y,\n );\n this.resizeStartCorner = initialCorner;\n this.resizeStartSize = {\n width: this.bounds.width,\n height: this.bounds.height,\n };\n this.resizeStartPosition = { x: this.bounds.x, y: this.bounds.y };\n }\n\n document.addEventListener(\"pointermove\", this.handlePointerMove, {\n passive: false,\n });\n document.addEventListener(\"pointerup\", this.handlePointerUp, {\n passive: false,\n });\n };\n\n private handlePointerMove = (e: PointerEvent) => {\n if (!this.isDragging || !this.dragStart) return;\n\n e.preventDefault();\n\n const deltaX = e.clientX - this.dragStart.x;\n const deltaY = e.clientY - this.dragStart.y;\n\n if (\n this.isResizing &&\n this.resizeStartCorner &&\n this.resizeStartSize &&\n this.resizeStartPosition\n ) {\n const oppositeCorner = getOppositeCorner(this.isResizing);\n const rotationRadians = 0;\n\n let newWidth = this.resizeStartSize.width;\n let newHeight = this.resizeStartSize.height;\n\n if (this.isResizing.includes(\"e\")) {\n newWidth = this.resizeStartSize.width + deltaX;\n } else if (this.isResizing.includes(\"w\")) {\n newWidth = this.resizeStartSize.width - deltaX;\n }\n\n if (this.isResizing.includes(\"s\")) {\n newHeight = this.resizeStartSize.height + deltaY;\n } else if (this.isResizing.includes(\"n\")) {\n newHeight = this.resizeStartSize.height - deltaY;\n }\n\n newWidth = Math.max(this.minSize, Math.min(this.containerWidth - this.bounds.x, newWidth));\n newHeight = Math.max(this.minSize, Math.min(this.containerHeight - this.bounds.y, newHeight));\n\n const newOppositeCorner = getCornerPoint(\n this.resizeStartPosition.x,\n this.resizeStartPosition.y,\n newWidth,\n newHeight,\n rotationRadians,\n oppositeCorner.x,\n oppositeCorner.y,\n );\n\n const offsetX = this.resizeStartCorner.x - newOppositeCorner.x;\n const offsetY = this.resizeStartCorner.y - newOppositeCorner.y;\n\n const newX = Math.max(\n 0,\n Math.min(this.containerWidth - newWidth, this.resizeStartPosition.x + offsetX),\n );\n const newY = Math.max(\n 0,\n Math.min(this.containerHeight - newHeight, this.resizeStartPosition.y + offsetY),\n );\n\n this.bounds = {\n x: newX,\n y: newY,\n width: newWidth,\n height: newHeight,\n };\n } else {\n const constrainedX = Math.max(\n 0,\n Math.min(this.containerWidth - this.bounds.width, this.dragStartPosition.x + deltaX),\n );\n const constrainedY = Math.max(\n 0,\n Math.min(this.containerHeight - this.bounds.height, this.dragStartPosition.y + deltaY),\n );\n\n this.bounds = {\n ...this.bounds,\n x: constrainedX,\n y: constrainedY,\n };\n }\n\n this.dispatchBoundsChange();\n };\n\n private handlePointerUp = (e: PointerEvent) => {\n e.preventDefault();\n this.cleanup();\n };\n\n private cleanup() {\n this.isDragging = false;\n this.isResizing = null;\n this.dragStart = null;\n this.dragStartPosition = { x: 0, y: 0 };\n this.resizeStartCorner = null;\n this.resizeStartSize = null;\n this.resizeStartPosition = null;\n document.removeEventListener(\"pointermove\", this.handlePointerMove);\n document.removeEventListener(\"pointerup\", this.handlePointerUp);\n }\n\n private dispatchBoundsChange() {\n this.dispatchEvent(\n new CustomEvent(\"bounds-change\", {\n detail: { bounds: this.bounds },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n render() {\n const boxStyles = {\n left: `${this.bounds.x}px`,\n top: `${this.bounds.y}px`,\n width: `${this.bounds.width}px`,\n height: `${this.bounds.height}px`,\n };\n\n const handles: ResizeHandle[] = [\"nw\", \"n\", \"ne\", \"e\", \"se\", \"s\", \"sw\", \"w\"];\n\n return html`\n <div\n class=\"box ${this.isDragging ? \"dragging\" : \"\"}\"\n style=${styleMap(boxStyles)}\n @pointerdown=${(e: PointerEvent) => this.handlePointerDown(e, \"move\")}\n >\n ${handles.map(\n (handle) => html`\n <div\n class=\"handle ${handle}\"\n @pointerdown=${(e: PointerEvent) => {\n e.stopPropagation();\n this.handlePointerDown(e, \"resize\", handle);\n }}\n ></div>\n `,\n )}\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
package/dist/gui/EFScrubber.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit19 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html18 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFScrubber.d.ts
|
|
7
7
|
declare const EFScrubber_base: (new (...args: any[]) => {
|
|
@@ -10,7 +10,7 @@ declare const EFScrubber_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFScrubber extends EFScrubber_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit19.CSSResult[];
|
|
14
14
|
playing: boolean;
|
|
15
15
|
contextCurrentTimeMs: number;
|
|
16
16
|
contextDurationMs: number;
|
|
@@ -51,7 +51,7 @@ declare class EFScrubber extends EFScrubber_base {
|
|
|
51
51
|
private boundHandlePointerUp;
|
|
52
52
|
private boundHandlePointerCancel;
|
|
53
53
|
private boundHandleContextMenu;
|
|
54
|
-
render():
|
|
54
|
+
render(): lit_html18.TemplateResult<1>;
|
|
55
55
|
connectedCallback(): void;
|
|
56
56
|
disconnectedCallback(): void;
|
|
57
57
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFScrubber.js","names":["EFScrubber"],"sources":["../../src/gui/EFScrubber.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\n\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport type { ControllableInterface } from \"./Controllable.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { efContext } from \"./efContext.js\";\nimport { playingContext } from \"./playingContext.js\";\nimport { TargetOrContextMixin } from \"./TargetOrContextMixin.js\";\nimport { quantizeToFrameTimeMs } from \"./EFTimelineRuler.js\";\n\nconst BASE_PIXELS_PER_SECOND = 100;\n\nfunction timeToPixels(\n timeMs: number,\n durationMs: number,\n _containerWidth: number,\n zoomScale: number,\n): number {\n if (durationMs <= 0) return 0;\n const pixelsPerSecond = BASE_PIXELS_PER_SECOND * zoomScale;\n return (timeMs / 1000) * pixelsPerSecond;\n}\n\nfunction pixelsToTime(\n pixels: number,\n durationMs: number,\n _containerWidth: number,\n zoomScale: number,\n): number {\n if (durationMs <= 0) return 0;\n const pixelsPerSecond = BASE_PIXELS_PER_SECOND * zoomScale;\n return (pixels / pixelsPerSecond) * 1000;\n}\n\n@customElement(\"ef-scrubber\")\nexport class EFScrubber extends TargetOrContextMixin(LitElement, efContext) {\n static styles = [\n css`\n :host {\n --ef-scrubber-height: 4px;\n --ef-scrubber-background: var(--ef-color-border, rgba(255, 255, 255, 0.2));\n --ef-scrubber-progress-color: var(--ef-color-primary, #fff);\n --ef-scrubber-handle-size: 12px;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n \n :host([orientation=\"vertical\"]) {\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n pointer-events: auto;\n }\n\n .scrubber {\n width: 100%;\n height: var(--ef-scrubber-height);\n background: var(--ef-scrubber-background);\n position: relative;\n cursor: pointer;\n border-radius: 2px;\n touch-action: none;\n user-select: none;\n }\n\n :host([orientation=\"vertical\"]) .scrubber {\n width: 100%;\n height: 100%;\n background: transparent;\n cursor: ew-resize;\n }\n\n .progress {\n position: absolute;\n height: 100%;\n background: var(--ef-scrubber-progress-color);\n border-radius: 2px;\n }\n\n :host([orientation=\"vertical\"]) .progress {\n display: none;\n }\n\n .handle {\n position: absolute;\n width: var(--ef-scrubber-handle-size);\n height: var(--ef-scrubber-handle-size);\n background: var(--ef-scrubber-progress-color);\n border-radius: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n cursor: grab;\n }\n\n :host([orientation=\"vertical\"]) .handle {\n display: none;\n }\n\n .playhead {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--ef-scrubber-progress-color);\n pointer-events: auto;\n cursor: ew-resize;\n z-index: 30;\n }\n\n ::part(playhead) {\n z-index: 30;\n }\n\n .playhead-handle {\n position: absolute;\n top: 0;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 12px;\n height: 12px;\n background: var(--ef-scrubber-progress-color);\n border-radius: 50%;\n }\n\n .raw-preview {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 2px;\n background: color-mix(in srgb, var(--ef-color-primary) 20%, transparent);\n pointer-events: none;\n z-index: 20;\n }\n\n /* Add CSS Shadow Parts */\n ::part(scrubber) { }\n ::part(progress) { }\n ::part(handle) { }\n `,\n ];\n\n @consume({ context: playingContext, subscribe: true })\n playing = false;\n\n @consume({ context: currentTimeContext, subscribe: true })\n contextCurrentTimeMs = Number.NaN;\n\n @consume({ context: durationContext, subscribe: true })\n contextDurationMs = 0;\n\n @property({ type: String, attribute: \"orientation\" })\n orientation: \"horizontal\" | \"vertical\" = \"horizontal\";\n\n @property({ type: Number, attribute: \"current-time-ms\" })\n currentTimeMs = Number.NaN;\n\n @property({ type: Number, attribute: \"duration-ms\" })\n durationMs = 0;\n\n @property({ type: Number, attribute: \"zoom-scale\" })\n zoomScale = 1.0;\n\n @property({ type: Number, attribute: \"container-width\" })\n containerWidth = 0;\n\n @property({ type: Number, attribute: \"fps\" })\n fps?: number;\n\n @property({ type: Number, attribute: \"raw-scrub-time-ms\" })\n rawScrubTimeMs?: number | null;\n\n @property({ attribute: false })\n scrollContainerRef?: { current: HTMLElement | null };\n\n /**\n * Reference to the element that represents the actual track content area.\n * Used to calculate the offset between the scroll container and where tracks begin.\n */\n @property({ attribute: false })\n trackContentRef?: { current: HTMLElement | null };\n\n @property({ attribute: false })\n onSeek?: (time: number) => void;\n\n @property({ attribute: false })\n isScrubbingRef?: { current: boolean };\n\n get context(): ControllableInterface | null {\n return this.effectiveContext;\n }\n\n get effectiveCurrentTimeMs(): number {\n if (!Number.isNaN(this.currentTimeMs)) {\n return this.currentTimeMs;\n }\n if (!Number.isNaN(this.contextCurrentTimeMs)) {\n return this.contextCurrentTimeMs;\n }\n return 0;\n }\n\n get effectiveDurationMs(): number {\n return this.durationMs || this.contextDurationMs || 0;\n }\n\n get isTimelineMode(): boolean {\n return this.orientation === \"vertical\" && this.zoomScale > 0;\n }\n\n @state()\n private scrubProgress = 0;\n\n @state()\n private isMoving = false;\n\n private scrubberRef = createRef<HTMLElement>();\n private _scrubberElement?: HTMLElement;\n private capturedPointerId: number | null = null;\n private _wasPlayingBeforeScrub = false;\n\n private updateProgress(e: PointerEvent) {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (!scrubberEl) return;\n\n const duration = this.effectiveDurationMs;\n if (duration <= 0) return;\n\n if (this.isTimelineMode) {\n // Timeline mode: use pixel-based positioning with zoom\n const scrollContainer =\n this.scrollContainerRef?.current || scrubberEl.parentElement;\n if (!scrollContainer) return;\n\n const scrollContainerRect = scrollContainer.getBoundingClientRect();\n const scrollLeft = scrollContainer.scrollLeft || 0;\n\n // Calculate pixel offset dynamically from the track content element\n // This accounts for any hierarchy panel or other elements before the tracks\n let pixelOffset = 0;\n if (this.trackContentRef?.current) {\n const trackRect = this.trackContentRef.current.getBoundingClientRect();\n pixelOffset =\n trackRect.left -\n scrollContainerRect.left +\n scrollContainer.scrollLeft;\n }\n\n const x = e.clientX - scrollContainerRect.left - pixelOffset;\n const pixelPosition = scrollLeft + x;\n const effectiveWidth =\n this.containerWidth > 0\n ? this.containerWidth\n : scrollContainerRect.width;\n if (effectiveWidth <= 0) return;\n\n let rawTime = pixelsToTime(\n pixelPosition,\n duration,\n effectiveWidth,\n this.zoomScale,\n );\n rawTime = Math.max(0, Math.min(rawTime, duration));\n\n // Quantize to frame boundaries if FPS is provided, then clamp to duration\n let quantizedTime =\n this.fps && this.fps > 0\n ? quantizeToFrameTimeMs(rawTime, this.fps)\n : rawTime;\n quantizedTime = Math.max(0, Math.min(quantizedTime, duration));\n\n this.scrubProgress = quantizedTime / duration;\n\n if (this.onSeek) {\n this.onSeek(quantizedTime);\n } else {\n // Emit seek event for event listeners\n this.dispatchEvent(\n new CustomEvent(\"seek\", {\n detail: quantizedTime,\n bubbles: true,\n composed: true,\n }),\n );\n if (this.context) {\n this.context.currentTimeMs = quantizedTime;\n }\n }\n } else {\n // Horizontal mode: simple progress calculation\n const rect = scrubberEl.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const progress = Math.max(0, Math.min(1, x / rect.width));\n\n this.scrubProgress = progress;\n const timeMs = progress * duration;\n\n if (this.onSeek) {\n this.onSeek(timeMs);\n } else {\n // Emit seek event for event listeners\n this.dispatchEvent(\n new CustomEvent(\"seek\", {\n detail: timeMs,\n bubbles: true,\n composed: true,\n }),\n );\n if (this.context) {\n this.context.currentTimeMs = timeMs;\n }\n }\n }\n }\n\n private handlePointerDown = (e: PointerEvent) => {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (!scrubberEl) return;\n\n this.isMoving = true;\n if (this.isScrubbingRef) {\n this.isScrubbingRef.current = true;\n }\n e.preventDefault();\n e.stopPropagation();\n this.capturedPointerId = e.pointerId;\n try {\n scrubberEl.setPointerCapture(e.pointerId);\n } catch (err) {\n // setPointerCapture may fail in some cases, continue anyway\n console.warn(\"Failed to set pointer capture:\", err);\n }\n\n if (this.playing && this.context) {\n this._wasPlayingBeforeScrub = true;\n this.context.pause();\n }\n\n this.updateProgress(e);\n };\n\n private boundHandlePointerMove = (e: PointerEvent) => {\n if (this.isMoving && e.pointerId === this.capturedPointerId) {\n e.preventDefault();\n e.stopPropagation();\n this.updateProgress(e);\n }\n };\n\n private boundHandlePointerUp = (e: PointerEvent) => {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (e.pointerId === this.capturedPointerId && scrubberEl) {\n e.preventDefault();\n e.stopPropagation();\n try {\n scrubberEl.releasePointerCapture(e.pointerId);\n } catch (_err) {\n // releasePointerCapture may fail if capture was already lost\n }\n this.capturedPointerId = null;\n this.isMoving = false;\n if (this.isScrubbingRef) {\n this.isScrubbingRef.current = false;\n }\n if (this._wasPlayingBeforeScrub) {\n this._wasPlayingBeforeScrub = false;\n this.context?.play();\n }\n }\n };\n\n private boundHandlePointerCancel = (e: PointerEvent) => {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (e.pointerId === this.capturedPointerId && scrubberEl) {\n try {\n scrubberEl.releasePointerCapture(e.pointerId);\n } catch (_err) {\n // releasePointerCapture may fail if capture was already lost\n }\n this.capturedPointerId = null;\n this.isMoving = false;\n if (this.isScrubbingRef) {\n this.isScrubbingRef.current = false;\n }\n if (this._wasPlayingBeforeScrub) {\n this._wasPlayingBeforeScrub = false;\n this.context?.play();\n }\n }\n };\n\n private boundHandleContextMenu = (e: Event) => {\n if (this.isMoving) {\n e.preventDefault();\n e.stopPropagation();\n }\n };\n\n render() {\n const duration = this.effectiveDurationMs;\n const currentTime = this.effectiveCurrentTimeMs;\n\n if (duration <= 0) {\n return html``;\n }\n\n if (this.isTimelineMode) {\n // Vertical timeline mode: render playhead line\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n const effectiveWidth =\n this.containerWidth > 0\n ? this.containerWidth\n : scrubberEl?.parentElement?.getBoundingClientRect().width || 0;\n\n const positionPixels =\n effectiveWidth > 0\n ? timeToPixels(currentTime, duration, effectiveWidth, this.zoomScale)\n : 0;\n\n const rawScrubPositionPixels =\n this.rawScrubTimeMs !== null &&\n this.rawScrubTimeMs !== undefined &&\n effectiveWidth > 0\n ? timeToPixels(\n this.rawScrubTimeMs,\n duration,\n effectiveWidth,\n this.zoomScale,\n )\n : null;\n\n return html`\n ${\n rawScrubPositionPixels !== null &&\n rawScrubPositionPixels !== positionPixels\n ? html`<div\n class=\"raw-preview\"\n style=\"left: ${rawScrubPositionPixels}px\"\n ></div>`\n : html``\n }\n <div\n ${ref(this.scrubberRef)}\n part=\"scrubber\"\n class=\"scrubber\"\n @pointerdown=${this.handlePointerDown}\n @contextmenu=${this.boundHandleContextMenu}\n >\n <div\n part=\"playhead\"\n class=\"playhead\"\n style=\"left: ${positionPixels}px\"\n @pointerdown=${this.handlePointerDown}\n >\n <div class=\"playhead-handle\"></div>\n </div>\n </div>\n `;\n } else {\n // Horizontal mode: render progress bar\n const currentProgress = duration > 0 ? currentTime / duration : 0;\n const displayProgress = this.isMoving\n ? this.scrubProgress\n : currentProgress;\n\n return html`\n <div\n ${ref(this.scrubberRef)}\n part=\"scrubber\"\n class=\"scrubber\"\n @pointerdown=${this.handlePointerDown}\n @contextmenu=${this.boundHandleContextMenu}\n >\n <div part=\"progress\" class=\"progress\" style=\"width: ${displayProgress * 100}%\"></div>\n <div part=\"handle\" class=\"handle\" style=\"left: ${displayProgress * 100}%\"></div>\n </div>\n `;\n }\n }\n\n connectedCallback() {\n super.connectedCallback();\n window.addEventListener(\n \"pointerup\",\n this.boundHandlePointerUp as EventListener,\n { passive: false },\n );\n window.addEventListener(\"pointermove\", this.boundHandlePointerMove, {\n passive: false,\n });\n window.addEventListener(\n \"pointercancel\",\n this.boundHandlePointerCancel as EventListener,\n { passive: false },\n );\n this.addEventListener(\"contextmenu\", this.boundHandleContextMenu, {\n passive: false,\n });\n this.addEventListener(\n \"pointerdown\",\n this.handlePointerDown as EventListener,\n { passive: false },\n );\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n window.removeEventListener(\n \"pointerup\",\n this.boundHandlePointerUp as EventListener,\n );\n window.removeEventListener(\"pointermove\", this.boundHandlePointerMove);\n window.removeEventListener(\n \"pointercancel\",\n this.boundHandlePointerCancel as EventListener,\n );\n this.removeEventListener(\"contextmenu\", this.boundHandleContextMenu);\n this.removeEventListener(\n \"pointerdown\",\n this.handlePointerDown as EventListener,\n );\n if (this._wasPlayingBeforeScrub) {\n this._wasPlayingBeforeScrub = false;\n if ((this.context as unknown as Element | null)?.isConnected) {\n this.context!.play();\n }\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-scrubber\": EFScrubber;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFScrubber.js","names":["EFScrubber"],"sources":["../../src/gui/EFScrubber.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\n\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport type { ControllableInterface } from \"./Controllable.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { efContext } from \"./efContext.js\";\nimport { playingContext } from \"./playingContext.js\";\nimport { TargetOrContextMixin } from \"./TargetOrContextMixin.js\";\nimport { quantizeToFrameTimeMs } from \"./EFTimelineRuler.js\";\n\nconst BASE_PIXELS_PER_SECOND = 100;\n\nfunction timeToPixels(\n timeMs: number,\n durationMs: number,\n _containerWidth: number,\n zoomScale: number,\n): number {\n if (durationMs <= 0) return 0;\n const pixelsPerSecond = BASE_PIXELS_PER_SECOND * zoomScale;\n return (timeMs / 1000) * pixelsPerSecond;\n}\n\nfunction pixelsToTime(\n pixels: number,\n durationMs: number,\n _containerWidth: number,\n zoomScale: number,\n): number {\n if (durationMs <= 0) return 0;\n const pixelsPerSecond = BASE_PIXELS_PER_SECOND * zoomScale;\n return (pixels / pixelsPerSecond) * 1000;\n}\n\n@customElement(\"ef-scrubber\")\nexport class EFScrubber extends TargetOrContextMixin(LitElement, efContext) {\n static styles = [\n css`\n :host {\n --ef-scrubber-height: 4px;\n --ef-scrubber-background: var(--ef-color-border, rgba(255, 255, 255, 0.2));\n --ef-scrubber-progress-color: var(--ef-color-primary, #fff);\n --ef-scrubber-handle-size: 12px;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n \n :host([orientation=\"vertical\"]) {\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n pointer-events: auto;\n }\n\n .scrubber {\n width: 100%;\n height: var(--ef-scrubber-height);\n background: var(--ef-scrubber-background);\n position: relative;\n cursor: pointer;\n border-radius: 2px;\n touch-action: none;\n user-select: none;\n }\n\n :host([orientation=\"vertical\"]) .scrubber {\n width: 100%;\n height: 100%;\n background: transparent;\n cursor: ew-resize;\n }\n\n .progress {\n position: absolute;\n height: 100%;\n background: var(--ef-scrubber-progress-color);\n border-radius: 2px;\n }\n\n :host([orientation=\"vertical\"]) .progress {\n display: none;\n }\n\n .handle {\n position: absolute;\n width: var(--ef-scrubber-handle-size);\n height: var(--ef-scrubber-handle-size);\n background: var(--ef-scrubber-progress-color);\n border-radius: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n cursor: grab;\n }\n\n :host([orientation=\"vertical\"]) .handle {\n display: none;\n }\n\n .playhead {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--ef-scrubber-progress-color);\n pointer-events: auto;\n cursor: ew-resize;\n z-index: 30;\n }\n\n ::part(playhead) {\n z-index: 30;\n }\n\n .playhead-handle {\n position: absolute;\n top: 0;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 12px;\n height: 12px;\n background: var(--ef-scrubber-progress-color);\n border-radius: 50%;\n }\n\n .raw-preview {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 2px;\n background: color-mix(in srgb, var(--ef-color-primary) 20%, transparent);\n pointer-events: none;\n z-index: 20;\n }\n\n /* Add CSS Shadow Parts */\n ::part(scrubber) { }\n ::part(progress) { }\n ::part(handle) { }\n `,\n ];\n\n @consume({ context: playingContext, subscribe: true })\n playing = false;\n\n @consume({ context: currentTimeContext, subscribe: true })\n contextCurrentTimeMs = Number.NaN;\n\n @consume({ context: durationContext, subscribe: true })\n contextDurationMs = 0;\n\n @property({ type: String, attribute: \"orientation\" })\n orientation: \"horizontal\" | \"vertical\" = \"horizontal\";\n\n @property({ type: Number, attribute: \"current-time-ms\" })\n currentTimeMs = Number.NaN;\n\n @property({ type: Number, attribute: \"duration-ms\" })\n durationMs = 0;\n\n @property({ type: Number, attribute: \"zoom-scale\" })\n zoomScale = 1.0;\n\n @property({ type: Number, attribute: \"container-width\" })\n containerWidth = 0;\n\n @property({ type: Number, attribute: \"fps\" })\n fps?: number;\n\n @property({ type: Number, attribute: \"raw-scrub-time-ms\" })\n rawScrubTimeMs?: number | null;\n\n @property({ attribute: false })\n scrollContainerRef?: { current: HTMLElement | null };\n\n /**\n * Reference to the element that represents the actual track content area.\n * Used to calculate the offset between the scroll container and where tracks begin.\n */\n @property({ attribute: false })\n trackContentRef?: { current: HTMLElement | null };\n\n @property({ attribute: false })\n onSeek?: (time: number) => void;\n\n @property({ attribute: false })\n isScrubbingRef?: { current: boolean };\n\n get context(): ControllableInterface | null {\n return this.effectiveContext;\n }\n\n get effectiveCurrentTimeMs(): number {\n if (!Number.isNaN(this.currentTimeMs)) {\n return this.currentTimeMs;\n }\n if (!Number.isNaN(this.contextCurrentTimeMs)) {\n return this.contextCurrentTimeMs;\n }\n return 0;\n }\n\n get effectiveDurationMs(): number {\n return this.durationMs || this.contextDurationMs || 0;\n }\n\n get isTimelineMode(): boolean {\n return this.orientation === \"vertical\" && this.zoomScale > 0;\n }\n\n @state()\n private scrubProgress = 0;\n\n @state()\n private isMoving = false;\n\n private scrubberRef = createRef<HTMLElement>();\n private _scrubberElement?: HTMLElement;\n private capturedPointerId: number | null = null;\n private _wasPlayingBeforeScrub = false;\n\n private updateProgress(e: PointerEvent) {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (!scrubberEl) return;\n\n const duration = this.effectiveDurationMs;\n if (duration <= 0) return;\n\n if (this.isTimelineMode) {\n // Timeline mode: use pixel-based positioning with zoom\n const scrollContainer = this.scrollContainerRef?.current || scrubberEl.parentElement;\n if (!scrollContainer) return;\n\n const scrollContainerRect = scrollContainer.getBoundingClientRect();\n const scrollLeft = scrollContainer.scrollLeft || 0;\n\n // Calculate pixel offset dynamically from the track content element\n // This accounts for any hierarchy panel or other elements before the tracks\n let pixelOffset = 0;\n if (this.trackContentRef?.current) {\n const trackRect = this.trackContentRef.current.getBoundingClientRect();\n pixelOffset = trackRect.left - scrollContainerRect.left + scrollContainer.scrollLeft;\n }\n\n const x = e.clientX - scrollContainerRect.left - pixelOffset;\n const pixelPosition = scrollLeft + x;\n const effectiveWidth =\n this.containerWidth > 0 ? this.containerWidth : scrollContainerRect.width;\n if (effectiveWidth <= 0) return;\n\n let rawTime = pixelsToTime(pixelPosition, duration, effectiveWidth, this.zoomScale);\n rawTime = Math.max(0, Math.min(rawTime, duration));\n\n // Quantize to frame boundaries if FPS is provided, then clamp to duration\n let quantizedTime =\n this.fps && this.fps > 0 ? quantizeToFrameTimeMs(rawTime, this.fps) : rawTime;\n quantizedTime = Math.max(0, Math.min(quantizedTime, duration));\n\n this.scrubProgress = quantizedTime / duration;\n\n if (this.onSeek) {\n this.onSeek(quantizedTime);\n } else {\n // Emit seek event for event listeners\n this.dispatchEvent(\n new CustomEvent(\"seek\", {\n detail: quantizedTime,\n bubbles: true,\n composed: true,\n }),\n );\n if (this.context) {\n this.context.currentTimeMs = quantizedTime;\n }\n }\n } else {\n // Horizontal mode: simple progress calculation\n const rect = scrubberEl.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const progress = Math.max(0, Math.min(1, x / rect.width));\n\n this.scrubProgress = progress;\n const timeMs = progress * duration;\n\n if (this.onSeek) {\n this.onSeek(timeMs);\n } else {\n // Emit seek event for event listeners\n this.dispatchEvent(\n new CustomEvent(\"seek\", {\n detail: timeMs,\n bubbles: true,\n composed: true,\n }),\n );\n if (this.context) {\n this.context.currentTimeMs = timeMs;\n }\n }\n }\n }\n\n private handlePointerDown = (e: PointerEvent) => {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (!scrubberEl) return;\n\n this.isMoving = true;\n if (this.isScrubbingRef) {\n this.isScrubbingRef.current = true;\n }\n e.preventDefault();\n e.stopPropagation();\n this.capturedPointerId = e.pointerId;\n try {\n scrubberEl.setPointerCapture(e.pointerId);\n } catch (err) {\n // setPointerCapture may fail in some cases, continue anyway\n console.warn(\"Failed to set pointer capture:\", err);\n }\n\n if (this.playing && this.context) {\n this._wasPlayingBeforeScrub = true;\n this.context.pause();\n }\n\n this.updateProgress(e);\n };\n\n private boundHandlePointerMove = (e: PointerEvent) => {\n if (this.isMoving && e.pointerId === this.capturedPointerId) {\n e.preventDefault();\n e.stopPropagation();\n this.updateProgress(e);\n }\n };\n\n private boundHandlePointerUp = (e: PointerEvent) => {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (e.pointerId === this.capturedPointerId && scrubberEl) {\n e.preventDefault();\n e.stopPropagation();\n try {\n scrubberEl.releasePointerCapture(e.pointerId);\n } catch (_err) {\n // releasePointerCapture may fail if capture was already lost\n }\n this.capturedPointerId = null;\n this.isMoving = false;\n if (this.isScrubbingRef) {\n this.isScrubbingRef.current = false;\n }\n if (this._wasPlayingBeforeScrub) {\n this._wasPlayingBeforeScrub = false;\n this.context?.play();\n }\n }\n };\n\n private boundHandlePointerCancel = (e: PointerEvent) => {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (e.pointerId === this.capturedPointerId && scrubberEl) {\n try {\n scrubberEl.releasePointerCapture(e.pointerId);\n } catch (_err) {\n // releasePointerCapture may fail if capture was already lost\n }\n this.capturedPointerId = null;\n this.isMoving = false;\n if (this.isScrubbingRef) {\n this.isScrubbingRef.current = false;\n }\n if (this._wasPlayingBeforeScrub) {\n this._wasPlayingBeforeScrub = false;\n this.context?.play();\n }\n }\n };\n\n private boundHandleContextMenu = (e: Event) => {\n if (this.isMoving) {\n e.preventDefault();\n e.stopPropagation();\n }\n };\n\n render() {\n const duration = this.effectiveDurationMs;\n const currentTime = this.effectiveCurrentTimeMs;\n\n if (duration <= 0) {\n return html``;\n }\n\n if (this.isTimelineMode) {\n // Vertical timeline mode: render playhead line\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n const effectiveWidth =\n this.containerWidth > 0\n ? this.containerWidth\n : scrubberEl?.parentElement?.getBoundingClientRect().width || 0;\n\n const positionPixels =\n effectiveWidth > 0\n ? timeToPixels(currentTime, duration, effectiveWidth, this.zoomScale)\n : 0;\n\n const rawScrubPositionPixels =\n this.rawScrubTimeMs !== null && this.rawScrubTimeMs !== undefined && effectiveWidth > 0\n ? timeToPixels(this.rawScrubTimeMs, duration, effectiveWidth, this.zoomScale)\n : null;\n\n return html`\n ${\n rawScrubPositionPixels !== null && rawScrubPositionPixels !== positionPixels\n ? html`<div\n class=\"raw-preview\"\n style=\"left: ${rawScrubPositionPixels}px\"\n ></div>`\n : html``\n }\n <div\n ${ref(this.scrubberRef)}\n part=\"scrubber\"\n class=\"scrubber\"\n @pointerdown=${this.handlePointerDown}\n @contextmenu=${this.boundHandleContextMenu}\n >\n <div\n part=\"playhead\"\n class=\"playhead\"\n style=\"left: ${positionPixels}px\"\n @pointerdown=${this.handlePointerDown}\n >\n <div class=\"playhead-handle\"></div>\n </div>\n </div>\n `;\n } else {\n // Horizontal mode: render progress bar\n const currentProgress = duration > 0 ? currentTime / duration : 0;\n const displayProgress = this.isMoving ? this.scrubProgress : currentProgress;\n\n return html`\n <div\n ${ref(this.scrubberRef)}\n part=\"scrubber\"\n class=\"scrubber\"\n @pointerdown=${this.handlePointerDown}\n @contextmenu=${this.boundHandleContextMenu}\n >\n <div part=\"progress\" class=\"progress\" style=\"width: ${displayProgress * 100}%\"></div>\n <div part=\"handle\" class=\"handle\" style=\"left: ${displayProgress * 100}%\"></div>\n </div>\n `;\n }\n }\n\n connectedCallback() {\n super.connectedCallback();\n window.addEventListener(\"pointerup\", this.boundHandlePointerUp as EventListener, {\n passive: false,\n });\n window.addEventListener(\"pointermove\", this.boundHandlePointerMove, {\n passive: false,\n });\n window.addEventListener(\"pointercancel\", this.boundHandlePointerCancel as EventListener, {\n passive: false,\n });\n this.addEventListener(\"contextmenu\", this.boundHandleContextMenu, {\n passive: false,\n });\n this.addEventListener(\"pointerdown\", this.handlePointerDown as EventListener, {\n passive: false,\n });\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n window.removeEventListener(\"pointerup\", this.boundHandlePointerUp as EventListener);\n window.removeEventListener(\"pointermove\", this.boundHandlePointerMove);\n window.removeEventListener(\"pointercancel\", this.boundHandlePointerCancel as EventListener);\n this.removeEventListener(\"contextmenu\", this.boundHandleContextMenu);\n this.removeEventListener(\"pointerdown\", this.handlePointerDown as EventListener);\n if (this._wasPlayingBeforeScrub) {\n this._wasPlayingBeforeScrub = false;\n if ((this.context as unknown as Element | null)?.isConnected) {\n this.context!.play();\n }\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-scrubber\": EFScrubber;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit20 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html19 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFTimeDisplay.d.ts
|
|
7
7
|
declare const EFTimeDisplay_base: (new (...args: any[]) => {
|
|
@@ -10,7 +10,7 @@ declare const EFTimeDisplay_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFTimeDisplay extends EFTimeDisplay_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit20.CSSResult;
|
|
14
14
|
currentTimeMs: number;
|
|
15
15
|
durationMs: number;
|
|
16
16
|
contextCurrentTimeMs: number;
|
|
@@ -18,7 +18,7 @@ declare class EFTimeDisplay extends EFTimeDisplay_base {
|
|
|
18
18
|
get effectiveCurrentTimeMs(): number;
|
|
19
19
|
get effectiveDurationMs(): number;
|
|
20
20
|
private formatTime;
|
|
21
|
-
render():
|
|
21
|
+
render(): lit_html19.TemplateResult<1>;
|
|
22
22
|
}
|
|
23
23
|
declare global {
|
|
24
24
|
interface HTMLElementTagNameMap {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFTimeDisplay.js","names":["EFTimeDisplay"],"sources":["../../src/gui/EFTimeDisplay.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { efContext } from \"./efContext.js\";\nimport { TargetOrContextMixin } from \"./TargetOrContextMixin.js\";\n\n@customElement(\"ef-time-display\")\nexport class EFTimeDisplay extends TargetOrContextMixin(LitElement, efContext) {\n static styles = css`\n :host {\n display: inline-flex;\n align-items: center;\n font-family: var(--ef-font-family, system-ui);\n font-size: var(--ef-font-size-xs, 0.75rem);\n color: var(--ef-text-color, var(--ef-color-text));\n white-space: nowrap;\n }\n\n ::part(time) {}\n `;\n\n @property({ type: Number, attribute: \"current-time-ms\" })\n currentTimeMs = Number.NaN;\n\n @property({ type: Number, attribute: \"duration-ms\" })\n durationMs = 0;\n\n @consume({ context: currentTimeContext, subscribe: true })\n contextCurrentTimeMs = Number.NaN;\n\n @consume({ context: durationContext, subscribe: true })\n contextDurationMs = 0;\n\n get effectiveCurrentTimeMs(): number {\n if (!Number.isNaN(this.currentTimeMs)) return this.currentTimeMs;\n if (!Number.isNaN(this.contextCurrentTimeMs))
|
|
1
|
+
{"version":3,"file":"EFTimeDisplay.js","names":["EFTimeDisplay"],"sources":["../../src/gui/EFTimeDisplay.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { efContext } from \"./efContext.js\";\nimport { TargetOrContextMixin } from \"./TargetOrContextMixin.js\";\n\n@customElement(\"ef-time-display\")\nexport class EFTimeDisplay extends TargetOrContextMixin(LitElement, efContext) {\n static styles = css`\n :host {\n display: inline-flex;\n align-items: center;\n font-family: var(--ef-font-family, system-ui);\n font-size: var(--ef-font-size-xs, 0.75rem);\n color: var(--ef-text-color, var(--ef-color-text));\n white-space: nowrap;\n }\n\n ::part(time) {}\n `;\n\n @property({ type: Number, attribute: \"current-time-ms\" })\n currentTimeMs = Number.NaN;\n\n @property({ type: Number, attribute: \"duration-ms\" })\n durationMs = 0;\n\n @consume({ context: currentTimeContext, subscribe: true })\n contextCurrentTimeMs = Number.NaN;\n\n @consume({ context: durationContext, subscribe: true })\n contextDurationMs = 0;\n\n get effectiveCurrentTimeMs(): number {\n if (!Number.isNaN(this.currentTimeMs)) return this.currentTimeMs;\n if (!Number.isNaN(this.contextCurrentTimeMs)) return this.contextCurrentTimeMs;\n return 0;\n }\n\n get effectiveDurationMs(): number {\n return this.durationMs || this.contextDurationMs || 0;\n }\n\n private formatTime(ms: number): string {\n // Handle NaN, undefined, null, or negative values\n if (!Number.isFinite(ms) || ms < 0) {\n return \"0:00\";\n }\n\n const totalSeconds = Math.floor(ms / 1000);\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n return `${minutes}:${seconds.toString().padStart(2, \"0\")}`;\n }\n\n render() {\n const currentTime = this.effectiveCurrentTimeMs;\n const totalTime = this.effectiveDurationMs;\n\n return html`\n <span part=\"time\">\n ${this.formatTime(currentTime)} / ${this.formatTime(totalTime)}\n </span>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-time-display\": EFTimeDisplay;\n }\n}\n"],"mappings":";;;;;;;;;;AASO,0BAAMA,wBAAsB,qBAAqB,YAAY,UAAU,CAAC;;;uBAe7D;oBAGH;8BAGU;2BAGH;;;gBAvBJ,GAAG;;;;;;;;;;;;;CAyBnB,IAAI,yBAAiC;AACnC,MAAI,CAAC,OAAO,MAAM,KAAK,cAAc,CAAE,QAAO,KAAK;AACnD,MAAI,CAAC,OAAO,MAAM,KAAK,qBAAqB,CAAE,QAAO,KAAK;AAC1D,SAAO;;CAGT,IAAI,sBAA8B;AAChC,SAAO,KAAK,cAAc,KAAK,qBAAqB;;CAGtD,AAAQ,WAAW,IAAoB;AAErC,MAAI,CAAC,OAAO,SAAS,GAAG,IAAI,KAAK,EAC/B,QAAO;EAGT,MAAM,eAAe,KAAK,MAAM,KAAK,IAAK;AAG1C,SAAO,GAFS,KAAK,MAAM,eAAe,GAAG,CAE3B,IADF,eAAe,IACF,UAAU,CAAC,SAAS,GAAG,IAAI;;CAG1D,SAAS;EACP,MAAM,cAAc,KAAK;EACzB,MAAM,YAAY,KAAK;AAEvB,SAAO,IAAI;;UAEL,KAAK,WAAW,YAAY,CAAC,KAAK,KAAK,WAAW,UAAU,CAAC;;;;;YAxCpE,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAmB,CAAC;YAGxD,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAe,CAAC;YAGpD,QAAQ;CAAE,SAAS;CAAoB,WAAW;CAAM,CAAC;YAGzD,QAAQ;CAAE,SAAS;CAAiB,WAAW;CAAM,CAAC;4BAxBxD,cAAc,kBAAkB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TimelineState } from "./timeline/timelineStateContext.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit33 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html31 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFTimelineRuler.d.ts
|
|
7
7
|
/**
|
|
@@ -25,7 +25,7 @@ declare function calculatePixelsPerFrame(frameIntervalMs: number, pixelsPerMs: n
|
|
|
25
25
|
*/
|
|
26
26
|
declare function shouldShowFrameMarkers(pixelsPerFrame: number, minSpacing?: number): boolean;
|
|
27
27
|
declare class EFTimelineRuler extends LitElement {
|
|
28
|
-
static styles:
|
|
28
|
+
static styles: lit33.CSSResult[];
|
|
29
29
|
durationMs: number;
|
|
30
30
|
contextDurationMs: number;
|
|
31
31
|
timelineState?: TimelineState;
|
|
@@ -59,7 +59,7 @@ declare class EFTimelineRuler extends LitElement {
|
|
|
59
59
|
private calculateLabelInterval;
|
|
60
60
|
private getVisibleLabels;
|
|
61
61
|
private renderCanvas;
|
|
62
|
-
render():
|
|
62
|
+
render(): lit_html31.TemplateResult<1>;
|
|
63
63
|
}
|
|
64
64
|
declare global {
|
|
65
65
|
interface HTMLElementTagNameMap {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFTimelineRuler.js","names":["EFTimelineRuler","labels: VisibleLabel[]"],"sources":["../../src/gui/EFTimelineRuler.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport {\n timelineStateContext,\n type TimelineState,\n DEFAULT_PIXELS_PER_MS,\n} from \"./timeline/timelineStateContext.js\";\n\nconst MIN_LABEL_SPACING_PX = 80;\nconst MIN_FRAME_SPACING_PX = 5;\n\n/** Maximum canvas width for ruler virtualization */\nconst MAX_RULER_CANVAS_WIDTH = 2000;\n\n/** Buffer pixels on each side of viewport for smooth scrolling */\nconst RULER_CANVAS_BUFFER = 200;\n\n/**\n * Quantize a time value to the nearest frame boundary.\n * This ensures frame markers align perfectly with playhead position.\n */\nexport function quantizeToFrameTimeMs(timeMs: number, fps: number): number {\n if (!fps || fps <= 0) return timeMs;\n const frameDurationS = 1 / fps;\n const timeSeconds = timeMs / 1000;\n const quantizedSeconds =\n Math.round(timeSeconds / frameDurationS) * frameDurationS;\n return quantizedSeconds * 1000;\n}\n\n/**\n * Calculate the duration of a single frame in milliseconds.\n */\nexport function calculateFrameIntervalMs(fps: number): number {\n if (fps <= 0) return 1000 / 30; // fallback to 30fps\n return 1000 / fps;\n}\n\n/**\n * Calculate pixels per frame given frame interval and zoom scale.\n * @param frameIntervalMs Duration of one frame in ms\n * @param pixelsPerMs Current zoom level (pixels per millisecond)\n */\nexport function calculatePixelsPerFrame(\n frameIntervalMs: number,\n pixelsPerMs: number,\n): number {\n return frameIntervalMs * pixelsPerMs;\n}\n\n/**\n * Determine if frame markers should be visible at the current zoom level.\n * Frame markers appear when each frame is at least MIN_FRAME_SPACING_PX wide.\n */\nexport function shouldShowFrameMarkers(\n pixelsPerFrame: number,\n minSpacing: number = MIN_FRAME_SPACING_PX,\n): boolean {\n return pixelsPerFrame >= minSpacing;\n}\n\ninterface VisibleLabel {\n timeMs: number;\n viewportX: number;\n text: string;\n}\n\n@customElement(\"ef-timeline-ruler\")\nexport class EFTimelineRuler extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n height: 100%;\n pointer-events: none;\n }\n \n .ruler-container {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n \n .canvas-viewport {\n position: absolute;\n top: 0;\n height: 100%;\n /* left and width set via inline styles for virtualization */\n }\n \n canvas {\n display: block;\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n }\n \n .label {\n position: absolute;\n top: 50%;\n font-size: 10px;\n color: var(--ef-color-text-muted);\n font-family: ui-monospace, monospace;\n white-space: nowrap;\n pointer-events: none;\n user-select: none;\n }\n `,\n ];\n\n @property({ type: Number, attribute: \"duration-ms\" })\n durationMs = 0;\n\n @consume({ context: durationContext, subscribe: true })\n contextDurationMs = 0;\n\n @consume({ context: timelineStateContext, subscribe: true })\n timelineState?: TimelineState;\n\n @property({ type: Number, attribute: \"fps\" })\n fps = 30;\n\n /** Full content width in pixels (for virtualization) */\n @property({ type: Number, attribute: \"content-width\" })\n contentWidth = 0;\n\n private containerRef = createRef<HTMLDivElement>();\n private canvasRef = createRef<HTMLCanvasElement>();\n private resizeObserver?: ResizeObserver;\n\n @state()\n private viewportWidth = 0;\n\n /** Canvas viewport left position for virtualization */\n @state()\n private _canvasViewportLeft = 0;\n\n /** Canvas viewport width for virtualization */\n @state()\n private _canvasViewportWidth = 0;\n\n /** Last rendered scroll position - for detecting scroll changes */\n private _lastRenderedScrollLeft = -1;\n\n /** Last rendered viewport width - for detecting viewport changes */\n private _lastRenderedViewportWidth = 0;\n\n get effectiveDurationMs(): number {\n return this.durationMs || this.contextDurationMs || 0;\n }\n\n get pixelsPerMs(): number {\n return this.timelineState?.pixelsPerMs ?? DEFAULT_PIXELS_PER_MS;\n }\n\n get scrollLeft(): number {\n return this.timelineState?.viewportScrollLeft ?? 0;\n }\n\n /**\n * Calculate canvas viewport bounds for virtualization.\n * Returns the left position and width of the canvas viewport.\n */\n private calculateCanvasViewport(): { left: number; width: number } {\n const totalWidth = this.contentWidth || this.viewportWidth;\n\n // If content is small enough, no virtualization needed\n if (totalWidth <= MAX_RULER_CANVAS_WIDTH) {\n return { left: 0, width: totalWidth };\n }\n\n // Get visible region from scroll position\n const viewportScrollLeft = this.scrollLeft;\n const viewportWidth =\n this.timelineState?.viewportWidth ?? this.viewportWidth;\n\n // Calculate canvas viewport with buffer for smooth scrolling\n const canvasLeft = Math.max(0, viewportScrollLeft - RULER_CANVAS_BUFFER);\n const canvasRight = Math.min(\n totalWidth,\n viewportScrollLeft + viewportWidth + RULER_CANVAS_BUFFER,\n );\n\n // Cap canvas width at maximum\n let canvasWidth = canvasRight - canvasLeft;\n if (canvasWidth > MAX_RULER_CANVAS_WIDTH) {\n canvasWidth = MAX_RULER_CANVAS_WIDTH;\n }\n\n return { left: canvasLeft, width: canvasWidth };\n }\n\n connectedCallback() {\n super.connectedCallback();\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n }\n\n protected firstUpdated(): void {\n const container = this.containerRef.value;\n if (container) {\n this.resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (entry) {\n const width = entry.contentRect.width;\n if (width !== this.viewportWidth) {\n this.viewportWidth = width;\n }\n }\n });\n this.resizeObserver.observe(container);\n this.viewportWidth = container.clientWidth;\n }\n }\n\n protected updated(\n changedProperties: Map<string | number | symbol, unknown>,\n ): void {\n // Check if scroll position or viewport changed from context\n const currentScrollLeft = this.scrollLeft;\n const currentViewportWidth =\n this.timelineState?.viewportWidth ?? this.viewportWidth;\n\n // Check if scroll changed, viewport changed, or other relevant properties changed\n const scrollChanged = currentScrollLeft !== this._lastRenderedScrollLeft;\n const viewportChanged =\n currentViewportWidth !== this._lastRenderedViewportWidth;\n const pixelsPerMsChanged =\n changedProperties.has(\"timelineState\") ||\n changedProperties.has(\"pixelsPerMs\");\n const contentWidthChanged = changedProperties.has(\"contentWidth\");\n const durationChanged =\n changedProperties.has(\"durationMs\") ||\n changedProperties.has(\"contextDurationMs\");\n\n // Only render if something actually changed\n if (\n scrollChanged ||\n viewportChanged ||\n pixelsPerMsChanged ||\n contentWidthChanged ||\n durationChanged ||\n this._lastRenderedScrollLeft < 0\n ) {\n this.renderCanvas();\n this._lastRenderedScrollLeft = currentScrollLeft;\n this._lastRenderedViewportWidth = currentViewportWidth;\n }\n }\n\n private calculateLabelInterval(): number {\n const pixelsPerMs = this.pixelsPerMs;\n const pixelsPerSecond = pixelsPerMs * 1000;\n\n const intervals = [0.1, 0.25, 0.5, 1, 2, 5, 10, 30, 60];\n\n for (const intervalS of intervals) {\n const pixelsPerInterval = intervalS * pixelsPerSecond;\n if (pixelsPerInterval >= MIN_LABEL_SPACING_PX) {\n return intervalS * 1000;\n }\n }\n\n return 60000;\n }\n\n private getVisibleLabels(): VisibleLabel[] {\n const canvasWidth = this._canvasViewportWidth;\n if (canvasWidth <= 0) return [];\n\n const pixelsPerMs = this.pixelsPerMs;\n const canvasLeft = this._canvasViewportLeft;\n\n const intervalMs = this.calculateLabelInterval();\n\n // Generate labels for the canvas viewport range\n const visibleStartTimeMs = Math.max(\n 0,\n canvasLeft / pixelsPerMs - intervalMs,\n );\n const visibleEndTimeMs =\n (canvasLeft + canvasWidth) / pixelsPerMs + intervalMs;\n\n const firstLabelIndex = Math.floor(visibleStartTimeMs / intervalMs);\n const lastLabelIndex = Math.ceil(visibleEndTimeMs / intervalMs);\n\n const labels: VisibleLabel[] = [];\n\n for (let i = firstLabelIndex; i <= lastLabelIndex; i++) {\n const timeMs = i * intervalMs;\n if (timeMs < 0) continue;\n\n const absoluteX = timeMs * pixelsPerMs;\n // Position relative to canvas viewport (canvas is positioned at canvasLeft)\n const viewportX = absoluteX - canvasLeft;\n\n if (viewportX >= -50 && viewportX <= canvasWidth + 50) {\n const timeSeconds = timeMs / 1000;\n const text =\n timeSeconds % 1 === 0\n ? `${timeSeconds}s`\n : `${timeSeconds.toFixed(1)}s`;\n\n labels.push({ timeMs, viewportX, text });\n }\n }\n\n return labels;\n }\n\n private renderCanvas(): void {\n const canvas = this.canvasRef.value;\n const container = this.containerRef.value;\n if (!canvas || !container) return;\n\n // Calculate virtualized canvas viewport\n const viewport = this.calculateCanvasViewport();\n this._canvasViewportLeft = viewport.left;\n this._canvasViewportWidth = viewport.width;\n\n const width = viewport.width;\n const height = container.getBoundingClientRect().height;\n\n if (width <= 0 || height <= 0) return;\n\n const dpr = window.devicePixelRatio || 1;\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n ctx.scale(dpr, dpr);\n ctx.clearRect(0, 0, width, height);\n\n const pixelsPerMs = this.pixelsPerMs;\n const canvasLeft = viewport.left;\n\n // Time label ticks - more prominent\n ctx.strokeStyle =\n getComputedStyle(this).getPropertyValue(\"--ef-color-text-muted\").trim() ||\n \"rgb(156, 163, 175)\";\n ctx.lineWidth = 1;\n\n const labelIntervalMs = this.calculateLabelInterval();\n // Fill the canvas viewport with ticks\n const visibleStartTimeMs = Math.max(\n 0,\n canvasLeft / pixelsPerMs - labelIntervalMs,\n );\n const visibleEndTimeMs =\n (canvasLeft + width) / pixelsPerMs + labelIntervalMs;\n\n const firstTickIndex = Math.floor(visibleStartTimeMs / labelIntervalMs);\n const lastTickIndex = Math.ceil(visibleEndTimeMs / labelIntervalMs);\n\n for (let i = firstTickIndex; i <= lastTickIndex; i++) {\n const timeMs = i * labelIntervalMs;\n if (timeMs < 0) continue;\n\n const absoluteX = timeMs * pixelsPerMs;\n // Position relative to canvas viewport\n const canvasX = absoluteX - canvasLeft;\n\n if (canvasX >= -1 && canvasX <= width + 1) {\n ctx.beginPath();\n ctx.moveTo(canvasX, 0);\n ctx.lineTo(canvasX, height * 0.4);\n ctx.stroke();\n }\n }\n\n const frameIntervalMs = 1000 / this.fps;\n const pixelsPerFrame = frameIntervalMs * pixelsPerMs;\n\n if (pixelsPerFrame >= MIN_FRAME_SPACING_PX) {\n // Frame markers should be lighter than background to be visible\n ctx.strokeStyle =\n getComputedStyle(this)\n .getPropertyValue(\"--ef-color-border-subtle\")\n .trim() || \"rgb(107, 114, 128)\";\n ctx.lineWidth = 1;\n\n const firstFrameIndex = Math.floor(visibleStartTimeMs / frameIntervalMs);\n const lastFrameIndex = Math.ceil(visibleEndTimeMs / frameIntervalMs);\n\n for (let i = firstFrameIndex; i <= lastFrameIndex; i++) {\n const timeMs = i * frameIntervalMs;\n if (timeMs < 0) continue;\n\n if (timeMs % labelIntervalMs === 0) continue;\n\n const absoluteX = timeMs * pixelsPerMs;\n // Position relative to canvas viewport\n const canvasX = absoluteX - canvasLeft;\n\n if (canvasX >= -1 && canvasX <= width + 1) {\n ctx.beginPath();\n ctx.moveTo(canvasX, 0);\n ctx.lineTo(canvasX, height * 0.25);\n ctx.stroke();\n }\n }\n }\n }\n\n render() {\n const visibleLabels = this.getVisibleLabels();\n\n const canvasViewportStyles = styleMap({\n left: `${this._canvasViewportLeft}px`,\n width: `${this._canvasViewportWidth}px`,\n });\n\n return html`\n <div ${ref(this.containerRef)} class=\"ruler-container\">\n <div class=\"canvas-viewport\" style=${canvasViewportStyles}>\n <canvas ${ref(this.canvasRef)}></canvas>\n ${visibleLabels.map(\n ({ viewportX, text }) => html`\n <span \n class=\"label\" \n style=\"transform: translateX(${viewportX}px)\"\n >${text}</span>\n `,\n )}\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timeline-ruler\": EFTimelineRuler;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFTimelineRuler.js","names":["EFTimelineRuler","labels: VisibleLabel[]"],"sources":["../../src/gui/EFTimelineRuler.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport {\n timelineStateContext,\n type TimelineState,\n DEFAULT_PIXELS_PER_MS,\n} from \"./timeline/timelineStateContext.js\";\n\nconst MIN_LABEL_SPACING_PX = 80;\nconst MIN_FRAME_SPACING_PX = 5;\n\n/** Maximum canvas width for ruler virtualization */\nconst MAX_RULER_CANVAS_WIDTH = 2000;\n\n/** Buffer pixels on each side of viewport for smooth scrolling */\nconst RULER_CANVAS_BUFFER = 200;\n\n/**\n * Quantize a time value to the nearest frame boundary.\n * This ensures frame markers align perfectly with playhead position.\n */\nexport function quantizeToFrameTimeMs(timeMs: number, fps: number): number {\n if (!fps || fps <= 0) return timeMs;\n const frameDurationS = 1 / fps;\n const timeSeconds = timeMs / 1000;\n const quantizedSeconds = Math.round(timeSeconds / frameDurationS) * frameDurationS;\n return quantizedSeconds * 1000;\n}\n\n/**\n * Calculate the duration of a single frame in milliseconds.\n */\nexport function calculateFrameIntervalMs(fps: number): number {\n if (fps <= 0) return 1000 / 30; // fallback to 30fps\n return 1000 / fps;\n}\n\n/**\n * Calculate pixels per frame given frame interval and zoom scale.\n * @param frameIntervalMs Duration of one frame in ms\n * @param pixelsPerMs Current zoom level (pixels per millisecond)\n */\nexport function calculatePixelsPerFrame(frameIntervalMs: number, pixelsPerMs: number): number {\n return frameIntervalMs * pixelsPerMs;\n}\n\n/**\n * Determine if frame markers should be visible at the current zoom level.\n * Frame markers appear when each frame is at least MIN_FRAME_SPACING_PX wide.\n */\nexport function shouldShowFrameMarkers(\n pixelsPerFrame: number,\n minSpacing: number = MIN_FRAME_SPACING_PX,\n): boolean {\n return pixelsPerFrame >= minSpacing;\n}\n\ninterface VisibleLabel {\n timeMs: number;\n viewportX: number;\n text: string;\n}\n\n@customElement(\"ef-timeline-ruler\")\nexport class EFTimelineRuler extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n height: 100%;\n pointer-events: none;\n }\n \n .ruler-container {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n \n .canvas-viewport {\n position: absolute;\n top: 0;\n height: 100%;\n /* left and width set via inline styles for virtualization */\n }\n \n canvas {\n display: block;\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n }\n \n .label {\n position: absolute;\n top: 50%;\n font-size: 10px;\n color: var(--ef-color-text-muted);\n font-family: ui-monospace, monospace;\n white-space: nowrap;\n pointer-events: none;\n user-select: none;\n }\n `,\n ];\n\n @property({ type: Number, attribute: \"duration-ms\" })\n durationMs = 0;\n\n @consume({ context: durationContext, subscribe: true })\n contextDurationMs = 0;\n\n @consume({ context: timelineStateContext, subscribe: true })\n timelineState?: TimelineState;\n\n @property({ type: Number, attribute: \"fps\" })\n fps = 30;\n\n /** Full content width in pixels (for virtualization) */\n @property({ type: Number, attribute: \"content-width\" })\n contentWidth = 0;\n\n private containerRef = createRef<HTMLDivElement>();\n private canvasRef = createRef<HTMLCanvasElement>();\n private resizeObserver?: ResizeObserver;\n\n @state()\n private viewportWidth = 0;\n\n /** Canvas viewport left position for virtualization */\n @state()\n private _canvasViewportLeft = 0;\n\n /** Canvas viewport width for virtualization */\n @state()\n private _canvasViewportWidth = 0;\n\n /** Last rendered scroll position - for detecting scroll changes */\n private _lastRenderedScrollLeft = -1;\n\n /** Last rendered viewport width - for detecting viewport changes */\n private _lastRenderedViewportWidth = 0;\n\n get effectiveDurationMs(): number {\n return this.durationMs || this.contextDurationMs || 0;\n }\n\n get pixelsPerMs(): number {\n return this.timelineState?.pixelsPerMs ?? DEFAULT_PIXELS_PER_MS;\n }\n\n get scrollLeft(): number {\n return this.timelineState?.viewportScrollLeft ?? 0;\n }\n\n /**\n * Calculate canvas viewport bounds for virtualization.\n * Returns the left position and width of the canvas viewport.\n */\n private calculateCanvasViewport(): { left: number; width: number } {\n const totalWidth = this.contentWidth || this.viewportWidth;\n\n // If content is small enough, no virtualization needed\n if (totalWidth <= MAX_RULER_CANVAS_WIDTH) {\n return { left: 0, width: totalWidth };\n }\n\n // Get visible region from scroll position\n const viewportScrollLeft = this.scrollLeft;\n const viewportWidth = this.timelineState?.viewportWidth ?? this.viewportWidth;\n\n // Calculate canvas viewport with buffer for smooth scrolling\n const canvasLeft = Math.max(0, viewportScrollLeft - RULER_CANVAS_BUFFER);\n const canvasRight = Math.min(\n totalWidth,\n viewportScrollLeft + viewportWidth + RULER_CANVAS_BUFFER,\n );\n\n // Cap canvas width at maximum\n let canvasWidth = canvasRight - canvasLeft;\n if (canvasWidth > MAX_RULER_CANVAS_WIDTH) {\n canvasWidth = MAX_RULER_CANVAS_WIDTH;\n }\n\n return { left: canvasLeft, width: canvasWidth };\n }\n\n connectedCallback() {\n super.connectedCallback();\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n }\n\n protected firstUpdated(): void {\n const container = this.containerRef.value;\n if (container) {\n this.resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (entry) {\n const width = entry.contentRect.width;\n if (width !== this.viewportWidth) {\n this.viewportWidth = width;\n }\n }\n });\n this.resizeObserver.observe(container);\n this.viewportWidth = container.clientWidth;\n }\n }\n\n protected updated(changedProperties: Map<string | number | symbol, unknown>): void {\n // Check if scroll position or viewport changed from context\n const currentScrollLeft = this.scrollLeft;\n const currentViewportWidth = this.timelineState?.viewportWidth ?? this.viewportWidth;\n\n // Check if scroll changed, viewport changed, or other relevant properties changed\n const scrollChanged = currentScrollLeft !== this._lastRenderedScrollLeft;\n const viewportChanged = currentViewportWidth !== this._lastRenderedViewportWidth;\n const pixelsPerMsChanged =\n changedProperties.has(\"timelineState\") || changedProperties.has(\"pixelsPerMs\");\n const contentWidthChanged = changedProperties.has(\"contentWidth\");\n const durationChanged =\n changedProperties.has(\"durationMs\") || changedProperties.has(\"contextDurationMs\");\n\n // Only render if something actually changed\n if (\n scrollChanged ||\n viewportChanged ||\n pixelsPerMsChanged ||\n contentWidthChanged ||\n durationChanged ||\n this._lastRenderedScrollLeft < 0\n ) {\n this.renderCanvas();\n this._lastRenderedScrollLeft = currentScrollLeft;\n this._lastRenderedViewportWidth = currentViewportWidth;\n }\n }\n\n private calculateLabelInterval(): number {\n const pixelsPerMs = this.pixelsPerMs;\n const pixelsPerSecond = pixelsPerMs * 1000;\n\n const intervals = [0.1, 0.25, 0.5, 1, 2, 5, 10, 30, 60];\n\n for (const intervalS of intervals) {\n const pixelsPerInterval = intervalS * pixelsPerSecond;\n if (pixelsPerInterval >= MIN_LABEL_SPACING_PX) {\n return intervalS * 1000;\n }\n }\n\n return 60000;\n }\n\n private getVisibleLabels(): VisibleLabel[] {\n const canvasWidth = this._canvasViewportWidth;\n if (canvasWidth <= 0) return [];\n\n const pixelsPerMs = this.pixelsPerMs;\n const canvasLeft = this._canvasViewportLeft;\n\n const intervalMs = this.calculateLabelInterval();\n\n // Generate labels for the canvas viewport range\n const visibleStartTimeMs = Math.max(0, canvasLeft / pixelsPerMs - intervalMs);\n const visibleEndTimeMs = (canvasLeft + canvasWidth) / pixelsPerMs + intervalMs;\n\n const firstLabelIndex = Math.floor(visibleStartTimeMs / intervalMs);\n const lastLabelIndex = Math.ceil(visibleEndTimeMs / intervalMs);\n\n const labels: VisibleLabel[] = [];\n\n for (let i = firstLabelIndex; i <= lastLabelIndex; i++) {\n const timeMs = i * intervalMs;\n if (timeMs < 0) continue;\n\n const absoluteX = timeMs * pixelsPerMs;\n // Position relative to canvas viewport (canvas is positioned at canvasLeft)\n const viewportX = absoluteX - canvasLeft;\n\n if (viewportX >= -50 && viewportX <= canvasWidth + 50) {\n const timeSeconds = timeMs / 1000;\n const text = timeSeconds % 1 === 0 ? `${timeSeconds}s` : `${timeSeconds.toFixed(1)}s`;\n\n labels.push({ timeMs, viewportX, text });\n }\n }\n\n return labels;\n }\n\n private renderCanvas(): void {\n const canvas = this.canvasRef.value;\n const container = this.containerRef.value;\n if (!canvas || !container) return;\n\n // Calculate virtualized canvas viewport\n const viewport = this.calculateCanvasViewport();\n this._canvasViewportLeft = viewport.left;\n this._canvasViewportWidth = viewport.width;\n\n const width = viewport.width;\n const height = container.getBoundingClientRect().height;\n\n if (width <= 0 || height <= 0) return;\n\n const dpr = window.devicePixelRatio || 1;\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n ctx.scale(dpr, dpr);\n ctx.clearRect(0, 0, width, height);\n\n const pixelsPerMs = this.pixelsPerMs;\n const canvasLeft = viewport.left;\n\n // Time label ticks - more prominent\n ctx.strokeStyle =\n getComputedStyle(this).getPropertyValue(\"--ef-color-text-muted\").trim() ||\n \"rgb(156, 163, 175)\";\n ctx.lineWidth = 1;\n\n const labelIntervalMs = this.calculateLabelInterval();\n // Fill the canvas viewport with ticks\n const visibleStartTimeMs = Math.max(0, canvasLeft / pixelsPerMs - labelIntervalMs);\n const visibleEndTimeMs = (canvasLeft + width) / pixelsPerMs + labelIntervalMs;\n\n const firstTickIndex = Math.floor(visibleStartTimeMs / labelIntervalMs);\n const lastTickIndex = Math.ceil(visibleEndTimeMs / labelIntervalMs);\n\n for (let i = firstTickIndex; i <= lastTickIndex; i++) {\n const timeMs = i * labelIntervalMs;\n if (timeMs < 0) continue;\n\n const absoluteX = timeMs * pixelsPerMs;\n // Position relative to canvas viewport\n const canvasX = absoluteX - canvasLeft;\n\n if (canvasX >= -1 && canvasX <= width + 1) {\n ctx.beginPath();\n ctx.moveTo(canvasX, 0);\n ctx.lineTo(canvasX, height * 0.4);\n ctx.stroke();\n }\n }\n\n const frameIntervalMs = 1000 / this.fps;\n const pixelsPerFrame = frameIntervalMs * pixelsPerMs;\n\n if (pixelsPerFrame >= MIN_FRAME_SPACING_PX) {\n // Frame markers should be lighter than background to be visible\n ctx.strokeStyle =\n getComputedStyle(this).getPropertyValue(\"--ef-color-border-subtle\").trim() ||\n \"rgb(107, 114, 128)\";\n ctx.lineWidth = 1;\n\n const firstFrameIndex = Math.floor(visibleStartTimeMs / frameIntervalMs);\n const lastFrameIndex = Math.ceil(visibleEndTimeMs / frameIntervalMs);\n\n for (let i = firstFrameIndex; i <= lastFrameIndex; i++) {\n const timeMs = i * frameIntervalMs;\n if (timeMs < 0) continue;\n\n if (timeMs % labelIntervalMs === 0) continue;\n\n const absoluteX = timeMs * pixelsPerMs;\n // Position relative to canvas viewport\n const canvasX = absoluteX - canvasLeft;\n\n if (canvasX >= -1 && canvasX <= width + 1) {\n ctx.beginPath();\n ctx.moveTo(canvasX, 0);\n ctx.lineTo(canvasX, height * 0.25);\n ctx.stroke();\n }\n }\n }\n }\n\n render() {\n const visibleLabels = this.getVisibleLabels();\n\n const canvasViewportStyles = styleMap({\n left: `${this._canvasViewportLeft}px`,\n width: `${this._canvasViewportWidth}px`,\n });\n\n return html`\n <div ${ref(this.containerRef)} class=\"ruler-container\">\n <div class=\"canvas-viewport\" style=${canvasViewportStyles}>\n <canvas ${ref(this.canvasRef)}></canvas>\n ${visibleLabels.map(\n ({ viewportX, text }) => html`\n <span \n class=\"label\" \n style=\"transform: translateX(${viewportX}px)\"\n >${text}</span>\n `,\n )}\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timeline-ruler\": EFTimelineRuler;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit18 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html17 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFToggleLoop.d.ts
|
|
7
7
|
declare const EFToggleLoop_base: (new (...args: any[]) => {
|
|
@@ -10,9 +10,9 @@ declare const EFToggleLoop_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFToggleLoop extends EFToggleLoop_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit18.CSSResult[];
|
|
14
14
|
get context(): ControllableInterface | null;
|
|
15
|
-
render():
|
|
15
|
+
render(): lit_html17.TemplateResult<1>;
|
|
16
16
|
}
|
|
17
17
|
declare global {
|
|
18
18
|
interface HTMLElementTagNameMap {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit15 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html14 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFTogglePlay.d.ts
|
|
7
7
|
declare const EFTogglePlay_base: (new (...args: any[]) => {
|
|
@@ -10,12 +10,12 @@ declare const EFTogglePlay_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFTogglePlay extends EFTogglePlay_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit15.CSSResult[];
|
|
14
14
|
playing: boolean;
|
|
15
15
|
get efContext(): ControllableInterface | null;
|
|
16
16
|
connectedCallback(): void;
|
|
17
17
|
disconnectedCallback(): void;
|
|
18
|
-
render():
|
|
18
|
+
render(): lit_html14.TemplateResult<1>;
|
|
19
19
|
togglePlay: () => void;
|
|
20
20
|
private getPlaybackController;
|
|
21
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFTogglePlay.js","names":["EFTogglePlay"],"sources":["../../src/gui/EFTogglePlay.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, state } from \"lit/decorators.js\";\nimport { attachContextRoot } from \"../attachContextRoot.js\";\nimport { isEFTemporal } from \"../elements/EFTemporal.js\";\nimport type { ControllableInterface } from \"./Controllable.js\";\nimport { efContext } from \"./efContext.js\";\nimport { playingContext } from \"./playingContext.js\";\nimport type { PlaybackController } from \"./PlaybackController.js\";\nimport { TargetOrContextMixin } from \"./TargetOrContextMixin.js\";\n\nattachContextRoot();\n\n@customElement(\"ef-toggle-play\")\nexport class EFTogglePlay extends TargetOrContextMixin(LitElement, efContext) {\n static styles = [\n css`\n :host {}\n div {\n all: inherit;\n }\n `,\n ];\n\n @consume({ context: playingContext, subscribe: true })\n @state()\n playing = false;\n\n get efContext(): ControllableInterface | null {\n return this.effectiveContext;\n }\n\n // Attach click listener to host\n connectedCallback() {\n super.connectedCallback();\n this.addEventListener(\"click\", this.togglePlay);\n }\n\n // Detach click listener from host\n disconnectedCallback() {\n super.disconnectedCallback();\n this.removeEventListener(\"click\", this.togglePlay);\n }\n\n render() {\n return html`\n <div>\n ${
|
|
1
|
+
{"version":3,"file":"EFTogglePlay.js","names":["EFTogglePlay"],"sources":["../../src/gui/EFTogglePlay.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, state } from \"lit/decorators.js\";\nimport { attachContextRoot } from \"../attachContextRoot.js\";\nimport { isEFTemporal } from \"../elements/EFTemporal.js\";\nimport type { ControllableInterface } from \"./Controllable.js\";\nimport { efContext } from \"./efContext.js\";\nimport { playingContext } from \"./playingContext.js\";\nimport type { PlaybackController } from \"./PlaybackController.js\";\nimport { TargetOrContextMixin } from \"./TargetOrContextMixin.js\";\n\nattachContextRoot();\n\n@customElement(\"ef-toggle-play\")\nexport class EFTogglePlay extends TargetOrContextMixin(LitElement, efContext) {\n static styles = [\n css`\n :host {}\n div {\n all: inherit;\n }\n `,\n ];\n\n @consume({ context: playingContext, subscribe: true })\n @state()\n playing = false;\n\n get efContext(): ControllableInterface | null {\n return this.effectiveContext;\n }\n\n // Attach click listener to host\n connectedCallback() {\n super.connectedCallback();\n this.addEventListener(\"click\", this.togglePlay);\n }\n\n // Detach click listener from host\n disconnectedCallback() {\n super.disconnectedCallback();\n this.removeEventListener(\"click\", this.togglePlay);\n }\n\n render() {\n return html`\n <div>\n ${this.playing ? html`<slot name=\"pause\"></slot>` : html`<slot name=\"play\"></slot>`}\n </div>\n `;\n }\n\n togglePlay = () => {\n if (this.efContext) {\n if (this.playing) {\n this.efContext.pause();\n } else {\n // Create and resume AudioContext synchronously within user interaction handler\n // This is required on mobile devices where AudioContext.resume() must be called\n // synchronously within a user interaction event handler\n const playbackController = this.getPlaybackController();\n if (playbackController) {\n try {\n const audioContext = new AudioContext({\n latencyHint: \"playback\",\n });\n // Resume synchronously (doesn't await, but initiates resume)\n // Once resumed via user interaction, the context stays \"unlocked\"\n audioContext.resume();\n playbackController.setPendingAudioContext(audioContext);\n } catch (error) {\n // If context creation/resume fails, continue with normal async flow\n // The fallback in startPlayback() will attempt resume (may not work on mobile)\n console.warn(\"Failed to create/resume AudioContext synchronously:\", error);\n }\n }\n this.efContext.play();\n }\n }\n };\n\n private getPlaybackController(): PlaybackController | null {\n const context = this.efContext;\n if (!context) {\n return null;\n }\n\n // Check if context is a temporal element with playbackController\n if (isEFTemporal(context) && context.playbackController) {\n return context.playbackController;\n }\n\n return null;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-toggle-play\": EFTogglePlay;\n }\n}\n"],"mappings":";;;;;;;;;;;AAWA,mBAAmB;AAGZ,yBAAMA,uBAAqB,qBAAqB,YAAY,UAAU,CAAC;;;iBAYlE;0BA0BS;AACjB,OAAI,KAAK,UACP,KAAI,KAAK,QACP,MAAK,UAAU,OAAO;QACjB;IAIL,MAAM,qBAAqB,KAAK,uBAAuB;AACvD,QAAI,mBACF,KAAI;KACF,MAAM,eAAe,IAAI,aAAa,EACpC,aAAa,YACd,CAAC;AAGF,kBAAa,QAAQ;AACrB,wBAAmB,uBAAuB,aAAa;aAChD,OAAO;AAGd,aAAQ,KAAK,uDAAuD,MAAM;;AAG9E,SAAK,UAAU,MAAM;;;;;gBA7DX,CACd,GAAG;;;;;MAMJ;;CAMD,IAAI,YAA0C;AAC5C,SAAO,KAAK;;CAId,oBAAoB;AAClB,QAAM,mBAAmB;AACzB,OAAK,iBAAiB,SAAS,KAAK,WAAW;;CAIjD,uBAAuB;AACrB,QAAM,sBAAsB;AAC5B,OAAK,oBAAoB,SAAS,KAAK,WAAW;;CAGpD,SAAS;AACP,SAAO,IAAI;;UAEL,KAAK,UAAU,IAAI,+BAA+B,IAAI,4BAA4B;;;;CAkC1F,AAAQ,wBAAmD;EACzD,MAAM,UAAU,KAAK;AACrB,MAAI,CAAC,QACH,QAAO;AAIT,MAAI,aAAa,QAAQ,IAAI,QAAQ,mBACnC,QAAO,QAAQ;AAGjB,SAAO;;;YApER,QAAQ;CAAE,SAAS;CAAgB,WAAW;CAAM,CAAC,EACrD,OAAO;2BAZT,cAAc,iBAAiB"}
|