@design-edito/tools 0.5.15 → 0.5.17

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 (55) hide show
  1. package/agnostic/colors/index.d.ts +3 -3
  2. package/agnostic/colors/index.js +3 -3
  3. package/agnostic/css/index.d.ts +2 -2
  4. package/agnostic/css/index.js +2 -2
  5. package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +14 -14
  6. package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +14 -14
  7. package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +5 -5
  8. package/agnostic/html/hyper-json/smart-tags/isolated/index.js +5 -5
  9. package/agnostic/html/index.d.ts +4 -4
  10. package/agnostic/html/index.js +4 -4
  11. package/agnostic/index.d.ts +2 -2
  12. package/agnostic/index.js +2 -2
  13. package/agnostic/misc/index.d.ts +5 -5
  14. package/agnostic/misc/index.js +5 -5
  15. package/agnostic/numbers/index.d.ts +2 -2
  16. package/agnostic/numbers/index.js +2 -2
  17. package/agnostic/objects/index.d.ts +3 -3
  18. package/agnostic/objects/index.js +3 -3
  19. package/agnostic/strings/index.d.ts +3 -3
  20. package/agnostic/strings/index.js +3 -3
  21. package/agnostic/time/index.d.ts +3 -3
  22. package/agnostic/time/index.js +3 -3
  23. package/components/IntersectionObserver/index.d.ts +28 -3
  24. package/components/IntersectionObserver/index.js +51 -20
  25. package/components/Video/index.controlled.d.ts +17 -1
  26. package/components/Video/index.controlled.js +20 -5
  27. package/components/Video/index.d.ts +13 -3
  28. package/components/Video/index.js +31 -12
  29. package/components/index.d.ts +3 -3
  30. package/components/index.js +3 -3
  31. package/components/public-classnames.d.ts +0 -1
  32. package/components/public-classnames.js +0 -1
  33. package/node/@aws-s3/storage/directory/index.d.ts +1 -1
  34. package/node/@aws-s3/storage/directory/index.js +1 -1
  35. package/node/@aws-s3/storage/file/index.d.ts +1 -1
  36. package/node/@aws-s3/storage/file/index.js +1 -1
  37. package/node/@aws-s3/storage/index.d.ts +1 -1
  38. package/node/@aws-s3/storage/index.js +1 -1
  39. package/node/@google-cloud/storage/file/index.d.ts +3 -3
  40. package/node/@google-cloud/storage/file/index.js +3 -3
  41. package/node/cloud-storage/operations/index.d.ts +2 -2
  42. package/node/cloud-storage/operations/index.js +2 -2
  43. package/node/files/index.d.ts +1 -1
  44. package/node/files/index.js +1 -1
  45. package/node/ftps/directory/index.d.ts +1 -1
  46. package/node/ftps/directory/index.js +1 -1
  47. package/node/ftps/file/index.d.ts +2 -2
  48. package/node/ftps/file/index.js +2 -2
  49. package/node/images/transform/operations/index.d.ts +4 -4
  50. package/node/images/transform/operations/index.js +4 -4
  51. package/node/index.d.ts +1 -1
  52. package/node/index.js +1 -1
  53. package/node/sftp/directory/index.d.ts +1 -1
  54. package/node/sftp/directory/index.js +1 -1
  55. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { type FunctionComponent, type PropsWithChildren, type VideoHTMLAttributes } from 'react';
