@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.
Files changed (129) hide show
  1. package/agnostic/arrays/index.d.ts +1 -1
  2. package/agnostic/arrays/index.js +1 -1
  3. package/agnostic/colors/index.d.ts +2 -2
  4. package/agnostic/colors/index.js +2 -2
  5. package/agnostic/css/clss/index.d.ts +53 -1
  6. package/agnostic/css/clss/index.js +1 -1
  7. package/agnostic/css/index.d.ts +2 -2
  8. package/agnostic/css/index.js +2 -2
  9. package/agnostic/errors/index.d.ts +1 -1
  10. package/agnostic/errors/index.js +1 -1
  11. package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +20 -20
  12. package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +20 -20
  13. package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +4 -4
  14. package/agnostic/html/hyper-json/smart-tags/isolated/index.js +4 -4
  15. package/agnostic/index.d.ts +4 -4
  16. package/agnostic/index.js +4 -4
  17. package/agnostic/misc/assert/index.d.ts +3 -0
  18. package/agnostic/misc/crossenv/index.d.ts +1 -1
  19. package/agnostic/misc/crossenv/index.js +1 -1
  20. package/agnostic/misc/index.d.ts +5 -5
  21. package/agnostic/misc/index.js +5 -5
  22. package/agnostic/misc/logs/index.d.ts +1 -1
  23. package/agnostic/misc/logs/index.js +1 -1
  24. package/agnostic/misc/logs/logger/index.d.ts +10 -0
  25. package/agnostic/misc/logs/logger/index.js +40 -10
  26. package/agnostic/misc/logs/styles/index.d.ts +1 -0
  27. package/agnostic/misc/logs/styles/index.js +27 -9
  28. package/agnostic/numbers/index.d.ts +2 -2
  29. package/agnostic/numbers/index.js +2 -2
  30. package/agnostic/objects/index.d.ts +4 -4
  31. package/agnostic/objects/index.js +4 -4
  32. package/agnostic/sanitization/index.d.ts +1 -1
  33. package/agnostic/sanitization/index.js +1 -1
  34. package/agnostic/strings/index.d.ts +1 -1
  35. package/agnostic/strings/index.js +1 -1
  36. package/agnostic/time/index.d.ts +2 -2
  37. package/agnostic/time/index.js +2 -2
  38. package/agnostic/time/transitions/index.d.ts +3 -3
  39. package/agnostic/time/transitions/index.js +4 -4
  40. package/components/Disclaimer/index.d.ts +45 -0
  41. package/components/Disclaimer/index.js +70 -0
  42. package/components/Drawer/index.d.ts +45 -0
  43. package/components/Drawer/index.js +82 -0
  44. package/components/Drawer/styles.module.css +0 -0
  45. package/components/EventListener/index.d.ts +20 -3
  46. package/components/EventListener/index.js +15 -22
  47. package/components/Gallery/index.d.ts +67 -0
  48. package/components/Gallery/index.js +173 -0
  49. package/components/Gallery/styles.module.css +33 -0
  50. package/components/Gallery/utils.d.ts +1 -0
  51. package/components/Image/index.d.ts +60 -0
  52. package/components/Image/index.js +99 -0
  53. package/components/Image/styles.module.css +0 -0
  54. package/components/IntersectionObserver/index.d.ts +48 -11
  55. package/components/IntersectionObserver/index.js +13 -22
  56. package/components/Paginator/index.d.ts +72 -0
  57. package/components/Paginator/index.js +116 -0
  58. package/components/Paginator/styles.module.css +9 -0
  59. package/components/ResizeObserver/index.d.ts +27 -0
  60. package/components/ResizeObserver/index.js +81 -0
  61. package/components/Scrllgngn/index.d.ts +123 -0
  62. package/components/Scrllgngn/index.js +175 -0
  63. package/components/Scrllgngn/styles.module.css +74 -0
  64. package/components/Sequencer/index.controlled.d.ts +78 -0
  65. package/components/Sequencer/index.d.ts +85 -0
  66. package/components/Sequencer/index.js +109 -0
  67. package/components/Sequencer/styles.module.css +0 -0
  68. package/components/ShadowRoot/index.d.ts +35 -0
  69. package/components/ShadowRoot/index.js +56 -0
  70. package/components/ShadowRoot/styles.module.css +0 -0
  71. package/components/Subtitles/index.d.ts +58 -0
  72. package/components/Subtitles/index.js +111 -0
  73. package/components/Subtitles/styles.module.css +0 -0
  74. package/components/Subtitles/types.d.ts +10 -0
  75. package/components/Subtitles/types.js +0 -0
  76. package/components/Subtitles/utils.d.ts +28 -0
  77. package/components/Theatre/index.d.ts +64 -0
  78. package/components/Theatre/index.js +97 -0
  79. package/components/Theatre/styles.module.css +0 -0
  80. package/components/Video/index.d.ts +119 -0
  81. package/components/Video/index.js +358 -0
  82. package/components/Video/styles.module.css +0 -0
  83. package/components/Video/utils.d.ts +10 -0
  84. package/components/_WIP_AudioQuote/index.d.ts +1 -0
  85. package/components/_WIP_AudioQuote/index.js +0 -0
  86. package/components/_WIP_Icon/index.d.ts +1 -0
  87. package/components/_WIP_Icon/index.js +0 -0
  88. package/components/index.d.ts +15 -1
  89. package/components/index.js +15 -1
  90. package/components/public-classnames.d.ts +14 -3
  91. package/components/utils/index.d.ts +1 -0
  92. package/components/utils/index.js +12 -0
  93. package/components/utils/types.d.ts +3 -0
  94. package/components/utils/types.js +0 -0
  95. package/index.d.ts +1 -1
  96. package/index.js +1 -1
  97. package/node/@aws-s3/index.test.d.ts +1 -0
  98. package/node/@aws-s3/storage/directory/index.d.ts +1 -1
  99. package/node/@aws-s3/storage/directory/index.js +1 -1
  100. package/node/@aws-s3/storage/file/index.d.ts +3 -3
  101. package/node/@aws-s3/storage/file/index.js +3 -3
  102. package/node/@google-cloud/storage/directory/index.d.ts +2 -2
  103. package/node/@google-cloud/storage/directory/index.js +2 -2
  104. package/node/@google-cloud/storage/file/index.d.ts +4 -4
  105. package/node/@google-cloud/storage/file/index.js +4 -4
  106. package/node/cloud-storage/operations/index.d.ts +3 -3
  107. package/node/cloud-storage/operations/index.js +3 -3
  108. package/node/encryption/index.d.ts +1 -1
  109. package/node/encryption/index.js +1 -1
  110. package/node/files/index.d.ts +1 -1
  111. package/node/files/index.js +1 -1
  112. package/node/ftps/directory/index.d.ts +2 -2
  113. package/node/ftps/directory/index.js +2 -2
  114. package/node/ftps/file/index.d.ts +1 -1
  115. package/node/ftps/file/index.js +1 -1
  116. package/node/images/index.d.ts +3 -3
  117. package/node/images/index.js +3 -3
  118. package/node/images/transform/operations/index.d.ts +6 -6
  119. package/node/images/transform/operations/index.js +6 -6
  120. package/node/index.d.ts +4 -4
  121. package/node/index.js +4 -4
  122. package/node/process/spawner/index.d.ts +61 -2
  123. package/node/process/spawner/index.js +6 -6
  124. package/node/sftp/file/index.d.ts +3 -3
  125. package/node/sftp/file/index.js +3 -3
  126. package/package.json +1030 -13
  127. package/components/Input/index.d.ts +0 -7
  128. package/components/Input/index.js +0 -29
  129. /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
@@ -0,0 +1,10 @@
1
+ export type ParsedSub = {
2
+ id: number;
3
+ start?: number;
4
+ end?: number;
5
+ content?: string;
6
+ };
7
+ export type SubGroupBoundaries = {
8
+ startId: number;
9
+ endId: number;
10
+ };
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