@editframe/react 0.37.3-beta → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/TimelineRoot.d.ts +19 -12
- package/dist/components/TimelineRoot.js +34 -34
- package/dist/components/TimelineRoot.js.map +1 -1
- package/dist/gui/OverlayItem.js.map +1 -1
- package/dist/gui/Scrubber.d.ts +2 -0
- package/dist/gui/Scrubber.js.map +1 -1
- package/dist/{elements → gui}/ThumbnailStrip.d.ts +1 -1
- package/dist/{elements → gui}/ThumbnailStrip.js +2 -4
- package/dist/gui/ThumbnailStrip.js.map +1 -0
- package/dist/gui/TimelineRuler.js.map +1 -1
- package/dist/gui/TrimHandles.d.ts +11 -0
- package/dist/gui/TrimHandles.js +18 -0
- package/dist/gui/TrimHandles.js.map +1 -0
- package/dist/hooks/create-element.d.ts +10 -3
- package/dist/hooks/create-element.js +4 -1
- package/dist/hooks/create-element.js.map +1 -1
- package/dist/hooks/useMediaInfo.d.ts +15 -0
- package/dist/hooks/useMediaInfo.js +59 -0
- package/dist/hooks/useMediaInfo.js.map +1 -0
- package/dist/hooks/usePanZoomTransform.js.map +1 -1
- package/dist/hooks/useTimingInfo.d.ts +2 -2
- package/dist/hooks/useTimingInfo.js +12 -11
- package/dist/hooks/useTimingInfo.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/r3f/CompositionCanvas.d.ts +32 -0
- package/dist/r3f/CompositionCanvas.js +94 -0
- package/dist/r3f/CompositionCanvas.js.map +1 -0
- package/dist/r3f/OffscreenCompositionCanvas.d.ts +28 -0
- package/dist/r3f/OffscreenCompositionCanvas.js +119 -0
- package/dist/r3f/OffscreenCompositionCanvas.js.map +1 -0
- package/dist/r3f/index.d.ts +5 -0
- package/dist/r3f/index.js +5 -0
- package/dist/r3f/renderOffscreen.d.ts +27 -0
- package/dist/r3f/renderOffscreen.js +291 -0
- package/dist/r3f/renderOffscreen.js.map +1 -0
- package/dist/r3f/worker-protocol.d.ts +39 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +11 -0
- package/package.json +45 -11
- package/tsdown.config.ts +1 -0
- package/dist/elements/ThumbnailStrip.js.map +0 -1
|
@@ -2,10 +2,15 @@ import * as React$1 from "react";
|
|
|
2
2
|
|
|
3
3
|
//#region src/components/TimelineRoot.d.ts
|
|
4
4
|
interface TimelineRootProps {
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Unique identifier used for playback control targeting (Preview, Scrubber, TogglePlay).
|
|
7
|
+
* Also passed as `id` prop to the component for clone rendering.
|
|
8
|
+
*/
|
|
6
9
|
id: string;
|
|
7
|
-
/**
|
|
8
|
-
|
|
10
|
+
/**
|
|
11
|
+
* React component that renders the timeline content (must include a Timegroup at root).
|
|
12
|
+
*/
|
|
13
|
+
component: React$1.ComponentType<Record<string, unknown>>;
|
|
9
14
|
/** Optional CSS class name for the container */
|
|
10
15
|
className?: string;
|
|
11
16
|
/** Optional inline styles for the container */
|
|
@@ -16,23 +21,25 @@ interface TimelineRootProps {
|
|
|
16
21
|
/**
|
|
17
22
|
* TimelineRoot - Factory wrapper for React-based timelines.
|
|
18
23
|
*
|
|
19
|
-
* This component enables proper clone rendering by
|
|
20
|
-
* for
|
|
21
|
-
* (for exports, thumbnails, etc.), the
|
|
22
|
-
* tree
|
|
24
|
+
* This component enables proper clone rendering by registering a clone factory
|
|
25
|
+
* for the managed ef-timegroup element. When render clones are needed
|
|
26
|
+
* (for exports, thumbnails, etc.), the factory mounts a fresh React component
|
|
27
|
+
* tree — producing a fully functional second instance with all hooks, state,
|
|
28
|
+
* and effects running.
|
|
29
|
+
*
|
|
30
|
+
* This is necessary because React DOM cannot be cloned via cloneNode() —
|
|
31
|
+
* cloned elements are dead HTML without React's fiber tree behind them.
|
|
32
|
+
* The factory pattern ensures each clone is a real React mount.
|
|
23
33
|
*
|
|
24
34
|
* @example
|
|
25
35
|
* ```tsx
|
|
26
|
-
* const
|
|
36
|
+
* const MyTimeline = () => (
|
|
27
37
|
* <Timegroup mode="sequence">
|
|
28
38
|
* <MyScenes />
|
|
29
39
|
* </Timegroup>
|
|
30
40
|
* );
|
|
31
41
|
*
|
|
32
|
-
*
|
|
33
|
-
* <Configuration apiHost="...">
|
|
34
|
-
* <TimelineRoot id="root" component={MyTimelineContent} />
|
|
35
|
-
* </Configuration>
|
|
42
|
+
* <TimelineRoot id="root" component={MyTimeline} />
|
|
36
43
|
* ```
|
|
37
44
|
*/
|
|
38
45
|
declare const TimelineRoot: React$1.FC<TimelineRootProps>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { registerCloneFactory, unregisterCloneFactory } from "@editframe/elements";
|
|
1
2
|
import { useEffect, useRef } from "react";
|
|
2
3
|
import * as ReactDOM from "react-dom/client";
|
|
3
4
|
import { flushSync } from "react-dom";
|
|
@@ -6,59 +7,58 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
6
7
|
//#region src/components/TimelineRoot.tsx
|
|
7
8
|
/**
|
|
8
9
|
* TimelineRoot - Factory wrapper for React-based timelines.
|
|
9
|
-
*
|
|
10
|
-
* This component enables proper clone rendering by
|
|
11
|
-
* for
|
|
12
|
-
* (for exports, thumbnails, etc.), the
|
|
13
|
-
* tree
|
|
14
|
-
*
|
|
10
|
+
*
|
|
11
|
+
* This component enables proper clone rendering by registering a clone factory
|
|
12
|
+
* for the managed ef-timegroup element. When render clones are needed
|
|
13
|
+
* (for exports, thumbnails, etc.), the factory mounts a fresh React component
|
|
14
|
+
* tree — producing a fully functional second instance with all hooks, state,
|
|
15
|
+
* and effects running.
|
|
16
|
+
*
|
|
17
|
+
* This is necessary because React DOM cannot be cloned via cloneNode() —
|
|
18
|
+
* cloned elements are dead HTML without React's fiber tree behind them.
|
|
19
|
+
* The factory pattern ensures each clone is a real React mount.
|
|
20
|
+
*
|
|
15
21
|
* @example
|
|
16
22
|
* ```tsx
|
|
17
|
-
* const
|
|
23
|
+
* const MyTimeline = () => (
|
|
18
24
|
* <Timegroup mode="sequence">
|
|
19
25
|
* <MyScenes />
|
|
20
26
|
* </Timegroup>
|
|
21
27
|
* );
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* <Configuration apiHost="...">
|
|
25
|
-
* <TimelineRoot id="root" component={MyTimelineContent} />
|
|
26
|
-
* </Configuration>
|
|
28
|
+
*
|
|
29
|
+
* <TimelineRoot id="root" component={MyTimeline} />
|
|
27
30
|
* ```
|
|
28
31
|
*/
|
|
29
32
|
const TimelineRoot = ({ id, component: Component, className, style, children }) => {
|
|
30
33
|
const containerRef = useRef(null);
|
|
34
|
+
const componentRef = useRef(Component);
|
|
35
|
+
componentRef.current = Component;
|
|
31
36
|
useEffect(() => {
|
|
32
37
|
const container = containerRef.current;
|
|
33
38
|
if (!container) return;
|
|
34
39
|
const timegroup = container.querySelector("ef-timegroup");
|
|
35
|
-
if (!timegroup)
|
|
36
|
-
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
timegroup.id = id;
|
|
40
|
-
timegroup.initializer = (cloneEl) => {
|
|
41
|
-
const cloneContainer = cloneEl.parentElement;
|
|
42
|
-
if (!cloneContainer) {
|
|
43
|
-
console.error("[TimelineRoot] No parent container for clone");
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
cloneEl.remove();
|
|
40
|
+
if (!timegroup) throw new Error("[TimelineRoot] No ef-timegroup found in component. Ensure your component renders a Timegroup.");
|
|
41
|
+
registerCloneFactory(timegroup, (cloneContainer) => {
|
|
47
42
|
const root = ReactDOM.createRoot(cloneContainer);
|
|
43
|
+
const Comp = componentRef.current;
|
|
48
44
|
flushSync(() => {
|
|
49
|
-
root.render(/* @__PURE__ */ jsx(
|
|
45
|
+
root.render(/* @__PURE__ */ jsx(Comp, { id }));
|
|
50
46
|
});
|
|
51
47
|
const newTimegroup = cloneContainer.querySelector("ef-timegroup");
|
|
52
|
-
if (newTimegroup)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
if (!newTimegroup) throw new Error("[TimelineRoot] Clone factory did not produce an ef-timegroup. Ensure your component renders a Timegroup.");
|
|
49
|
+
return {
|
|
50
|
+
timegroup: newTimegroup,
|
|
51
|
+
cleanup: () => {
|
|
52
|
+
queueMicrotask(() => {
|
|
53
|
+
root.unmount();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
58
|
return () => {
|
|
59
|
-
timegroup
|
|
59
|
+
unregisterCloneFactory(timegroup);
|
|
60
60
|
};
|
|
61
|
-
}, [id
|
|
61
|
+
}, [id]);
|
|
62
62
|
return /* @__PURE__ */ jsxs("div", {
|
|
63
63
|
ref: containerRef,
|
|
64
64
|
className,
|
|
@@ -66,7 +66,7 @@ const TimelineRoot = ({ id, component: Component, className, style, children })
|
|
|
66
66
|
display: "contents",
|
|
67
67
|
...style
|
|
68
68
|
},
|
|
69
|
-
children: [children, /* @__PURE__ */ jsx(Component, {})]
|
|
69
|
+
children: [children, /* @__PURE__ */ jsx(Component, { id })]
|
|
70
70
|
});
|
|
71
71
|
};
|
|
72
72
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimelineRoot.js","names":["TimelineRoot: React.FC<TimelineRootProps>"],"sources":["../../src/components/TimelineRoot.tsx"],"sourcesContent":["import * as React from
|
|
1
|
+
{"version":3,"file":"TimelineRoot.js","names":["TimelineRoot: React.FC<TimelineRootProps>"],"sources":["../../src/components/TimelineRoot.tsx"],"sourcesContent":["import * as React from \"react\";\nimport * as ReactDOM from \"react-dom/client\";\nimport { flushSync } from \"react-dom\";\nimport { useEffect, useRef } from \"react\";\nimport {\n registerCloneFactory,\n unregisterCloneFactory,\n type EFTimegroup,\n} from \"@editframe/elements\";\n\ninterface TimelineRootProps {\n /**\n * Unique identifier used for playback control targeting (Preview, Scrubber, TogglePlay).\n * Also passed as `id` prop to the component for clone rendering.\n */\n id: string;\n /**\n * React component that renders the timeline content (must include a Timegroup at root).\n */\n component: React.ComponentType<Record<string, unknown>>;\n /** Optional CSS class name for the container */\n className?: string;\n /** Optional inline styles for the container */\n style?: React.CSSProperties;\n /** Optional children to render alongside the component (e.g., Configuration wrapper) */\n children?: React.ReactNode;\n}\n\n/**\n * TimelineRoot - Factory wrapper for React-based timelines.\n *\n * This component enables proper clone rendering by registering a clone factory\n * for the managed ef-timegroup element. When render clones are needed\n * (for exports, thumbnails, etc.), the factory mounts a fresh React component\n * tree — producing a fully functional second instance with all hooks, state,\n * and effects running.\n *\n * This is necessary because React DOM cannot be cloned via cloneNode() —\n * cloned elements are dead HTML without React's fiber tree behind them.\n * The factory pattern ensures each clone is a real React mount.\n *\n * @example\n * ```tsx\n * const MyTimeline = () => (\n * <Timegroup mode=\"sequence\">\n * <MyScenes />\n * </Timegroup>\n * );\n *\n * <TimelineRoot id=\"root\" component={MyTimeline} />\n * ```\n */\nexport const TimelineRoot: React.FC<TimelineRootProps> = ({\n id,\n component: Component,\n className,\n style,\n children,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const componentRef = useRef(Component);\n componentRef.current = Component;\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n // Find the root timegroup rendered by Component\n const timegroup = container.querySelector(\"ef-timegroup\") as EFTimegroup;\n if (!timegroup) {\n throw new Error(\n \"[TimelineRoot] No ef-timegroup found in component. \" +\n \"Ensure your component renders a Timegroup.\",\n );\n }\n\n // Register a clone factory for this element.\n // When createRenderClone is called, it will use this factory\n // to mount a fresh React tree instead of cloning dead DOM.\n registerCloneFactory(timegroup, (cloneContainer: HTMLElement) => {\n const root = ReactDOM.createRoot(cloneContainer);\n const Comp = componentRef.current;\n flushSync(() => {\n root.render(<Comp id={id} />);\n });\n\n const newTimegroup = cloneContainer.querySelector(\n \"ef-timegroup\",\n ) as EFTimegroup | null;\n if (!newTimegroup) {\n throw new Error(\n \"[TimelineRoot] Clone factory did not produce an ef-timegroup. \" +\n \"Ensure your component renders a Timegroup.\",\n );\n }\n\n return {\n timegroup: newTimegroup,\n cleanup: () => {\n queueMicrotask(() => {\n root.unmount();\n });\n },\n };\n });\n\n return () => {\n unregisterCloneFactory(timegroup);\n };\n }, [id]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ display: \"contents\", ...style }}\n >\n {children}\n <Component id={id} />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,MAAaA,gBAA6C,EACxD,IACA,WAAW,WACX,WACA,OACA,eACI;CACJ,MAAM,eAAe,OAAuB,KAAK;CACjD,MAAM,eAAe,OAAO,UAAU;AACtC,cAAa,UAAU;AAEvB,iBAAgB;EACd,MAAM,YAAY,aAAa;AAC/B,MAAI,CAAC,UAAW;EAGhB,MAAM,YAAY,UAAU,cAAc,eAAe;AACzD,MAAI,CAAC,UACH,OAAM,IAAI,MACR,gGAED;AAMH,uBAAqB,YAAY,mBAAgC;GAC/D,MAAM,OAAO,SAAS,WAAW,eAAe;GAChD,MAAM,OAAO,aAAa;AAC1B,mBAAgB;AACd,SAAK,OAAO,oBAAC,QAAS,KAAM,CAAC;KAC7B;GAEF,MAAM,eAAe,eAAe,cAClC,eACD;AACD,OAAI,CAAC,aACH,OAAM,IAAI,MACR,2GAED;AAGH,UAAO;IACL,WAAW;IACX,eAAe;AACb,0BAAqB;AACnB,WAAK,SAAS;OACd;;IAEL;IACD;AAEF,eAAa;AACX,0BAAuB,UAAU;;IAElC,CAAC,GAAG,CAAC;AAER,QACE,qBAAC;EACC,KAAK;EACM;EACX,OAAO;GAAE,SAAS;GAAY,GAAG;GAAO;aAEvC,UACD,oBAAC,aAAc,KAAM;GACjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OverlayItem.js","names":["EFOverlayItemElement"],"sources":["../../src/gui/OverlayItem.tsx"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"OverlayItem.js","names":["EFOverlayItemElement"],"sources":["../../src/gui/OverlayItem.tsx"],"sourcesContent":["import { EFOverlayItem as EFOverlayItemElement } from \"@editframe/elements\";\nimport React from \"react\";\nimport { createComponent } from \"../hooks/create-element\";\n\nexport interface OverlayItemProps {\n /** Element ID - automatically resolves to [data-element-id] or [data-timegroup-id] selector */\n elementId?: string;\n /** Target element or selector - use for custom targeting when elementId doesn't work */\n target?: HTMLElement | string;\n /** Called when position changes. Receives CustomEvent with OverlayItemPosition as detail. */\n onPositionChanged?: (e: Event) => void;\n}\n\nexport const OverlayItem = createComponent<\n EFOverlayItemElement,\n { onPositionChanged: \"position-changed\" }\n>({\n tagName: \"ef-overlay-item\",\n elementClass: EFOverlayItemElement,\n react: React,\n displayName: \"OverlayItem\",\n events: {\n onPositionChanged: \"position-changed\",\n },\n}) as React.ForwardRefExoticComponent<\n OverlayItemProps & React.RefAttributes<EFOverlayItemElement>\n>;\n"],"mappings":";;;;;AAaA,MAAa,cAAc,gBAGzB;CACA,SAAS;CACT,cAAcA;CACd,OAAO;CACP,aAAa;CACb,QAAQ,EACN,mBAAmB,oBACpB;CACF,CAAC"}
|
package/dist/gui/Scrubber.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ interface ScrubberProps {
|
|
|
13
13
|
scrollContainerRef?: React.RefObject<HTMLElement>;
|
|
14
14
|
isScrubbingRef?: React.MutableRefObject<boolean>;
|
|
15
15
|
onSeek?: (time: number) => void;
|
|
16
|
+
className?: string;
|
|
17
|
+
style?: React.CSSProperties;
|
|
16
18
|
}
|
|
17
19
|
declare const Scrubber: React.ForwardRefExoticComponent<ScrubberProps & React.RefAttributes<EFScrubber>>;
|
|
18
20
|
//#endregion
|
package/dist/gui/Scrubber.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Scrubber.js","names":[],"sources":["../../src/gui/Scrubber.tsx"],"sourcesContent":["import { EFScrubber } from \"@editframe/elements\";\nimport React from \"react\";\nimport { createComponent } from \"../hooks/create-element\";\n\nexport interface ScrubberProps {\n orientation?: \"horizontal\" | \"vertical\";\n currentTimeMs?: number;\n durationMs?: number;\n zoomScale?: number;\n containerWidth?: number;\n fps?: number;\n rawScrubTimeMs?: number | null;\n scrollContainerRef?: React.RefObject<HTMLElement>;\n isScrubbingRef?: React.MutableRefObject<boolean>;\n onSeek?: (time: number) => void;\n}\n\nconst BaseScrubber = createComponent
|
|
1
|
+
{"version":3,"file":"Scrubber.js","names":[],"sources":["../../src/gui/Scrubber.tsx"],"sourcesContent":["import { EFScrubber } from \"@editframe/elements\";\nimport React from \"react\";\nimport { createComponent } from \"../hooks/create-element\";\n\nexport interface ScrubberProps {\n orientation?: \"horizontal\" | \"vertical\";\n currentTimeMs?: number;\n durationMs?: number;\n zoomScale?: number;\n containerWidth?: number;\n fps?: number;\n rawScrubTimeMs?: number | null;\n scrollContainerRef?: React.RefObject<HTMLElement>;\n isScrubbingRef?: React.MutableRefObject<boolean>;\n onSeek?: (time: number) => void;\n className?: string;\n style?: React.CSSProperties;\n}\n\nconst BaseScrubber = createComponent<EFScrubber, { onSeek: \"seek\" }>({\n tagName: \"ef-scrubber\",\n elementClass: EFScrubber,\n react: React,\n displayName: \"Scrubber\",\n events: {\n onSeek: \"seek\",\n },\n}) as React.ForwardRefExoticComponent<\n ScrubberProps & React.RefAttributes<EFScrubber>\n>;\n\nexport const Scrubber = React.forwardRef<EFScrubber, ScrubberProps>(\n (props, ref) => {\n const { scrollContainerRef, isScrubbingRef, ...restProps } = props;\n const elementRef = React.useRef<EFScrubber | null>(null);\n\n React.useLayoutEffect(() => {\n if (elementRef.current) {\n if (scrollContainerRef?.current) {\n elementRef.current.scrollContainerRef = scrollContainerRef;\n } else {\n elementRef.current.scrollContainerRef = undefined;\n }\n if (isScrubbingRef) {\n elementRef.current.isScrubbingRef = isScrubbingRef;\n } else {\n elementRef.current.isScrubbingRef = undefined;\n }\n }\n }, [scrollContainerRef?.current, isScrubbingRef]);\n\n return (\n <BaseScrubber\n {...restProps}\n ref={(node) => {\n elementRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n }}\n />\n );\n },\n);\n\nScrubber.displayName = \"Scrubber\";\n"],"mappings":";;;;;;AAmBA,MAAM,eAAe,gBAAgD;CACnE,SAAS;CACT,cAAc;CACd,OAAO;CACP,aAAa;CACb,QAAQ,EACN,QAAQ,QACT;CACF,CAAC;AAIF,MAAa,WAAW,MAAM,YAC3B,OAAO,QAAQ;CACd,MAAM,EAAE,oBAAoB,eAAgB,GAAG,cAAc;CAC7D,MAAM,aAAa,MAAM,OAA0B,KAAK;AAExD,OAAM,sBAAsB;AAC1B,MAAI,WAAW,SAAS;AACtB,OAAI,oBAAoB,QACtB,YAAW,QAAQ,qBAAqB;OAExC,YAAW,QAAQ,qBAAqB;AAE1C,OAAI,eACF,YAAW,QAAQ,iBAAiB;OAEpC,YAAW,QAAQ,iBAAiB;;IAGvC,CAAC,oBAAoB,SAAS,eAAe,CAAC;AAEjD,QACE,oBAAC;EACC,GAAI;EACJ,MAAM,SAAS;AACb,cAAW,UAAU;AACrB,OAAI,OAAO,QAAQ,WAAY,KAAI,KAAK;YAC/B,IAAK,KAAI,UAAU;;GAE9B;EAGP;AAED,SAAS,cAAc"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReactWebComponent } from "../hooks/create-element.js";
|
|
2
2
|
import { EFThumbnailStrip } from "@editframe/elements";
|
|
3
3
|
|
|
4
|
-
//#region src/
|
|
4
|
+
//#region src/gui/ThumbnailStrip.d.ts
|
|
5
5
|
declare const ThumbnailStrip: ReactWebComponent<EFThumbnailStrip, {}>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { ThumbnailStrip };
|
|
@@ -2,13 +2,11 @@ import { createComponent } from "../hooks/create-element.js";
|
|
|
2
2
|
import { EFThumbnailStrip } from "@editframe/elements";
|
|
3
3
|
import React from "react";
|
|
4
4
|
|
|
5
|
-
//#region src/
|
|
5
|
+
//#region src/gui/ThumbnailStrip.tsx
|
|
6
6
|
const ThumbnailStrip = createComponent({
|
|
7
7
|
tagName: "ef-thumbnail-strip",
|
|
8
8
|
elementClass: EFThumbnailStrip,
|
|
9
|
-
react: React
|
|
10
|
-
displayName: "ThumbnailStrip",
|
|
11
|
-
events: {}
|
|
9
|
+
react: React
|
|
12
10
|
});
|
|
13
11
|
|
|
14
12
|
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThumbnailStrip.js","names":["EFThumbnailStripElement"],"sources":["../../src/gui/ThumbnailStrip.tsx"],"sourcesContent":["import { EFThumbnailStrip as EFThumbnailStripElement } from \"@editframe/elements\";\nimport React from \"react\";\nimport { createComponent } from \"../hooks/create-element\";\n\nexport const ThumbnailStrip = createComponent({\n tagName: \"ef-thumbnail-strip\",\n elementClass: EFThumbnailStripElement,\n react: React,\n});\n"],"mappings":";;;;;AAIA,MAAa,iBAAiB,gBAAgB;CAC5C,SAAS;CACT,cAAcA;CACd,OAAO;CACR,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimelineRuler.js","names":[],"sources":["../../src/gui/TimelineRuler.tsx"],"sourcesContent":["import { EFTimelineRuler } from \"@editframe/elements\";\nimport React from \"react\";\nimport { createComponent } from \"../hooks/create-element\";\n\nexport interface TimelineRulerProps {\n durationMs?: number;\n zoomScale?: number;\n containerWidth?: number;\n fps?: number;\n scrollContainerSelector?: string;\n scrollContainerRef?: React.RefObject<HTMLElement>;\n}\n\nconst BaseTimelineRuler = createComponent
|
|
1
|
+
{"version":3,"file":"TimelineRuler.js","names":[],"sources":["../../src/gui/TimelineRuler.tsx"],"sourcesContent":["import { EFTimelineRuler } from \"@editframe/elements\";\nimport React from \"react\";\nimport { createComponent } from \"../hooks/create-element\";\n\nexport interface TimelineRulerProps {\n durationMs?: number;\n zoomScale?: number;\n containerWidth?: number;\n fps?: number;\n scrollContainerSelector?: string;\n scrollContainerRef?: React.RefObject<HTMLElement>;\n}\n\nconst BaseTimelineRuler = createComponent<EFTimelineRuler, {}>({\n tagName: \"ef-timeline-ruler\",\n elementClass: EFTimelineRuler,\n react: React,\n displayName: \"TimelineRuler\",\n}) as React.ForwardRefExoticComponent<\n TimelineRulerProps & React.RefAttributes<EFTimelineRuler>\n>;\n\nexport const TimelineRuler = React.forwardRef<\n EFTimelineRuler,\n TimelineRulerProps\n>((props, ref) => {\n const { scrollContainerRef, ...restProps } = props;\n const elementRef = React.useRef<EFTimelineRuler | null>(null);\n\n React.useLayoutEffect(() => {\n if (elementRef.current && scrollContainerRef?.current) {\n (elementRef.current as any).scrollContainerElement =\n scrollContainerRef.current;\n } else if (elementRef.current) {\n (elementRef.current as any).scrollContainerElement = null;\n }\n }, [scrollContainerRef?.current]);\n\n return (\n <BaseTimelineRuler\n {...(restProps as any)}\n ref={(node) => {\n elementRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n }}\n />\n );\n});\n\nTimelineRuler.displayName = \"TimelineRuler\";\n"],"mappings":";;;;;;AAaA,MAAM,oBAAoB,gBAAqC;CAC7D,SAAS;CACT,cAAc;CACd,OAAO;CACP,aAAa;CACd,CAAC;AAIF,MAAa,gBAAgB,MAAM,YAGhC,OAAO,QAAQ;CAChB,MAAM,EAAE,mBAAoB,GAAG,cAAc;CAC7C,MAAM,aAAa,MAAM,OAA+B,KAAK;AAE7D,OAAM,sBAAsB;AAC1B,MAAI,WAAW,WAAW,oBAAoB,QAC5C,CAAC,WAAW,QAAgB,yBAC1B,mBAAmB;WACZ,WAAW,QACpB,CAAC,WAAW,QAAgB,yBAAyB;IAEtD,CAAC,oBAAoB,QAAQ,CAAC;AAEjC,QACE,oBAAC;EACC,GAAK;EACL,MAAM,SAAS;AACb,cAAW,UAAU;AACrB,OAAI,OAAO,QAAQ,WAAY,KAAI,KAAK;YAC/B,IAAK,KAAI,UAAU;;GAE9B;EAEJ;AAEF,cAAc,cAAc"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { EventName, ReactWebComponent } from "../hooks/create-element.js";
|
|
2
|
+
import { EFTrimHandles, TrimChangeDetail } from "@editframe/elements";
|
|
3
|
+
|
|
4
|
+
//#region src/gui/TrimHandles.d.ts
|
|
5
|
+
declare const TrimHandles: ReactWebComponent<EFTrimHandles, {
|
|
6
|
+
onTrimChange: EventName<CustomEvent<TrimChangeDetail>>;
|
|
7
|
+
onTrimChangeEnd: EventName<CustomEvent<TrimChangeDetail>>;
|
|
8
|
+
}>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { TrimHandles };
|
|
11
|
+
//# sourceMappingURL=TrimHandles.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createComponent } from "../hooks/create-element.js";
|
|
2
|
+
import { EFTrimHandles } from "@editframe/elements";
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
//#region src/gui/TrimHandles.ts
|
|
6
|
+
const TrimHandles = createComponent({
|
|
7
|
+
tagName: "ef-trim-handles",
|
|
8
|
+
elementClass: EFTrimHandles,
|
|
9
|
+
react: React,
|
|
10
|
+
events: {
|
|
11
|
+
onTrimChange: "trim-change",
|
|
12
|
+
onTrimChangeEnd: "trim-change-end"
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { TrimHandles };
|
|
18
|
+
//# sourceMappingURL=TrimHandles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TrimHandles.js","names":["EFTrimHandlesElement"],"sources":["../../src/gui/TrimHandles.ts"],"sourcesContent":["import {\n EFTrimHandles as EFTrimHandlesElement,\n type TrimChangeDetail,\n} from \"@editframe/elements\";\nimport React from \"react\";\nimport { createComponent, type EventName } from \"../hooks/create-element\";\n\nexport const TrimHandles = createComponent({\n tagName: \"ef-trim-handles\",\n elementClass: EFTrimHandlesElement,\n react: React,\n events: {\n onTrimChange: \"trim-change\" as EventName<CustomEvent<TrimChangeDetail>>,\n onTrimChangeEnd: \"trim-change-end\" as EventName<\n CustomEvent<TrimChangeDetail>\n >,\n },\n});\n"],"mappings":";;;;;AAOA,MAAa,cAAc,gBAAgB;CACzC,SAAS;CACT,cAAcA;CACd,OAAO;CACP,QAAQ;EACN,cAAc;EACd,iBAAiB;EAGlB;CACF,CAAC"}
|
|
@@ -2,11 +2,18 @@ import React from "react";
|
|
|
2
2
|
|
|
3
3
|
//#region src/hooks/create-element.d.ts
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Branded string that carries the DOM event type at the type level.
|
|
7
|
+
* At runtime it's just a string (the DOM event name).
|
|
8
|
+
*/
|
|
9
|
+
type EventName<T$1 extends Event = Event> = string & {
|
|
10
|
+
__eventType?: T$1;
|
|
11
|
+
};
|
|
12
|
+
type EventNames = Record<string, EventName>;
|
|
13
|
+
type EventListeners<E extends EventNames> = { [K in keyof E]?: (e: E[K] extends EventName<infer T> ? T : Event) => void };
|
|
7
14
|
type ElementProps<I> = Partial<Omit<I, keyof HTMLElement>>;
|
|
8
15
|
type ComponentProps<I, E extends EventNames = {}> = Omit<React.HTMLAttributes<I>, keyof E | keyof ElementProps<I>> & EventListeners<E> & ElementProps<I>;
|
|
9
16
|
type ReactWebComponent<I extends HTMLElement, E extends EventNames = {}> = React.ForwardRefExoticComponent<ComponentProps<I, E> & React.RefAttributes<I>>;
|
|
10
17
|
//#endregion
|
|
11
|
-
export { ReactWebComponent };
|
|
18
|
+
export { EventName, ReactWebComponent };
|
|
12
19
|
//# sourceMappingURL=create-element.d.ts.map
|
|
@@ -48,7 +48,10 @@ function createComponent({ react: React$1, tagName, elementClass, events, displa
|
|
|
48
48
|
reactProps[k === "className" ? "class" : k] = v;
|
|
49
49
|
continue;
|
|
50
50
|
}
|
|
51
|
-
if (eventProps.has(k) || k in elementClass.prototype)
|
|
51
|
+
if (eventProps.has(k) || k in elementClass.prototype) {
|
|
52
|
+
elementProps[k] = v;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
52
55
|
reactProps[k] = v;
|
|
53
56
|
}
|
|
54
57
|
isomorphicEffect(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-element.js","names":["React","reactProps: Record<string, unknown>","elementProps: Record<string, unknown>"],"sources":["../../src/hooks/create-element.ts"],"sourcesContent":["import React from \"react\";\n\nlet isomorphicEffect =\n typeof window !== \"undefined\" ? React.useLayoutEffect : React.useEffect;\n\nexport function setIsomorphicEffect(\n effect: typeof React.useLayoutEffect | typeof React.useEffect,\n) {\n isomorphicEffect = effect;\n}\n\nconst reservedReactProperties = new Set([\n \"children\",\n \"localName\",\n \"ref\",\n \"style\",\n \"className\",\n]);\nconst listenedEvents = new WeakMap<Element, Map<string, EventListenerObject>>();\n\ntype Constructor<T> = { new (): T };\ntype EventNames = Record<string,
|
|
1
|
+
{"version":3,"file":"create-element.js","names":["React","reactProps: Record<string, unknown>","elementProps: Record<string, unknown>"],"sources":["../../src/hooks/create-element.ts"],"sourcesContent":["import React from \"react\";\n\nlet isomorphicEffect =\n typeof window !== \"undefined\" ? React.useLayoutEffect : React.useEffect;\n\nexport function setIsomorphicEffect(\n effect: typeof React.useLayoutEffect | typeof React.useEffect,\n) {\n isomorphicEffect = effect;\n}\n\nconst reservedReactProperties = new Set([\n \"children\",\n \"localName\",\n \"ref\",\n \"style\",\n \"className\",\n]);\nconst listenedEvents = new WeakMap<Element, Map<string, EventListenerObject>>();\n\ntype Constructor<T> = { new (): T };\n\n/**\n * Branded string that carries the DOM event type at the type level.\n * At runtime it's just a string (the DOM event name).\n */\nexport type EventName<T extends Event = Event> = string & { __eventType?: T };\n\ntype EventNames = Record<string, EventName>;\n\ntype EventListeners<E extends EventNames> = {\n [K in keyof E]?: (e: E[K] extends EventName<infer T> ? T : Event) => void;\n};\n\ntype ElementProps<I> = Partial<Omit<I, keyof HTMLElement>>;\ntype ComponentProps<I, E extends EventNames = {}> = Omit<\n React.HTMLAttributes<I>,\n keyof E | keyof ElementProps<I>\n> &\n EventListeners<E> &\n ElementProps<I>;\n\nexport type ReactWebComponent<\n I extends HTMLElement,\n E extends EventNames = {},\n> = React.ForwardRefExoticComponent<\n ComponentProps<I, E> & React.RefAttributes<I>\n>;\n\nexport interface Options<I extends HTMLElement, E extends EventNames = {}> {\n react: typeof React;\n tagName: string;\n elementClass: Constructor<I>;\n events?: E;\n displayName?: string;\n}\n\nfunction addOrUpdateEventListener(\n node: Element,\n event: string,\n listener?: (e?: Event) => void,\n) {\n let events = listenedEvents.get(node);\n if (!events) {\n events = new Map();\n listenedEvents.set(node, events);\n }\n let handler = events.get(event);\n\n if (listener) {\n if (!handler) {\n handler = { handleEvent: listener };\n events.set(event, handler);\n node.addEventListener(event, handler);\n } else {\n handler.handleEvent = listener;\n }\n } else if (handler) {\n events.delete(event);\n node.removeEventListener(event, handler);\n }\n}\n\nfunction setProperty<E extends Element>(\n node: E,\n name: string,\n value: unknown,\n old: unknown,\n events?: EventNames,\n) {\n const event = events?.[name];\n if (event) {\n if (value !== old)\n addOrUpdateEventListener(node, event, value as (e?: Event) => void);\n return;\n }\n node[name as keyof E] = value as E[keyof E];\n if (\n (value === undefined || value === null) &&\n name in HTMLElement.prototype\n ) {\n node.removeAttribute(name);\n }\n}\n\nexport function createComponent<\n I extends HTMLElement,\n E extends EventNames = {},\n>({\n react: React,\n tagName,\n elementClass,\n events,\n displayName,\n}: Options<I, E>): ReactWebComponent<I, E> {\n const eventProps = new Set(Object.keys(events ?? {}));\n\n const ReactComponent = React.forwardRef<I, ComponentProps<I, E>>(\n (props, ref) => {\n const elementRef = React.useRef<I | null>(null);\n const prevPropsRef = React.useRef(new Map<string, unknown>());\n\n const reactProps: Record<string, unknown> = {\n suppressHydrationWarning: true,\n };\n const elementProps: Record<string, unknown> = {};\n\n for (const [k, v] of Object.entries(props)) {\n if (reservedReactProperties.has(k)) {\n reactProps[k === \"className\" ? \"class\" : k] = v;\n continue;\n }\n if (eventProps.has(k) || k in elementClass.prototype) {\n elementProps[k] = v;\n continue;\n }\n reactProps[k] = v;\n }\n\n isomorphicEffect(() => {\n if (!elementRef.current) return;\n const newProps = new Map<string, unknown>();\n for (const key in elementProps) {\n setProperty(\n elementRef.current,\n key,\n props[key as keyof typeof props],\n prevPropsRef.current.get(key),\n events,\n );\n prevPropsRef.current.delete(key);\n newProps.set(key, props[key as keyof typeof props]);\n }\n for (const [key, value] of prevPropsRef.current) {\n setProperty(elementRef.current, key, undefined, value, events);\n }\n prevPropsRef.current = newProps;\n\n // Remove defer-hydration if present\n elementRef.current.removeAttribute(\"defer-hydration\");\n }, [props]);\n\n return React.createElement(tagName, {\n ...reactProps,\n ref: (node: I) => {\n elementRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n },\n });\n },\n );\n\n ReactComponent.displayName = displayName ?? elementClass.name;\n return ReactComponent as ReactWebComponent<I, E>;\n}\n"],"mappings":";;;AAEA,IAAI,mBACF,OAAO,WAAW,cAAc,MAAM,kBAAkB,MAAM;AAQhE,MAAM,0BAA0B,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACD,CAAC;AACF,MAAM,iCAAiB,IAAI,SAAoD;AAuC/E,SAAS,yBACP,MACA,OACA,UACA;CACA,IAAI,SAAS,eAAe,IAAI,KAAK;AACrC,KAAI,CAAC,QAAQ;AACX,2BAAS,IAAI,KAAK;AAClB,iBAAe,IAAI,MAAM,OAAO;;CAElC,IAAI,UAAU,OAAO,IAAI,MAAM;AAE/B,KAAI,SACF,KAAI,CAAC,SAAS;AACZ,YAAU,EAAE,aAAa,UAAU;AACnC,SAAO,IAAI,OAAO,QAAQ;AAC1B,OAAK,iBAAiB,OAAO,QAAQ;OAErC,SAAQ,cAAc;UAEf,SAAS;AAClB,SAAO,OAAO,MAAM;AACpB,OAAK,oBAAoB,OAAO,QAAQ;;;AAI5C,SAAS,YACP,MACA,MACA,OACA,KACA,QACA;CACA,MAAM,QAAQ,SAAS;AACvB,KAAI,OAAO;AACT,MAAI,UAAU,IACZ,0BAAyB,MAAM,OAAO,MAA6B;AACrE;;AAEF,MAAK,QAAmB;AACxB,MACG,UAAU,UAAa,UAAU,SAClC,QAAQ,YAAY,UAEpB,MAAK,gBAAgB,KAAK;;AAI9B,SAAgB,gBAGd,EACA,OAAOA,SACP,SACA,cACA,QACA,eACyC;CACzC,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC,CAAC;CAErD,MAAM,iBAAiBA,QAAM,YAC1B,OAAO,QAAQ;EACd,MAAM,aAAaA,QAAM,OAAiB,KAAK;EAC/C,MAAM,eAAeA,QAAM,uBAAO,IAAI,KAAsB,CAAC;EAE7D,MAAMC,aAAsC,EAC1C,0BAA0B,MAC3B;EACD,MAAMC,eAAwC,EAAE;AAEhD,OAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,EAAE;AAC1C,OAAI,wBAAwB,IAAI,EAAE,EAAE;AAClC,eAAW,MAAM,cAAc,UAAU,KAAK;AAC9C;;AAEF,OAAI,WAAW,IAAI,EAAE,IAAI,KAAK,aAAa,WAAW;AACpD,iBAAa,KAAK;AAClB;;AAEF,cAAW,KAAK;;AAGlB,yBAAuB;AACrB,OAAI,CAAC,WAAW,QAAS;GACzB,MAAM,2BAAW,IAAI,KAAsB;AAC3C,QAAK,MAAM,OAAO,cAAc;AAC9B,gBACE,WAAW,SACX,KACA,MAAM,MACN,aAAa,QAAQ,IAAI,IAAI,EAC7B,OACD;AACD,iBAAa,QAAQ,OAAO,IAAI;AAChC,aAAS,IAAI,KAAK,MAAM,KAA2B;;AAErD,QAAK,MAAM,CAAC,KAAK,UAAU,aAAa,QACtC,aAAY,WAAW,SAAS,KAAK,QAAW,OAAO,OAAO;AAEhE,gBAAa,UAAU;AAGvB,cAAW,QAAQ,gBAAgB,kBAAkB;KACpD,CAAC,MAAM,CAAC;AAEX,SAAOF,QAAM,cAAc,SAAS;GAClC,GAAG;GACH,MAAM,SAAY;AAChB,eAAW,UAAU;AACrB,QAAI,OAAO,QAAQ,WAAY,KAAI,KAAK;aAC/B,IAAK,KAAI,UAAU;;GAE/B,CAAC;GAEL;AAED,gBAAe,cAAc,eAAe,aAAa;AACzD,QAAO"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ReactiveControllerHost } from "lit";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/useMediaInfo.d.ts
|
|
4
|
+
interface MediaInfo {
|
|
5
|
+
intrinsicDurationMs: number | undefined;
|
|
6
|
+
loading: boolean;
|
|
7
|
+
}
|
|
8
|
+
type MediaElement = ReactiveControllerHost & {
|
|
9
|
+
intrinsicDurationMs: number | undefined;
|
|
10
|
+
isConnected: boolean;
|
|
11
|
+
};
|
|
12
|
+
declare const useMediaInfo: (mediaRef: React.RefObject<MediaElement | null>) => MediaInfo;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { MediaInfo, useMediaInfo };
|
|
15
|
+
//# sourceMappingURL=useMediaInfo.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/useMediaInfo.ts
|
|
4
|
+
var MediaInfoController = class {
|
|
5
|
+
#isConnected = false;
|
|
6
|
+
#lastIntrinsicDurationMs = void 0;
|
|
7
|
+
constructor(host, setMediaInfo) {
|
|
8
|
+
this.host = host;
|
|
9
|
+
this.setMediaInfo = setMediaInfo;
|
|
10
|
+
this.host.addController(this);
|
|
11
|
+
}
|
|
12
|
+
hostConnected() {
|
|
13
|
+
this.#isConnected = true;
|
|
14
|
+
}
|
|
15
|
+
hostDisconnected() {
|
|
16
|
+
this.#isConnected = false;
|
|
17
|
+
this.host.removeController(this);
|
|
18
|
+
}
|
|
19
|
+
hostUpdated() {
|
|
20
|
+
if (!this.#isConnected) return;
|
|
21
|
+
const intrinsicDurationMs = this.host.intrinsicDurationMs;
|
|
22
|
+
if (intrinsicDurationMs !== this.#lastIntrinsicDurationMs) {
|
|
23
|
+
this.#lastIntrinsicDurationMs = intrinsicDurationMs;
|
|
24
|
+
this.#updateReactState();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
#updateReactState() {
|
|
28
|
+
const intrinsicDurationMs = this.host.intrinsicDurationMs;
|
|
29
|
+
this.setMediaInfo({
|
|
30
|
+
intrinsicDurationMs,
|
|
31
|
+
loading: intrinsicDurationMs === void 0
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
syncNow() {
|
|
35
|
+
this.#updateReactState();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const useMediaInfo = (mediaRef) => {
|
|
39
|
+
const [mediaInfo, setMediaInfo] = useState({
|
|
40
|
+
intrinsicDurationMs: void 0,
|
|
41
|
+
loading: true
|
|
42
|
+
});
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!mediaRef.current) return;
|
|
45
|
+
const controller = new MediaInfoController(mediaRef.current, setMediaInfo);
|
|
46
|
+
if (mediaRef.current.isConnected) {
|
|
47
|
+
controller.hostConnected();
|
|
48
|
+
controller.syncNow();
|
|
49
|
+
}
|
|
50
|
+
return () => {
|
|
51
|
+
controller.hostDisconnected();
|
|
52
|
+
};
|
|
53
|
+
}, [mediaRef.current]);
|
|
54
|
+
return mediaInfo;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { useMediaInfo };
|
|
59
|
+
//# sourceMappingURL=useMediaInfo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMediaInfo.js","names":["host: MediaElement","setMediaInfo: React.Dispatch<React.SetStateAction<MediaInfo>>","#isConnected","#lastIntrinsicDurationMs","#updateReactState"],"sources":["../../src/hooks/useMediaInfo.ts"],"sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from \"lit\";\nimport { useEffect, useState } from \"react\";\n\nexport interface MediaInfo {\n intrinsicDurationMs: number | undefined;\n loading: boolean;\n}\n\ntype MediaElement = ReactiveControllerHost & {\n intrinsicDurationMs: number | undefined;\n isConnected: boolean;\n};\n\nexport class MediaInfoController implements ReactiveController {\n #isConnected = false;\n #lastIntrinsicDurationMs: number | undefined = undefined;\n\n constructor(\n private host: MediaElement,\n private setMediaInfo: React.Dispatch<React.SetStateAction<MediaInfo>>,\n ) {\n this.host.addController(this);\n }\n\n hostConnected(): void {\n this.#isConnected = true;\n }\n\n hostDisconnected(): void {\n this.#isConnected = false;\n this.host.removeController(this);\n }\n\n hostUpdated(): void {\n if (!this.#isConnected) return;\n\n const intrinsicDurationMs = this.host.intrinsicDurationMs;\n\n if (intrinsicDurationMs !== this.#lastIntrinsicDurationMs) {\n this.#lastIntrinsicDurationMs = intrinsicDurationMs;\n this.#updateReactState();\n }\n }\n\n #updateReactState(): void {\n const intrinsicDurationMs = this.host.intrinsicDurationMs;\n this.setMediaInfo({\n intrinsicDurationMs,\n loading: intrinsicDurationMs === undefined,\n });\n }\n\n syncNow(): void {\n this.#updateReactState();\n }\n}\n\nexport const useMediaInfo = (\n mediaRef: React.RefObject<MediaElement | null>,\n) => {\n const [mediaInfo, setMediaInfo] = useState<MediaInfo>({\n intrinsicDurationMs: undefined,\n loading: true,\n });\n\n useEffect(() => {\n if (!mediaRef.current) return;\n\n const controller = new MediaInfoController(mediaRef.current, setMediaInfo);\n\n if (mediaRef.current.isConnected) {\n controller.hostConnected();\n controller.syncNow();\n }\n\n return () => {\n controller.hostDisconnected();\n };\n }, [mediaRef.current]);\n\n return mediaInfo;\n};\n"],"mappings":";;;AAaA,IAAa,sBAAb,MAA+D;CAC7D,eAAe;CACf,2BAA+C;CAE/C,YACE,AAAQA,MACR,AAAQC,cACR;EAFQ;EACA;AAER,OAAK,KAAK,cAAc,KAAK;;CAG/B,gBAAsB;AACpB,QAAKC,cAAe;;CAGtB,mBAAyB;AACvB,QAAKA,cAAe;AACpB,OAAK,KAAK,iBAAiB,KAAK;;CAGlC,cAAoB;AAClB,MAAI,CAAC,MAAKA,YAAc;EAExB,MAAM,sBAAsB,KAAK,KAAK;AAEtC,MAAI,wBAAwB,MAAKC,yBAA0B;AACzD,SAAKA,0BAA2B;AAChC,SAAKC,kBAAmB;;;CAI5B,oBAA0B;EACxB,MAAM,sBAAsB,KAAK,KAAK;AACtC,OAAK,aAAa;GAChB;GACA,SAAS,wBAAwB;GAClC,CAAC;;CAGJ,UAAgB;AACd,QAAKA,kBAAmB;;;AAI5B,MAAa,gBACX,aACG;CACH,MAAM,CAAC,WAAW,gBAAgB,SAAoB;EACpD,qBAAqB;EACrB,SAAS;EACV,CAAC;AAEF,iBAAgB;AACd,MAAI,CAAC,SAAS,QAAS;EAEvB,MAAM,aAAa,IAAI,oBAAoB,SAAS,SAAS,aAAa;AAE1E,MAAI,SAAS,QAAQ,aAAa;AAChC,cAAW,eAAe;AAC1B,cAAW,SAAS;;AAGtB,eAAa;AACX,cAAW,kBAAkB;;IAE9B,CAAC,SAAS,QAAQ,CAAC;AAEtB,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePanZoomTransform.js","names":[],"sources":["../../src/hooks/usePanZoomTransform.ts"],"sourcesContent":["import { type PanZoomTransform } from \"@editframe/elements\";\nimport React, { useEffect, useState } from \"react\";\n\n/**\n * Hook to get PanZoom transform values from a PanZoom element ref.\n * Listens to transform-changed events to keep transform values in sync.\n *\n * @param panZoomRef - Ref to the PanZoom element\n * @returns Current transform values (scale, x, y)\n *\n * @example\n * const panZoomRef = useRef<EFPanZoom | null>(null);\n * const transform = usePanZoomTransform(panZoomRef);\n * // transform.scale, transform.x, transform.y\n */\nexport function usePanZoomTransform(\n panZoomRef: React.RefObject
|
|
1
|
+
{"version":3,"file":"usePanZoomTransform.js","names":[],"sources":["../../src/hooks/usePanZoomTransform.ts"],"sourcesContent":["import { type PanZoomTransform } from \"@editframe/elements\";\nimport React, { useEffect, useState } from \"react\";\n\n/**\n * Hook to get PanZoom transform values from a PanZoom element ref.\n * Listens to transform-changed events to keep transform values in sync.\n *\n * @param panZoomRef - Ref to the PanZoom element\n * @returns Current transform values (scale, x, y)\n *\n * @example\n * const panZoomRef = useRef<EFPanZoom | null>(null);\n * const transform = usePanZoomTransform(panZoomRef);\n * // transform.scale, transform.x, transform.y\n */\nexport function usePanZoomTransform(\n panZoomRef: React.RefObject<\n (EventTarget & { scale: number; x: number; y: number }) | null\n >,\n): PanZoomTransform {\n const [transform, setTransform] = useState<PanZoomTransform>({\n scale: 1,\n x: 0,\n y: 0,\n });\n\n useEffect(() => {\n const panZoom = panZoomRef.current;\n if (!panZoom) {\n return;\n }\n\n // Initialize with current values\n setTransform({\n scale: panZoom.scale ?? 1,\n x: panZoom.x ?? 0,\n y: panZoom.y ?? 0,\n });\n\n // Listen for transform changes\n const handleTransformChanged = (e: Event) => {\n const customEvent = e as CustomEvent<PanZoomTransform>;\n if (customEvent.detail) {\n setTransform(customEvent.detail);\n } else {\n // Fallback to reading from element if detail is not available\n setTransform({\n scale: panZoom.scale ?? 1,\n x: panZoom.x ?? 0,\n y: panZoom.y ?? 0,\n });\n }\n };\n\n panZoom.addEventListener(\"transform-changed\", handleTransformChanged);\n\n return () => {\n panZoom.removeEventListener(\"transform-changed\", handleTransformChanged);\n };\n }, [panZoomRef]);\n\n return transform;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,SAAgB,oBACd,YAGkB;CAClB,MAAM,CAAC,WAAW,gBAAgB,SAA2B;EAC3D,OAAO;EACP,GAAG;EACH,GAAG;EACJ,CAAC;AAEF,iBAAgB;EACd,MAAM,UAAU,WAAW;AAC3B,MAAI,CAAC,QACH;AAIF,eAAa;GACX,OAAO,QAAQ,SAAS;GACxB,GAAG,QAAQ,KAAK;GAChB,GAAG,QAAQ,KAAK;GACjB,CAAC;EAGF,MAAM,0BAA0B,MAAa;GAC3C,MAAM,cAAc;AACpB,OAAI,YAAY,OACd,cAAa,YAAY,OAAO;OAGhC,cAAa;IACX,OAAO,QAAQ,SAAS;IACxB,GAAG,QAAQ,KAAK;IAChB,GAAG,QAAQ,KAAK;IACjB,CAAC;;AAIN,UAAQ,iBAAiB,qBAAqB,uBAAuB;AAErE,eAAa;AACX,WAAQ,oBAAoB,qBAAqB,uBAAuB;;IAEzE,CAAC,WAAW,CAAC;AAEhB,QAAO"}
|
|
@@ -2,8 +2,8 @@ import { EFTimegroup } from "@editframe/elements";
|
|
|
2
2
|
import * as react0 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/hooks/useTimingInfo.d.ts
|
|
5
|
-
declare const useTimingInfo: (timegroupRef?: React.RefObject<EFTimegroup>) => {
|
|
6
|
-
ref: react0.RefObject<EFTimegroup>;
|
|
5
|
+
declare const useTimingInfo: (timegroupRef?: React.RefObject<EFTimegroup | null>) => {
|
|
6
|
+
ref: react0.RefObject<EFTimegroup | null>;
|
|
7
7
|
ownCurrentTimeMs: number;
|
|
8
8
|
durationMs: number;
|
|
9
9
|
percentComplete: number;
|
|
@@ -2,8 +2,9 @@ import { useEffect, useRef, useState } from "react";
|
|
|
2
2
|
|
|
3
3
|
//#region src/hooks/useTimingInfo.ts
|
|
4
4
|
var CurrentTimeController = class {
|
|
5
|
-
#lastTaskPromise = null;
|
|
6
5
|
#isConnected = false;
|
|
6
|
+
#lastOwnCurrentTimeMs = NaN;
|
|
7
|
+
#lastDurationMs = NaN;
|
|
7
8
|
constructor(host, setCurrentTime) {
|
|
8
9
|
this.host = host;
|
|
9
10
|
this.setCurrentTime = setCurrentTime;
|
|
@@ -14,23 +15,23 @@ var CurrentTimeController = class {
|
|
|
14
15
|
}
|
|
15
16
|
hostDisconnected() {
|
|
16
17
|
this.#isConnected = false;
|
|
17
|
-
this.#lastTaskPromise = null;
|
|
18
18
|
this.host.removeController(this);
|
|
19
19
|
}
|
|
20
20
|
hostUpdated() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
if (!this.#isConnected) return;
|
|
22
|
+
const { ownCurrentTimeMs, durationMs } = this.host;
|
|
23
|
+
if (ownCurrentTimeMs !== this.#lastOwnCurrentTimeMs || durationMs !== this.#lastDurationMs) {
|
|
24
|
+
this.#lastOwnCurrentTimeMs = ownCurrentTimeMs;
|
|
25
|
+
this.#lastDurationMs = durationMs;
|
|
26
|
+
this.#updateReactState();
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
#updateReactState() {
|
|
30
|
+
const { ownCurrentTimeMs, durationMs } = this.host;
|
|
30
31
|
this.setCurrentTime({
|
|
31
|
-
ownCurrentTimeMs
|
|
32
|
-
durationMs
|
|
33
|
-
percentComplete:
|
|
32
|
+
ownCurrentTimeMs,
|
|
33
|
+
durationMs,
|
|
34
|
+
percentComplete: durationMs > 0 ? ownCurrentTimeMs / durationMs : 0
|
|
34
35
|
});
|
|
35
36
|
}
|
|
36
37
|
syncNow() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTimingInfo.js","names":["host: {\n ownCurrentTimeMs: number;\n durationMs: number;\n
|
|
1
|
+
{"version":3,"file":"useTimingInfo.js","names":["host: {\n ownCurrentTimeMs: number;\n durationMs: number;\n } & ReactiveControllerHost","setCurrentTime: React.Dispatch<React.SetStateAction<TimeInfo>>","#isConnected","#lastOwnCurrentTimeMs","#lastDurationMs","#updateReactState"],"sources":["../../src/hooks/useTimingInfo.ts"],"sourcesContent":["import type { EFTimegroup } from \"@editframe/elements\";\nimport type { ReactiveController, ReactiveControllerHost } from \"lit\";\nimport { useEffect, useRef, useState } from \"react\";\n\ninterface TimeInfo {\n ownCurrentTimeMs: number;\n durationMs: number;\n percentComplete: number;\n}\n\nclass CurrentTimeController implements ReactiveController {\n #isConnected = false;\n #lastOwnCurrentTimeMs = Number.NaN;\n #lastDurationMs = Number.NaN;\n\n constructor(\n private host: {\n ownCurrentTimeMs: number;\n durationMs: number;\n } & ReactiveControllerHost,\n private setCurrentTime: React.Dispatch<React.SetStateAction<TimeInfo>>,\n ) {\n this.host.addController(this);\n }\n\n hostConnected(): void {\n this.#isConnected = true;\n }\n\n hostDisconnected(): void {\n this.#isConnected = false;\n this.host.removeController(this);\n }\n\n hostUpdated(): void {\n if (!this.#isConnected) return;\n\n const { ownCurrentTimeMs, durationMs } = this.host;\n\n if (\n ownCurrentTimeMs !== this.#lastOwnCurrentTimeMs ||\n durationMs !== this.#lastDurationMs\n ) {\n this.#lastOwnCurrentTimeMs = ownCurrentTimeMs;\n this.#lastDurationMs = durationMs;\n this.#updateReactState();\n }\n }\n\n #updateReactState(): void {\n const { ownCurrentTimeMs, durationMs } = this.host;\n this.setCurrentTime({\n ownCurrentTimeMs,\n durationMs,\n percentComplete: durationMs > 0 ? ownCurrentTimeMs / durationMs : 0,\n });\n }\n\n syncNow(): void {\n this.#updateReactState();\n }\n}\n\nexport const useTimingInfo = (\n timegroupRef: React.RefObject<EFTimegroup | null> = useRef<EFTimegroup>(null),\n) => {\n const [timeInfo, setTimeInfo] = useState<TimeInfo>({\n ownCurrentTimeMs: 0,\n durationMs: 0,\n percentComplete: 0,\n });\n\n useEffect(() => {\n if (!timegroupRef.current) {\n throw new Error(\"Timegroup ref not set\");\n }\n\n const controller = new CurrentTimeController(\n timegroupRef.current,\n setTimeInfo,\n );\n\n if (timegroupRef.current.isConnected) {\n controller.hostConnected();\n controller.syncNow();\n }\n\n return () => {\n controller.hostDisconnected();\n };\n }, [timegroupRef.current]);\n\n return { ...timeInfo, ref: timegroupRef };\n};\n"],"mappings":";;;AAUA,IAAM,wBAAN,MAA0D;CACxD,eAAe;CACf,wBAAwB;CACxB,kBAAkB;CAElB,YACE,AAAQA,MAIR,AAAQC,gBACR;EALQ;EAIA;AAER,OAAK,KAAK,cAAc,KAAK;;CAG/B,gBAAsB;AACpB,QAAKC,cAAe;;CAGtB,mBAAyB;AACvB,QAAKA,cAAe;AACpB,OAAK,KAAK,iBAAiB,KAAK;;CAGlC,cAAoB;AAClB,MAAI,CAAC,MAAKA,YAAc;EAExB,MAAM,EAAE,kBAAkB,eAAe,KAAK;AAE9C,MACE,qBAAqB,MAAKC,wBAC1B,eAAe,MAAKC,gBACpB;AACA,SAAKD,uBAAwB;AAC7B,SAAKC,iBAAkB;AACvB,SAAKC,kBAAmB;;;CAI5B,oBAA0B;EACxB,MAAM,EAAE,kBAAkB,eAAe,KAAK;AAC9C,OAAK,eAAe;GAClB;GACA;GACA,iBAAiB,aAAa,IAAI,mBAAmB,aAAa;GACnE,CAAC;;CAGJ,UAAgB;AACd,QAAKA,kBAAmB;;;AAI5B,MAAa,iBACX,eAAoD,OAAoB,KAAK,KAC1E;CACH,MAAM,CAAC,UAAU,eAAe,SAAmB;EACjD,kBAAkB;EAClB,YAAY;EACZ,iBAAiB;EAClB,CAAC;AAEF,iBAAgB;AACd,MAAI,CAAC,aAAa,QAChB,OAAM,IAAI,MAAM,wBAAwB;EAG1C,MAAM,aAAa,IAAI,sBACrB,aAAa,SACb,YACD;AAED,MAAI,aAAa,QAAQ,aAAa;AACpC,cAAW,eAAe;AAC1B,cAAW,SAAS;;AAGtB,eAAa;AACX,cAAW,kBAAkB;;IAE9B,CAAC,aAAa,QAAQ,CAAC;AAE1B,QAAO;EAAE,GAAG;EAAU,KAAK;EAAc"}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { Captions, CaptionsActiveWord, CaptionsAfterActiveWord, CaptionsBeforeAc
|
|
|
4
4
|
import { Text, TextSegment } from "./elements/Text.js";
|
|
5
5
|
import { Image } from "./elements/Image.js";
|
|
6
6
|
import { Surface } from "./elements/Surface.js";
|
|
7
|
-
import { ThumbnailStrip } from "./elements/ThumbnailStrip.js";
|
|
8
7
|
import { Timegroup } from "./elements/Timegroup.js";
|
|
9
8
|
import { TimelineRoot } from "./components/TimelineRoot.js";
|
|
10
9
|
import { Video } from "./elements/Video.js";
|
|
@@ -24,11 +23,14 @@ import { Pause } from "./gui/Pause.js";
|
|
|
24
23
|
import { Play } from "./gui/Play.js";
|
|
25
24
|
import { Preview } from "./gui/Preview.js";
|
|
26
25
|
import { Scrubber, ScrubberProps } from "./gui/Scrubber.js";
|
|
26
|
+
import { ThumbnailStrip } from "./gui/ThumbnailStrip.js";
|
|
27
|
+
import { TrimHandles } from "./gui/TrimHandles.js";
|
|
27
28
|
import { TimelineRuler } from "./gui/TimelineRuler.js";
|
|
28
29
|
import { ToggleLoop } from "./gui/ToggleLoop.js";
|
|
29
30
|
import { TogglePlay } from "./gui/TogglePlay.js";
|
|
30
31
|
import { Workbench } from "./gui/Workbench.js";
|
|
31
32
|
import { useTimingInfo } from "./hooks/useTimingInfo.js";
|
|
33
|
+
import { MediaInfo, useMediaInfo } from "./hooks/useMediaInfo.js";
|
|
32
34
|
import { usePanZoomTransform } from "./hooks/usePanZoomTransform.js";
|
|
33
|
-
import { elementNeedsFitScale, needsFitScale } from "@editframe/elements";
|
|
34
|
-
export { Audio, Captions, CaptionsActiveWord, CaptionsAfterActiveWord, CaptionsBeforeActiveWord, CaptionsSegment, Configuration, Controls, Dial, Filmstrip, FitScale, FocusOverlay, Image, OverlayItem, type OverlayItemProps, OverlayLayer, PanZoom, Pause, Play, Preview, ResizableBox, Scrubber, type ScrubberProps, Surface, Text, TextSegment, ThumbnailStrip, TimeDisplay, Timegroup, TimelineRoot, TimelineRuler, ToggleLoop, TogglePlay, TransformHandles, Video, Waveform, Workbench, elementNeedsFitScale, needsFitScale, usePanZoomTransform, useTimingInfo };
|
|
35
|
+
import { TrimChangeDetail, TrimValue, elementNeedsFitScale, needsFitScale } from "@editframe/elements";
|
|
36
|
+
export { Audio, Captions, CaptionsActiveWord, CaptionsAfterActiveWord, CaptionsBeforeActiveWord, CaptionsSegment, Configuration, Controls, Dial, Filmstrip, FitScale, FocusOverlay, Image, type MediaInfo, OverlayItem, type OverlayItemProps, OverlayLayer, PanZoom, Pause, Play, Preview, ResizableBox, Scrubber, type ScrubberProps, Surface, Text, TextSegment, ThumbnailStrip, TimeDisplay, Timegroup, TimelineRoot, TimelineRuler, ToggleLoop, TogglePlay, TransformHandles, type TrimChangeDetail, TrimHandles, type TrimValue, Video, Waveform, Workbench, elementNeedsFitScale, needsFitScale, useMediaInfo, usePanZoomTransform, useTimingInfo };
|