@design-edito/tools 0.3.10 → 0.3.12
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/agnostic/colors/index.d.ts +2 -2
- package/agnostic/colors/index.js +2 -2
- package/agnostic/css/clss/index.d.ts +53 -1
- package/agnostic/css/clss/index.js +1 -1
- package/agnostic/css/index.d.ts +1 -1
- package/agnostic/css/index.js +1 -1
- package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +15 -15
- package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +15 -15
- package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +6 -6
- package/agnostic/html/hyper-json/smart-tags/isolated/index.js +6 -6
- package/agnostic/index.d.ts +4 -4
- package/agnostic/index.js +4 -4
- package/agnostic/misc/assert/index.d.ts +3 -0
- package/agnostic/misc/index.d.ts +2 -2
- package/agnostic/misc/index.js +2 -2
- package/agnostic/misc/logs/index.d.ts +1 -1
- package/agnostic/misc/logs/index.js +1 -1
- package/agnostic/misc/logs/logger/index.d.ts +10 -0
- package/agnostic/misc/logs/logger/index.js +40 -10
- package/agnostic/misc/logs/styles/index.d.ts +1 -0
- package/agnostic/misc/logs/styles/index.js +27 -9
- package/agnostic/numbers/index.d.ts +2 -2
- package/agnostic/numbers/index.js +2 -2
- package/agnostic/objects/index.d.ts +2 -2
- package/agnostic/objects/index.js +2 -2
- package/agnostic/optim/index.d.ts +1 -1
- package/agnostic/optim/index.js +1 -1
- package/agnostic/random/index.d.ts +1 -1
- package/agnostic/random/index.js +1 -1
- package/agnostic/sanitization/index.d.ts +2 -2
- package/agnostic/sanitization/index.js +2 -2
- package/agnostic/strings/index.d.ts +1 -1
- package/agnostic/strings/index.js +1 -1
- package/agnostic/time/index.d.ts +1 -1
- package/agnostic/time/index.js +1 -1
- package/agnostic/time/transitions/index.d.ts +3 -3
- package/agnostic/time/transitions/index.js +4 -4
- package/components/BeforeAfter/index.controlled.d.ts +46 -0
- package/components/BeforeAfter/index.d.ts +55 -0
- package/components/BeforeAfter/index.js +40 -0
- package/components/BeforeAfter/utils.d.ts +4 -0
- package/components/Disclaimer/index.d.ts +45 -0
- package/components/Disclaimer/index.js +70 -0
- package/components/Disclaimer/styles.module.css +0 -0
- package/components/Drawer/index.d.ts +45 -0
- package/components/Drawer/index.js +82 -0
- package/components/Drawer/styles.module.css +0 -0
- package/components/EventListener/index.d.ts +20 -3
- package/components/EventListener/index.js +15 -22
- package/components/Gallery/index.d.ts +67 -0
- package/components/Gallery/index.js +173 -0
- package/components/Gallery/styles.module.css +33 -0
- package/components/Gallery/utils.d.ts +1 -0
- package/components/Image/index.d.ts +60 -0
- package/components/Image/index.js +99 -0
- package/components/Image/styles.module.css +0 -0
- package/components/IntersectionObserver/index.d.ts +48 -11
- package/components/IntersectionObserver/index.js +13 -22
- package/components/Paginator/index.d.ts +72 -0
- package/components/Paginator/index.js +116 -0
- package/components/Paginator/styles.module.css +9 -0
- package/components/ResizeObserver/index.d.ts +27 -0
- package/components/ResizeObserver/index.js +81 -0
- package/components/Scrllgngn/index.d.ts +123 -0
- package/components/Scrllgngn/index.js +175 -0
- package/components/Scrllgngn/styles.module.css +74 -0
- package/components/ScrollListener/index.d.ts +47 -0
- package/components/ScrollListener/index.js +110 -0
- package/components/ScrollListener/styles.module.css +0 -0
- package/components/ScrollListener/utils.d.ts +41 -0
- package/components/Sequencer/index.controlled.d.ts +78 -0
- package/components/Sequencer/index.d.ts +85 -0
- package/components/Sequencer/index.js +109 -0
- package/components/Sequencer/styles.module.css +0 -0
- package/components/ShadowRoot/index.d.ts +35 -0
- package/components/ShadowRoot/index.js +56 -0
- package/components/ShadowRoot/styles.module.css +0 -0
- package/components/Subtitles/index.d.ts +58 -0
- package/components/Subtitles/index.js +111 -0
- package/components/Subtitles/styles.module.css +0 -0
- package/components/Subtitles/types.d.ts +10 -0
- package/components/Subtitles/types.js +0 -0
- package/components/Subtitles/utils.d.ts +28 -0
- package/components/Theatre/index.d.ts +64 -0
- package/components/Theatre/index.js +97 -0
- package/components/Theatre/styles.module.css +0 -0
- package/components/UIModule/index.d.ts +85 -0
- package/components/UIModule/index.js +134 -0
- package/components/UIModule/styles.module.css +0 -0
- package/components/Video/index.d.ts +139 -0
- package/components/Video/index.js +385 -0
- package/components/Video/styles.module.css +0 -0
- package/components/Video/utils.d.ts +14 -0
- package/components/_WIP_AudioQuote/index.d.ts +1 -0
- package/components/_WIP_AudioQuote/index.js +0 -0
- package/components/_WIP_Icon/index.d.ts +1 -0
- package/components/_WIP_Icon/index.js +0 -0
- package/components/index.d.ts +18 -1
- package/components/index.js +18 -1
- package/components/public-classnames.d.ts +17 -3
- package/components/utils/index.d.ts +1 -0
- package/components/utils/index.js +12 -0
- package/components/utils/types.d.ts +3 -0
- package/components/utils/types.js +0 -0
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/node/@aws-s3/index.test.d.ts +1 -0
- package/node/@aws-s3/storage/file/index.d.ts +3 -3
- package/node/@aws-s3/storage/file/index.js +3 -3
- package/node/@google-cloud/storage/directory/index.d.ts +1 -1
- package/node/@google-cloud/storage/directory/index.js +1 -1
- package/node/@google-cloud/storage/file/index.d.ts +4 -4
- package/node/@google-cloud/storage/file/index.js +4 -4
- package/node/cloud-storage/operations/index.d.ts +1 -1
- package/node/cloud-storage/operations/index.js +1 -1
- package/node/ftps/directory/index.d.ts +1 -1
- package/node/ftps/directory/index.js +1 -1
- package/node/ftps/file/index.d.ts +1 -1
- package/node/ftps/file/index.js +1 -1
- package/node/images/index.d.ts +1 -1
- package/node/images/index.js +1 -1
- package/node/images/transform/operations/index.d.ts +4 -4
- package/node/images/transform/operations/index.js +4 -4
- package/node/index.d.ts +3 -3
- package/node/index.js +3 -3
- package/node/process/spawner/index.d.ts +61 -2
- package/node/process/spawner/index.js +6 -6
- package/node/sftp/file/index.d.ts +3 -3
- package/node/sftp/file/index.js +3 -3
- package/package.json +1051 -13
- package/components/Input/index.d.ts +0 -7
- package/components/Input/index.js +0 -29
- /package/components/{Input → BeforeAfter}/styles.module.css +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// src/components/Subtitles/index.tsx
|
|
2
|
+
import {
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState
|
|
7
|
+
} from "react";
|
|
8
|
+
import { clss } from "../../agnostic/css/clss/index.js";
|
|
9
|
+
import { toError } from "../../agnostic/misc/cast/index.js";
|
|
10
|
+
import { unknownToString } from "../../agnostic/errors/unknown-to-string/index.js";
|
|
11
|
+
import { mergeClassNames } from "../utils/index.js";
|
|
12
|
+
import { subtitles as publicClassName } from "../public-classnames.js";
|
|
13
|
+
import {
|
|
14
|
+
computeSubGroupsWithBoundaries,
|
|
15
|
+
getCurrentGroup,
|
|
16
|
+
parseSubs
|
|
17
|
+
} from "./utils.js";
|
|
18
|
+
import cssModule from "./styles.module.css";
|
|
19
|
+
import { jsx } from "react/jsx-runtime";
|
|
20
|
+
var Subtitles = ({
|
|
21
|
+
src,
|
|
22
|
+
srtFileContent,
|
|
23
|
+
subsGroups,
|
|
24
|
+
timecodeMs,
|
|
25
|
+
isEnded,
|
|
26
|
+
className,
|
|
27
|
+
onLoaded,
|
|
28
|
+
onParsed,
|
|
29
|
+
onLoadFailed
|
|
30
|
+
}) => {
|
|
31
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
32
|
+
const [loadError, setLoadError] = useState(null);
|
|
33
|
+
const [parsedSubs, setParsedSubs] = useState([]);
|
|
34
|
+
const pParsedSubs = useRef(parsedSubs);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (pParsedSubs.current === parsedSubs) return;
|
|
37
|
+
onParsed?.(parsedSubs);
|
|
38
|
+
}, [parsedSubs]);
|
|
39
|
+
const fetchAndParseSubs = useCallback(async (src2, srtFileContent2) => {
|
|
40
|
+
if (src2 === void 0) return;
|
|
41
|
+
if (srtFileContent2 !== void 0) return setParsedSubs(parseSubs(srtFileContent2));
|
|
42
|
+
setIsLoading(true);
|
|
43
|
+
setLoadError(null);
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(src2);
|
|
46
|
+
const srtContent = await response.text();
|
|
47
|
+
onLoaded?.(srtContent);
|
|
48
|
+
const parsedSubs2 = parseSubs(srtContent);
|
|
49
|
+
setParsedSubs(parsedSubs2);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
setLoadError(error instanceof Error ? error : new Error(unknownToString(error)));
|
|
52
|
+
console.error(error);
|
|
53
|
+
onLoadFailed?.(toError(error));
|
|
54
|
+
} finally {
|
|
55
|
+
setIsLoading(false);
|
|
56
|
+
}
|
|
57
|
+
}, [onLoadFailed, onLoaded]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
fetchAndParseSubs(src, srtFileContent).catch((error) => {
|
|
60
|
+
console.error(error);
|
|
61
|
+
});
|
|
62
|
+
}, [fetchAndParseSubs, src, srtFileContent]);
|
|
63
|
+
const c = clss(publicClassName, { cssModule });
|
|
64
|
+
const rootClss = mergeClassNames(
|
|
65
|
+
c(null, {
|
|
66
|
+
loading: isLoading,
|
|
67
|
+
error: loadError !== null
|
|
68
|
+
}),
|
|
69
|
+
className
|
|
70
|
+
);
|
|
71
|
+
const prevSubs = parsedSubs.filter(({ start }) => start != null && start < (timecodeMs ?? 0));
|
|
72
|
+
const lastPrevSub = prevSubs[prevSubs.length - 1];
|
|
73
|
+
const highestSubId = Math.max(...parsedSubs.map((sub) => sub.id));
|
|
74
|
+
const subsGroupsWithBoundaries = computeSubGroupsWithBoundaries(subsGroups, highestSubId);
|
|
75
|
+
const currentGroup = getCurrentGroup(subsGroupsWithBoundaries, lastPrevSub?.id, isEnded);
|
|
76
|
+
return /* @__PURE__ */ jsx("div", { className: rootClss, children: timecodeMs !== void 0 && parsedSubs.length > 0 && subsGroupsWithBoundaries.map((group) => {
|
|
77
|
+
const groupSubs = parsedSubs.filter((sub) => sub.id >= group.startId && sub.id <= group.endId);
|
|
78
|
+
const totalSubs = groupSubs.length;
|
|
79
|
+
const groupClass = c("group", { curr: currentGroup?.startId === group.startId });
|
|
80
|
+
const subsNodes = groupSubs.map((sub, subIndex) => {
|
|
81
|
+
let subText = sub.content?.trim() ?? "";
|
|
82
|
+
if (subIndex !== totalSubs - 1) subText += " ";
|
|
83
|
+
const subClass = c("sub", {
|
|
84
|
+
prev: sub.start !== void 0 && lastPrevSub?.start !== void 0 && sub.start <= lastPrevSub.start,
|
|
85
|
+
curr: sub.start !== void 0 && timecodeMs >= sub.start && sub.end !== void 0 && timecodeMs <= sub.end
|
|
86
|
+
});
|
|
87
|
+
return /* @__PURE__ */ jsx(
|
|
88
|
+
"span",
|
|
89
|
+
{
|
|
90
|
+
className: subClass,
|
|
91
|
+
"data-sub-pos": sub.id,
|
|
92
|
+
children: subText
|
|
93
|
+
},
|
|
94
|
+
sub.id
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
return /* @__PURE__ */ jsx(
|
|
98
|
+
"div",
|
|
99
|
+
{
|
|
100
|
+
className: groupClass,
|
|
101
|
+
"data-start-sub-pos": group.startId,
|
|
102
|
+
"data-end-sub-pos": group.endId,
|
|
103
|
+
children: subsNodes
|
|
104
|
+
},
|
|
105
|
+
group.startId
|
|
106
|
+
);
|
|
107
|
+
}) });
|
|
108
|
+
};
|
|
109
|
+
export {
|
|
110
|
+
Subtitles
|
|
111
|
+
};
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ParsedSub, SubGroupBoundaries } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Convertit un timecode au format SRT (hh:mm:ss,ms) en millisecondes.
|
|
4
|
+
* @param timecode - Timecode sous forme de chaîne (ex: '00:01:23,456')
|
|
5
|
+
* @returns Le temps en millisecondes
|
|
6
|
+
*/
|
|
7
|
+
export declare const getTimecodeToMs: (timecode: string) => number;
|
|
8
|
+
/**
|
|
9
|
+
* Parse un texte brut de sous-titres SRT en une liste d'objets ParsedSub.
|
|
10
|
+
* @param rawSubs - Sous-titres bruts au format SRT
|
|
11
|
+
* @returns Tableau d'objets ParsedSub
|
|
12
|
+
*/
|
|
13
|
+
export declare const parseSubs: (rawSubs: string) => ParsedSub[];
|
|
14
|
+
/**
|
|
15
|
+
* Calcule les groupes de sous-titres avec leurs bornes (startId, endId).
|
|
16
|
+
* @param subsGroups - Tableau d'IDs de fin de groupe
|
|
17
|
+
* @param highestSubId - ID le plus élevé des sous-titres
|
|
18
|
+
* @returns Tableau de SubGroupBoundaries
|
|
19
|
+
*/
|
|
20
|
+
export declare const computeSubGroupsWithBoundaries: (subsGroups: number[] | undefined, highestSubId: number) => SubGroupBoundaries[];
|
|
21
|
+
/**
|
|
22
|
+
* Retourne le groupe de sous-titres courant selon l'ID du dernier sous-titre précédent et l'état.
|
|
23
|
+
* @param subsGroupsWithBoundaries - Tableau des groupes avec bornes
|
|
24
|
+
* @param lastPrevSubId - ID du dernier sous-titre précédent
|
|
25
|
+
* @param isEnded - Indique si la lecture est terminée
|
|
26
|
+
* @returns Le groupe courant ou undefined
|
|
27
|
+
*/
|
|
28
|
+
export declare const getCurrentGroup: (subsGroupsWithBoundaries: SubGroupBoundaries[], lastPrevSubId: number | undefined, isEnded: boolean | undefined) => SubGroupBoundaries | undefined;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type FunctionComponent, type PropsWithChildren, type ReactNode } from 'react';
|
|
2
|
+
import type { WithClassName } from '../utils/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Props for the {@link Theatre} component.
|
|
5
|
+
*
|
|
6
|
+
* @property closeBtnContent - Custom content rendered inside the close/exit button.
|
|
7
|
+
* @property openBtnContent - Custom content rendered inside the open/enter button.
|
|
8
|
+
* @property isOn - Controlled theatre mode state. When provided, overrides the
|
|
9
|
+
* internal state. Use together with {@link Props.onToggleClick} for fully
|
|
10
|
+
* controlled usage.
|
|
11
|
+
* @property defaultIsOn - Default state for the theatre mode.
|
|
12
|
+
* @property exitOnEscape — When uncontrolled and on, toggles internal state to off when 'esc' key is pressed
|
|
13
|
+
* @property exitOnBgClick — When uncontrolled and on, toggles internal state to off when the background is clicked
|
|
14
|
+
* @property stateHandlers - Callbacks called after the internal state changed
|
|
15
|
+
* @property stateHandlers.toggled - Callback invoked after the state changed
|
|
16
|
+
* @property actionHandlers - Callbacks called after a user action on children elements
|
|
17
|
+
* @property actionHandlers.toggleClick - Callback invoked when either the open or close
|
|
18
|
+
* button is clicked, the 'esc' key pressed or the background clicked. Receives the theatre state value (`isOn`) at the time of the click,
|
|
19
|
+
* i.e. the previous state before the toggle.
|
|
20
|
+
* @property className - Optional additional class name(s) applied to the root element.
|
|
21
|
+
* @property children - Content rendered both in the default slot and, when theatre
|
|
22
|
+
* mode is active, duplicated inside the stage element.
|
|
23
|
+
*/
|
|
24
|
+
export type Props = PropsWithChildren<WithClassName<{
|
|
25
|
+
closeBtnContent?: ReactNode;
|
|
26
|
+
openBtnContent?: ReactNode;
|
|
27
|
+
isOn?: boolean;
|
|
28
|
+
defaultIsOn?: boolean;
|
|
29
|
+
exitOnEscape?: boolean;
|
|
30
|
+
exitOnBgClick?: boolean;
|
|
31
|
+
stateHandlers?: {
|
|
32
|
+
toggled?: (isOn: boolean) => void;
|
|
33
|
+
};
|
|
34
|
+
actionHandlers?: {
|
|
35
|
+
toggleClick?: (prevIsOn: boolean) => void;
|
|
36
|
+
};
|
|
37
|
+
}>>;
|
|
38
|
+
/**
|
|
39
|
+
* Theatre mode component. Wraps content in a toggleable fullscreen-like "stage"
|
|
40
|
+
* overlay. Supports both controlled and uncontrolled usage.
|
|
41
|
+
*
|
|
42
|
+
* When `isOn` is not provided the component manages its own open/closed state
|
|
43
|
+
* internally. When `isOn` is provided it acts as the source of truth and the
|
|
44
|
+
* internal state is ignored.
|
|
45
|
+
*
|
|
46
|
+
* ### Root element modifiers
|
|
47
|
+
* The root `<div>` receives the public class name defined by `theatre` and the
|
|
48
|
+
* following BEM-style modifier classes:
|
|
49
|
+
* - `--on` — when theatre mode is active.
|
|
50
|
+
* - `--off` — when theatre mode is inactive.
|
|
51
|
+
*
|
|
52
|
+
* ### Child elements
|
|
53
|
+
* - `__stage` — container rendered inside the root that holds the duplicated
|
|
54
|
+
* `children` when theatre mode is active. Only mounted when `isOn` is `true`.
|
|
55
|
+
* - `__open-btn` — clickable element that activates theatre mode.
|
|
56
|
+
* - `__close-btn` — clickable element that deactivates theatre mode.
|
|
57
|
+
*
|
|
58
|
+
* @param props - Component properties.
|
|
59
|
+
* @see {@link Props}
|
|
60
|
+
* @returns A root `<div>` containing the children in their original position,
|
|
61
|
+
* a stage overlay with the duplicated children (when active), and the open/close
|
|
62
|
+
* toggle buttons.
|
|
63
|
+
*/
|
|
64
|
+
export declare const Theatre: FunctionComponent<Props>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// src/components/Theatre/index.tsx
|
|
2
|
+
import {
|
|
3
|
+
useEffect,
|
|
4
|
+
useState,
|
|
5
|
+
useRef
|
|
6
|
+
} from "react";
|
|
7
|
+
import { clss } from "../../agnostic/css/clss/index.js";
|
|
8
|
+
import { mergeClassNames } from "../utils/index.js";
|
|
9
|
+
import { theatre as publicClassName } from "../public-classnames.js";
|
|
10
|
+
import cssModule from "./styles.module.css";
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
var Theatre = ({
|
|
13
|
+
closeBtnContent,
|
|
14
|
+
openBtnContent,
|
|
15
|
+
isOn,
|
|
16
|
+
defaultIsOn,
|
|
17
|
+
exitOnEscape,
|
|
18
|
+
exitOnBgClick,
|
|
19
|
+
stateHandlers,
|
|
20
|
+
actionHandlers,
|
|
21
|
+
children,
|
|
22
|
+
className
|
|
23
|
+
}) => {
|
|
24
|
+
const [internalIsOn, setInternalIsOn] = useState(defaultIsOn ?? false);
|
|
25
|
+
const stageRef = useRef(null);
|
|
26
|
+
const isTheatreOn = isOn ?? internalIsOn;
|
|
27
|
+
const prevIsTheatreOnRef = useRef(isTheatreOn);
|
|
28
|
+
const handleCloseBtnClick = () => {
|
|
29
|
+
actionHandlers?.toggleClick?.(isTheatreOn);
|
|
30
|
+
if (isOn === void 0) setInternalIsOn(false);
|
|
31
|
+
};
|
|
32
|
+
const handleOpenBtnClick = () => {
|
|
33
|
+
actionHandlers?.toggleClick?.(isTheatreOn);
|
|
34
|
+
if (isOn === void 0) setInternalIsOn(true);
|
|
35
|
+
};
|
|
36
|
+
const handleStageBgClick = (e) => {
|
|
37
|
+
if (exitOnBgClick !== true) return;
|
|
38
|
+
if (e.target !== stageRef.current) return;
|
|
39
|
+
actionHandlers?.toggleClick?.(isTheatreOn);
|
|
40
|
+
if (isOn === void 0) setInternalIsOn(false);
|
|
41
|
+
};
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (prevIsTheatreOnRef.current !== isTheatreOn) {
|
|
44
|
+
stateHandlers?.toggled?.(isTheatreOn);
|
|
45
|
+
prevIsTheatreOnRef.current = isTheatreOn;
|
|
46
|
+
}
|
|
47
|
+
}, [isTheatreOn, stateHandlers]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (exitOnEscape === true || !isTheatreOn || isOn !== void 0) return;
|
|
50
|
+
const listener = (e) => {
|
|
51
|
+
if (e.key !== "Escape") return;
|
|
52
|
+
actionHandlers?.toggleClick?.(isTheatreOn);
|
|
53
|
+
setInternalIsOn(false);
|
|
54
|
+
};
|
|
55
|
+
window.addEventListener("keydown", listener);
|
|
56
|
+
return () => window.removeEventListener("keydown", listener);
|
|
57
|
+
}, [exitOnEscape, isTheatreOn, isOn]);
|
|
58
|
+
const c = clss(publicClassName, { cssModule });
|
|
59
|
+
const rootClss = mergeClassNames(c(null, {
|
|
60
|
+
"on": isTheatreOn,
|
|
61
|
+
"off": !isTheatreOn
|
|
62
|
+
}), className);
|
|
63
|
+
const stageClass = c("stage");
|
|
64
|
+
const openBtnClass = c("open-btn");
|
|
65
|
+
const closeBtnClass = c("close-btn");
|
|
66
|
+
return /* @__PURE__ */ jsxs("div", { className: rootClss, children: [
|
|
67
|
+
children,
|
|
68
|
+
/* @__PURE__ */ jsx(
|
|
69
|
+
"div",
|
|
70
|
+
{
|
|
71
|
+
className: stageClass,
|
|
72
|
+
onClick: handleStageBgClick,
|
|
73
|
+
ref: stageRef,
|
|
74
|
+
children: isTheatreOn && children
|
|
75
|
+
}
|
|
76
|
+
),
|
|
77
|
+
/* @__PURE__ */ jsx(
|
|
78
|
+
"div",
|
|
79
|
+
{
|
|
80
|
+
className: closeBtnClass,
|
|
81
|
+
onClick: handleCloseBtnClick,
|
|
82
|
+
children: closeBtnContent
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
/* @__PURE__ */ jsx(
|
|
86
|
+
"div",
|
|
87
|
+
{
|
|
88
|
+
className: openBtnClass,
|
|
89
|
+
onClick: handleOpenBtnClick,
|
|
90
|
+
children: openBtnContent
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
] });
|
|
94
|
+
};
|
|
95
|
+
export {
|
|
96
|
+
Theatre
|
|
97
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'react';
|
|
2
|
+
import type { WithClassName } from '../utils/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Describes the contract a dynamically imported UI module must satisfy.
|
|
5
|
+
* Every member is validated at runtime after the import resolves.
|
|
6
|
+
*
|
|
7
|
+
* @property init - Called once after the module loads. Receives the current
|
|
8
|
+
* `props` and must return the root `Element` that will be appended to the
|
|
9
|
+
* host `<div>`. Throwing inside `init` is caught and surfaced as an error state.
|
|
10
|
+
* @property destroy - Called when the component unmounts or `src` changes.
|
|
11
|
+
* Receives the `Element` previously returned by `init`. Use it to tear down
|
|
12
|
+
* event listeners, timers, or third-party instances.
|
|
13
|
+
* @property update - Optional. Called when `props` change after the module is
|
|
14
|
+
* already initialized. Receives the live `Element` and the new props object.
|
|
15
|
+
* @property css - Optional array of raw CSS strings scoped automatically to
|
|
16
|
+
* the host element via `.<publicClassName>#<id> { … }` and injected as
|
|
17
|
+
* `<style>` elements.
|
|
18
|
+
*/
|
|
19
|
+
type ModuleData = {
|
|
20
|
+
init: (props: Record<string, unknown>) => Element;
|
|
21
|
+
destroy: (target: Element) => void;
|
|
22
|
+
update?: (target: Element, props: Record<string, unknown>) => void;
|
|
23
|
+
css?: string[];
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Props for the {@link UIModule} component.
|
|
27
|
+
*
|
|
28
|
+
* @property src - URL of the ES module to import dynamically. The module must
|
|
29
|
+
* satisfy the {@link ModuleData} interface — `init` and `destroy` are required,
|
|
30
|
+
* `update` and `css` are optional. When `undefined`, nothing is loaded and the
|
|
31
|
+
* component stays in the `--no-module` state.
|
|
32
|
+
* @property props - Arbitrary key-value object forwarded verbatim to the
|
|
33
|
+
* module's `init` call and, on subsequent changes, to `update` (if exported).
|
|
34
|
+
* @property stateHandlers - Optional callbacks invoked whenever internal state changes:
|
|
35
|
+
* - `idChanged` — called with the stable instance ID once on mount.
|
|
36
|
+
* - `isLoadingChanged` — called with the new loading state on every transition.
|
|
37
|
+
* - `loadedModuleChanged` — called with the new module value (`ModuleData`, `Error`, or `null`)
|
|
38
|
+
* after each load attempt or teardown.
|
|
39
|
+
* - `moduleTargetChanged` — called with the `Element` returned by `init`, or `null`
|
|
40
|
+
* when the module is unloaded or errored.
|
|
41
|
+
* @property className - Optional additional class name(s) applied to the root element.
|
|
42
|
+
*/
|
|
43
|
+
export type Props = WithClassName<{
|
|
44
|
+
src?: string;
|
|
45
|
+
props?: Record<string, unknown>;
|
|
46
|
+
stateHandlers?: {
|
|
47
|
+
idChanged?: (id: string) => void;
|
|
48
|
+
isLoadingChanged?: (isLoading: boolean) => void;
|
|
49
|
+
loadedModuleChanged?: (loadedModule: ModuleData | Error | null) => void;
|
|
50
|
+
moduleTargetChanged?: (moduleTarget: Element | null) => void;
|
|
51
|
+
};
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Dynamic UI module host component. Asynchronously imports an ES module by URL,
|
|
55
|
+
* validates its exported interface, calls its `init` lifecycle to obtain a DOM
|
|
56
|
+
* `Element`, and appends that element to its own root `<div>`. Handles loading,
|
|
57
|
+
* error, and teardown states automatically.
|
|
58
|
+
*
|
|
59
|
+
* The imported module is expected to conform to the {@link ModuleData} interface.
|
|
60
|
+
* Any violation (missing exports, wrong types, `init` not returning an `Element`)
|
|
61
|
+
* transitions the component into the `--error` state and logs to `console.error`.
|
|
62
|
+
*
|
|
63
|
+
* ### Root element modifiers
|
|
64
|
+
* The root `<div>` receives the public class name defined by `uiModule` and
|
|
65
|
+
* the following BEM-style modifier classes reflecting the current load lifecycle:
|
|
66
|
+
* - `--loading` — the module fetch is in progress.
|
|
67
|
+
* - `--no-module` — no module has been loaded yet (`src` is undefined or the
|
|
68
|
+
* effect has not run).
|
|
69
|
+
* - `--error` — the import, validation, or `init` call failed.
|
|
70
|
+
* - `--loaded` — the module passed validation and `init` returned successfully.
|
|
71
|
+
* - `--initialized` — the `Element` returned by `init` has been appended to the
|
|
72
|
+
* host `<div>`.
|
|
73
|
+
*
|
|
74
|
+
* ### Root element attributes
|
|
75
|
+
* - `id` — a stable randomly generated ID (prefixed `f`) assigned once on mount.
|
|
76
|
+
* Used to scope the module's `css` entries to this specific instance.
|
|
77
|
+
*
|
|
78
|
+
* @param props - Component properties.
|
|
79
|
+
* @see {@link Props}
|
|
80
|
+
* @see {@link ModuleData}
|
|
81
|
+
* @returns A host `<div>` into which the module's root `Element` is appended,
|
|
82
|
+
* along with any `<style>` blocks exported by the module.
|
|
83
|
+
*/
|
|
84
|
+
export declare const UIModule: FunctionComponent<Props>;
|
|
85
|
+
export {};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// src/components/UIModule/index.tsx
|
|
2
|
+
import {
|
|
3
|
+
useEffect,
|
|
4
|
+
useRef,
|
|
5
|
+
useState
|
|
6
|
+
} from "react";
|
|
7
|
+
import { clss } from "../../agnostic/css/clss/index.js";
|
|
8
|
+
import { unknownToString } from "../../agnostic/errors/unknown-to-string/index.js";
|
|
9
|
+
import { isNonNullObject } from "../../agnostic/objects/is-object/index.js";
|
|
10
|
+
import { randomHash } from "../../agnostic/random/uuid/index.js";
|
|
11
|
+
import { mergeClassNames } from "../utils/index.js";
|
|
12
|
+
import { uiModule as publicClassName } from "../public-classnames.js";
|
|
13
|
+
import cssModule from "./styles.module.css";
|
|
14
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
var UIModule = ({
|
|
16
|
+
src,
|
|
17
|
+
props,
|
|
18
|
+
stateHandlers,
|
|
19
|
+
className
|
|
20
|
+
}) => {
|
|
21
|
+
const [id] = useState(`f${randomHash(10)}`);
|
|
22
|
+
const [loading, setLoading] = useState(false);
|
|
23
|
+
const [loadedModule, _setLoadedModule] = useState(null);
|
|
24
|
+
const [moduleTarget, _setModuleTarget] = useState(null);
|
|
25
|
+
const rootRef = useRef(null);
|
|
26
|
+
const loadedModuleRef = useRef(null);
|
|
27
|
+
const moduleTargetRef = useRef(null);
|
|
28
|
+
const setLoadedModule = (data) => {
|
|
29
|
+
loadedModuleRef.current = data;
|
|
30
|
+
_setLoadedModule(data);
|
|
31
|
+
};
|
|
32
|
+
const setModuleTarget = (data) => {
|
|
33
|
+
moduleTargetRef.current = data;
|
|
34
|
+
_setModuleTarget(data);
|
|
35
|
+
};
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
stateHandlers?.idChanged?.(id);
|
|
38
|
+
}, [id, stateHandlers]);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
stateHandlers?.isLoadingChanged?.(loading);
|
|
41
|
+
}, [loading, stateHandlers]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
stateHandlers?.loadedModuleChanged?.(loadedModule);
|
|
44
|
+
}, [loadedModule, stateHandlers]);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
stateHandlers?.moduleTargetChanged?.(moduleTarget);
|
|
47
|
+
}, [moduleTarget, stateHandlers]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (src === void 0) return;
|
|
50
|
+
try {
|
|
51
|
+
setLoading(true);
|
|
52
|
+
import(src).then((data) => {
|
|
53
|
+
setLoading(false);
|
|
54
|
+
const errs = {
|
|
55
|
+
notMod: new Error("Not a module"),
|
|
56
|
+
initFunc: new Error("Module exported member `init` must be a function"),
|
|
57
|
+
destroyFunc: new Error("Module exported member `destroy` must be a function"),
|
|
58
|
+
cssStrArr: new Error("Module exported member `css` must be an array of strings"),
|
|
59
|
+
updFunc: new Error("Module exported member `update` must be a function"),
|
|
60
|
+
initRetElt: new Error("Module exported function `init` must return an Element"),
|
|
61
|
+
initRetFirstElt: new Error("Module exported function `init` must return an array containing an Element in its first position")
|
|
62
|
+
};
|
|
63
|
+
if (!isNonNullObject(data)) return setLoadedModule(errs.notMod);
|
|
64
|
+
if (!("init" in data)) return setLoadedModule(errs.initFunc);
|
|
65
|
+
if (typeof data.init !== "function") return setLoadedModule(errs.initFunc);
|
|
66
|
+
if (!("destroy" in data) || typeof data.destroy !== "function") return setLoadedModule(errs.destroyFunc);
|
|
67
|
+
if ("css" in data) {
|
|
68
|
+
if (!Array.isArray(data.css)) return setLoadedModule(errs.cssStrArr);
|
|
69
|
+
if (data.css.some((i) => typeof i !== "string")) return setLoadedModule(errs.cssStrArr);
|
|
70
|
+
}
|
|
71
|
+
if ("update" in data && typeof data.update !== "function") return setLoadedModule(errs.updFunc);
|
|
72
|
+
const module = data;
|
|
73
|
+
setLoadedModule(module);
|
|
74
|
+
try {
|
|
75
|
+
const target = module.init(props ?? {});
|
|
76
|
+
if (!(target instanceof Element)) return setLoadedModule(errs.initRetElt);
|
|
77
|
+
setModuleTarget(target);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
setModuleTarget(null);
|
|
80
|
+
const e = err instanceof Error ? err : new Error(unknownToString(err));
|
|
81
|
+
setLoadedModule(e);
|
|
82
|
+
}
|
|
83
|
+
}).catch((err) => {
|
|
84
|
+
setLoading(false);
|
|
85
|
+
setLoadedModule(err instanceof Error ? err : new Error(unknownToString(err)));
|
|
86
|
+
setModuleTarget(null);
|
|
87
|
+
});
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (err instanceof Error) return setLoadedModule(err);
|
|
90
|
+
const errStr = unknownToString(err);
|
|
91
|
+
return setLoadedModule(new Error(errStr));
|
|
92
|
+
}
|
|
93
|
+
return () => {
|
|
94
|
+
if (moduleTargetRef.current === null) return;
|
|
95
|
+
if (loadedModuleRef.current instanceof Error) return;
|
|
96
|
+
if (loadedModuleRef.current === null) return;
|
|
97
|
+
loadedModuleRef.current.destroy(moduleTargetRef.current);
|
|
98
|
+
};
|
|
99
|
+
}, [src]);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (loadedModule instanceof Error) console.error(loadedModule);
|
|
102
|
+
}, [loadedModule]);
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (moduleTarget === null) return;
|
|
105
|
+
if (rootRef.current === null) return;
|
|
106
|
+
rootRef.current.appendChild(moduleTarget);
|
|
107
|
+
}, [moduleTarget]);
|
|
108
|
+
const c = clss(publicClassName, { cssModule });
|
|
109
|
+
const rootClss = mergeClassNames(
|
|
110
|
+
c(null, {
|
|
111
|
+
loading,
|
|
112
|
+
"no-module": loadedModule === null,
|
|
113
|
+
"error": loadedModule instanceof Error,
|
|
114
|
+
"loaded": !loading && loadedModule !== null && !(loadedModule instanceof Error),
|
|
115
|
+
"initialized": moduleTarget !== null
|
|
116
|
+
}),
|
|
117
|
+
className
|
|
118
|
+
);
|
|
119
|
+
return /* @__PURE__ */ jsxs(
|
|
120
|
+
"div",
|
|
121
|
+
{
|
|
122
|
+
className: rootClss,
|
|
123
|
+
ref: rootRef,
|
|
124
|
+
id,
|
|
125
|
+
children: [
|
|
126
|
+
loadedModule === null && "",
|
|
127
|
+
loadedModule !== null && !(loadedModule instanceof Error) && loadedModule.css?.map((css) => /* @__PURE__ */ jsx("style", { children: `.${publicClassName}#${id} { ${css} }` }))
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
export {
|
|
133
|
+
UIModule
|
|
134
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { type FunctionComponent, type PropsWithChildren, type VideoHTMLAttributes } from 'react';
|
|
2
|
+
import { type Props as DisclaimerProps } from '../Disclaimer/index.js';
|
|
3
|
+
import { type Props as SubsProps } from '../Subtitles/index.js';
|
|
4
|
+
import type { WithClassName } from '../utils/types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Describes a single video source.
|
|
7
|
+
*
|
|
8
|
+
* @property src - URL of the video file.
|
|
9
|
+
* @property type - MIME type of the source (e.g. `'video/mp4'`).
|
|
10
|
+
*/
|
|
11
|
+
type SourceData = {
|
|
12
|
+
src?: string;
|
|
13
|
+
type?: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Describes a single text track (subtitles, captions, chapters, etc.).
|
|
17
|
+
*
|
|
18
|
+
* @property src - URL of the track file.
|
|
19
|
+
* @property kind - Track type, maps directly to the `<track>` `kind` attribute.
|
|
20
|
+
* @property srclang - Language of the track content (e.g. `'fr'`, `'en'`).
|
|
21
|
+
* @property label - Human-readable label shown in the browser's track selector.
|
|
22
|
+
* @property default - When `true`, marks this track as the default selection.
|
|
23
|
+
*/
|
|
24
|
+
type TrackData = {
|
|
25
|
+
src?: string;
|
|
26
|
+
kind?: 'subtitles' | 'captions' | 'descriptions' | 'chapters' | 'metadata';
|
|
27
|
+
srclang?: string;
|
|
28
|
+
label?: string;
|
|
29
|
+
default?: boolean;
|
|
30
|
+
};
|
|
31
|
+
type ActionHandlersProps = {
|
|
32
|
+
playButtonClick?: (e: React.MouseEvent<HTMLButtonElement>, isPlaying: boolean, video: HTMLVideoElement | null) => void;
|
|
33
|
+
pauseButtonClick?: (e: React.MouseEvent<HTMLButtonElement>, isPlaying: boolean, video: HTMLVideoElement | null) => void;
|
|
34
|
+
loudButtonClick?: (e: React.MouseEvent<HTMLButtonElement>, isLoud: boolean, video: HTMLVideoElement | null) => void;
|
|
35
|
+
muteButtonClick?: (e: React.MouseEvent<HTMLButtonElement>, isLoud: boolean, video: HTMLVideoElement | null) => void;
|
|
36
|
+
volumeRangeChange?: (e: React.ChangeEvent<HTMLInputElement>, targetRangeVolume: number, volume: number, video: HTMLVideoElement | null) => void;
|
|
37
|
+
fullscreenButtonClick?: (e: React.MouseEvent<HTMLButtonElement>, isFullscreen: boolean, video: HTMLVideoElement | null) => void;
|
|
38
|
+
rateRangeChange?: (e: React.ChangeEvent<HTMLInputElement>, targetRangeRate: number, rate: number, video: HTMLVideoElement | null) => void;
|
|
39
|
+
timelineClick?: (e: React.MouseEvent<HTMLDivElement>, targetTimeSec: number, timeSec: number, video: HTMLVideoElement | null) => void;
|
|
40
|
+
};
|
|
41
|
+
type StateHandlersProps = {
|
|
42
|
+
isPlaying?: (isPlaying: boolean) => void;
|
|
43
|
+
isFullScreen?: (isFullScreen: boolean) => void;
|
|
44
|
+
isLoud?: (isLoud: boolean) => void;
|
|
45
|
+
volume?: (volume: number) => void;
|
|
46
|
+
currentTime?: (currentTime: number) => void;
|
|
47
|
+
playbackRate?: (rate: number) => void;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Props for the {@link Video} component.
|
|
51
|
+
*
|
|
52
|
+
* Extends all native `VideoHTMLAttributes<HTMLVideoElement>`, so any standard
|
|
53
|
+
* video attribute (`autoPlay`, `muted`, `loop`, `poster`, etc.) can be passed
|
|
54
|
+
* and will be forwarded to the underlying `<video>` element.
|
|
55
|
+
*
|
|
56
|
+
* @property sources - One or more video sources. Accepts:
|
|
57
|
+
* - a single URL string,
|
|
58
|
+
* - an array of URL strings,
|
|
59
|
+
* - an array of {@link SourceData} objects for fine-grained `type` control.
|
|
60
|
+
* @property tracks - One or more text tracks. Accepts:
|
|
61
|
+
* - a single URL string,
|
|
62
|
+
* - an array of URL strings,
|
|
63
|
+
* - an array of {@link TrackData} objects.
|
|
64
|
+
* @property playBtnContent - Custom content for the play button.
|
|
65
|
+
* @property pauseBtnContent - Custom content for the pause button.
|
|
66
|
+
* @property loudBtnContent - Custom content for the unmute button.
|
|
67
|
+
* @property muteBtnContent - Custom content for the mute button.
|
|
68
|
+
* @property fullScreenBtnContent - Custom content for the fullScreen button.
|
|
69
|
+
* @property subtitles - Props forwarded to the internal {@link Subtitles} component.
|
|
70
|
+
* `timecodeMs` is injected automatically from the current playback position.
|
|
71
|
+
* @property disclaimer - Props forwarded to the internal {@link Disclaimer} component.
|
|
72
|
+
* While the disclaimer is active, `autoPlay` and `muted` are suppressed on the
|
|
73
|
+
* underlying `<video>` element.
|
|
74
|
+
* @property autoPlayWhenVisible - When `true`, triggers playback the first time the
|
|
75
|
+
* component intersects the viewport, provided no disclaimer is active.
|
|
76
|
+
* @property autoPauseWhenHidden - When `true`, pauses playback whenever the component
|
|
77
|
+
* leaves the viewport.
|
|
78
|
+
* @property autoLoudWhenVisible - When `true`, unmutes the video the first time the
|
|
79
|
+
* component intersects the viewport, provided no disclaimer is active.
|
|
80
|
+
* @property autoMuteWhenHidden - When `true`, mutes the video whenever the component
|
|
81
|
+
* leaves the viewport.
|
|
82
|
+
* @property className - Optional additional class name(s) applied to the root element.
|
|
83
|
+
* @property children - React children rendered inside the `<video>` element itself
|
|
84
|
+
* (e.g. fallback content).
|
|
85
|
+
*/
|
|
86
|
+
export type Props = PropsWithChildren<WithClassName<{
|
|
87
|
+
sources?: string | string[] | SourceData[];
|
|
88
|
+
tracks?: string | string[] | TrackData[];
|
|
89
|
+
playBtnContent?: React.ReactNode;
|
|
90
|
+
pauseBtnContent?: React.ReactNode;
|
|
91
|
+
loudBtnContent?: React.ReactNode;
|
|
92
|
+
muteBtnContent?: React.ReactNode;
|
|
93
|
+
fullScreenBtnContent?: React.ReactNode;
|
|
94
|
+
subtitles?: SubsProps;
|
|
95
|
+
disclaimer?: DisclaimerProps;
|
|
96
|
+
autoPlayWhenVisible?: boolean;
|
|
97
|
+
autoPauseWhenHidden?: boolean;
|
|
98
|
+
autoLoudWhenVisible?: boolean;
|
|
99
|
+
autoMuteWhenHidden?: boolean;
|
|
100
|
+
actionHandlers: ActionHandlersProps;
|
|
101
|
+
stateHandlers: StateHandlersProps;
|
|
102
|
+
}> & VideoHTMLAttributes<HTMLVideoElement>>;
|
|
103
|
+
/**
|
|
104
|
+
* Full-featured video player component. Wraps a native `<video>` element with
|
|
105
|
+
* playback controls, volume, playback rate, a timeline, optional subtitles,
|
|
106
|
+
* an optional disclaimer gate, and viewport-driven auto-play/mute behaviours.
|
|
107
|
+
*
|
|
108
|
+
* ### Root element modifiers
|
|
109
|
+
* The root `<figure>` receives the public class name defined by `video` and
|
|
110
|
+
* the following BEM-style modifier classes:
|
|
111
|
+
* - `--play-on` / `--play-off` — reflects current playback state.
|
|
112
|
+
* - `--fullscreen-on` / `--fullscreen-off` — reflects fullscreen state.
|
|
113
|
+
* - `--loud` / `--muted` — reflects mute state.
|
|
114
|
+
*
|
|
115
|
+
* ### Data attributes on the root element
|
|
116
|
+
* - `data-play-on` — present (empty string) when playing.
|
|
117
|
+
* - `data-play-off` — present (empty string) when paused.
|
|
118
|
+
* - `data-fullscreen-on` — present (empty string) when in fullScreen.
|
|
119
|
+
* - `data-fullscreen-off` — present (empty string) when not in fullScreen.
|
|
120
|
+
* - `data-loud` — present (empty string) when unmuted.
|
|
121
|
+
* - `data-muted` — present (empty string) when muted.
|
|
122
|
+
* - `data-volume` — current volume as a `0–1` float.
|
|
123
|
+
* - `data-volume-percent` — current volume as a `0–100` float.
|
|
124
|
+
* - `data-playback-rate` — current playback rate (e.g. `1`, `1.5`).
|
|
125
|
+
* - `data-current-time-ms` — current time in milliseconds, fixed to 2 decimals.
|
|
126
|
+
* - `data-current-time-ratio` — current / total ratio, fixed to 8 decimals.
|
|
127
|
+
* - `data-total-time-ms` — total duration in milliseconds.
|
|
128
|
+
*
|
|
129
|
+
* ### CSS custom properties on the root element
|
|
130
|
+
* - `--video-current-time-ratio` — current / total ratio, fixed to 8 decimals.
|
|
131
|
+
* Useful for driving progress-bar animations purely in CSS.
|
|
132
|
+
*
|
|
133
|
+
* @param props - Component properties.
|
|
134
|
+
* @see {@link Props}
|
|
135
|
+
* @returns A `<figure>` element containing the video, its controls, optional
|
|
136
|
+
* subtitles, and an optional disclaimer overlay.
|
|
137
|
+
*/
|
|
138
|
+
export declare const Video: FunctionComponent<Props>;
|
|
139
|
+
export {};
|