1
+ import { type FunctionComponent, type PropsWithChildren, type Ref, 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
4
  /**
@@ -66,6 +66,8 @@ type TrackData = {
66
66
  * the component reacts, with the target and current rates.
67
67
  * @property onFullscreenButtonClicked - Called when the fullscreen button is
68
68
  * clicked, before the component reacts, with the fullscreen state as it was.
69
+ * @property onSubtitlesButtonClicked - Called when the subtitles button is clicked,
70
+ * before the component reacts, with the subtitles state as it was.
69
71
  * @property onTimelineClicked - Called when the timeline is clicked, before the
70
72
  * component reacts, with the target and current times (in seconds). The component
71
73
  * seeks to the target right after, unless the time is controlled.
@@ -77,6 +79,8 @@ type TrackData = {
77
79
  * @property onIsEndedChanged - Called after playback reached the end, and again
78
80
  * once it left it — a seek back or a new play. Never on mount.
79
81
  * @property onIsLoudChanged - Called once the mute state has changed (`true` = unmuted).
82
+ * @property onIsSubtitlesOnChanged - Called once the subtitles have been shown or
83
+ * hidden. Never on mount.
80
84
  * @property onVolumeChanged - Called once the volume has changed (`0` to `1`).
81
85
  * @property onPlaybackRateChanged - Called once the playback rate has changed.
82
86
  * @property onCurrentTimeMsChanged - Called once the current time has changed, in
@@ -96,12 +100,20 @@ type TrackData = {
96
100
  * gets, each taking its whole part and handing the rest down. So `'{{mm}}:{{ss}}'` on
97
101
  * an hour-long video reads `62:05` rather than dropping the hour — nothing is ever
98
102
  * lost for not having been asked for.
103
+ * @property subtitlesBtnContent - Glyph for the subtitles button, like the five other
104
+ * button contents. The button renders only when `subtitles` is set: with no cues there
105
+ * is nothing to toggle, and unlike `Disclaimer`'s missing toggler this withholds no
106
+ * behaviour — a stylesheet has nothing it could do with it either.
107
+ * @property subtitlesOn - Whether the subtitles show. Controlled: the component never
108
+ * changes it, it reports a click through `onSubtitlesButtonClicked` and leaves the
109
+ * decision to the parent. `Video` holds it for you.
99
110
  * @property className - Additional CSS class for the root element.
100
111
  * @property children - React content inserted into the <video> tag (fallback, etc).
101
112
  *
102
113
  * Also inherits all standard HTML props for a <video> element.
103
114
  */
104
115
  export type Props = PropsWithChildren<WithClassName<{
116
+ rootRef?: Ref<HTMLElement>;
105
117
  sources?: string | string[] | SourceData[];
106
118
  tracks?: string | string[] | TrackData[];
107
119
  subtitles?: SubsProps;
@@ -110,7 +122,9 @@ export type Props = PropsWithChildren<WithClassName<{
110
122
  loudBtnContent?: React.ReactNode;
111
123
  muteBtnContent?: React.ReactNode;
112
124
  fullscreenBtnContent?: React.ReactNode;
125
+ subtitlesBtnContent?: React.ReactNode;
113
126
  play?: boolean;
127
+ subtitlesOn?: boolean;
114
128
  fullscreen?: boolean;
115
129
  volume?: number;
116
130
  mute?: boolean;
@@ -124,10 +138,12 @@ export type Props = PropsWithChildren<WithClassName<{
124
138
  onVolumeRangeChanged?: (e: React.ChangeEvent<HTMLInputElement>, targetVolume: number, currentVolume: number, video: HTMLVideoElement | null) => void;
125
139
  onRateRangeChanged?: (e: React.ChangeEvent<HTMLInputElement>, targetRate: number, currentRate: number, video: HTMLVideoElement | null) => void;
126
140
  onFullscreenButtonClicked?: (e: React.MouseEvent<HTMLButtonElement>, isFullscreen: boolean, video: HTMLVideoElement | null) => void;
141
+ onSubtitlesButtonClicked?: (e: React.MouseEvent<HTMLButtonElement>, isSubtitlesOn: boolean, video: HTMLVideoElement | null) => void;
127
142
  onTimelineClicked?: (e: React.MouseEvent<HTMLDivElement>, targetTime: number, currentTime: number, video: HTMLVideoElement | null) => void;
128
143
  onIsPlayingChanged?: (isPlaying: boolean) => void;
129
144
  onIsFullscreenChanged?: (isFullscreen: boolean) => void;
130
145
  onIsLoudChanged?: (isLoud: boolean) => void;
146
+ onIsSubtitlesOnChanged?: (isSubtitlesOn: boolean) => void;
131
147
  onIsEndedChanged?: (isEnded: boolean) => void;
132
148
  onVolumeChanged?: (volume: number) => void;
133
149
  onPlaybackRateChanged?: (playbackRate: number) => void;
@@ -50,11 +50,14 @@ import { forceExitFullscreen, forceFullscreen, forceLoud, forceMute, forcePause,
50
50
  * @returns A `<figure>` element containing the video, its controls, optional
51
51
  * subtitles.
52
52
  */
53
- export const ControlledVideo = ({ sources, tracks, subtitles, playBtnContent, pauseBtnContent, loudBtnContent, muteBtnContent, fullscreenBtnContent, play, fullscreen, mute, muted, volume = 1, playbackRate = 1, currentTimeMs: givenCurrentTimeMs,
53
+ export const ControlledVideo = ({ sources, tracks, subtitles, playBtnContent, pauseBtnContent, loudBtnContent, muteBtnContent, fullscreenBtnContent, subtitlesBtnContent, play, fullscreen,
54
+ // Les sous-titres sont montrés tant que personne n'a dit le contraire : un article qui
55
+ // en fournit veut qu'on les lise.
56
+ subtitlesOn = true, mute, muted, volume = 1, playbackRate = 1, currentTimeMs: givenCurrentTimeMs,
54
57
  // Milliseconds by default, which is what this component has always rendered — a
55
58
  // default that changes under a consumer is a change nobody asked for. An article
56
59
  // usually wants `'{{mm}}:{{ss}}'` and says so.
57
- timeFormat = '{{mm}}:{{ss}}:{{ms}}', onPlayButtonClicked, onPauseButtonClicked, onLoudButtonClicked, onMuteButtonClicked, onVolumeRangeChanged, onRateRangeChanged, onFullscreenButtonClicked, onTimelineClicked, onIsPlayingChanged, onIsFullscreenChanged, onIsLoudChanged, onIsEndedChanged, onVolumeChanged, onPlaybackRateChanged, onCurrentTimeMsChanged, onFullscreenChange, children, className, ...intrinsicVideoAttributes }) => {
60
+ timeFormat = '{{mm}}:{{ss}}:{{ms}}', onPlayButtonClicked, onPauseButtonClicked, onLoudButtonClicked, onMuteButtonClicked, onVolumeRangeChanged, onRateRangeChanged, onFullscreenButtonClicked, onSubtitlesButtonClicked, onTimelineClicked, onIsPlayingChanged, onIsFullscreenChanged, onIsLoudChanged, onIsSubtitlesOnChanged, onIsEndedChanged, onVolumeChanged, onPlaybackRateChanged, onCurrentTimeMsChanged, onFullscreenChange, children, className, rootRef, ...intrinsicVideoAttributes }) => {
58
61
  const videoRef = useRef(null);
59
62
  const [totalTime, setTotalTime] = useState(0);
60
63
  const totalTimeMs = useMemo(() => secondsToMs(totalTime), [totalTime]);
@@ -131,6 +134,9 @@ timeFormat = '{{mm}}:{{ss}}:{{ms}}', onPlayButtonClicked, onPauseButtonClicked,
131
134
  const handleFullscreenButtonClick = useCallback((e) => {
132
135
  onFullscreenButtonClicked?.(e, isFullscreen, videoRef.current);
133
136
  }, [onFullscreenButtonClicked, isFullscreen]);
137
+ const handleSubtitlesButtonClick = useCallback((e) => {
138
+ onSubtitlesButtonClicked?.(e, subtitlesOn, videoRef.current);
139
+ }, [onSubtitlesButtonClicked, subtitlesOn]);
134
140
  const handleVolumeRangeChange = useCallback((e) => {
135
141
  const targetVolume = Number(e.currentTarget.value) / 100;
136
142
  onVolumeRangeChanged?.(e, targetVolume, volume, videoRef.current);
@@ -159,7 +165,12 @@ timeFormat = '{{mm}}:{{ss}}:{{ms}}', onPlayButtonClicked, onPauseButtonClicked,
159
165
  'fullscreen-on': isFullscreen,
160
166
  'fullscreen-off': !isFullscreen,
161
167
  'loud': isLoud,
162
- 'muted': !isLoud
168
+ 'muted': !isLoud,
169
+ // Rien des deux quand aucun sous-titre n'est fourni : l'absence n'est pas un état
170
+ // caché, c'est l'absence d'un état. Une feuille qui veut viser ce cas le reconnaît à
171
+ // ce qu'aucun des deux modifieurs n'est là.
172
+ 'subtitles-on': subtitles !== undefined && subtitlesOn,
173
+ 'subtitles-off': subtitles !== undefined && !subtitlesOn
163
174
  }), className);
164
175
  // Guarded: the duration is unknown until the metadata lands, and an unguarded
165
176
  // division would expose the string 'NaN' on every render until then.
@@ -172,6 +183,8 @@ timeFormat = '{{mm}}:{{ss}}:{{ms}}', onPlayButtonClicked, onPauseButtonClicked,
172
183
  'data-fullscreen-off': !isFullscreen ? '' : undefined,
173
184
  'data-loud': isLoud ? '' : undefined,
174
185
  'data-muted': !isLoud ? '' : undefined,
186
+ 'data-subtitles-on': subtitles !== undefined && subtitlesOn ? '' : undefined,
187
+ 'data-subtitles-off': subtitles !== undefined && !subtitlesOn ? '' : undefined,
175
188
  'data-volume': volume.toFixed(8),
176
189
  'data-volume-percent': volumePercent,
177
190
  'data-playback-rate': playbackRate,
@@ -228,6 +241,7 @@ timeFormat = '{{mm}}:{{ss}}:{{ms}}', onPlayButtonClicked, onPauseButtonClicked,
228
241
  const muteBtnClss = c('mute-btn');
229
242
  const volumePcntClss = c('volume-percent');
230
243
  const fullscreenBtnClss = c('fullscreen-btn');
244
+ const subtitlesBtnClss = c('subtitles-btn');
231
245
  const volumeRangeClss = c('volume-range');
232
246
  const playbackRateRangeClss = c('playback-rate-range');
233
247
  const playbackRateClss = c('playback-rate');
@@ -300,12 +314,13 @@ timeFormat = '{{mm}}:{{ss}}:{{ms}}', onPlayButtonClicked, onPauseButtonClicked,
300
314
  useChangeDispatch(isElementPlaying, onIsPlayingChanged);
301
315
  useChangeDispatch(isFullscreen, onIsFullscreenChanged);
302
316
  useChangeDispatch(isLoud, onIsLoudChanged);
317
+ useChangeDispatch(subtitlesOn, onIsSubtitlesOnChanged);
303
318
  useChangeDispatch(isEnded, onIsEndedChanged);
304
319
  useChangeDispatch(volume, onVolumeChanged);
305
320
  useChangeDispatch(playbackRate, onPlaybackRateChanged);
306
- return _jsxs("figure", { className: rootClss, style: rootStyles, ...rootAttributes, children: [_jsxs("video", { ref: videoRef, className: videoClss, ...intrinsicVideoAttributes, autoPlay: isTimeControlled ? false : intrinsicVideoAttributes.autoPlay, onLoadedMetadata: handleMetadataLoadEvent, onTimeUpdate: handleOnTimeUpdateEvent, onEnded: handleEndedEvent, onPlay: handlePlayEvent, onPause: handlePauseEvent, children: [parsedSources.map((source, index) => typeof source === 'string'
321
+ return _jsxs("figure", { ref: rootRef, className: rootClss, style: rootStyles, ...rootAttributes, children: [_jsxs("video", { ref: videoRef, className: videoClss, ...intrinsicVideoAttributes, autoPlay: isTimeControlled ? false : intrinsicVideoAttributes.autoPlay, onLoadedMetadata: handleMetadataLoadEvent, onTimeUpdate: handleOnTimeUpdateEvent, onEnded: handleEndedEvent, onPlay: handlePlayEvent, onPause: handlePauseEvent, children: [parsedSources.map((source, index) => typeof source === 'string'
307
322
  ? _jsx("source", { src: source }, index)
308
323
  : _jsx("source", { src: source.src, type: source.type }, index)), parsedTracks.map((track, index) => typeof track === 'string'
309
324
  ? _jsx("track", { src: track }, index)
310
- : _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 }), _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 })] });
325
+ : _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 })] });
311
326
  };
@@ -30,12 +30,16 @@ import { type Props as ControlledProps } from './index.controlled.js';
30
30
  * stopped video, since a playing element would advance a value it does not own:
31
31
  * `autoPlay`, `autoPlayWhenVisible` and the play button have no effect for as
32
32
  * long as this prop is provided.
33
- * @property wrapperClassName - Optional additional class name(s) applied to the root wrapper element.
33
+ * @property defaultSubtitlesOn - Whether the subtitles start shown. `true` by default —
34
+ * an article that supplies cues means them to be read. The reader's button takes it from
35
+ * there, so this is a starting point and not a setting: `default…` and never `initial…`,
36
+ * as everywhere else here.
34
37
  * @property className - Optional additional class name(s) applied to the root element.
35
38
  * @property children - React children rendered inside the `<video>` element itself
36
39
  * (e.g. fallback content).
37
40
  */
38
- export type Props = WithViewportObservation<Omit<ControlledProps, 'play' | 'fullscreen' | 'volume' | 'mute' | 'playbackRate'>> & {
41
+ export type Props = WithViewportObservation<Omit<ControlledProps, 'play' | 'fullscreen' | 'volume' | 'mute' | 'playbackRate' | 'subtitlesOn' | 'rootRef'>> & {
42
+ defaultSubtitlesOn?: boolean;
39
43
  autoPlayWhenVisible?: boolean;
40
44
  autoPlayOnceVisible?: boolean;
41
45
  autoPauseWhenHidden?: boolean;
@@ -44,7 +48,6 @@ export type Props = WithViewportObservation<Omit<ControlledProps, 'play' | 'full
44
48
  autoLoudOnceVisible?: boolean;
45
49
  autoMuteWhenHidden?: boolean;
46
50
  autoMuteOnceHidden?: boolean;
47
- wrapperClassName?: string;
48
51
  };
49
52
  /**
50
53
  * Full-featured video player component. Wraps a native `<video>` element with
@@ -73,6 +76,13 @@ export type Props = WithViewportObservation<Omit<ControlledProps, 'play' | 'full
73
76
  * automatic play the component still owed. Setting both flavours of the same
74
77
  * behaviour is the same as setting the `…When…` one alone.
75
78
  *
79
+ * **`loop` reaches the element untouched**, like every other native media attribute
80
+ * this component does not drive itself. It was once destructured out of the props and
81
+ * never forwarded, which silently dropped it — so if a native attribute ever stops
82
+ * arriving, look at the destructuring here before looking at the caller. One
83
+ * consequence worth knowing: a looping element never fires `ended`, so the `--ended`
84
+ * modifier and the `isEnded` it feeds to `Subtitles` simply never come.
85
+ *
76
86
  * Browsers refuse an unmuted `play()` outside a user gesture, so pairing an
77
87
  * `autoLoud…` with an `autoPlay…` will usually have the playback rejected. The
78
88
  * refusal is caught rather than ignored — the element is read back once the attempt
@@ -1,11 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
- import { clss } from '../../agnostic/css/clss/index.js';
4
- import { IntersectionObserverComponent } from '../IntersectionObserver/index.js';
5
- import { mergeClassNames } from '../utils/index.js';
6
- import { videoWrapper as publicClassName } from '../public-classnames.js';
3
+ import { useIntersectionObserver } from '../IntersectionObserver/index.js';
7
4
  import { muteAttributeWorkaround, shouldRunAutoBehaviour } from './utils.js';
8
- import cssModule from './styles.module.css';
9
5
  import { ControlledVideo } from './index.controlled.js';
10
6
  /**
11
7
  * Full-featured video player component. Wraps a native `<video>` element with
@@ -34,6 +30,13 @@ import { ControlledVideo } from './index.controlled.js';
34
30
  * automatic play the component still owed. Setting both flavours of the same
35
31
  * behaviour is the same as setting the `…When…` one alone.
36
32
  *
33
+ * **`loop` reaches the element untouched**, like every other native media attribute
34
+ * this component does not drive itself. It was once destructured out of the props and
35
+ * never forwarded, which silently dropped it — so if a native attribute ever stops
36
+ * arriving, look at the destructuring here before looking at the caller. One
37
+ * consequence worth knowing: a looping element never fires `ended`, so the `--ended`
38
+ * modifier and the `isEnded` it feeds to `Subtitles` simply never come.
39
+ *
37
40
  * Browsers refuse an unmuted `play()` outside a user gesture, so pairing an
38
41
  * `autoLoud…` with an `autoPlay…` will usually have the playback rejected. The
39
42
  * refusal is caught rather than ignored — the element is read back once the attempt
@@ -41,13 +44,14 @@ import { ControlledVideo } from './index.controlled.js';
41
44
  * The media still won't play, though: autoplay muted, and leave unmuting to the
42
45
  * reader.
43
46
  */
44
- export const Video = ({ loop, autoPlayWhenVisible, autoPlayOnceVisible, autoPauseWhenHidden, autoPauseOnceHidden, autoMuteWhenHidden, autoMuteOnceHidden, autoLoudWhenVisible, autoLoudOnceVisible, threshold, root, rootMargin, onVisibilityChanged, wrapperClassName, onPlayButtonClicked, onPauseButtonClicked, onLoudButtonClicked, onMuteButtonClicked, onVolumeRangeChanged, onRateRangeChanged, onFullscreenButtonClicked, ...controlledProps }) => {
47
+ export const Video = ({ defaultSubtitlesOn = true, autoPlayWhenVisible, autoPlayOnceVisible, autoPauseWhenHidden, autoPauseOnceHidden, autoMuteWhenHidden, autoMuteOnceHidden, autoLoudWhenVisible, autoLoudOnceVisible, threshold, root, rootMargin, onVisibilityChanged, onPlayButtonClicked, onPauseButtonClicked, onLoudButtonClicked, onMuteButtonClicked, onVolumeRangeChanged, onRateRangeChanged, onFullscreenButtonClicked, onSubtitlesButtonClicked, ...controlledProps }) => {
45
48
  // State & refs
46
49
  const [play, setPlay] = useState(false);
47
50
  const [volume, setVolume] = useState(1);
48
51
  const [mute, setMute] = useState(false);
49
52
  const [playbackRate, setPlaybackRate] = useState(1);
50
53
  const [fullscreen, setFullscreen] = useState(false);
54
+ const [subtitlesOn, setSubtitlesOn] = useState(defaultSubtitlesOn);
51
55
  // One flag per `…Once…` behaviour, and each is armed by that behaviour's own
52
56
  // automatic trigger. A single shared flag conflated four questions, and being set
53
57
  // on any `play` event — a user click included — meant the first press cancelled
@@ -61,6 +65,7 @@ export const Video = ({ loop, autoPlayWhenVisible, autoPlayOnceVisible, autoPaus
61
65
  // invariant is applied where the state is forwarded rather than guarded at each
62
66
  // of those call sites.
63
67
  const isTimeControlled = controlledProps.currentTimeMs !== undefined;
68
+ const rootRef = useRef(null);
64
69
  const needsObserve = useMemo(() => onVisibilityChanged !== undefined
65
70
  || autoLoudWhenVisible === true
66
71
  || autoLoudOnceVisible === true
@@ -143,6 +148,14 @@ export const Video = ({ loop, autoPlayWhenVisible, autoPlayOnceVisible, autoPaus
143
148
  onFullscreenButtonClicked?.(e, isFullscreen, video);
144
149
  setFullscreen(!isFullscreen);
145
150
  }, [onFullscreenButtonClicked]);
151
+ // Un seul bouton pour les deux sens, contrairement à lecture/pause et son/muet qui en
152
+ // ont un chacun : ceux-là existent en paire dans le DOM et la feuille en montre un,
153
+ // parce qu'ils portent deux glyphes opposés. Les sous-titres n'en portent qu'un, dont
154
+ // seul l'habillage change — inutile d'en rendre deux pour n'en montrer jamais qu'un.
155
+ const handleSubtitlesButtonClick = useCallback((e, isSubtitlesOn, video) => {
156
+ onSubtitlesButtonClicked?.(e, isSubtitlesOn, video);
157
+ setSubtitlesOn(!isSubtitlesOn);
158
+ }, [onSubtitlesButtonClicked]);
146
159
  // Intersection Observer
147
160
  const onIntersected = useCallback(({ ioEntry }) => {
148
161
  if (ioEntry === undefined)
@@ -185,11 +198,17 @@ export const Video = ({ loop, autoPlayWhenVisible, autoPlayOnceVisible, autoPaus
185
198
  if (controlledProps.autoPlay === true)
186
199
  setPlay(true);
187
200
  }, []);
201
+ // The observer watches the `<figure>` itself. `needsObserve` says whether anything
202
+ // asked for it — a hook cannot be skipped, so the condition is passed in rather than
203
+ // wrapped around the call, and no `IntersectionObserver` is created without a reason
204
+ // to create one.
205
+ useIntersectionObserver(rootRef, { threshold, root, rootMargin }, onIntersected, needsObserve);
188
206
  // Render
189
- const c = clss(publicClassName, { cssModule });
190
- const rootClss = mergeClassNames(c(), wrapperClassName);
191
- const videoContent = _jsx(ControlledVideo, { ...controlledProps, play: play && !isTimeControlled, volume: volume, mute: mute, playbackRate: playbackRate, fullscreen: fullscreen, onPlay: handleOnPlayEvent, onPause: handleOnPauseEvent, onIsPlayingChanged: handleIsPlayingChanged, onVolumeChange: handleOnVolumeChangeEvent, onRateChange: handleOnRateChangeEvent, onLoadedMetadata: handleOnLoadedMetadataEvent, onFullscreenChange: handleFullscreenChange, onPlayButtonClicked: handlePlayButtonClick, onPauseButtonClicked: handlePauseButtonClick, onLoudButtonClicked: handleLoudButtonClick, onMuteButtonClicked: handleMuteButtonClick, onVolumeRangeChanged: handleVolumeRangeChange, onRateRangeChanged: handleRateRangeChange, onFullscreenButtonClicked: handleFullscreenButtonClick });
192
- return _jsx("div", { className: rootClss, children: needsObserve
193
- ? _jsx(IntersectionObserverComponent, { threshold: threshold, root: root, rootMargin: rootMargin, onIntersected: onIntersected, children: videoContent })
194
- : videoContent });
207
+ //
208
+ // **The `<figure>` is the root, and there is nothing above it.** The observer runs on
209
+ // that element rather than on a box built to hold it: a wrapper would take the outer
210
+ // position — the one a consumer lays out — and leave `className` naming an inner
211
+ // element, which is the wrong way round. So no wrapper, and no second class-name prop
212
+ // to reach past it.
213
+ return _jsx(ControlledVideo, { ...controlledProps, rootRef: rootRef, play: play && !isTimeControlled, volume: volume, mute: mute, playbackRate: playbackRate, fullscreen: fullscreen, subtitlesOn: subtitlesOn, onPlay: handleOnPlayEvent, onPause: handleOnPauseEvent, onIsPlayingChanged: handleIsPlayingChanged, onVolumeChange: handleOnVolumeChangeEvent, onRateChange: handleOnRateChangeEvent, onLoadedMetadata: handleOnLoadedMetadataEvent, onFullscreenChange: handleFullscreenChange, onPlayButtonClicked: handlePlayButtonClick, onPauseButtonClicked: handlePauseButtonClick, onLoudButtonClicked: handleLoudButtonClick, onMuteButtonClicked: handleMuteButtonClick, onVolumeRangeChanged: handleVolumeRangeChange, onRateRangeChanged: handleRateRangeChange, onFullscreenButtonClicked: handleFullscreenButtonClick, onSubtitlesButtonClicked: handleSubtitlesButtonClick });
195
214
  };
@@ -3,13 +3,13 @@ export * as button from './Button/index.js'
3
3
  export * as clippable from './Clippable/index.js'
4
4
  export * as disclaimer from './Disclaimer/index.js'
5
5
  export * as drawer from './Drawer/index.js'
6
- export * as eventListener from './EventListener/index.js'
7
6
  export * as gallery from './Gallery/index.js'
7
+ export * as eventListener from './EventListener/index.js'
8
8
  export * as iframe from './Iframe/index.js'
9
9
  export * as image from './Image/index.js'
10
10
  export * as input from './Input/index.js'
11
- export * as jsonEditor from './JsonEditor/index.js'
12
11
  export * as intersectionObserver from './IntersectionObserver/index.js'
12
+ export * as jsonEditor from './JsonEditor/index.js'
13
13
  export * as lightbox from './Lightbox/index.js'
14
14
  export * as listLoader from './ListLoader/index.js'
15
15
  export * as overlayer from './Overlayer/index.js'
@@ -17,8 +17,8 @@ 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
19
  export * as scrollListener from './ScrollListener/index.js'
20
- export * as sequencer from './Sequencer/index.js'
21
20
  export * as select from './Select/index.js'
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'
24
24
  export * as textarea from './Textarea/index.js'
@@ -3,13 +3,13 @@ export * as button from './Button/index.js'
3
3
  export * as clippable from './Clippable/index.js'
4
4
  export * as disclaimer from './Disclaimer/index.js'
5
5
  export * as drawer from './Drawer/index.js'
6
- export * as eventListener from './EventListener/index.js'
7
6
  export * as gallery from './Gallery/index.js'
7
+ export * as eventListener from './EventListener/index.js'
8
8
  export * as iframe from './Iframe/index.js'
9
9
  export * as image from './Image/index.js'
10
10
  export * as input from './Input/index.js'
11
- export * as jsonEditor from './JsonEditor/index.js'
12
11
  export * as intersectionObserver from './IntersectionObserver/index.js'
12
+ export * as jsonEditor from './JsonEditor/index.js'
13
13
  export * as lightbox from './Lightbox/index.js'
14
14
  export * as listLoader from './ListLoader/index.js'
15
15
  export * as overlayer from './Overlayer/index.js'
@@ -17,8 +17,8 @@ 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
19
  export * as scrollListener from './ScrollListener/index.js'
20
- export * as sequencer from './Sequencer/index.js'
21
20
  export * as select from './Select/index.js'
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'
24
24
  export * as textarea from './Textarea/index.js'
@@ -24,4 +24,3 @@ export declare const subtitles = "lm-subtitles";
24
24
  export declare const textarea = "lm-textarea";
25
25
  export declare const uiModule = "lm-ui-module";
26
26
  export declare const video = "lm-video";
27
- export declare const videoWrapper = "lm-video-wrapper";
@@ -24,4 +24,3 @@ export const subtitles = 'lm-subtitles';
24
24
  export const textarea = 'lm-textarea';
25
25
  export const uiModule = 'lm-ui-module';
26
26
  export const video = 'lm-video';
27
- export const videoWrapper = 'lm-video-wrapper';
@@ -1,4 +1,4 @@
1
1
  export * as copyDir from './copy-dir/index.js'
2
2
  export * as list from './list/index.js'
3
- export * as removeDir from './remove-dir/index.js'
4
3
  export * as moveDir from './move-dir/index.js'
4
+ export * as removeDir from './remove-dir/index.js'
@@ -1,4 +1,4 @@
1
1
  export * as copyDir from './copy-dir/index.js'
2
2
  export * as list from './list/index.js'
3
- export * as removeDir from './remove-dir/index.js'
4
3
  export * as moveDir from './move-dir/index.js'
4
+ export * as removeDir from './remove-dir/index.js'
@@ -1,5 +1,5 @@
1
- export * as download from './download/index.js'
2
1
  export * as copy from './copy/index.js'
2
+ export * as download from './download/index.js'
3
3
  export * as exists from './exists/index.js'
4
4
  export * as move from './move/index.js'
5
5
  export * as remove from './remove/index.js'
@@ -1,5 +1,5 @@
1
- export * as download from './download/index.js'
2
1
  export * as copy from './copy/index.js'
2
+ export * as download from './download/index.js'
3
3
  export * as exists from './exists/index.js'
4
4
  export * as move from './move/index.js'
5
5
  export * as remove from './remove/index.js'
@@ -1,2 +1,2 @@
1
- export * as file from './file/index.js'
2
1
  export * as directory from './directory/index.js'
2
+ export * as file from './file/index.js'
@@ -1,2 +1,2 @@
1
- export * as file from './file/index.js'
2
1
  export * as directory from './directory/index.js'
2
+ export * as file from './file/index.js'
@@ -1,11 +1,11 @@
1
1
  export * as copy from './copy/index.js'
2
2
  export * as download from './download/index.js'
3
- export * as generateSignedUrl from './generate-signed-url/index.js'
4
3
  export * as exists from './exists/index.js'
5
- export * as getPermissions from './get-permissions/index.js'
4
+ export * as generateSignedUrl from './generate-signed-url/index.js'
6
5
  export * as getMetadata from './get-metadata/index.js'
7
- export * as remove from './remove/index.js'
6
+ export * as getPermissions from './get-permissions/index.js'
8
7
  export * as move from './move/index.js'
8
+ export * as remove from './remove/index.js'
9
9
  export * as revokeSignedUrls from './revoke-signed-urls/index.js'
10
10
  export * as stat from './stat/index.js'
11
11
  export * as updateMetadata from './update-metadata/index.js'
@@ -1,11 +1,11 @@
1
1
  export * as copy from './copy/index.js'
2
2
  export * as download from './download/index.js'
3
- export * as generateSignedUrl from './generate-signed-url/index.js'
4
3
  export * as exists from './exists/index.js'
5
- export * as getPermissions from './get-permissions/index.js'
4
+ export * as generateSignedUrl from './generate-signed-url/index.js'
6
5
  export * as getMetadata from './get-metadata/index.js'
7
- export * as remove from './remove/index.js'
6
+ export * as getPermissions from './get-permissions/index.js'
8
7
  export * as move from './move/index.js'
8
+ export * as remove from './remove/index.js'
9
9
  export * as revokeSignedUrls from './revoke-signed-urls/index.js'
10
10
  export * as stat from './stat/index.js'
11
11
  export * as updateMetadata from './update-metadata/index.js'
@@ -2,10 +2,10 @@ export * as copyDir from './copy-dir/index.js'
2
2
  export * as copyFile from './copy-file/index.js'
3
3
  export * as downloadFile from './download-file/index.js'
4
4
  export * as existsFile from './exists-file/index.js'
5
- export * as moveDir from './move-dir/index.js'
6
5
  export * as listDir from './list-dir/index.js'
6
+ export * as moveDir from './move-dir/index.js'
7
7
  export * as moveFile from './move-file/index.js'
8
8
  export * as removeDir from './remove-dir/index.js'
9
+ export * as removeFile from './remove-file/index.js'
9
10
  export * as statFile from './stat-file/index.js'
10
11
  export * as uploadFile from './upload-file/index.js'
11
- export * as removeFile from './remove-file/index.js'
@@ -2,10 +2,10 @@ export * as copyDir from './copy-dir/index.js'
2
2
  export * as copyFile from './copy-file/index.js'
3
3
  export * as downloadFile from './download-file/index.js'
4
4
  export * as existsFile from './exists-file/index.js'
5
- export * as moveDir from './move-dir/index.js'
6
5
  export * as listDir from './list-dir/index.js'
6
+ export * as moveDir from './move-dir/index.js'
7
7
  export * as moveFile from './move-file/index.js'
8
8
  export * as removeDir from './remove-dir/index.js'
9
+ export * as removeFile from './remove-file/index.js'
9
10
  export * as statFile from './stat-file/index.js'
10
11
  export * as uploadFile from './upload-file/index.js'
11
- export * as removeFile from './remove-file/index.js'
@@ -1,4 +1,4 @@
1
1
  export * as getSize from './get-size/index.js'
2
2
  export * as isInDirectory from './is-in-directory/index.js'
3
- export * as subpaths from './subpaths/index.js'
4
3
  export * as readWrite from './read-write/index.js'
4
+ export * as subpaths from './subpaths/index.js'
@@ -1,4 +1,4 @@
1
1
  export * as getSize from './get-size/index.js'
2
2
  export * as isInDirectory from './is-in-directory/index.js'
3
- export * as subpaths from './subpaths/index.js'
4
3
  export * as readWrite from './read-write/index.js'
4
+ export * as subpaths from './subpaths/index.js'
@@ -1,4 +1,4 @@
1
1
  export * as copyDir from './copy-dir/index.js'
2
2
  export * as list from './list/index.js'
3
- export * as removeDir from './remove-dir/index.js'
4
3
  export * as moveDir from './move-dir/index.js'
4
+ export * as removeDir from './remove-dir/index.js'
@@ -1,4 +1,4 @@
1
1
  export * as copyDir from './copy-dir/index.js'
2
2
  export * as list from './list/index.js'
3
- export * as removeDir from './remove-dir/index.js'
4
3
  export * as moveDir from './move-dir/index.js'
4
+ export * as removeDir from './remove-dir/index.js'
@@ -1,7 +1,7 @@
1
- export * as download from './download/index.js'
2
1
  export * as copy from './copy/index.js'
2
+ export * as download from './download/index.js'
3
3
  export * as exists from './exists/index.js'
4
+ export * as move from './move/index.js'
4
5
  export * as remove from './remove/index.js'
5
6
  export * as stat from './stat/index.js'
6
- export * as move from './move/index.js'
7
7
  export * as upload from './upload/index.js'
@@ -1,7 +1,7 @@
1
- export * as download from './download/index.js'
2
1
  export * as copy from './copy/index.js'
2
+ export * as download from './download/index.js'
3
3
  export * as exists from './exists/index.js'
4
+ export * as move from './move/index.js'
4
5
  export * as remove from './remove/index.js'
5
6
  export * as stat from './stat/index.js'
6
- export * as move from './move/index.js'
7
7
  export * as upload from './upload/index.js'
@@ -1,15 +1,15 @@
1
1
  export * as blur from './blur/index.js'
2
+ export * as brighten from './brighten/index.js'
2
3
  export * as extend from './extend/index.js'
3
4
  export * as extract from './extract/index.js'
4
- export * as brighten from './brighten/index.js'
5
5
  export * as flatten from './flatten/index.js'
6
6
  export * as flip from './flip/index.js'
7
7
  export * as flop from './flop/index.js'
8
+ export * as hue from './hue/index.js'
8
9
  export * as level from './level/index.js'
9
10
  export * as lighten from './lighten/index.js'
10
- export * as hue from './hue/index.js'
11
- export * as overlay from './overlay/index.js'
12
11
  export * as normalize from './normalize/index.js'
12
+ export * as overlay from './overlay/index.js'
13
13
  export * as resize from './resize/index.js'
14
- export * as saturate from './saturate/index.js'
15
14
  export * as rotate from './rotate/index.js'
15
+ export * as saturate from './saturate/index.js'
@@ -1,15 +1,15 @@
1
1
  export * as blur from './blur/index.js'
2
+ export * as brighten from './brighten/index.js'
2
3
  export * as extend from './extend/index.js'
3
4
  export * as extract from './extract/index.js'
4
- export * as brighten from './brighten/index.js'
5
5
  export * as flatten from './flatten/index.js'
6
6
  export * as flip from './flip/index.js'
7
7
  export * as flop from './flop/index.js'
8
+ export * as hue from './hue/index.js'
8
9
  export * as level from './level/index.js'
9
10
  export * as lighten from './lighten/index.js'
10
- export * as hue from './hue/index.js'
11
- export * as overlay from './overlay/index.js'
12
11
  export * as normalize from './normalize/index.js'
12
+ export * as overlay from './overlay/index.js'
13
13
  export * as resize from './resize/index.js'
14
- export * as saturate from './saturate/index.js'
15
14
  export * as rotate from './rotate/index.js'
15
+ export * as saturate from './saturate/index.js'
package/node/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export * as awsS3 from './@aws-s3/index.js'
2
2
  export * as designEdito from './@design-edito/index.js'
3
3
  export * as express from './@express/index.js'
4
- export * as cloudStorage from './cloud-storage/index.js'
5
4
  export * as googleCloud from './@google-cloud/index.js'
5
+ export * as cloudStorage from './cloud-storage/index.js'
6
6
  export * as encryption from './encryption/index.js'
7
7
  export * as files from './files/index.js'
8
8
  export * as ftps from './ftps/index.js'
package/node/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  export * as awsS3 from './@aws-s3/index.js'
2
2
  export * as designEdito from './@design-edito/index.js'
3
3
  export * as express from './@express/index.js'
4
- export * as cloudStorage from './cloud-storage/index.js'
5
4
  export * as googleCloud from './@google-cloud/index.js'
5
+ export * as cloudStorage from './cloud-storage/index.js'
6
6
  export * as encryption from './encryption/index.js'
7
7
  export * as files from './files/index.js'
8
8
  export * as ftps from './ftps/index.js'
@@ -1,4 +1,4 @@
1
+ export * as copyDir from './copy-dir/index.js'
1
2
  export * as list from './list/index.js'
2
3
  export * as moveDir from './move-dir/index.js'
3
4
  export * as removeDir from './remove-dir/index.js'
4
- export * as copyDir from './copy-dir/index.js'
@@ -1,4 +1,4 @@
1
+ export * as copyDir from './copy-dir/index.js'
1
2
  export * as list from './list/index.js'
2
3
  export * as moveDir from './move-dir/index.js'
3
4
  export * as removeDir from './remove-dir/index.js'
4
- export * as copyDir from './copy-dir/index.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@design-edito/tools",
3
- "version": "0.5.15",
3
+ "version": "0.5.17",
4
4
  "description": "",
5
5
  "author": "Maxime Fabas",
6
6
  "license": "ISC",