@design-edito/tools 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agnostic/arrays/index.d.ts +1 -1
- package/agnostic/arrays/index.js +1 -1
- package/agnostic/colors/index.d.ts +1 -1
- package/agnostic/colors/index.js +1 -1
- package/agnostic/css/index.d.ts +1 -1
- package/agnostic/css/index.js +1 -1
- package/agnostic/html/deep-select/index.js +5 -3
- package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +9 -9
- package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +9 -9
- package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +1 -1
- package/agnostic/html/hyper-json/smart-tags/isolated/index.js +1 -1
- package/agnostic/html/index.d.ts +3 -3
- package/agnostic/html/index.js +3 -3
- package/agnostic/index.d.ts +3 -3
- package/agnostic/index.js +3 -3
- package/agnostic/misc/index.d.ts +1 -1
- package/agnostic/misc/index.js +1 -1
- package/agnostic/misc/logs/index.d.ts +1 -1
- package/agnostic/misc/logs/index.js +1 -1
- package/agnostic/numbers/index.d.ts +3 -3
- package/agnostic/numbers/index.js +3 -3
- package/agnostic/objects/index.d.ts +3 -3
- package/agnostic/objects/index.js +3 -3
- package/agnostic/random/index.d.ts +1 -1
- package/agnostic/random/index.js +1 -1
- package/agnostic/strings/index.d.ts +3 -3
- package/agnostic/strings/index.js +3 -3
- package/agnostic/time/dates/format-date/index.d.ts +4 -2
- package/agnostic/time/dates/format-date/index.js +43 -94
- package/agnostic/time/dates/format-date/index.test.js +154 -0
- package/agnostic/time/dates/format-date/parts.d.ts +28 -0
- package/agnostic/time/dates/format-date/parts.js +136 -0
- package/agnostic/time/dates/format-date/types.d.ts +84 -0
- package/agnostic/time/dates/format-date/types.js +1 -0
- package/agnostic/time/duration/format-duration/index.d.ts +42 -0
- package/agnostic/time/duration/format-duration/index.js +94 -0
- package/agnostic/time/duration/format-duration/index.test.d.ts +1 -0
- package/agnostic/time/duration/format-duration/index.test.js +148 -0
- package/agnostic/time/duration/format-duration/parts.d.ts +32 -0
- package/agnostic/time/duration/format-duration/parts.js +107 -0
- package/agnostic/time/duration/format-duration/types.d.ts +93 -0
- package/agnostic/time/duration/format-duration/types.js +1 -0
- package/agnostic/time/duration/index.d.ts +6 -1
- package/agnostic/time/duration/index.js +2 -2
- package/agnostic/time/duration/index.test.d.ts +1 -0
- package/agnostic/time/duration/index.test.js +168 -0
- package/components/BeforeAfter/index.controlled.d.ts +25 -25
- package/components/BeforeAfter/index.controlled.js +47 -53
- package/components/BeforeAfter/index.d.ts +20 -23
- package/components/BeforeAfter/index.js +48 -40
- package/components/Button/index.js +1 -1
- package/components/Clippable/index.d.ts +17 -26
- package/components/Clippable/index.js +21 -23
- package/components/Disclaimer/index.d.ts +29 -22
- package/components/Disclaimer/index.js +29 -24
- package/components/Drawer/index.d.ts +45 -25
- package/components/Drawer/index.js +61 -52
- package/components/EventListener/index.d.ts +4 -5
- package/components/Gallery/index.d.ts +14 -16
- package/components/Gallery/index.js +25 -30
- package/components/Iframe/index.js +2 -2
- package/components/Image/index.d.ts +5 -12
- package/components/Image/index.js +11 -32
- package/components/Input/index.d.ts +23 -19
- package/components/Input/index.js +30 -20
- package/components/JsonEditor/index.d.ts +177 -9
- package/components/JsonEditor/index.js +133 -75
- package/components/JsonEditor/types.d.ts +11 -0
- package/components/JsonEditor/types.js +1 -0
- package/components/JsonEditor/utils.d.ts +8 -0
- package/components/JsonEditor/utils.js +23 -0
- package/components/ListLoader/index.controlled.d.ts +78 -0
- package/components/ListLoader/index.controlled.js +99 -0
- package/components/ListLoader/index.d.ts +69 -0
- package/components/ListLoader/index.js +146 -0
- package/components/ListLoader/utils.d.ts +8 -0
- package/components/ListLoader/utils.js +10 -0
- package/components/Overlayer/index.js +4 -4
- package/components/Paginator/index.d.ts +7 -12
- package/components/Paginator/index.js +7 -11
- package/components/ResizeObserver/index.d.ts +14 -3
- package/components/ResizeObserver/index.js +49 -24
- package/components/ResizeObserver/styles.module.css +0 -0
- package/components/Scrllgngn/index.d.ts +17 -13
- package/components/Scrllgngn/index.js +25 -84
- package/components/Scrllgngn/utils.d.ts +52 -0
- package/components/Scrllgngn/utils.js +102 -0
- package/components/ScrollListener/index.d.ts +43 -28
- package/components/ScrollListener/index.js +59 -79
- package/components/ScrollListener/utils.d.ts +37 -16
- package/components/ScrollListener/utils.js +141 -28
- package/components/Select/index.d.ts +24 -20
- package/components/Select/index.js +30 -20
- package/components/Sequencer/index.controlled.d.ts +16 -26
- package/components/Sequencer/index.controlled.js +15 -12
- package/components/Sequencer/index.d.ts +25 -30
- package/components/Sequencer/index.js +20 -34
- package/components/Subtitles/index.d.ts +29 -5
- package/components/Subtitles/index.js +90 -12
- package/components/Textarea/index.d.ts +26 -20
- package/components/Textarea/index.js +40 -21
- package/components/Theatre/index.d.ts +35 -39
- package/components/Theatre/index.js +46 -58
- package/components/UIModule/index.d.ts +25 -29
- package/components/UIModule/index.js +81 -110
- package/components/Video/index.controlled.d.ts +51 -51
- package/components/Video/index.controlled.js +75 -72
- package/components/Video/index.d.ts +12 -14
- package/components/Video/index.js +41 -86
- package/components/Video/utils.d.ts +35 -3
- package/components/Video/utils.js +37 -14
- package/components/index.d.ts +5 -4
- package/components/index.js +5 -4
- package/components/public-classnames.d.ts +1 -0
- package/components/public-classnames.js +1 -0
- package/components/utils/index.d.ts +22 -0
- package/components/utils/index.js +31 -0
- 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 +2 -2
- package/node/@google-cloud/storage/file/index.js +2 -2
- package/node/@google-cloud/storage/index.d.ts +1 -1
- package/node/@google-cloud/storage/index.js +1 -1
- package/node/cloud-storage/operations/index.d.ts +2 -2
- package/node/cloud-storage/operations/index.js +2 -2
- package/node/ftps/directory/index.d.ts +1 -1
- package/node/ftps/directory/index.js +1 -1
- package/node/ftps/file/index.d.ts +2 -2
- package/node/ftps/file/index.js +2 -2
- package/node/images/index.d.ts +1 -1
- package/node/images/index.js +1 -1
- package/node/images/transform/operations/index.d.ts +3 -3
- package/node/images/transform/operations/index.js +3 -3
- package/node/index.d.ts +2 -2
- package/node/index.js +2 -2
- package/node/process/index.d.ts +1 -1
- package/node/process/index.js +1 -1
- package/node/sftp/directory/index.d.ts +1 -1
- package/node/sftp/directory/index.js +1 -1
- package/node/sftp/file/index.d.ts +1 -1
- package/node/sftp/file/index.js +1 -1
- package/package.json +27 -5
- package/TODO.md +0 -269
- package/components/BeforeAfter/utils.d.ts +0 -4
- package/components/BeforeAfter/utils.js +0 -9
- package/components/Input/index.controlled.d.ts +0 -42
- package/components/Input/index.controlled.js +0 -54
- package/components/Select/index.controlled.d.ts +0 -43
- package/components/Select/index.controlled.js +0 -54
- package/components/Subtitles/types.d.ts +0 -24
- package/components/Subtitles/utils.d.ts +0 -32
- package/components/Subtitles/utils.js +0 -107
- package/components/Textarea/index.controlled.d.ts +0 -50
- package/components/Textarea/index.controlled.js +0 -75
- /package/{components/Subtitles/types.js → agnostic/time/dates/format-date/index.test.d.ts} +0 -0
- /package/components/{ResizeObserver/style.module.css → ListLoader/styles.module.css} +0 -0
|
@@ -1,43 +1,42 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect, useCallback, Children
|
|
2
|
+
import { useState, useEffect, useCallback, Children } from 'react';
|
|
3
3
|
import { absoluteModulo } from '../../agnostic/numbers/absolute-modulo/index.js';
|
|
4
4
|
import { clamp } from '../../agnostic/numbers/clamp/index.js';
|
|
5
5
|
import { IntersectionObserverComponent } from '../IntersectionObserver/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import { useChangeDispatch } from '../utils/index.js';
|
|
7
|
+
import { ControlledSequencer } from './index.controlled.js';
|
|
7
8
|
/**
|
|
8
9
|
* Uncontrolled, self-advancing sequencer component. Drives a
|
|
9
|
-
* {@link
|
|
10
|
+
* {@link ControlledSequencer} instance with an internal tempo-based interval,
|
|
10
11
|
* optional loop/clamp boundary behaviour, and viewport-driven play/reset triggers
|
|
11
12
|
* via an {@link IntersectionObserverComponent}.
|
|
12
13
|
*
|
|
13
14
|
* Supports mixed controlled/uncontrolled usage: passing `step` disables the
|
|
14
15
|
* internal interval while still applying loop/clamp arithmetic before forwarding
|
|
15
16
|
* to the controlled layer. Passing `play` disables internal play state management
|
|
16
|
-
* while still allowing viewport handlers to fire `
|
|
17
|
+
* while still allowing viewport handlers to fire `onIntersected`.
|
|
17
18
|
*
|
|
18
|
-
* ### Forwarded
|
|
19
|
-
*
|
|
20
|
-
* - `
|
|
21
|
-
* - `
|
|
22
|
-
* - `at-end` — `true` when the forwarded step equals `stepsCount - 1`.
|
|
19
|
+
* ### Forwarded to {@link ControlledSequencer}
|
|
20
|
+
* - `step` — the effective step, after loop/clamp arithmetic.
|
|
21
|
+
* - `isPlaying` — the effective play state, controlled or internal.
|
|
22
|
+
* - `tempo` — the current tempo, which the controlled layer exposes as `data-tempo`.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* The `--at-start` and `--at-end` modifiers are derived by the controlled layer
|
|
25
|
+
* from `step` and the children count.
|
|
26
26
|
*
|
|
27
27
|
* @param props - Component properties.
|
|
28
28
|
* @see {@link Props}
|
|
29
|
-
* @see {@link
|
|
29
|
+
* @see {@link ControlledSequencer}
|
|
30
30
|
* @returns An {@link IntersectionObserverComponent} wrapping a
|
|
31
|
-
* {@link
|
|
31
|
+
* {@link ControlledSequencer} with the computed step and modifiers applied.
|
|
32
32
|
*/
|
|
33
|
-
export const Sequencer = ({ defaultStep, tempo = 60, play, loop, clampFirst, clampLast, resetOnVisible, resetOnHidden, playOnVisible, pauseOnHidden,
|
|
33
|
+
export const Sequencer = ({ defaultStep, tempo = 60, play, loop, clampFirst, clampLast, resetOnVisible, resetOnHidden, playOnVisible, pauseOnHidden, onIntersected, onIsPlayingChanged, onStepChanged, ...controlledProps }) => {
|
|
34
34
|
// State
|
|
35
35
|
const { step, activateOnStep, children } = controlledProps;
|
|
36
36
|
const [internalPlay, setInternalPlay] = useState(play ?? false);
|
|
37
37
|
const [internalStep, setInternalStep] = useState(step ?? defaultStep ?? 0);
|
|
38
38
|
const actualPlay = play ?? internalPlay;
|
|
39
39
|
const actualStep = step ?? internalStep;
|
|
40
|
-
const actualPlayRef = useRef(actualPlay);
|
|
41
40
|
// Effects
|
|
42
41
|
useEffect(() => {
|
|
43
42
|
const clampedTempo = Math.max(tempo, 1);
|
|
@@ -61,24 +60,15 @@ export const Sequencer = ({ defaultStep, tempo = 60, play, loop, clampFirst, cla
|
|
|
61
60
|
const rightClamp = clampLast === true ? stepsCount - 1 : Infinity;
|
|
62
61
|
forwardedStep = clamp(actualStep, leftClamp, rightClamp);
|
|
63
62
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (actualPlay !== actualPlayRef.current) {
|
|
68
|
-
actualPlayRef.current = actualPlay;
|
|
69
|
-
stateHandlers?.isPlaying?.(actualPlay);
|
|
70
|
-
}
|
|
71
|
-
if (forwardedStep !== forwardedStepRef.current) {
|
|
72
|
-
forwardedStepRef.current = forwardedStep;
|
|
73
|
-
stateHandlers?.stepChanged?.(forwardedStep);
|
|
74
|
-
}
|
|
75
|
-
}, [actualPlay, forwardedStep]);
|
|
63
|
+
// State dispatch
|
|
64
|
+
useChangeDispatch(actualPlay, onIsPlayingChanged);
|
|
65
|
+
useChangeDispatch(forwardedStep, onStepChanged);
|
|
76
66
|
// Action handlers
|
|
77
67
|
const handleIntersection = useCallback(({ ioEntry, observer }) => {
|
|
68
|
+
onIntersected?.({ ioEntry, observer });
|
|
78
69
|
if (play === true || step !== undefined)
|
|
79
70
|
return;
|
|
80
71
|
const { isIntersecting } = ioEntry ?? {};
|
|
81
|
-
actionHandlers?.intersected?.({ ioEntry, observer });
|
|
82
72
|
if (isIntersecting === true) {
|
|
83
73
|
if (resetOnVisible === true)
|
|
84
74
|
setInternalStep(0);
|
|
@@ -98,12 +88,8 @@ export const Sequencer = ({ defaultStep, tempo = 60, play, loop, clampFirst, cla
|
|
|
98
88
|
pauseOnHidden,
|
|
99
89
|
play,
|
|
100
90
|
step,
|
|
101
|
-
|
|
91
|
+
onIntersected
|
|
102
92
|
]);
|
|
103
93
|
// Rendering
|
|
104
|
-
return _jsx(IntersectionObserverComponent, { onIntersected: handleIntersection, children: _jsx(
|
|
105
|
-
playing: actualPlay,
|
|
106
|
-
'at-start': forwardedStep === 0,
|
|
107
|
-
'at-end': forwardedStep === stepsCount - 1
|
|
108
|
-
}, _dataAttributes: { tempo } }) });
|
|
94
|
+
return _jsx(IntersectionObserverComponent, { onIntersected: handleIntersection, children: _jsx(ControlledSequencer, { ...controlledProps, step: forwardedStep, isPlaying: actualPlay, tempo: tempo }) });
|
|
109
95
|
};
|
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import { type FunctionComponent, type PropsWithChildren } from 'react';
|
|
2
2
|
import type { WithClassName } from '../utils/types.js';
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* A single parsed subtitle entry from an SRT file.
|
|
5
|
+
*
|
|
6
|
+
* @property id - Sequential subtitle number.
|
|
7
|
+
* @property start - Start time in milliseconds.
|
|
8
|
+
* @property end - End time in milliseconds.
|
|
9
|
+
* @property content - Subtitle text content.
|
|
10
|
+
*/
|
|
11
|
+
export type ParsedSub = {
|
|
12
|
+
id: number;
|
|
13
|
+
start?: number;
|
|
14
|
+
end?: number;
|
|
15
|
+
content?: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The inclusive ID boundaries of a subtitle group.
|
|
19
|
+
*
|
|
20
|
+
* @property startId - ID of the first subtitle in the group.
|
|
21
|
+
* @property endId - ID of the last subtitle in the group.
|
|
22
|
+
*/
|
|
23
|
+
export type SubGroupBoundaries = {
|
|
24
|
+
startId: number;
|
|
25
|
+
endId: number;
|
|
26
|
+
};
|
|
4
27
|
/**
|
|
5
28
|
* Props for the {@link Subtitles} component.
|
|
6
29
|
*
|
|
@@ -16,10 +39,11 @@ import type { ParsedSub } from './types.js';
|
|
|
16
39
|
* @property isEnded - When `true`, forces the last group to be treated as current,
|
|
17
40
|
* regardless of `timecodeMs`. Useful to keep the final subtitle group visible after
|
|
18
41
|
* media playback finishes.
|
|
19
|
-
* @property onLoaded -
|
|
20
|
-
*
|
|
21
|
-
* @property onParsed -
|
|
22
|
-
*
|
|
42
|
+
* @property onLoaded - Called with the raw SRT string once fetched. Not called
|
|
43
|
+
* when `srtFileContent` is used directly, since nothing is fetched then.
|
|
44
|
+
* @property onParsed - Called after the subtitles have been parsed, with the
|
|
45
|
+
* resulting entries.
|
|
46
|
+
* @property onLoadFailed - Called with an `Error` when the fetch or the parse fails.
|
|
23
47
|
* @property className - Optional additional class name(s) applied to the root element.
|
|
24
48
|
* @property children - React children rendered inside the root element, after the subtitle groups.
|
|
25
49
|
*/
|
|
@@ -1,12 +1,95 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect,
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
3
|
import { clss } from '../../agnostic/css/clss/index.js';
|
|
4
4
|
import { toError } from '../../agnostic/misc/cast/index.js';
|
|
5
5
|
import { unknownToString } from '../../agnostic/errors/unknown-to-string/index.js';
|
|
6
|
-
import { mergeClassNames } from '../utils/index.js';
|
|
6
|
+
import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
|
|
7
7
|
import { subtitles as publicClassName } from '../public-classnames.js';
|
|
8
|
-
import { computeSubGroupsWithBoundaries, getCurrentGroup, parseSubs } from './utils.js';
|
|
9
8
|
import cssModule from './styles.module.css';
|
|
9
|
+
/** Converts an SRT timecode (hh:mm:ss,ms) to milliseconds. */
|
|
10
|
+
const getTimecodeToMs = (timecode) => {
|
|
11
|
+
const [hours = '0', minutes = '0', secondsAndMs = '0,0'] = timecode.split(':');
|
|
12
|
+
const [seconds = '0', milliseconds = '0'] = secondsAndMs.split(',');
|
|
13
|
+
let result = parseInt(hours) * 60 * 60 * 1000;
|
|
14
|
+
result += parseInt(minutes) * 60 * 1000;
|
|
15
|
+
result += parseInt(seconds) * 1000;
|
|
16
|
+
result += parseInt(milliseconds);
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
/** Parses a raw SRT subtitle text into a list of {@link ParsedSub} entries. */
|
|
20
|
+
const parseSubs = (rawSubs) => {
|
|
21
|
+
const numberRegex = /^\d+$/v;
|
|
22
|
+
const timecodeRegex = /^[0-9]+:[0-9]+:[0-9]+,[0-9]+\s*-->\s*[0-9]+:[0-9]+:[0-9]+,[0-9]+$/v;
|
|
23
|
+
const parsedSubs = [];
|
|
24
|
+
rawSubs.split('\n').forEach(line => {
|
|
25
|
+
if (line.trim() === '')
|
|
26
|
+
return;
|
|
27
|
+
const lastParsedSub = parsedSubs[parsedSubs.length - 1];
|
|
28
|
+
const matchId = line.match(numberRegex);
|
|
29
|
+
const matchTimecode = line.match(timecodeRegex);
|
|
30
|
+
// id
|
|
31
|
+
if (matchId !== null) {
|
|
32
|
+
if (lastParsedSub === undefined
|
|
33
|
+
|| lastParsedSub.content !== undefined) {
|
|
34
|
+
const parsedSub = { id: parseInt(line) };
|
|
35
|
+
parsedSubs.push(parsedSub);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// timecode
|
|
40
|
+
if (matchTimecode !== null) {
|
|
41
|
+
if (lastParsedSub?.id !== undefined) {
|
|
42
|
+
const [rawStart = '', rawEnd = ''] = line.split('-->');
|
|
43
|
+
const startTime = rawStart.trim();
|
|
44
|
+
const endTime = rawEnd.trim();
|
|
45
|
+
lastParsedSub.start = getTimecodeToMs(startTime);
|
|
46
|
+
lastParsedSub.end = getTimecodeToMs(endTime);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// content
|
|
51
|
+
if (lastParsedSub?.id !== undefined
|
|
52
|
+
&& lastParsedSub.start !== undefined
|
|
53
|
+
&& lastParsedSub.end !== undefined) {
|
|
54
|
+
if (lastParsedSub.content !== undefined) {
|
|
55
|
+
lastParsedSub.content += `\n${line}`;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
lastParsedSub.content = line;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return parsedSubs;
|
|
62
|
+
};
|
|
63
|
+
/** Computes subtitle groups with their inclusive `startId` / `endId` boundaries. */
|
|
64
|
+
const computeSubGroupsWithBoundaries = (subsGroups, highestSubId) => {
|
|
65
|
+
const fallback = [{ startId: 1, endId: highestSubId }];
|
|
66
|
+
if (subsGroups === undefined || subsGroups.length === 0)
|
|
67
|
+
return fallback;
|
|
68
|
+
const emptySubGroupBoundaries = [];
|
|
69
|
+
return subsGroups.reduce((acc, curr, currIndex) => {
|
|
70
|
+
const lastInAcc = acc[acc.length - 1];
|
|
71
|
+
const startId = lastInAcc === undefined ? 1 : lastInAcc.endId + 1;
|
|
72
|
+
const endId = curr;
|
|
73
|
+
if (currIndex === subsGroups.length - 1
|
|
74
|
+
&& endId !== highestSubId) {
|
|
75
|
+
return [
|
|
76
|
+
...acc,
|
|
77
|
+
{ startId, endId },
|
|
78
|
+
{ startId: endId + 1, endId: highestSubId }
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
return [...acc, { startId, endId }];
|
|
82
|
+
}, emptySubGroupBoundaries);
|
|
83
|
+
};
|
|
84
|
+
/** Returns the group holding the last elapsed subtitle, or the last group once playback ended. */
|
|
85
|
+
const getCurrentGroup = (subsGroupsWithBoundaries, lastPrevSubId, isEnded) => {
|
|
86
|
+
const previousGroups = subsGroupsWithBoundaries.filter(group => group.startId <= (lastPrevSubId ?? 0));
|
|
87
|
+
if (previousGroups.length === 0)
|
|
88
|
+
return isEnded === true
|
|
89
|
+
? subsGroupsWithBoundaries[subsGroupsWithBoundaries.length - 1]
|
|
90
|
+
: subsGroupsWithBoundaries[0];
|
|
91
|
+
return previousGroups[previousGroups.length - 1];
|
|
92
|
+
};
|
|
10
93
|
/**
|
|
11
94
|
* Subtitle synchronization component. Fetches or receives an SRT source, parses it,
|
|
12
95
|
* and renders subtitle groups whose individual spans are styled according to the
|
|
@@ -34,19 +117,14 @@ export const Subtitles = ({ src, srtFileContent, subsGroups, timecodeMs, isEnded
|
|
|
34
117
|
const [isLoading, setIsLoading] = useState(false);
|
|
35
118
|
const [loadError, setLoadError] = useState(null);
|
|
36
119
|
const [parsedSubs, setParsedSubs] = useState([]);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
if (pParsedSubs.current === parsedSubs)
|
|
41
|
-
return;
|
|
42
|
-
onParsed?.(parsedSubs);
|
|
43
|
-
}, [parsedSubs]);
|
|
120
|
+
// State dispatch
|
|
121
|
+
useChangeDispatch(parsedSubs, onParsed);
|
|
44
122
|
// Effects
|
|
45
123
|
const fetchAndParseSubs = useCallback(async (src, srtFileContent) => {
|
|
46
|
-
if (src === undefined)
|
|
47
|
-
return;
|
|
48
124
|
if (srtFileContent !== undefined)
|
|
49
125
|
return setParsedSubs(parseSubs(srtFileContent));
|
|
126
|
+
if (src === undefined)
|
|
127
|
+
return;
|
|
50
128
|
setIsLoading(true);
|
|
51
129
|
setLoadError(null);
|
|
52
130
|
try {
|
|
@@ -1,20 +1,30 @@
|
|
|
1
|
-
import { type FunctionComponent } from 'react';
|
|
2
|
-
import
|
|
1
|
+
import { type FunctionComponent, type ReactNode, type TextareaHTMLAttributes } from 'react';
|
|
2
|
+
import type { WithClassName } from '../utils/types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Props for the {@link Textarea} component.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Extends all native {@link TextareaHTMLAttributes} and {@link WithClassName}
|
|
7
|
+
* with optional label, error content, and automatic height adjustment.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* @property label - Content rendered as an associated `<label>`. When omitted, no label is rendered.
|
|
10
|
+
* @property error - Content rendered as an error message below the textarea. When omitted, no error is rendered.
|
|
11
|
+
* @property autoHeight - When `true`, grows and shrinks the textarea to fit its
|
|
12
|
+
* content on every value change. Defaults to `false`.
|
|
13
|
+
* @property className - Additional class name(s) applied to the textarea element.
|
|
11
14
|
*/
|
|
12
|
-
export type Props =
|
|
15
|
+
export type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & WithClassName<{
|
|
16
|
+
label?: ReactNode;
|
|
17
|
+
error?: ReactNode;
|
|
18
|
+
autoHeight?: boolean;
|
|
19
|
+
}>;
|
|
13
20
|
/**
|
|
14
|
-
* Textarea field
|
|
21
|
+
* Textarea field supporting controlled and uncontrolled usage.
|
|
15
22
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
23
|
+
* Renders a native `<textarea>` with optional label and error feedback. All
|
|
24
|
+
* standard textarea attributes are forwarded to the underlying element.
|
|
25
|
+
*
|
|
26
|
+
* A stable auto-generated `id` is created on mount and used to associate the
|
|
27
|
+
* rendered label through the `htmlFor` attribute.
|
|
18
28
|
*
|
|
19
29
|
* ### CSS elements
|
|
20
30
|
* - `label`
|
|
@@ -22,17 +32,13 @@ export type Props = ControlledProps;
|
|
|
22
32
|
*
|
|
23
33
|
* @param props - Component properties.
|
|
24
34
|
* @see {@link Props}
|
|
25
|
-
*
|
|
26
|
-
* @returns A labelled textarea with optional internal value management.
|
|
35
|
+
* @returns A labelled textarea with optional error feedback.
|
|
27
36
|
*
|
|
28
37
|
* @remarks
|
|
29
|
-
* - In controlled mode (`value` defined), the
|
|
30
|
-
* by the parent
|
|
31
|
-
* - In
|
|
32
|
-
*
|
|
33
|
-
* -
|
|
34
|
-
* `onChange` callback.
|
|
35
|
-
* - `defaultValue` is only used to initialize internal state and is not
|
|
36
|
-
* forwarded to the underlying controlled component.
|
|
38
|
+
* - In controlled mode (`value` defined), the displayed value is fully driven
|
|
39
|
+
* by the parent and internal state is never updated.
|
|
40
|
+
* - In uncontrolled mode, internal state is initialized from `defaultValue` and
|
|
41
|
+
* updated before `onChange` is forwarded.
|
|
42
|
+
* - `onChange` fires in both modes.
|
|
37
43
|
*/
|
|
38
44
|
export declare const Textarea: FunctionComponent<Props>;
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import {
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
import { clss } from '../../agnostic/css/clss/index.js';
|
|
4
|
+
import { isNotFalsy } from '../../agnostic/booleans/is-falsy/index.js';
|
|
5
|
+
import { randomHash } from '../../agnostic/random/uuid/index.js';
|
|
6
|
+
import { mergeClassNames } from '../utils/index.js';
|
|
7
|
+
import { textarea as publicClassName } from '../public-classnames.js';
|
|
8
|
+
import cssModule from './styles.module.css';
|
|
4
9
|
/**
|
|
5
|
-
* Textarea field
|
|
10
|
+
* Textarea field supporting controlled and uncontrolled usage.
|
|
6
11
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
12
|
+
* Renders a native `<textarea>` with optional label and error feedback. All
|
|
13
|
+
* standard textarea attributes are forwarded to the underlying element.
|
|
14
|
+
*
|
|
15
|
+
* A stable auto-generated `id` is created on mount and used to associate the
|
|
16
|
+
* rendered label through the `htmlFor` attribute.
|
|
9
17
|
*
|
|
10
18
|
* ### CSS elements
|
|
11
19
|
* - `label`
|
|
@@ -13,27 +21,38 @@ import { ControlledTextarea } from './index.controlled.js';
|
|
|
13
21
|
*
|
|
14
22
|
* @param props - Component properties.
|
|
15
23
|
* @see {@link Props}
|
|
16
|
-
*
|
|
17
|
-
* @returns A labelled textarea with optional internal value management.
|
|
24
|
+
* @returns A labelled textarea with optional error feedback.
|
|
18
25
|
*
|
|
19
26
|
* @remarks
|
|
20
|
-
* - In controlled mode (`value` defined), the
|
|
21
|
-
* by the parent
|
|
22
|
-
* - In
|
|
23
|
-
*
|
|
24
|
-
* -
|
|
25
|
-
* `onChange` callback.
|
|
26
|
-
* - `defaultValue` is only used to initialize internal state and is not
|
|
27
|
-
* forwarded to the underlying controlled component.
|
|
27
|
+
* - In controlled mode (`value` defined), the displayed value is fully driven
|
|
28
|
+
* by the parent and internal state is never updated.
|
|
29
|
+
* - In uncontrolled mode, internal state is initialized from `defaultValue` and
|
|
30
|
+
* updated before `onChange` is forwarded.
|
|
31
|
+
* - `onChange` fires in both modes.
|
|
28
32
|
*/
|
|
29
|
-
export const Textarea = ({
|
|
33
|
+
export const Textarea = ({ label, error, autoHeight = false, value, defaultValue, onChange, className, ...rest }) => {
|
|
34
|
+
// State & refs
|
|
35
|
+
const [id] = useState(`_${randomHash(12)}`);
|
|
36
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? '');
|
|
37
|
+
const textareaRef = useRef(null);
|
|
30
38
|
const isControlled = value !== undefined;
|
|
31
|
-
const
|
|
32
|
-
|
|
39
|
+
const currentValue = isControlled ? value : internalValue;
|
|
40
|
+
// Fx. dep. `autoHeight`, `currentValue` - fit the height to the content
|
|
41
|
+
useLayoutEffect(() => {
|
|
42
|
+
const textarea = textareaRef.current;
|
|
43
|
+
if (!autoHeight || textarea === null)
|
|
44
|
+
return;
|
|
45
|
+
textarea.style.height = 'auto';
|
|
46
|
+
textarea.style.height = `${textarea.scrollHeight}px`;
|
|
47
|
+
}, [autoHeight, currentValue]);
|
|
48
|
+
// User action handlers
|
|
33
49
|
const handleChange = (e) => {
|
|
34
50
|
if (!isControlled)
|
|
35
|
-
|
|
51
|
+
setInternalValue(e.target.value);
|
|
36
52
|
onChange?.(e);
|
|
37
53
|
};
|
|
38
|
-
|
|
54
|
+
// Rendering
|
|
55
|
+
const c = clss(publicClassName, { cssModule });
|
|
56
|
+
const rootClss = mergeClassNames(c(), className);
|
|
57
|
+
return _jsxs(_Fragment, { children: [isNotFalsy(label) && _jsx("label", { className: c('label'), htmlFor: id, children: label }), _jsx("textarea", { ...rest, ref: textareaRef, id: id, className: rootClss, value: currentValue, onChange: handleChange }), isNotFalsy(error) && _jsx("span", { className: c('error'), children: error })] });
|
|
39
58
|
};
|
|
@@ -3,23 +3,24 @@ import type { WithClassName } from '../utils/types.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Props for the {@link Theatre} component.
|
|
5
5
|
*
|
|
6
|
-
* @property closeBtnContent -
|
|
7
|
-
* @property openBtnContent -
|
|
8
|
-
* @property isOn - Controlled theatre mode state. When
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* @property exitOnEscape
|
|
13
|
-
*
|
|
14
|
-
* @property
|
|
15
|
-
*
|
|
16
|
-
* @property
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
6
|
+
* @property closeBtnContent - Content rendered inside the close/exit button.
|
|
7
|
+
* @property openBtnContent - Content rendered inside the open/enter button.
|
|
8
|
+
* @property isOn - Controlled theatre mode state. When defined, the component
|
|
9
|
+
* behaves as a controlled component and internal state is never updated.
|
|
10
|
+
* @property defaultIsOn - Initial theatre mode state in uncontrolled mode.
|
|
11
|
+
* Ignored when `isOn` is provided. Defaults to `false`.
|
|
12
|
+
* @property exitOnEscape - When `true`, pressing `Escape` while the stage is
|
|
13
|
+
* open counts as a toggle.
|
|
14
|
+
* @property exitOnBgClick - When `true`, clicking the stage background — and
|
|
15
|
+
* not its content — counts as a toggle.
|
|
16
|
+
* @property onToggleClicked - Called whenever a toggle is requested: the open
|
|
17
|
+
* or close button, the `Escape` key, or the stage background. Fires before the
|
|
18
|
+
* theatre reacts, with the state as it was.
|
|
19
|
+
* @property onIsOnChanged - Called after the theatre mode changed, with the new
|
|
20
|
+
* value.
|
|
20
21
|
* @property className - Optional additional class name(s) applied to the root element.
|
|
21
|
-
* @property children - Content rendered both in the default slot and, when
|
|
22
|
-
* mode is active, duplicated inside the stage element.
|
|
22
|
+
* @property children - Content rendered both in the default slot and, when
|
|
23
|
+
* theatre mode is active, duplicated inside the stage element.
|
|
23
24
|
*/
|
|
24
25
|
export type Props = PropsWithChildren<WithClassName<{
|
|
25
26
|
closeBtnContent?: ReactNode;
|
|
@@ -28,37 +29,32 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
28
29
|
defaultIsOn?: boolean;
|
|
29
30
|
exitOnEscape?: boolean;
|
|
30
31
|
exitOnBgClick?: boolean;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
};
|
|
34
|
-
actionHandlers?: {
|
|
35
|
-
toggleClick?: (prevIsOn: boolean) => void;
|
|
36
|
-
};
|
|
32
|
+
onToggleClicked?: (isOn: boolean) => void;
|
|
33
|
+
onIsOnChanged?: (isOn: boolean) => void;
|
|
37
34
|
}>>;
|
|
38
35
|
/**
|
|
39
36
|
* Theatre mode component. Wraps content in a toggleable fullscreen-like "stage"
|
|
40
|
-
* overlay.
|
|
37
|
+
* overlay.
|
|
41
38
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
39
|
+
* ### CSS modifiers
|
|
40
|
+
* - `on` — theatre mode is active.
|
|
41
|
+
* - `off` — theatre mode is inactive.
|
|
45
42
|
*
|
|
46
|
-
* ###
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* -
|
|
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.
|
|
43
|
+
* ### CSS elements
|
|
44
|
+
* - `stage` — holds the duplicated `children`, mounted only when `isOn`.
|
|
45
|
+
* - `open-btn`
|
|
46
|
+
* - `close-btn`
|
|
57
47
|
*
|
|
58
48
|
* @param props - Component properties.
|
|
59
49
|
* @see {@link Props}
|
|
60
50
|
* @returns A root `<div>` containing the children in their original position,
|
|
61
|
-
* a stage overlay with the duplicated children
|
|
62
|
-
*
|
|
51
|
+
* a stage overlay with the duplicated children when active, and both toggles.
|
|
52
|
+
*
|
|
53
|
+
* @remarks
|
|
54
|
+
* - In controlled mode (`isOn` defined), the state is fully driven by the
|
|
55
|
+
* parent and internal state is never updated.
|
|
56
|
+
* - `onToggleClicked` fires in both modes, for all four toggle sources — a
|
|
57
|
+
* controlled parent needs it to know a toggle was requested at all.
|
|
58
|
+
* - `onIsOnChanged` fires in both modes too, and never on mount.
|
|
63
59
|
*/
|
|
64
60
|
export declare const Theatre: FunctionComponent<Props>;
|