@design-edito/tools 0.3.10 → 0.3.11
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/arrays/index.d.ts +1 -1
- package/agnostic/arrays/index.js +1 -1
- 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 +2 -2
- package/agnostic/css/index.js +2 -2
- package/agnostic/errors/index.d.ts +1 -1
- package/agnostic/errors/index.js +1 -1
- package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +20 -20
- package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +20 -20
- package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +4 -4
- package/agnostic/html/hyper-json/smart-tags/isolated/index.js +4 -4
- 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/crossenv/index.d.ts +1 -1
- package/agnostic/misc/crossenv/index.js +1 -1
- package/agnostic/misc/index.d.ts +5 -5
- package/agnostic/misc/index.js +5 -5
- 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 +4 -4
- package/agnostic/objects/index.js +4 -4
- package/agnostic/sanitization/index.d.ts +1 -1
- package/agnostic/sanitization/index.js +1 -1
- package/agnostic/strings/index.d.ts +1 -1
- package/agnostic/strings/index.js +1 -1
- package/agnostic/time/index.d.ts +2 -2
- package/agnostic/time/index.js +2 -2
- package/agnostic/time/transitions/index.d.ts +3 -3
- package/agnostic/time/transitions/index.js +4 -4
- package/components/Disclaimer/index.d.ts +45 -0
- package/components/Disclaimer/index.js +70 -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/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/Video/index.d.ts +119 -0
- package/components/Video/index.js +358 -0
- package/components/Video/styles.module.css +0 -0
- package/components/Video/utils.d.ts +10 -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 +15 -1
- package/components/index.js +15 -1
- package/components/public-classnames.d.ts +14 -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/directory/index.d.ts +1 -1
- package/node/@aws-s3/storage/directory/index.js +1 -1
- 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 +2 -2
- package/node/@google-cloud/storage/directory/index.js +2 -2
- 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 +3 -3
- package/node/cloud-storage/operations/index.js +3 -3
- package/node/encryption/index.d.ts +1 -1
- package/node/encryption/index.js +1 -1
- package/node/files/index.d.ts +1 -1
- package/node/files/index.js +1 -1
- package/node/ftps/directory/index.d.ts +2 -2
- package/node/ftps/directory/index.js +2 -2
- package/node/ftps/file/index.d.ts +1 -1
- package/node/ftps/file/index.js +1 -1
- package/node/images/index.d.ts +3 -3
- package/node/images/index.js +3 -3
- package/node/images/transform/operations/index.d.ts +6 -6
- package/node/images/transform/operations/index.js +6 -6
- package/node/index.d.ts +4 -4
- package/node/index.js +4 -4
- 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 +1030 -13
- package/components/Input/index.d.ts +0 -7
- package/components/Input/index.js +0 -29
- /package/components/{Input → Disclaimer}/styles.module.css +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'react';
|
|
2
|
+
import { type Props as IOCompProps } from '../IntersectionObserver/index.js';
|
|
3
|
+
import { type ControlledProps } from './index.controlled.js';
|
|
4
|
+
/**
|
|
5
|
+
* Props for the {@link Sequencer} component.
|
|
6
|
+
*
|
|
7
|
+
* Extends {@link ControlledProps} (minus `_modifiers`, which are derived
|
|
8
|
+
* internally) with uncontrolled playback and viewport-driven behaviour.
|
|
9
|
+
*
|
|
10
|
+
* @property defaultStep - Initial step index when running in uncontrolled mode.
|
|
11
|
+
* Ignored if `step` is provided. Defaults to `0`.
|
|
12
|
+
* @property tempo - Playback speed in beats per minute. The interval between
|
|
13
|
+
* steps is derived as `1000 / (tempo / 60)` ms. Clamped to a minimum of `1`.
|
|
14
|
+
* Defaults to `60`.
|
|
15
|
+
* @property play - Controlled play state. When provided, overrides the internal
|
|
16
|
+
* play state. The auto-advance interval only runs when this is `true`.
|
|
17
|
+
* @property loop - When `true`, the step wraps around using absolute modulo so
|
|
18
|
+
* it never exceeds the number of steps.
|
|
19
|
+
* @property clampFirst - When `true` (and `loop` is `false`), clamps the step
|
|
20
|
+
* to `0` at the lower bound, preventing negative step values.
|
|
21
|
+
* @property clampLast - When `true` (and `loop` is `false`), clamps the step
|
|
22
|
+
* to `stepsCount - 1` at the upper bound, preventing overflow.
|
|
23
|
+
* @property resetOnVisible - When `true`, resets the internal step to `0` each
|
|
24
|
+
* time the component enters the viewport. No-op when `step` or `play` is controlled.
|
|
25
|
+
* @property resetOnHidden - When `true`, resets the internal step to `0` each
|
|
26
|
+
* time the component leaves the viewport. No-op when `step` or `play` is controlled.
|
|
27
|
+
* @property playOnVisible - When `true`, starts internal playback when the
|
|
28
|
+
* component enters the viewport. No-op when `play` is controlled.
|
|
29
|
+
* @property pauseOnHidden - When `true`, pauses internal playback when the
|
|
30
|
+
* component leaves the viewport. No-op when `play` is controlled.
|
|
31
|
+
* @property actionHandlers - Optional handlers for imperative actions triggered
|
|
32
|
+
* by external events:
|
|
33
|
+
* - `intersected` — forwarded verbatim to the internal
|
|
34
|
+
* {@link IntersectionObserverComponent}'s `onIntersected`, called on every
|
|
35
|
+
* intersection change regardless of controlled state.
|
|
36
|
+
* @property stateHandlers - Optional callbacks invoked when derived state changes:
|
|
37
|
+
* - `isPlaying` — called with the new play state whenever it changes.
|
|
38
|
+
* - `stepChanged` — called with the new forwarded step index whenever it changes.
|
|
39
|
+
*/
|
|
40
|
+
export type Props = Omit<ControlledProps, '_modifiers'> & {
|
|
41
|
+
defaultStep?: number;
|
|
42
|
+
tempo?: number;
|
|
43
|
+
play?: boolean;
|
|
44
|
+
loop?: boolean;
|
|
45
|
+
clampFirst?: boolean;
|
|
46
|
+
clampLast?: boolean;
|
|
47
|
+
resetOnVisible?: boolean;
|
|
48
|
+
resetOnHidden?: boolean;
|
|
49
|
+
playOnVisible?: boolean;
|
|
50
|
+
pauseOnHidden?: boolean;
|
|
51
|
+
actionHandlers?: {
|
|
52
|
+
intersected?: IOCompProps['onIntersected'];
|
|
53
|
+
};
|
|
54
|
+
stateHandlers?: {
|
|
55
|
+
isPlaying?: (isPlaying: boolean) => void;
|
|
56
|
+
stepChanged?: (step: number) => void;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Uncontrolled, self-advancing sequencer component. Drives a
|
|
61
|
+
* {@link SequencerControlled} instance with an internal tempo-based interval,
|
|
62
|
+
* optional loop/clamp boundary behaviour, and viewport-driven play/reset triggers
|
|
63
|
+
* via an {@link IntersectionObserverComponent}.
|
|
64
|
+
*
|
|
65
|
+
* Supports mixed controlled/uncontrolled usage: passing `step` disables the
|
|
66
|
+
* internal interval while still applying loop/clamp arithmetic before forwarding
|
|
67
|
+
* to the controlled layer. Passing `play` disables internal play state management
|
|
68
|
+
* while still allowing viewport handlers to fire `actionHandlers.intersected`.
|
|
69
|
+
*
|
|
70
|
+
* ### Forwarded modifiers to {@link SequencerControlled}
|
|
71
|
+
* The following `_modifiers` are computed and injected automatically:
|
|
72
|
+
* - `playing` — `true` when the effective play state is active.
|
|
73
|
+
* - `at-start` — `true` when the forwarded step is `0`.
|
|
74
|
+
* - `at-end` — `true` when the forwarded step equals `stepsCount - 1`.
|
|
75
|
+
*
|
|
76
|
+
* ### Forwarded data attributes to {@link SequencerControlled}
|
|
77
|
+
* - `data-tempo` — the current `tempo` value.
|
|
78
|
+
*
|
|
79
|
+
* @param props - Component properties.
|
|
80
|
+
* @see {@link Props}
|
|
81
|
+
* @see {@link SequencerControlled}
|
|
82
|
+
* @returns An {@link IntersectionObserverComponent} wrapping a
|
|
83
|
+
* {@link SequencerControlled} with the computed step and modifiers applied.
|
|
84
|
+
*/
|
|
85
|
+
export declare const Sequencer: FunctionComponent<Props>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// src/components/Sequencer/index.tsx
|
|
2
|
+
import {
|
|
3
|
+
useState,
|
|
4
|
+
useEffect,
|
|
5
|
+
useCallback,
|
|
6
|
+
Children,
|
|
7
|
+
useRef
|
|
8
|
+
} from "react";
|
|
9
|
+
import { absoluteModulo } from "../../agnostic/numbers/absolute-modulo/index.js";
|
|
10
|
+
import { clamp } from "../../agnostic/numbers/clamp/index.js";
|
|
11
|
+
import {
|
|
12
|
+
IntersectionObserverComponent
|
|
13
|
+
} from "../IntersectionObserver/index.js";
|
|
14
|
+
import {
|
|
15
|
+
SequencerControlled
|
|
16
|
+
} from "./index.controlled.js";
|
|
17
|
+
import { jsx } from "react/jsx-runtime";
|
|
18
|
+
var Sequencer = ({
|
|
19
|
+
defaultStep,
|
|
20
|
+
tempo = 60,
|
|
21
|
+
play,
|
|
22
|
+
loop,
|
|
23
|
+
clampFirst,
|
|
24
|
+
clampLast,
|
|
25
|
+
resetOnVisible,
|
|
26
|
+
resetOnHidden,
|
|
27
|
+
playOnVisible,
|
|
28
|
+
pauseOnHidden,
|
|
29
|
+
actionHandlers,
|
|
30
|
+
stateHandlers,
|
|
31
|
+
...controlledProps
|
|
32
|
+
}) => {
|
|
33
|
+
const { step, activateOnStep, children } = controlledProps;
|
|
34
|
+
const [internalPlay, setInternalPlay] = useState(play ?? false);
|
|
35
|
+
const [internalStep, setInternalStep] = useState(step ?? defaultStep ?? 0);
|
|
36
|
+
const actualPlay = play ?? internalPlay;
|
|
37
|
+
const actualStep = step ?? internalStep;
|
|
38
|
+
const actualPlayRef = useRef(actualPlay);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const clampedTempo = Math.max(tempo, 1);
|
|
41
|
+
if (!actualPlay || step !== void 0) return;
|
|
42
|
+
const interval = window.setInterval(() => {
|
|
43
|
+
setInternalStep((s) => s + 1);
|
|
44
|
+
}, 1e3 / (clampedTempo / 60));
|
|
45
|
+
return () => window.clearInterval(interval);
|
|
46
|
+
}, [actualPlay, tempo, step]);
|
|
47
|
+
const stepsCount = activateOnStep !== void 0 ? activateOnStep.length : Children.toArray(children).length;
|
|
48
|
+
let forwardedStep;
|
|
49
|
+
if (loop === true) {
|
|
50
|
+
forwardedStep = absoluteModulo(actualStep, stepsCount);
|
|
51
|
+
} else {
|
|
52
|
+
const leftClamp = clampFirst === true ? 0 : -Infinity;
|
|
53
|
+
const rightClamp = clampLast === true ? stepsCount - 1 : Infinity;
|
|
54
|
+
forwardedStep = clamp(actualStep, leftClamp, rightClamp);
|
|
55
|
+
}
|
|
56
|
+
const forwardedStepRef = useRef(forwardedStep);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (actualPlay !== actualPlayRef.current) {
|
|
59
|
+
actualPlayRef.current = actualPlay;
|
|
60
|
+
stateHandlers?.isPlaying?.(actualPlay);
|
|
61
|
+
}
|
|
62
|
+
if (forwardedStep !== forwardedStepRef.current) {
|
|
63
|
+
forwardedStepRef.current = forwardedStep;
|
|
64
|
+
stateHandlers?.stepChanged?.(forwardedStep);
|
|
65
|
+
}
|
|
66
|
+
}, [actualPlay, forwardedStep]);
|
|
67
|
+
const handleIntersection = useCallback(({ ioEntry, observer }) => {
|
|
68
|
+
if (play === true || step !== void 0) return;
|
|
69
|
+
const { isIntersecting } = ioEntry ?? {};
|
|
70
|
+
actionHandlers?.intersected?.({ ioEntry, observer });
|
|
71
|
+
if (isIntersecting === true) {
|
|
72
|
+
if (resetOnVisible === true) setInternalStep(0);
|
|
73
|
+
if (playOnVisible === true) setInternalPlay(true);
|
|
74
|
+
} else {
|
|
75
|
+
if (resetOnHidden === true) setInternalStep(0);
|
|
76
|
+
if (pauseOnHidden === true) setInternalPlay(false);
|
|
77
|
+
}
|
|
78
|
+
}, [
|
|
79
|
+
resetOnVisible,
|
|
80
|
+
playOnVisible,
|
|
81
|
+
resetOnHidden,
|
|
82
|
+
pauseOnHidden,
|
|
83
|
+
play,
|
|
84
|
+
step,
|
|
85
|
+
actionHandlers
|
|
86
|
+
]);
|
|
87
|
+
return /* @__PURE__ */ jsx(
|
|
88
|
+
IntersectionObserverComponent,
|
|
89
|
+
{
|
|
90
|
+
onIntersected: handleIntersection,
|
|
91
|
+
children: /* @__PURE__ */ jsx(
|
|
92
|
+
SequencerControlled,
|
|
93
|
+
{
|
|
94
|
+
...controlledProps,
|
|
95
|
+
step: forwardedStep,
|
|
96
|
+
_modifiers: {
|
|
97
|
+
playing: actualPlay,
|
|
98
|
+
"at-start": forwardedStep === 0,
|
|
99
|
+
"at-end": forwardedStep === stepsCount - 1
|
|
100
|
+
},
|
|
101
|
+
_dataAttributes: { tempo }
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
export {
|
|
108
|
+
Sequencer
|
|
109
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type PropsWithChildren, type FunctionComponent } from 'react';
|
|
2
|
+
import type { WithClassName } from '../utils/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Props for the ShadowRootComponent.
|
|
5
|
+
*
|
|
6
|
+
* @property className - Optional additional class name(s) applied to the host element
|
|
7
|
+
* that owns the Shadow Root.
|
|
8
|
+
* @property mode - Shadow DOM mode. `"open"` exposes the shadowRoot via `element.shadowRoot`,
|
|
9
|
+
* `"closed"` keeps it inaccessible from the outside. Defaults to `"open"`.
|
|
10
|
+
* @property delegatesFocus - When true, enables focus delegation from the host
|
|
11
|
+
* to the first focusable element inside the Shadow Root.
|
|
12
|
+
* @property slotAssignment - Slot assignment mode. `"named"` uses standard named slot behavior,
|
|
13
|
+
* `"manual"` requires manual slot assignment via `HTMLSlotElement.assign()`.
|
|
14
|
+
* @property adoptedStyleSheets - Array of constructable `CSSStyleSheet` instances
|
|
15
|
+
* assigned to `shadowRoot.adoptedStyleSheets` (if supported by the browser).
|
|
16
|
+
* @property injectedStyles - Raw CSS string injected into the Shadow Root inside a `<style>` element.
|
|
17
|
+
* Useful as a fallback when constructable stylesheets are not used.
|
|
18
|
+
* @property children - React children rendered inside the Shadow Root via a React portal.
|
|
19
|
+
*/
|
|
20
|
+
export type Props = PropsWithChildren<WithClassName<{
|
|
21
|
+
mode?: 'open' | 'closed';
|
|
22
|
+
delegatesFocus?: boolean;
|
|
23
|
+
slotAssignment?: 'named' | 'manual';
|
|
24
|
+
adoptedStyleSheets?: CSSStyleSheet[];
|
|
25
|
+
injectedStyles?: string;
|
|
26
|
+
}>>;
|
|
27
|
+
/**
|
|
28
|
+
* Component that creates a Shadow Root on its host element and renders
|
|
29
|
+
* its children inside that Shadow Root using a React portal.
|
|
30
|
+
*
|
|
31
|
+
* @param props - Component properties
|
|
32
|
+
* @see {@link Props}
|
|
33
|
+
* @returns A host `div` element that owns the created Shadow Root.
|
|
34
|
+
*/
|
|
35
|
+
export declare const ShadowRootComponent: FunctionComponent<Props>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/components/ShadowRoot/index.tsx
|
|
2
|
+
import {
|
|
3
|
+
useRef,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState
|
|
6
|
+
} from "react";
|
|
7
|
+
import { createPortal } from "react-dom";
|
|
8
|
+
import { clss } from "../../agnostic/css/clss/index.js";
|
|
9
|
+
import { shadowRoot as publicClassName } from "../public-classnames.js";
|
|
10
|
+
import { mergeClassNames } from "../utils/index.js";
|
|
11
|
+
import cssModule from "./styles.module.css";
|
|
12
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
var ShadowRootComponent = ({
|
|
14
|
+
mode = "open",
|
|
15
|
+
delegatesFocus,
|
|
16
|
+
slotAssignment,
|
|
17
|
+
adoptedStyleSheets,
|
|
18
|
+
injectedStyles,
|
|
19
|
+
className,
|
|
20
|
+
children
|
|
21
|
+
}) => {
|
|
22
|
+
const hostRef = useRef(null);
|
|
23
|
+
const [shadowRoot, setShadowRoot] = useState(null);
|
|
24
|
+
const [styles, setStyles] = useState(injectedStyles);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (hostRef.current === null || shadowRoot !== null) return;
|
|
27
|
+
const root = hostRef.current.attachShadow({ mode, delegatesFocus, slotAssignment });
|
|
28
|
+
if (adoptedStyleSheets !== void 0 && "adoptedStyleSheets" in root) {
|
|
29
|
+
root.adoptedStyleSheets = adoptedStyleSheets;
|
|
30
|
+
}
|
|
31
|
+
if (injectedStyles !== void 0) setStyles(injectedStyles);
|
|
32
|
+
setShadowRoot(root);
|
|
33
|
+
}, [
|
|
34
|
+
mode,
|
|
35
|
+
delegatesFocus,
|
|
36
|
+
slotAssignment,
|
|
37
|
+
adoptedStyleSheets,
|
|
38
|
+
injectedStyles
|
|
39
|
+
]);
|
|
40
|
+
const c = clss(publicClassName, { cssModule });
|
|
41
|
+
const rootClss = mergeClassNames(c(), className);
|
|
42
|
+
return /* @__PURE__ */ jsx(
|
|
43
|
+
"div",
|
|
44
|
+
{
|
|
45
|
+
ref: hostRef,
|
|
46
|
+
className: rootClss,
|
|
47
|
+
children: shadowRoot !== null && createPortal(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
48
|
+
styles !== void 0 && /* @__PURE__ */ jsx("style", { children: styles }),
|
|
49
|
+
children
|
|
50
|
+
] }), shadowRoot)
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
export {
|
|
55
|
+
ShadowRootComponent
|
|
56
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type FunctionComponent, type PropsWithChildren } from 'react';
|
|
2
|
+
import type { WithClassName } from '../utils/types.js';
|
|
3
|
+
import type { ParsedSub } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Props for the {@link Subtitles} component.
|
|
6
|
+
*
|
|
7
|
+
* @property src - URL of an SRT file to fetch. Ignored if `srtFileContent` is provided.
|
|
8
|
+
* If both are undefined, no subtitles are loaded.
|
|
9
|
+
* @property srtFileContent - Raw SRT string used directly, bypassing any network fetch.
|
|
10
|
+
* Takes precedence over `src`.
|
|
11
|
+
* @property subsGroups - Optional array of subtitle IDs that act as group boundaries,
|
|
12
|
+
* splitting the full subtitle list into named sections. If omitted, all subtitles
|
|
13
|
+
* belong to a single group.
|
|
14
|
+
* @property timecodeMs - Current media position in milliseconds. Drives which subtitles
|
|
15
|
+
* receive the `--prev` and `--curr` modifiers. When `undefined`, nothing is rendered.
|
|
16
|
+
* @property isEnded - When `true`, forces the last group to be treated as current,
|
|
17
|
+
* regardless of `timecodeMs`. Useful to keep the final subtitle group visible after
|
|
18
|
+
* media playback finishes.
|
|
19
|
+
* @property onLoaded - Callback invoked with the raw SRT string after a successful
|
|
20
|
+
* fetch and parse. Not called when `srtFileContent` is used directly.
|
|
21
|
+
* @property onParsed - Callback invoked with the raw SRT string has been parsed.
|
|
22
|
+
* @property onLoadFailed - Callback invoked with an `Error` if the fetch or parse step fails.
|
|
23
|
+
* @property className - Optional additional class name(s) applied to the root element.
|
|
24
|
+
* @property children - React children rendered inside the root element, after the subtitle groups.
|
|
25
|
+
*/
|
|
26
|
+
export type Props = PropsWithChildren<WithClassName<{
|
|
27
|
+
src?: string;
|
|
28
|
+
srtFileContent?: string;
|
|
29
|
+
subsGroups?: number[];
|
|
30
|
+
timecodeMs?: number;
|
|
31
|
+
isEnded?: boolean;
|
|
32
|
+
onLoaded?: (subs: string) => void;
|
|
33
|
+
onParsed?: (subs: ParsedSub[]) => void;
|
|
34
|
+
onLoadFailed?: (error: Error) => void;
|
|
35
|
+
}>>;
|
|
36
|
+
/**
|
|
37
|
+
* Subtitle synchronization component. Fetches or receives an SRT source, parses it,
|
|
38
|
+
* and renders subtitle groups whose individual spans are styled according to the
|
|
39
|
+
* current media timecode.
|
|
40
|
+
*
|
|
41
|
+
* ### Group elements
|
|
42
|
+
* Each subtitle group is a `<div>` with the following:
|
|
43
|
+
* - `--curr` modifier when the group contains the subtitle at the current timecode.
|
|
44
|
+
* - `data-start-sub-pos` — ID of the first subtitle in the group.
|
|
45
|
+
* - `data-end-sub-pos` — ID of the last subtitle in the group.
|
|
46
|
+
*
|
|
47
|
+
* ### Subtitle span elements
|
|
48
|
+
* Each individual subtitle is a `<span>` with the following:
|
|
49
|
+
* - `--prev` modifier when the subtitle's start time is at or before the last elapsed subtitle.
|
|
50
|
+
* - `--curr` modifier when `timecodeMs` falls within the subtitle's `[start, end]` interval.
|
|
51
|
+
* - `data-sub-pos` — the subtitle's numeric ID from the SRT source.
|
|
52
|
+
*
|
|
53
|
+
* @param props - Component properties.
|
|
54
|
+
* @see {@link Props}
|
|
55
|
+
* @returns A root `<div>` containing the rendered subtitle groups, or an empty `<div>`
|
|
56
|
+
* when `timecodeMs` is undefined or no subtitles have been parsed yet.
|
|
57
|
+
*/
|
|
58
|
+
export declare const Subtitles: FunctionComponent<Props>;
|
|
@@ -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.toggleTrigger - 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
|
+
toggleTrigger?: (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?.toggleTrigger?.(isTheatreOn);
|
|
30
|
+
if (isOn === void 0) setInternalIsOn(false);
|
|
31
|
+
};
|
|
32
|
+
const handleOpenBtnClick = () => {
|
|
33
|
+
actionHandlers?.toggleTrigger?.(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?.toggleTrigger?.(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?.toggleTrigger?.(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
|