@design-edito/tools 0.5.37 → 0.5.39

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 (115) hide show
  1. package/agnostic/@react/effects/index.d.ts +1 -0
  2. package/agnostic/@react/effects/index.js +1 -0
  3. package/agnostic/@react/effects/use-state-change-dispatch/index.d.ts +22 -0
  4. package/agnostic/@react/effects/use-state-change-dispatch/index.js +31 -0
  5. package/agnostic/@react/index.d.ts +1 -0
  6. package/agnostic/@react/index.js +1 -0
  7. package/agnostic/arrays/index.d.ts +3 -2
  8. package/agnostic/arrays/index.js +3 -2
  9. package/agnostic/arrays/integers-between/index.d.ts +8 -0
  10. package/agnostic/arrays/integers-between/index.js +11 -0
  11. package/agnostic/arrays/integers-between/index.test.d.ts +1 -0
  12. package/agnostic/arrays/integers-between/index.test.js +16 -0
  13. package/agnostic/colors/index.d.ts +2 -2
  14. package/agnostic/colors/index.js +2 -2
  15. package/agnostic/css/index.d.ts +2 -1
  16. package/agnostic/css/index.js +2 -1
  17. package/agnostic/css/merge-class-names/index.d.ts +8 -0
  18. package/agnostic/css/merge-class-names/index.js +23 -0
  19. package/agnostic/css/merge-class-names/index.test.d.ts +1 -0
  20. package/agnostic/css/merge-class-names/index.test.js +52 -0
  21. package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +11 -11
  22. package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +11 -11
  23. package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +2 -2
  24. package/agnostic/html/hyper-json/smart-tags/isolated/index.js +2 -2
  25. package/agnostic/html/index.d.ts +2 -2
  26. package/agnostic/html/index.js +2 -2
  27. package/agnostic/index.d.ts +2 -1
  28. package/agnostic/index.js +2 -1
  29. package/agnostic/misc/index.d.ts +1 -1
  30. package/agnostic/misc/index.js +1 -1
  31. package/agnostic/numbers/index.d.ts +2 -2
  32. package/agnostic/numbers/index.js +2 -2
  33. package/agnostic/objects/index.d.ts +2 -2
  34. package/agnostic/objects/index.js +2 -2
  35. package/agnostic/random/index.d.ts +1 -1
  36. package/agnostic/random/index.js +1 -1
  37. package/agnostic/time/dates/index.d.ts +1 -1
  38. package/agnostic/time/dates/index.js +1 -1
  39. package/components/BeforeAfter/index.controlled.js +1 -1
  40. package/components/BeforeAfter/index.js +2 -2
  41. package/components/Button/index.js +1 -1
  42. package/components/Clippable/index.js +1 -1
  43. package/components/Disclaimer/index.d.ts +2 -2
  44. package/components/Disclaimer/index.js +4 -3
  45. package/components/Drawer/index.js +4 -3
  46. package/components/EventListener/index.js +1 -1
  47. package/components/Gallery/index.js +5 -4
  48. package/components/Iframe/index.js +1 -1
  49. package/components/Image/index.d.ts +1 -1
  50. package/components/Image/index.js +3 -2
  51. package/components/Input/index.js +1 -1
  52. package/components/IntersectionObserver/index.js +1 -1
  53. package/components/JsonEditor/index.js +1 -1
  54. package/components/JsonEditor/types.d.ts +0 -6
  55. package/components/JsonEditor/utils.d.ts +0 -5
  56. package/components/JsonEditor/utils.js +0 -5
  57. package/components/Lightbox/index.d.ts +12 -12
  58. package/components/Lightbox/index.js +37 -35
  59. package/components/Lightbox/store.d.ts +9 -43
  60. package/components/Lightbox/store.js +51 -73
  61. package/components/Lightbox/types.d.ts +16 -0
  62. package/components/Lightbox/types.js +1 -0
  63. package/components/ListLoader/index.controlled.js +19 -13
  64. package/components/ListLoader/index.js +19 -3
  65. package/components/Overlayer/index.js +1 -1
  66. package/components/Paginator/index.js +4 -3
  67. package/components/ResizeObserver/index.js +1 -1
  68. package/components/Scrllgngn/index.d.ts +4 -4
  69. package/components/Scrllgngn/index.js +4 -3
  70. package/components/Scrllgngn/types.d.ts +55 -0
  71. package/components/Scrllgngn/types.js +24 -0
  72. package/components/Scrllgngn/utils.d.ts +2 -38
  73. package/components/ScrollListener/index.d.ts +1 -6
  74. package/components/ScrollListener/index.js +10 -9
  75. package/components/ScrollListener/types.d.ts +0 -3
  76. package/components/Select/index.js +1 -1
  77. package/components/Sequencer/index.d.ts +2 -9
  78. package/components/Sequencer/index.js +26 -128
  79. package/components/Sequencer/types.d.ts +30 -0
  80. package/components/Sequencer/types.js +30 -0
  81. package/components/Sequencer/utils.d.ts +4 -0
  82. package/components/Sequencer/utils.js +25 -0
  83. package/components/ShadowRoot/index.js +1 -1
  84. package/components/Subtitles/index.js +3 -5
  85. package/components/Subtitles/utils.d.ts +0 -51
  86. package/components/Subtitles/utils.js +0 -41
  87. package/components/Textarea/index.js +1 -1
  88. package/components/UIModule/index.d.ts +8 -6
  89. package/components/UIModule/index.js +11 -14
  90. package/components/Video/index.controlled.d.ts +1 -7
  91. package/components/Video/index.controlled.js +13 -12
  92. package/components/Video/index.d.ts +0 -8
  93. package/components/Video/index.js +11 -15
  94. package/components/Video/types.d.ts +33 -0
  95. package/components/Video/utils.d.ts +1 -67
  96. package/components/Video/utils.js +25 -44
  97. package/components/index.d.ts +3 -3
  98. package/components/index.js +3 -3
  99. package/components/utils/index.d.ts +1 -44
  100. package/components/utils/index.js +1 -83
  101. package/components/utils/viewport-behaviours/index.d.ts +8 -14
  102. package/components/utils/viewport-behaviours/index.js +15 -37
  103. package/components/utils/viewport-behaviours/types.d.ts +3 -9
  104. package/components/utils/viewport-behaviours/types.js +3 -4
  105. package/node/ftps/directory/index.d.ts +1 -1
  106. package/node/ftps/directory/index.js +1 -1
  107. package/node/images/index.d.ts +1 -1
  108. package/node/images/index.js +1 -1
  109. package/node/images/transform/operations/index.d.ts +3 -3
  110. package/node/images/transform/operations/index.js +3 -3
  111. package/node/index.d.ts +2 -2
  112. package/node/index.js +2 -2
  113. package/package.json +44 -1
  114. package/components/ListLoader/utils.d.ts +0 -8
  115. package/components/ListLoader/utils.js +0 -10
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from 'react';
3
+ import { useStateChangeDispatch } from '../../agnostic/@react/effects/use-state-change-dispatch/index.js';
3
4
  import { clss } from '../../agnostic/css/clss/index.js';
4
5
  import { unknownToString } from '../../agnostic/errors/unknown-to-string/index.js';
5
6
  import { isNonNullObject } from '../../agnostic/objects/is-object/index.js';
6
7
  import { randomHash } from '../../agnostic/random/uuid/index.js';
7
- import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
8
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
8
9
  import { uiModule as publicClassName } from '../public-classnames.js';
9
10
  import cssModule from './styles.module.css';
10
11
  /**
@@ -34,13 +35,13 @@ import cssModule from './styles.module.css';
34
35
  *
35
36
  * ### Root element attributes
36
37
  * - `id` — a stable generated id, assigned once on mount and used to scope the
37
- * module's `css` entries to this specific instance.
38
+ * module's `css` to this specific instance.
38
39
  *
39
40
  * @param props - Component properties.
40
41
  * @see {@link Props}
41
42
  * @see {@link ModuleData}
42
43
  * @returns A host `<div>` into which the module's root `Element` is appended,
43
- * along with any `<style>` blocks exported by the module.
44
+ * along with the `<style>` the module exported, if any.
44
45
  */
45
46
  export const UIModule = ({ src, props, onIdGenerated, onIsLoadingChanged, onLoadedModuleChanged, onModuleTargetChanged, className }) => {
46
47
  // State & refs
@@ -56,9 +57,9 @@ export const UIModule = ({ src, props, onIdGenerated, onIsLoadingChanged, onLoad
56
57
  // the render it ran on. This is what `init` and `postInit` read instead.
57
58
  const propsRef = useRef(props);
58
59
  // State dispatch
59
- useChangeDispatch(isLoading, onIsLoadingChanged);
60
- useChangeDispatch(loadedModule, onLoadedModuleChanged);
61
- useChangeDispatch(moduleTarget, onModuleTargetChanged);
60
+ useStateChangeDispatch(isLoading, onIsLoadingChanged);
61
+ useStateChangeDispatch(loadedModule, onLoadedModuleChanged);
62
+ useStateChangeDispatch(moduleTarget, onModuleTargetChanged);
62
63
  // Fx. no dep. - keep the latest props reachable from the effects that run outside
63
64
  // their own render, declared first so they read this render's value and not the last
64
65
  useEffect(() => { propsRef.current = props; });
@@ -80,12 +81,8 @@ export const UIModule = ({ src, props, onIdGenerated, onIsLoadingChanged, onLoad
80
81
  return setLoadedModule(new Error('Module exported member `destroy` must be a function'));
81
82
  if ('postInit' in data && typeof data.postInit !== 'function')
82
83
  return setLoadedModule(new Error('Module exported member `postInit` must be a function'));
83
- if ('css' in data) {
84
- if (!Array.isArray(data.css))
85
- return setLoadedModule(new Error('Module exported member `css` must be an array of strings'));
86
- if (data.css.some(entry => typeof entry !== 'string'))
87
- return setLoadedModule(new Error('Module exported member `css` must be an array of strings'));
88
- }
84
+ if ('css' in data && typeof data.css !== 'string')
85
+ return setLoadedModule(new Error('Module exported member `css` must be a string'));
89
86
  if ('update' in data && typeof data.update !== 'function')
90
87
  return setLoadedModule(new Error('Module exported member `update` must be a function'));
91
88
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- shape fully validated by the checks above
@@ -162,6 +159,6 @@ export const UIModule = ({ src, props, onIdGenerated, onIsLoadingChanged, onLoad
162
159
  'loaded': !isLoading && loadedModule !== null && !hasErrored,
163
160
  'initialized': moduleTarget !== null
164
161
  }), className);
165
- const moduleCss = hasErrored || loadedModule === null ? [] : loadedModule.css ?? [];
166
- return _jsx("div", { className: rootClss, ref: rootRef, id: id, children: moduleCss.map((css, cssPos) => _jsx("style", { children: `.${publicClassName}#${id} { ${css} }` }, cssPos)) });
162
+ const moduleCss = hasErrored || loadedModule === null ? undefined : loadedModule.css;
163
+ return _jsx("div", { className: rootClss, ref: rootRef, id: id, children: moduleCss !== undefined && _jsx("style", { children: `.${publicClassName}#${id} { ${moduleCss} }` }) });
167
164
  };
@@ -1,13 +1,7 @@
1
1
  import { type FunctionComponent, type PropsWithChildren, type Ref, type RefObject, type VideoHTMLAttributes } from 'react';
2
2
  import type { WithClassName } from '../utils/types.js';
3
3
  import { type Props as SubsProps } from '../Subtitles/index.js';
4
- import { type SourceData, type TrackData } from './utils.js';
5
- /**
6
- * Describes a single video source.
7
- *
8
- * @property src - URL of the video file.
9
- * @property type - MIME type of the source (e.g. `'video/mp4'`).
10
- */
4
+ import type { SourceData, TrackData } from './types.js';
11
5
  /**
12
6
  * Props for the ControlledVideo component.
13
7
  *
@@ -1,12 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useRef, useState, useCallback, useEffect } from 'react';
3
+ import { useStateChangeDispatch } from '../../agnostic/@react/effects/use-state-change-dispatch/index.js';
3
4
  import { clss } from '../../agnostic/css/clss/index.js';
4
5
  import { formatDuration } from '../../agnostic/time/duration/format-duration/index.js';
5
- import { mergeClassNames, parseSourceList, useChangeDispatch } from '../utils/index.js';
6
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
6
7
  import cssModule from './styles.module.css';
7
8
  import { video as publicClassName } from '../public-classnames.js';
8
9
  import { Subtitles } from '../Subtitles/index.js';
9
- import { forceExitFullscreen, forceFullscreen, forceLoud, forceMute, forcePause, forcePlay, forcePlaybackRate, forceVolume, getTimelineClickProgress, msToSeconds, secondsToMs } from './utils.js';
10
+ import { forceExitFullscreen, forceFullscreen, forceLoud, forceMute, forcePause, forcePlay, forcePlaybackRate, forceVolume, getTimelineClickProgress, msToSeconds, parseSrcSetSourcesList, secondsToMs } from './utils.js';
10
11
  /**
11
12
  * Full-featured video player component. Wraps a native `<video>` element with
12
13
  * playback controls, volume, playback rate, a timeline, optional subtitles
@@ -226,8 +227,8 @@ timeFormat = '{{mm}}:{{ss}}:{{ms}}', onPlayButtonClicked, onPauseButtonClicked,
226
227
  '--lm-video-volume-ratio': `${volume}`,
227
228
  '--lm-video-playback-rate': `${playbackRate}`
228
229
  };
229
- const parsedSources = useMemo(() => parseSourceList(sources, 'src'), [sources]);
230
- const parsedTracks = useMemo(() => parseSourceList(tracks, 'src'), [tracks]);
230
+ const parsedSources = useMemo(() => parseSrcSetSourcesList(sources, 'src'), [sources]);
231
+ const parsedTracks = useMemo(() => parseSrcSetSourcesList(tracks, 'src'), [tracks]);
231
232
  const videoClss = c('video');
232
233
  const videoControlsClss = c('video-controls');
233
234
  const playBtnClss = c('play-btn');
@@ -305,13 +306,13 @@ timeFormat = '{{mm}}:{{ss}}:{{ms}}', onPlayButtonClicked, onPauseButtonClicked,
305
306
  return () => { document.removeEventListener('fullscreenchange', onDocumentFullscreenChange); };
306
307
  }, [onFullscreenChange]);
307
308
  // State handlers
308
- useChangeDispatch(currentTimeMs, onCurrentTimeMsChanged);
309
- useChangeDispatch(isElementPlaying, onIsPlayingChanged);
310
- useChangeDispatch(isFullscreen, onIsFullscreenChanged);
311
- useChangeDispatch(isLoud, onIsLoudChanged);
312
- useChangeDispatch(subtitlesOn, onIsSubtitlesOnChanged);
313
- useChangeDispatch(isEnded, onIsEndedChanged);
314
- useChangeDispatch(volume, onVolumeChanged);
315
- useChangeDispatch(playbackRate, onPlaybackRateChanged);
309
+ useStateChangeDispatch(currentTimeMs, onCurrentTimeMsChanged);
310
+ useStateChangeDispatch(isElementPlaying, onIsPlayingChanged);
311
+ useStateChangeDispatch(isFullscreen, onIsFullscreenChanged);
312
+ useStateChangeDispatch(isLoud, onIsLoudChanged);
313
+ useStateChangeDispatch(subtitlesOn, onIsSubtitlesOnChanged);
314
+ useStateChangeDispatch(isEnded, onIsEndedChanged);
315
+ useStateChangeDispatch(volume, onVolumeChanged);
316
+ useStateChangeDispatch(playbackRate, onPlaybackRateChanged);
316
317
  return _jsxs("figure", { ref: rootRef, className: rootClss, style: rootStyles, ...rootAttributes, children: [_jsxs("video", { ref: videoRef, className: videoClss, ...intrinsicVideoAttributes, autoPlay: isTimeControlled ? false : intrinsicVideoAttributes.autoPlay, onClick: handleVideoClick, onLoadedMetadata: handleMetadataLoadEvent, onTimeUpdate: handleOnTimeUpdateEvent, onEnded: handleEndedEvent, onPlay: handlePlayEvent, onPause: handlePauseEvent, children: [parsedSources.map((source, index) => _jsx("source", { src: source.src, type: source.type }, index)), parsedTracks.map((track, index) => _jsx("track", { src: track.src, kind: track.kind, srcLang: track.srclang, label: track.label, default: track.default }, index)), children] }), _jsxs("div", { className: videoControlsClss, children: [_jsx("button", { className: playBtnClss, onClick: handlePlayButtonClick, children: playBtnContent }), _jsx("button", { className: pauseBtnClss, onClick: handlePauseButtonClick, children: pauseBtnContent }), _jsx("button", { className: loudBtnClss, onClick: handleLoudButtonClick, children: loudBtnContent }), _jsx("button", { className: muteBtnClss, onClick: handleMuteButtonClick, children: muteBtnContent }), _jsx("input", { type: 'range', className: volumeRangeClss, value: volumePercent, onChange: handleVolumeRangeChange, min: 0, max: 100, step: 1 }), _jsx("span", { className: volumePcntClss, children: Math.round(volumePercent) }), _jsx("button", { className: fullscreenBtnClss, onClick: handleFullscreenButtonClick, children: fullscreenBtnContent }), subtitles !== undefined && _jsx("button", { className: subtitlesBtnClss, onClick: handleSubtitlesButtonClick, children: subtitlesBtnContent }), _jsx("input", { type: 'range', className: playbackRateRangeClss, value: playbackRate, onChange: handleRateRangeChange, min: 0.25, max: 4, step: 0.25 }), _jsx("span", { className: playbackRateClss, children: playbackRate })] }), _jsxs("div", { className: timeControlsClss, children: [_jsx("span", { className: currentTimeClss, children: formatDuration(currentTimeMs, timeFormat) }), _jsx("span", { className: totalTimeClss, children: formatDuration(totalTimeMs, timeFormat) }), _jsx("div", { className: timelineClss, onClick: handleTimelineClick })] }), subtitles !== undefined && _jsx(Subtitles, { ...subtitles, timecodeMs: currentTimeMs, isEnded: isEnded })] });
317
318
  };
@@ -6,13 +6,6 @@ import { type Props as ControlledProps } from './index.controlled.js';
6
6
  * Props for the {@link Video} component.
7
7
  *
8
8
  * Extends all ControlledVideo props except play, mute, fullscreen, volume, playbackRate, and their associated event handlers
9
- * @property behavioursSuspended - Holds back the instructions that **start** something,
10
- * and lets through those that stop it. For a consumer withholding the video behind
11
- * something — a warning to accept, typically: a video kept back that plays anyway is not
12
- * withheld, it is merely hard to see. What was held back is replayed the moment this
13
- * goes false, so lifting the veil on an already-visible video does what was asked, which
14
- * a crossing-based trigger could never do. `':force'` does not override it: it speaks of
15
- * consent not yet given, where a surrender speaks of an intention already expressed.
16
9
  * @property visibilityThreshold - How much of the video has to be on screen to count as
17
10
  * seen. See {@link VisibilityOptions} for this and the four that follow it.
18
11
  * @property whenVisible - What to do each time it becomes visible.
@@ -39,7 +32,6 @@ import { type Props as ControlledProps } from './index.controlled.js';
39
32
  */
40
33
  export type Props = Omit<ControlledProps, 'play' | 'fullscreen' | 'volume' | 'mute' | 'playbackRate' | 'subtitlesOn' | 'rootRef' | 'videoRef'> & ViewportBehaviours<VideoAction> & {
41
34
  defaultSubtitlesOn?: boolean;
42
- behavioursSuspended?: boolean;
43
35
  };
44
36
  /**
45
37
  * Full-featured video player component. Wraps a native `<video>` element with
@@ -51,7 +51,7 @@ import { ControlledVideo } from './index.controlled.js';
51
51
  * The media still won't play, though: autoplay muted, and leave unmuting to the
52
52
  * reader.
53
53
  */
54
- export const Video = ({ defaultSubtitlesOn = true, behavioursSuspended, visibilityThreshold, visibilityRoot, visibilityRootMargin, visibilityOnAfterMs, visibilityOffAfterMs, whenVisible, whenHidden, onVisibilityChanged, onVideoClicked, onPlayButtonClicked, onPauseButtonClicked, onLoudButtonClicked, onMuteButtonClicked, onVolumeRangeChanged, onRateRangeChanged, onFullscreenButtonClicked, onSubtitlesButtonClicked, ...controlledProps }) => {
54
+ export const Video = ({ defaultSubtitlesOn = true, visibilityThreshold, visibilityRoot, visibilityRootMargin, visibilityOnAfterMs, visibilityOffAfterMs, whenVisible, whenHidden, onVisibilityChanged, onVideoClicked, onPlayButtonClicked, onPauseButtonClicked, onLoudButtonClicked, onMuteButtonClicked, onVolumeRangeChanged, onRateRangeChanged, onFullscreenButtonClicked, onSubtitlesButtonClicked, ...controlledProps }) => {
55
55
  // State & refs
56
56
  const [play, setPlay] = useState(false);
57
57
  const [volume, setVolume] = useState(1);
@@ -73,14 +73,11 @@ export const Video = ({ defaultSubtitlesOn = true, behavioursSuspended, visibili
73
73
  // **Seeking belongs to `playback`.** A reader who has dragged the timeline has said
74
74
  // where they want to be, and an automatic jump would take it back from them.
75
75
  const actions = {
76
- play: { kind: 'start', domain: 'playback', run: () => setPlay(true) },
77
- pause: { kind: 'stop', domain: 'playback', run: () => setPlay(false) },
78
- loud: { kind: 'start', domain: 'sound', run: () => setMute(false) },
79
- mute: { kind: 'stop', domain: 'sound', run: () => setMute(true) },
80
- // A seek starts nothing on its own — it moves a playhead, playing or not — so a gate
81
- // has no reason to hold it back.
76
+ play: { domain: 'playback', run: () => setPlay(true) },
77
+ pause: { domain: 'playback', run: () => setPlay(false) },
78
+ loud: { domain: 'sound', run: () => setMute(false) },
79
+ mute: { domain: 'sound', run: () => setMute(true) },
82
80
  'jump-to': {
83
- kind: 'stop',
84
81
  domain: 'playback',
85
82
  run: arg => {
86
83
  const targetMs = Number(arg);
@@ -89,8 +86,8 @@ export const Video = ({ defaultSubtitlesOn = true, behavioursSuspended, visibili
89
86
  forceJumpTo(videoRef.current, targetMs);
90
87
  }
91
88
  },
92
- 'jump-start': { kind: 'stop', domain: 'playback', run: () => forceJumpTo(videoRef.current, 0) },
93
- 'jump-end': { kind: 'stop', domain: 'playback', run: () => forceJumpTo(videoRef.current, -1) }
89
+ 'jump-start': { domain: 'playback', run: () => forceJumpTo(videoRef.current, 0) },
90
+ 'jump-end': { domain: 'playback', run: () => forceJumpTo(videoRef.current, -1) }
94
91
  };
95
92
  const { surrender: surrenderAnyDomain } = useViewportBehaviours(rootRef, {
96
93
  visibilityThreshold,
@@ -101,7 +98,7 @@ export const Video = ({ defaultSubtitlesOn = true, behavioursSuspended, visibili
101
98
  whenVisible,
102
99
  whenHidden,
103
100
  onVisibilityChanged
104
- }, actions, behavioursSuspended === true);
101
+ }, actions);
105
102
  // Narrowed back to this component's own domains. The generic layer takes a `string`,
106
103
  // having no vocabulary of its own; handing the handlers a typed door means a
107
104
  // misspelled domain fails to compile instead of quietly never surrendering anything.
@@ -185,10 +182,9 @@ export const Video = ({ defaultSubtitlesOn = true, behavioursSuspended, visibili
185
182
  onFullscreenButtonClicked?.(payload);
186
183
  setFullscreen(!payload.isFullscreen);
187
184
  }, [onFullscreenButtonClicked]);
188
- // Un seul bouton pour les deux sens, contrairement à lecture/pause et son/muet qui en
189
- // ont un chacun : ceux-là existent en paire dans le DOM et la feuille en montre un,
190
- // parce qu'ils portent deux glyphes opposés. Les sous-titres n'en portent qu'un, dont
191
- // seul l'habillage change — inutile d'en rendre deux pour n'en montrer jamais qu'un.
185
+ // One button for both directions, where play/pause and loud/mute get one each: those
186
+ // carry two opposed glyphs, so both are rendered and the stylesheet shows one. The
187
+ // subtitles button carries a single glyph whose styling alone changes.
192
188
  const handleSubtitlesButtonClick = useCallback(payload => {
193
189
  onSubtitlesButtonClicked?.(payload);
194
190
  setSubtitlesOn(!payload.isSubtitlesOn);
@@ -40,3 +40,36 @@ export type VideoAction = Exclude<VideoVerb, 'jump-to'> | `jump-to:${number}`;
40
40
  * want to be, and an automatic jump would take it back from them.
41
41
  */
42
42
  export type VideoDomain = 'playback' | 'sound';
43
+ /**
44
+ * One `<source>` of a `<video>`.
45
+ *
46
+ * **Not interchangeable with a picture source**: a `<source>` inside a `<video>` carries
47
+ * `src`, one inside a `<picture>` carries `srcSet`, `media` and `sizes`, and neither
48
+ * element accepts the other's attributes.
49
+ *
50
+ * @property src - URL of the video file.
51
+ * @property type - MIME type, so the browser can skip what it cannot play
52
+ * (`'video/mp4'`).
53
+ */
54
+ export type SourceData = {
55
+ src?: string;
56
+ type?: string;
57
+ };
58
+ /**
59
+ * One `<track>` of a `<video>` — subtitles, captions, chapters.
60
+ *
61
+ * @property src - URL of the WebVTT file.
62
+ * @property kind - What the track holds. Browsers default to `'subtitles'`.
63
+ * @property srclang - Its BCP 47 language tag, required by the element whenever `kind`
64
+ * is `'subtitles'`.
65
+ * @property label - The name a browser shows in its own track menu.
66
+ * @property default - Whether it is the one enabled when no preference says otherwise.
67
+ * At most one track per element may carry it.
68
+ */
69
+ export type TrackData = {
70
+ src?: string;
71
+ kind?: 'subtitles' | 'captions' | 'descriptions' | 'chapters' | 'metadata';
72
+ srclang?: string;
73
+ label?: string;
74
+ default?: boolean;
75
+ };
@@ -1,41 +1,7 @@
1
- /**
2
- * The two list shapes a `<video>` accepts as children, as records.
3
- *
4
- * They live here and not next to the component because nothing about them is React:
5
- * they describe a `<source>` and a `<track>` element.
6
- *
7
- * **A video source is not a picture source**, which is why `Image` keeps a shape of its
8
- * own rather than sharing this one: a `<source>` inside a `<video>` carries `src`, one
9
- * inside a `<picture>` carries `srcSet`, `media` and `sizes`, and neither element accepts
10
- * the other's attributes. What the two do share is how a list of them is read — see
11
- * `parseSourceList` in `components/utils`.
12
- */
13
- export type SourceData = {
14
- src?: string;
15
- type?: string;
16
- };
17
- export type TrackData = {
18
- src?: string;
19
- kind?: 'subtitles' | 'captions' | 'descriptions' | 'chapters' | 'metadata';
20
- srclang?: string;
21
- label?: string;
22
- default?: boolean;
23
- };
24
1
  export declare const muteAttributeWorkaround: (video: HTMLVideoElement | null, shouldMute: boolean) => void;
25
2
  export declare const forceMute: (video: HTMLVideoElement | null) => void;
26
3
  export declare const forceLoud: (video: HTMLVideoElement | null) => void;
27
4
  export declare const forceVolume: (video: HTMLVideoElement | null, volume: number) => void;
28
- /**
29
- * Asks the element to play, and reports what came of it.
30
- *
31
- * A browser may refuse — an unmuted media outside a user gesture, typically — and
32
- * it refuses **silently**: the promise rejects and no event fires, so this is the
33
- * only moment the outcome can be known. Hence the return value, and hence no
34
- * logging: a refusal is not an anomaly to print, it is an answer to hand back.
35
- *
36
- * @param video - The element, or `null` before it mounts.
37
- * @returns Whether it is playing now.
38
- */
39
5
  export declare const forcePlay: (video: HTMLVideoElement | null) => Promise<boolean>;
40
6
  export declare const forcePause: (video: HTMLVideoElement | null) => boolean;
41
7
  export declare const forcePlaybackRate: (video: HTMLVideoElement | null, rate: number) => void;
@@ -43,38 +9,6 @@ export declare const forceFullscreen: (video: HTMLVideoElement | null) => Promis
43
9
  export declare const forceExitFullscreen: (video: HTMLVideoElement | null) => Promise<boolean>;
44
10
  export declare function secondsToMs(seconds: number): number;
45
11
  export declare function msToSeconds(ms: number): number;
46
- /**
47
- * Where along the timeline a click landed.
48
- *
49
- * @param event - The click on the timeline element.
50
- * @returns A ratio between `0` and `1`, clamped to the element's own bounds.
51
- */
52
12
  export declare const getTimelineClickProgress: (event: React.MouseEvent<HTMLDivElement>) => number;
53
- /**
54
- * Whether a viewport-driven behaviour should run on this crossing.
55
- *
56
- * Each behaviour comes in two flavours: `…When…` on every crossing, `…Once…` on the
57
- * first one only. Setting both is the same as setting `…When…` alone.
58
- *
59
- * @param whenCrossed - The `…When…` prop.
60
- * @param onceOnly - The `…Once…` prop.
61
- * @param hasFired - Whether this behaviour's own automatic trigger already ran. It
62
- * tracks that behaviour and nothing else: a user pressing play must not spend an
63
- * automatic mute the component still owed.
64
- * @returns Whether to apply it now.
65
- */
66
- /**
67
- * Seeks the element, in milliseconds, counting from the end when asked negatively.
68
- *
69
- * **A negative target counts back from the duration**, so `-1` is the last reachable
70
- * timecode. It is `-1` and not `-0` because `-0 === 0` in JavaScript, which would make
71
- * the end of a video indistinguishable from its start.
72
- *
73
- * Nothing happens before the metadata lands: the duration is `NaN` until then, and a
74
- * seek computed against it would land anywhere. An instruction asking for the end of a
75
- * video whose length is still unknown is simply not yet answerable.
76
- *
77
- * @param video - The element, or `null` before it mounts.
78
- * @param targetMs - Where to go. Negative counts from the end.
79
- */
80
13
  export declare const forceJumpTo: (video: HTMLVideoElement | null, targetMs: number) => void;
14
+ export declare function parseSrcSetSourcesList<T extends Record<string, unknown>>(given: string | string[] | T[] | undefined, stringKey: keyof T & string): T[];
@@ -28,17 +28,8 @@ export const forceVolume = (video, volume) => {
28
28
  // eslint-disable-next-line no-param-reassign
29
29
  video.volume = volume;
30
30
  };
31
- /**
32
- * Asks the element to play, and reports what came of it.
33
- *
34
- * A browser may refuse — an unmuted media outside a user gesture, typically — and
35
- * it refuses **silently**: the promise rejects and no event fires, so this is the
36
- * only moment the outcome can be known. Hence the return value, and hence no
37
- * logging: a refusal is not an anomaly to print, it is an answer to hand back.
38
- *
39
- * @param video - The element, or `null` before it mounts.
40
- * @returns Whether it is playing now.
41
- */
31
+ // A browser refuses playback silently: the promise rejects and no event fires, so the
32
+ // return value is the only way the caller learns the outcome.
42
33
  export const forcePlay = async (video) => {
43
34
  if (video === null)
44
35
  return false;
@@ -107,44 +98,13 @@ export function secondsToMs(seconds) {
107
98
  export function msToSeconds(ms) {
108
99
  return ms / 1000;
109
100
  }
110
- /**
111
- * Where along the timeline a click landed.
112
- *
113
- * @param event - The click on the timeline element.
114
- * @returns A ratio between `0` and `1`, clamped to the element's own bounds.
115
- */
116
101
  export const getTimelineClickProgress = (event) => {
117
102
  const timelineRect = event.currentTarget.getBoundingClientRect();
118
103
  const position = event.clientX - timelineRect.left;
119
104
  return Math.min(1, Math.max(0, position / timelineRect.width));
120
105
  };
121
- /**
122
- * Whether a viewport-driven behaviour should run on this crossing.
123
- *
124
- * Each behaviour comes in two flavours: `…When…` on every crossing, `…Once…` on the
125
- * first one only. Setting both is the same as setting `…When…` alone.
126
- *
127
- * @param whenCrossed - The `…When…` prop.
128
- * @param onceOnly - The `…Once…` prop.
129
- * @param hasFired - Whether this behaviour's own automatic trigger already ran. It
130
- * tracks that behaviour and nothing else: a user pressing play must not spend an
131
- * automatic mute the component still owed.
132
- * @returns Whether to apply it now.
133
- */
134
- /**
135
- * Seeks the element, in milliseconds, counting from the end when asked negatively.
136
- *
137
- * **A negative target counts back from the duration**, so `-1` is the last reachable
138
- * timecode. It is `-1` and not `-0` because `-0 === 0` in JavaScript, which would make
139
- * the end of a video indistinguishable from its start.
140
- *
141
- * Nothing happens before the metadata lands: the duration is `NaN` until then, and a
142
- * seek computed against it would land anywhere. An instruction asking for the end of a
143
- * video whose length is still unknown is simply not yet answerable.
144
- *
145
- * @param video - The element, or `null` before it mounts.
146
- * @param targetMs - Where to go. Negative counts from the end.
147
- */
106
+ // A negative target counts back from the duration, `-1` being the last reachable
107
+ // timecode — not `-0`, which `===` makes indistinguishable from the start.
148
108
  export const forceJumpTo = (video, targetMs) => {
149
109
  if (video === null)
150
110
  return;
@@ -155,3 +115,24 @@ export const forceJumpTo = (video, targetMs) => {
155
115
  // eslint-disable-next-line no-param-reassign
156
116
  video.currentTime = msToSeconds(Math.min(Math.max(absolute, 0), durationMs));
157
117
  };
118
+ /* Source lists */
119
+ export function parseSrcSetSourcesList(given, stringKey) {
120
+ const fromString = (value) => {
121
+ const single = { [stringKey]: value };
122
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- a one-key record is the narrowest `T` a bare string can describe; every other field is optional by contract
123
+ return single;
124
+ };
125
+ if (given === undefined)
126
+ return [];
127
+ if (typeof given === 'string')
128
+ return [fromString(given)];
129
+ if (!Array.isArray(given))
130
+ return [];
131
+ if (given.length === 0)
132
+ return [];
133
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- first element sampled just above; array is read as homogeneous
134
+ if (typeof given[0] === 'string')
135
+ return given.map(fromString);
136
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- first element was checked not to be a string just above; array is read as homogeneous
137
+ return given;
138
+ }
@@ -1,9 +1,9 @@
1
1
  export * as beforeAfter from './BeforeAfter/index.js'
2
- export * as clippable from './Clippable/index.js'
3
2
  export * as button from './Button/index.js'
3
+ export * as clippable from './Clippable/index.js'
4
+ export * as disclaimer from './Disclaimer/index.js'
4
5
  export * as drawer from './Drawer/index.js'
5
6
  export * as eventListener from './EventListener/index.js'
6
- export * as disclaimer from './Disclaimer/index.js'
7
7
  export * as gallery from './Gallery/index.js'
8
8
  export * as iframe from './Iframe/index.js'
9
9
  export * as image from './Image/index.js'
@@ -16,8 +16,8 @@ export * as overlayer from './Overlayer/index.js'
16
16
  export * as paginator from './Paginator/index.js'
17
17
  export * as resizeObserver from './ResizeObserver/index.js'
18
18
  export * as scrllgngn from './Scrllgngn/index.js'
19
- export * as select from './Select/index.js'
20
19
  export * as scrollListener from './ScrollListener/index.js'
20
+ export * as select from './Select/index.js'
21
21
  export * as sequencer from './Sequencer/index.js'
22
22
  export * as shadowRoot from './ShadowRoot/index.js'
23
23
  export * as subtitles from './Subtitles/index.js'
@@ -1,9 +1,9 @@
1
1
  export * as beforeAfter from './BeforeAfter/index.js'
2
- export * as clippable from './Clippable/index.js'
3
2
  export * as button from './Button/index.js'
3
+ export * as clippable from './Clippable/index.js'
4
+ export * as disclaimer from './Disclaimer/index.js'
4
5
  export * as drawer from './Drawer/index.js'
5
6
  export * as eventListener from './EventListener/index.js'
6
- export * as disclaimer from './Disclaimer/index.js'
7
7
  export * as gallery from './Gallery/index.js'
8
8
  export * as iframe from './Iframe/index.js'
9
9
  export * as image from './Image/index.js'
@@ -16,8 +16,8 @@ export * as overlayer from './Overlayer/index.js'
16
16
  export * as paginator from './Paginator/index.js'
17
17
  export * as resizeObserver from './ResizeObserver/index.js'
18
18
  export * as scrllgngn from './Scrllgngn/index.js'
19
- export * as select from './Select/index.js'
20
19
  export * as scrollListener from './ScrollListener/index.js'
20
+ export * as select from './Select/index.js'
21
21
  export * as sequencer from './Sequencer/index.js'
22
22
  export * as shadowRoot from './ShadowRoot/index.js'
23
23
  export * as subtitles from './Subtitles/index.js'
@@ -1,44 +1 @@
1
- export declare function mergeClassNames(...names: Array<string | null | undefined | Array<string | null | undefined>>): string;
2
- /**
3
- * Calls `onChange` whenever `value` changes, and only then — never on mount.
4
- *
5
- * This is how a component reports one of its state items to its consumer:
6
- * a bare `useEffect` on the value would also fire on the first render, telling
7
- * the consumer something changed when nothing did.
8
- *
9
- * @template T - Type of the watched value.
10
- * @param value - The value to watch.
11
- * @param onChange - Called with the new value, once the change has happened.
12
- * @param isEqual - Decides whether the value actually changed. Defaults to
13
- * `Object.is`, which covers booleans, numbers and strings. Pass a custom
14
- * comparison for values rebuilt on every render, such as an array or an object
15
- * derived from state, whose identity always differs.
16
- *
17
- * @remarks
18
- * `onChange` is deliberately left out of the effect's dependencies. A consumer
19
- * passing an inline arrow creates a new function on every render, which would
20
- * re-run the effect each time and defeat the guard. The handler is read when the
21
- * effect fires, so it is never a stale one.
22
- */
23
- export declare function useChangeDispatch<T>(value: T, onChange?: (value: T) => void, isEqual?: (a: T, b: T) => boolean): void;
24
- /**
25
- * The three forms a `<source>` list is written in, reduced to the one a render uses.
26
- *
27
- * A bare string is a single source, an array of strings is several, an array of records
28
- * is taken as given. **The array is read as homogeneous** — the first element decides for
29
- * all of them —, which is what someone writing one by hand means anyway, and the only
30
- * reading a static hyper-json array could support.
31
- *
32
- * `stringKey` is the whole reason this is shared rather than written twice. The parsing
33
- * is identical for `Video` and `Image`, but **the shorthand does not name the same
34
- * attribute**: a `<source>` inside a `<video>` carries `src`, one inside a `<picture>`
35
- * carries `srcSet`. The two record shapes stay apart for the same reason — the picture
36
- * source also takes `media` and `sizes`, which a video source has no use for, and neither
37
- * element accepts the other's key. One element, one shape; only the reading is common.
38
- *
39
- * @template T - The record shape the caller's element accepts.
40
- * @param given - What the consumer wrote.
41
- * @param stringKey - Where a bare string goes.
42
- * @returns The list as records, empty when there is nothing to render.
43
- */
44
- export declare function parseSourceList<T extends Record<string, unknown>>(given: string | string[] | T[] | undefined, stringKey: keyof T & string): T[];
1
+ export * as viewportBehaviours from './viewport-behaviours/index.js'
@@ -1,83 +1 @@
1
- import { useEffect, useRef } from 'react';
2
- export function mergeClassNames(...names) {
3
- return names.map(name => {
4
- if (typeof name !== 'string' && !Array.isArray(name))
5
- return false;
6
- if (Array.isArray(name))
7
- return mergeClassNames(...name);
8
- if (name.trim() === '')
9
- return false;
10
- return name.trim();
11
- }).filter((name) => typeof name === 'string')
12
- .join(' ');
13
- }
14
- /**
15
- * Calls `onChange` whenever `value` changes, and only then — never on mount.
16
- *
17
- * This is how a component reports one of its state items to its consumer:
18
- * a bare `useEffect` on the value would also fire on the first render, telling
19
- * the consumer something changed when nothing did.
20
- *
21
- * @template T - Type of the watched value.
22
- * @param value - The value to watch.
23
- * @param onChange - Called with the new value, once the change has happened.
24
- * @param isEqual - Decides whether the value actually changed. Defaults to
25
- * `Object.is`, which covers booleans, numbers and strings. Pass a custom
26
- * comparison for values rebuilt on every render, such as an array or an object
27
- * derived from state, whose identity always differs.
28
- *
29
- * @remarks
30
- * `onChange` is deliberately left out of the effect's dependencies. A consumer
31
- * passing an inline arrow creates a new function on every render, which would
32
- * re-run the effect each time and defeat the guard. The handler is read when the
33
- * effect fires, so it is never a stale one.
34
- */
35
- export function useChangeDispatch(value, onChange, isEqual = Object.is) {
36
- const previousRef = useRef(value);
37
- useEffect(() => {
38
- if (isEqual(previousRef.current, value))
39
- return;
40
- previousRef.current = value;
41
- onChange?.(value);
42
- }, [value]);
43
- }
44
- /**
45
- * The three forms a `<source>` list is written in, reduced to the one a render uses.
46
- *
47
- * A bare string is a single source, an array of strings is several, an array of records
48
- * is taken as given. **The array is read as homogeneous** — the first element decides for
49
- * all of them —, which is what someone writing one by hand means anyway, and the only
50
- * reading a static hyper-json array could support.
51
- *
52
- * `stringKey` is the whole reason this is shared rather than written twice. The parsing
53
- * is identical for `Video` and `Image`, but **the shorthand does not name the same
54
- * attribute**: a `<source>` inside a `<video>` carries `src`, one inside a `<picture>`
55
- * carries `srcSet`. The two record shapes stay apart for the same reason — the picture
56
- * source also takes `media` and `sizes`, which a video source has no use for, and neither
57
- * element accepts the other's key. One element, one shape; only the reading is common.
58
- *
59
- * @template T - The record shape the caller's element accepts.
60
- * @param given - What the consumer wrote.
61
- * @param stringKey - Where a bare string goes.
62
- * @returns The list as records, empty when there is nothing to render.
63
- */
64
- export function parseSourceList(given, stringKey) {
65
- const fromString = (value) => {
66
- const single = { [stringKey]: value };
67
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- a one-key record is the narrowest `T` a bare string can describe; every other field is optional by contract
68
- return single;
69
- };
70
- if (given === undefined)
71
- return [];
72
- if (typeof given === 'string')
73
- return [fromString(given)];
74
- if (!Array.isArray(given))
75
- return [];
76
- if (given.length === 0)
77
- return [];
78
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- first element sampled just above; array is read as homogeneous
79
- if (typeof given[0] === 'string')
80
- return given.map(fromString);
81
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- first element was checked not to be a string just above; array is read as homogeneous
82
- return given;
83
- }
1
+ export * as viewportBehaviours from './viewport-behaviours/index.js'