@design-edito/tools 0.5.6 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agnostic/colors/index.d.ts +2 -2
- package/agnostic/colors/index.js +2 -2
- package/agnostic/css/index.d.ts +1 -1
- package/agnostic/css/index.js +1 -1
- package/agnostic/errors/index.d.ts +1 -1
- package/agnostic/errors/index.js +1 -1
- package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +8 -8
- package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +8 -8
- package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +4 -4
- package/agnostic/html/hyper-json/smart-tags/isolated/index.js +4 -4
- package/agnostic/html/index.d.ts +1 -1
- package/agnostic/html/index.js +1 -1
- package/agnostic/index.d.ts +1 -0
- package/agnostic/index.js +1 -0
- package/agnostic/misc/index.d.ts +2 -2
- package/agnostic/misc/index.js +2 -2
- package/agnostic/numbers/index.d.ts +1 -1
- package/agnostic/numbers/index.js +1 -1
- package/agnostic/objects/index.d.ts +3 -3
- package/agnostic/objects/index.js +3 -3
- package/agnostic/random/index.d.ts +1 -1
- package/agnostic/random/index.js +1 -1
- package/agnostic/strings/index.d.ts +1 -1
- package/agnostic/strings/index.js +1 -1
- package/agnostic/subtitles/index.d.ts +2 -0
- package/agnostic/subtitles/index.js +2 -0
- package/agnostic/subtitles/parse-srt/index.d.ts +17 -0
- package/agnostic/subtitles/parse-srt/index.js +67 -0
- package/agnostic/subtitles/parse-srt/index.test.d.ts +1 -0
- package/agnostic/subtitles/parse-srt/index.test.js +94 -0
- package/agnostic/subtitles/parse-srt/types.d.ts +22 -0
- package/agnostic/subtitles/parse-srt/types.js +1 -0
- package/agnostic/subtitles/srt-timecode-to-ms/index.d.ts +14 -0
- package/agnostic/subtitles/srt-timecode-to-ms/index.js +36 -0
- package/agnostic/subtitles/srt-timecode-to-ms/index.test.d.ts +1 -0
- package/agnostic/subtitles/srt-timecode-to-ms/index.test.js +44 -0
- package/agnostic/time/dates/index.d.ts +1 -1
- package/agnostic/time/dates/index.js +1 -1
- package/components/ListLoader/index.controlled.d.ts +8 -3
- package/components/ListLoader/index.controlled.js +2 -2
- package/components/ListLoader/index.d.ts +1 -1
- package/components/ListLoader/index.js +2 -2
- package/components/Scrllgngn/index.d.ts +37 -7
- package/components/Scrllgngn/index.js +81 -24
- package/components/Scrllgngn/styles.module.css +30 -5
- package/components/Scrllgngn/utils.d.ts +87 -4
- package/components/Scrllgngn/utils.js +142 -8
- package/components/ScrollListener/index.d.ts +7 -3
- package/components/ScrollListener/index.js +2 -2
- package/components/Sequencer/index.d.ts +10 -1
- package/components/Sequencer/index.js +7 -3
- package/components/Subtitles/index.d.ts +53 -62
- package/components/Subtitles/index.js +90 -158
- package/components/Subtitles/utils.d.ts +60 -0
- package/components/Subtitles/utils.js +76 -0
- package/components/Video/index.controlled.d.ts +5 -0
- package/components/Video/index.controlled.js +22 -3
- package/components/Video/index.d.ts +47 -9
- package/components/Video/index.js +71 -20
- package/components/Video/utils.d.ts +14 -0
- package/components/Video/utils.js +18 -0
- package/components/utils/types.d.ts +32 -0
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/node/@aws-s3/storage/directory/index.d.ts +1 -1
- package/node/@aws-s3/storage/directory/index.js +1 -1
- package/node/@google-cloud/storage/file/index.d.ts +2 -2
- package/node/@google-cloud/storage/file/index.js +2 -2
- package/node/cloud-storage/operations/index.d.ts +1 -1
- package/node/cloud-storage/operations/index.js +1 -1
- package/node/files/index.d.ts +1 -1
- package/node/files/index.js +1 -1
- package/node/images/index.d.ts +1 -1
- package/node/images/index.js +1 -1
- package/node/index.d.ts +1 -1
- package/node/index.js +1 -1
- package/node/sftp/file/index.d.ts +1 -1
- package/node/sftp/file/index.js +1 -1
- package/package.json +26 -1
|
@@ -75,10 +75,16 @@ export function lazyLoadedBlocks(blocks, depth, currentPagePos, lazyLoadDistance
|
|
|
75
75
|
* never inherit the same names from an ancestor component. `initial` leaves them
|
|
76
76
|
* behaving exactly as an unset variable would, without opening the hole.
|
|
77
77
|
*
|
|
78
|
+
* The public set describes the component's own box and nothing else. The private
|
|
79
|
+
* one is narrowed to what the fixed layers may actually occupy — the component's
|
|
80
|
+
* box **intersected** with the visible zone — so a viewport offset pushes them off
|
|
81
|
+
* whatever covers that edge without the stylesheet doing arithmetic.
|
|
82
|
+
*
|
|
78
83
|
* @param rect - The measured bounding rect, absent until the first resize lands.
|
|
84
|
+
* @param zone - The measured visible zone, absent until the probe reports.
|
|
79
85
|
* @returns The custom properties, keyed by their full name.
|
|
80
86
|
*/
|
|
81
|
-
export function toScreenCssProps(rect) {
|
|
87
|
+
export function toScreenCssProps(rect, zone) {
|
|
82
88
|
const publicProps = rect === undefined
|
|
83
89
|
? {}
|
|
84
90
|
: {
|
|
@@ -91,14 +97,30 @@ export function toScreenCssProps(rect) {
|
|
|
91
97
|
'--lm-scrllgngn-screen-height': `${rect.height}px`,
|
|
92
98
|
'--lm-scrllgngn-screen-height-raw': `${rect.height}`
|
|
93
99
|
};
|
|
100
|
+
const inZone = toZoneClampedRect(rect, zone);
|
|
94
101
|
return {
|
|
95
102
|
...publicProps,
|
|
96
|
-
'--PRIVATE-left': toPrivateLength(
|
|
97
|
-
'--PRIVATE-right': toPrivateLength(
|
|
98
|
-
'--PRIVATE-width': toPrivateLength(
|
|
103
|
+
'--PRIVATE-left': toPrivateLength(inZone?.left),
|
|
104
|
+
'--PRIVATE-right': toPrivateLength(inZone?.right),
|
|
105
|
+
'--PRIVATE-width': toPrivateLength(inZone?.width),
|
|
99
106
|
'--PRIVATE-height': toPrivateLength(rect?.height)
|
|
100
107
|
};
|
|
101
108
|
}
|
|
109
|
+
/** The component's box, cut down to the horizontal span of the visible zone. */
|
|
110
|
+
function toZoneClampedRect(rect, zone) {
|
|
111
|
+
if (rect === undefined)
|
|
112
|
+
return undefined;
|
|
113
|
+
if (zone === undefined)
|
|
114
|
+
return rect;
|
|
115
|
+
const left = Math.max(rect.left, zone.left);
|
|
116
|
+
const right = Math.min(rect.right, window.innerWidth - zone.right);
|
|
117
|
+
return {
|
|
118
|
+
left,
|
|
119
|
+
right,
|
|
120
|
+
width: Math.max(right - left, 0),
|
|
121
|
+
height: rect.height
|
|
122
|
+
};
|
|
123
|
+
}
|
|
102
124
|
function toPrivateLength(value) {
|
|
103
125
|
return value === undefined ? 'initial' : `${value}px`;
|
|
104
126
|
}
|
|
@@ -155,12 +177,11 @@ export function contiguousRunContaining(zone, page) {
|
|
|
155
177
|
*
|
|
156
178
|
* @param pageElements - Every page slot, indexed by position.
|
|
157
179
|
* @param pagePositions - The positions worth measuring.
|
|
158
|
-
* @param
|
|
159
|
-
*
|
|
180
|
+
* @param thresholdY - The threshold line, in pixels from the viewport top. See
|
|
181
|
+
* {@link toThresholdY}.
|
|
160
182
|
* @returns The metrics of each measurable page, keyed by position.
|
|
161
183
|
*/
|
|
162
|
-
export function measurePages(pageElements, pagePositions,
|
|
163
|
-
const thresholdY = window.innerHeight * (thresholdOffsetPercent ?? 0) / 100;
|
|
184
|
+
export function measurePages(pageElements, pagePositions, thresholdY) {
|
|
164
185
|
const measured = new Map();
|
|
165
186
|
for (const pagePos of pagePositions) {
|
|
166
187
|
const element = pageElements[pagePos];
|
|
@@ -293,3 +314,116 @@ export function scrollKey(pagePos, blockPos) {
|
|
|
293
314
|
export function scrollBlocksOf(page) {
|
|
294
315
|
return page.blocks?.filter(block => block.depth === 'scroll' || block.depth === undefined) ?? [];
|
|
295
316
|
}
|
|
317
|
+
function toCssLength(offset) {
|
|
318
|
+
if (offset === undefined)
|
|
319
|
+
return '0px';
|
|
320
|
+
return typeof offset === 'number' ? `${offset}px` : offset;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Builds the custom properties carrying the visible zone's insets.
|
|
324
|
+
*
|
|
325
|
+
* They are emitted whatever the props hold, `0px` standing in for an edge left
|
|
326
|
+
* unset: the probe and the fixed layers position themselves against them, and an
|
|
327
|
+
* absent name would let an enclosing component's value inherit through.
|
|
328
|
+
*
|
|
329
|
+
* @param offsets - The edges as authored.
|
|
330
|
+
* @returns The custom properties, keyed by their full name.
|
|
331
|
+
*/
|
|
332
|
+
export function toViewportOffsetCssProps(offsets) {
|
|
333
|
+
const top = toCssLength(offsets.top);
|
|
334
|
+
const right = toCssLength(offsets.right);
|
|
335
|
+
const bottom = toCssLength(offsets.bottom);
|
|
336
|
+
const left = toCssLength(offsets.left);
|
|
337
|
+
return {
|
|
338
|
+
'--lm-scrllgngn-viewport-offset-top': top,
|
|
339
|
+
'--lm-scrllgngn-viewport-offset-right': right,
|
|
340
|
+
'--lm-scrllgngn-viewport-offset-bottom': bottom,
|
|
341
|
+
'--lm-scrllgngn-viewport-offset-left': left,
|
|
342
|
+
'--PRIVATE-viewport-offset-top': top,
|
|
343
|
+
'--PRIVATE-viewport-offset-right': right,
|
|
344
|
+
'--PRIVATE-viewport-offset-bottom': bottom,
|
|
345
|
+
'--PRIVATE-viewport-offset-left': left
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Reads the visible zone off the probe element.
|
|
350
|
+
*
|
|
351
|
+
* The probe is fixed and inset by the four offsets, so its rect *is* the zone —
|
|
352
|
+
* which is how a `var()`, a `clamp()` or a breakpoint-dependent length reaches
|
|
353
|
+
* JavaScript without anyone parsing a CSS length.
|
|
354
|
+
*
|
|
355
|
+
* @param probeRect - The probe's bounding rect.
|
|
356
|
+
* @returns The zone, as insets from each viewport edge plus its own size.
|
|
357
|
+
*/
|
|
358
|
+
export function toVisibleZoneRect(probeRect) {
|
|
359
|
+
return {
|
|
360
|
+
top: probeRect.top,
|
|
361
|
+
right: window.innerWidth - probeRect.right,
|
|
362
|
+
bottom: window.innerHeight - probeRect.bottom,
|
|
363
|
+
left: probeRect.left,
|
|
364
|
+
width: probeRect.width,
|
|
365
|
+
height: probeRect.height
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Shrinks an {@link IntersectionObserver} root down to the visible zone.
|
|
370
|
+
*
|
|
371
|
+
* @param zone - The measured zone, absent until the probe reports.
|
|
372
|
+
* @returns A `rootMargin`, or `undefined` to leave the observer on the full
|
|
373
|
+
* viewport.
|
|
374
|
+
*/
|
|
375
|
+
export function toVisibleZoneRootMargin(zone) {
|
|
376
|
+
if (zone === undefined)
|
|
377
|
+
return undefined;
|
|
378
|
+
return `${-zone.top}px ${-zone.right}px ${-zone.bottom}px ${-zone.left}px`;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Places the threshold line, in pixels from the viewport top.
|
|
382
|
+
*
|
|
383
|
+
* `thresholdOffsetPercent` reads as a percentage **of the visible zone**, not of
|
|
384
|
+
* the viewport: a threshold at 50% has to sit halfway down what the reader can
|
|
385
|
+
* actually see, or it lands under whatever covers the top of the screen.
|
|
386
|
+
*
|
|
387
|
+
* @param zone - The measured zone, absent until the probe reports.
|
|
388
|
+
* @param thresholdOffsetPercent - The offset as authored.
|
|
389
|
+
* @returns The line's distance from the viewport top.
|
|
390
|
+
*/
|
|
391
|
+
export function toThresholdY(zone, thresholdOffsetPercent) {
|
|
392
|
+
const percent = thresholdOffsetPercent ?? 0;
|
|
393
|
+
if (zone === undefined)
|
|
394
|
+
return window.innerHeight * percent / 100;
|
|
395
|
+
return zone.top + zone.height * percent / 100;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Restates the threshold as the percentage of the **viewport** an
|
|
399
|
+
* {@link IntersectionObserver} needs, since that is the only frame `rootMargin`
|
|
400
|
+
* knows.
|
|
401
|
+
*
|
|
402
|
+
* With no offsets set the zone spans the viewport and the number comes back
|
|
403
|
+
* unchanged, so nothing moves for a component that never asked for any of this.
|
|
404
|
+
*
|
|
405
|
+
* @param zone - The measured zone, absent until the probe reports.
|
|
406
|
+
* @param thresholdOffsetPercent - The offset as authored.
|
|
407
|
+
* @returns The equivalent viewport percentage, to hand to the internal `Paginator`.
|
|
408
|
+
*/
|
|
409
|
+
export function toPaginatorThresholdPercent(zone, thresholdOffsetPercent) {
|
|
410
|
+
if (zone === undefined)
|
|
411
|
+
return thresholdOffsetPercent;
|
|
412
|
+
const viewportHeight = window.innerHeight;
|
|
413
|
+
if (viewportHeight === 0)
|
|
414
|
+
return thresholdOffsetPercent;
|
|
415
|
+
return toThresholdY(zone, thresholdOffsetPercent) / viewportHeight * 100;
|
|
416
|
+
}
|
|
417
|
+
/** How often the visible zone is re-read while the component is on screen, in ms. */
|
|
418
|
+
export const visibleZonePollInterval = 100;
|
|
419
|
+
/** Whether two zones hold the same six measurements. */
|
|
420
|
+
export function visibleZonesAreEqual(a, b) {
|
|
421
|
+
if (a === undefined || b === undefined)
|
|
422
|
+
return a === b;
|
|
423
|
+
return a.top === b.top
|
|
424
|
+
&& a.right === b.right
|
|
425
|
+
&& a.bottom === b.bottom
|
|
426
|
+
&& a.left === b.left
|
|
427
|
+
&& a.width === b.width
|
|
428
|
+
&& a.height === b.height;
|
|
429
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type FunctionComponent, type PropsWithChildren } from 'react';
|
|
2
|
-
import type { WithClassName } from '../utils/types.js';
|
|
2
|
+
import type { WithClassName, WithViewportObservation } from '../utils/types.js';
|
|
3
3
|
import { type ScrollState } from './utils.js';
|
|
4
4
|
/**
|
|
5
5
|
* Props for the {@link ScrollListener} component.
|
|
@@ -17,17 +17,21 @@ import { type ScrollState } from './utils.js';
|
|
|
17
17
|
* it has fully left it. Never on mount.
|
|
18
18
|
* @property onScrollDirectionChanged - Called after the document scroll
|
|
19
19
|
* direction changed, with `'up'` or `'down'`. Never on mount.
|
|
20
|
+
* @property threshold - How much of the component has to be in view before it
|
|
21
|
+
* counts as visible, forwarded to the internal {@link IntersectionObserver}.
|
|
22
|
+
* @property root - The observer's root. Defaults to the viewport.
|
|
23
|
+
* @property rootMargin - Grows or shrinks that root before measuring.
|
|
20
24
|
* @property className - Optional additional class name(s) applied to the root element.
|
|
21
25
|
* @property children - React nodes rendered inside the scroll listener container.
|
|
22
26
|
*/
|
|
23
|
-
export type Props = PropsWithChildren<WithClassName<{
|
|
27
|
+
export type Props = PropsWithChildren<WithClassName<WithViewportObservation<{
|
|
24
28
|
startOnVisible?: boolean;
|
|
25
29
|
stopOnHidden?: boolean;
|
|
26
30
|
onScrollStateChanged?: (scrollState?: ScrollState) => void;
|
|
27
31
|
onVisibilityChanged?: (isVisible: boolean) => void;
|
|
28
32
|
onScrollProgressChanged?: (progress: number) => void;
|
|
29
33
|
onScrollDirectionChanged?: (direction: 'up' | 'down') => void;
|
|
30
|
-
}
|
|
34
|
+
}>>>;
|
|
31
35
|
/**
|
|
32
36
|
* Exposes scroll metrics — both the document's and its own — as CSS custom
|
|
33
37
|
* properties on its root element, so scroll-driven styling needs no JavaScript
|
|
@@ -45,7 +45,7 @@ import cssModule from './styles.module.css';
|
|
|
45
45
|
* for everyone, each element only for itself. The listeners exist only while at
|
|
46
46
|
* least one instance is tracking.
|
|
47
47
|
*/
|
|
48
|
-
export const ScrollListener = ({ startOnVisible, stopOnHidden, onScrollStateChanged, onVisibilityChanged, onScrollProgressChanged, onScrollDirectionChanged, className, children }) => {
|
|
48
|
+
export const ScrollListener = ({ startOnVisible, stopOnHidden, threshold, root, rootMargin, onScrollStateChanged, onVisibilityChanged, onScrollProgressChanged, onScrollDirectionChanged, className, children }) => {
|
|
49
49
|
// State & refs
|
|
50
50
|
const [subscriberId] = useState(() => randomHash(6));
|
|
51
51
|
const [scrollState, setScrollState] = useState();
|
|
@@ -96,5 +96,5 @@ export const ScrollListener = ({ startOnVisible, stopOnHidden, onScrollStateChan
|
|
|
96
96
|
const customProps = scrollState === undefined
|
|
97
97
|
? {}
|
|
98
98
|
: toScrollCssProps(scrollState);
|
|
99
|
-
return _jsx("div", { className: rootClss, ref: rootRef, style: { ...customProps }, children: _jsx(IntersectionObserverComponent, { onIntersected: handleIntersection, children: children }) });
|
|
99
|
+
return _jsx("div", { className: rootClss, ref: rootRef, style: { ...customProps }, children: _jsx(IntersectionObserverComponent, { threshold: threshold, root: root, rootMargin: rootMargin, onIntersected: handleIntersection, children: children }) });
|
|
100
100
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'react';
|
|
2
2
|
import { type Props as IOCompProps } from '../IntersectionObserver/index.js';
|
|
3
|
+
import type { ViewportObserverOptions } from '../utils/types.js';
|
|
3
4
|
import { type Props as ControlledProps } from './index.controlled.js';
|
|
4
5
|
/**
|
|
5
6
|
* Props for the {@link Sequencer} component.
|
|
@@ -29,6 +30,13 @@ import { type Props as ControlledProps } from './index.controlled.js';
|
|
|
29
30
|
* component enters the viewport. No-op when `play` is controlled.
|
|
30
31
|
* @property pauseOnHidden - When `true`, pauses internal playback when the
|
|
31
32
|
* component leaves the viewport. No-op when `play` is controlled.
|
|
33
|
+
* @property threshold - How much of the component has to be in view before it
|
|
34
|
+
* counts as visible, forwarded to the internal {@link IntersectionObserver}.
|
|
35
|
+
* @property root - The observer's root. Defaults to the viewport.
|
|
36
|
+
* @property rootMargin - Grows or shrinks that root before measuring.
|
|
37
|
+
* @property onVisibilityChanged - Called on every crossing with the new value.
|
|
38
|
+
* `onIntersected` is the raw form of the same event, carrying the entry and the
|
|
39
|
+
* observer; this one carries the answer.
|
|
32
40
|
* @property onIntersected - Forwarded verbatim to the internal
|
|
33
41
|
* {@link IntersectionObserverComponent}, and called on every intersection
|
|
34
42
|
* change whichever mode the sequencer runs in.
|
|
@@ -42,7 +50,7 @@ import { type Props as ControlledProps } from './index.controlled.js';
|
|
|
42
50
|
* @property onReachedLastStep - Called when the forwarded step becomes the last
|
|
43
51
|
* one.
|
|
44
52
|
*/
|
|
45
|
-
export type Props = Omit<ControlledProps, 'isPlaying' | 'tempo'> & {
|
|
53
|
+
export type Props = ViewportObserverOptions & Omit<ControlledProps, 'isPlaying' | 'tempo'> & {
|
|
46
54
|
defaultStep?: number;
|
|
47
55
|
tempo?: number;
|
|
48
56
|
play?: boolean;
|
|
@@ -54,6 +62,7 @@ export type Props = Omit<ControlledProps, 'isPlaying' | 'tempo'> & {
|
|
|
54
62
|
playOnVisible?: boolean;
|
|
55
63
|
pauseOnHidden?: boolean;
|
|
56
64
|
onIntersected?: IOCompProps['onIntersected'];
|
|
65
|
+
onVisibilityChanged?: (isVisible: boolean) => void;
|
|
57
66
|
onIsPlayingChanged?: (isPlaying: boolean) => void;
|
|
58
67
|
onStepChanged?: (step: number) => void;
|
|
59
68
|
onLooped?: () => void;
|
|
@@ -30,7 +30,7 @@ import { ControlledSequencer } from './index.controlled.js';
|
|
|
30
30
|
* @returns An {@link IntersectionObserverComponent} wrapping a
|
|
31
31
|
* {@link ControlledSequencer} with the computed step and modifiers applied.
|
|
32
32
|
*/
|
|
33
|
-
export const Sequencer = ({ defaultStep, tempo = 60, play, loop, clampFirst, clampLast, resetOnVisible, resetOnHidden, playOnVisible, pauseOnHidden, onIntersected, onIsPlayingChanged, onStepChanged, onLooped, onReachedFirstStep, onReachedLastStep, ...controlledProps }) => {
|
|
33
|
+
export const Sequencer = ({ defaultStep, tempo = 60, play, loop, clampFirst, clampLast, resetOnVisible, resetOnHidden, playOnVisible, pauseOnHidden, onIntersected, onVisibilityChanged, threshold, root, rootMargin, onIsPlayingChanged, onStepChanged, onLooped, onReachedFirstStep, onReachedLastStep, ...controlledProps }) => {
|
|
34
34
|
// State
|
|
35
35
|
const { step, activateOnStep, children } = controlledProps;
|
|
36
36
|
const [internalPlay, setInternalPlay] = useState(play ?? false);
|
|
@@ -83,9 +83,13 @@ export const Sequencer = ({ defaultStep, tempo = 60, play, loop, clampFirst, cla
|
|
|
83
83
|
// Action handlers
|
|
84
84
|
const handleIntersection = useCallback(({ ioEntry, observer }) => {
|
|
85
85
|
onIntersected?.({ ioEntry, observer });
|
|
86
|
+
const { isIntersecting } = ioEntry ?? {};
|
|
87
|
+
if (isIntersecting !== undefined)
|
|
88
|
+
onVisibilityChanged?.(isIntersecting);
|
|
89
|
+
// Both handlers fire whatever the mode: a controlled sequencer still wants to
|
|
90
|
+
// hear about the viewport, it just doesn't let it drive the step.
|
|
86
91
|
if (play === true || step !== undefined)
|
|
87
92
|
return;
|
|
88
|
-
const { isIntersecting } = ioEntry ?? {};
|
|
89
93
|
if (isIntersecting === true) {
|
|
90
94
|
if (resetOnVisible === true)
|
|
91
95
|
setInternalStep(0);
|
|
@@ -108,5 +112,5 @@ export const Sequencer = ({ defaultStep, tempo = 60, play, loop, clampFirst, cla
|
|
|
108
112
|
onIntersected
|
|
109
113
|
]);
|
|
110
114
|
// Rendering
|
|
111
|
-
return _jsx(IntersectionObserverComponent, { onIntersected: handleIntersection, children: _jsx(ControlledSequencer, { ...controlledProps, step: forwardedStep, isPlaying: actualPlay, tempo: tempo }) });
|
|
115
|
+
return _jsx(IntersectionObserverComponent, { threshold: threshold, root: root, rootMargin: rootMargin, onIntersected: handleIntersection, children: _jsx(ControlledSequencer, { ...controlledProps, step: forwardedStep, isPlaying: actualPlay, tempo: tempo }) });
|
|
112
116
|
};
|
|
@@ -1,82 +1,73 @@
|
|
|
1
|
-
import { type FunctionComponent
|
|
1
|
+
import { type FunctionComponent } from 'react';
|
|
2
|
+
import type { SrtCue } from '../../agnostic/subtitles/parse-srt/types.js';
|
|
2
3
|
import type { WithClassName } from '../utils/types.js';
|
|
3
|
-
/**
|
|
4
|
-
* A single parsed subtitle entry from an SRT file.
|
|
5
|
-
*
|
|
6
|
-
* @property id - Sequential subtitle number.
|
|
7
|
-
* @property start - Start time in milliseconds.
|
|
8
|
-
* @property end - End time in milliseconds.
|
|
9
|
-
* @property content - Subtitle text content.
|
|
10
|
-
*/
|
|
11
|
-
export type ParsedSub = {
|
|
12
|
-
id: number;
|
|
13
|
-
start?: number;
|
|
14
|
-
end?: number;
|
|
15
|
-
content?: string;
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* The inclusive ID boundaries of a subtitle group.
|
|
19
|
-
*
|
|
20
|
-
* @property startId - ID of the first subtitle in the group.
|
|
21
|
-
* @property endId - ID of the last subtitle in the group.
|
|
22
|
-
*/
|
|
23
|
-
export type SubGroupBoundaries = {
|
|
24
|
-
startId: number;
|
|
25
|
-
endId: number;
|
|
26
|
-
};
|
|
27
4
|
/**
|
|
28
5
|
* Props for the {@link Subtitles} component.
|
|
29
6
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* @property
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* @property
|
|
43
|
-
*
|
|
44
|
-
* @property
|
|
45
|
-
*
|
|
46
|
-
* @property
|
|
7
|
+
* The three sources are tried in order — `cues`, then `srtFileContent`, then `src` —
|
|
8
|
+
* and the first one present wins. With none of them, nothing is rendered.
|
|
9
|
+
*
|
|
10
|
+
* @property cues - Already parsed cues, used as they are. Spares a consumer that
|
|
11
|
+
* holds them — from a previous `onParsed`, or from its own call to `parseSrt` —
|
|
12
|
+
* having to serialise them back to SRT.
|
|
13
|
+
* @property srtFileContent - A raw SRT string, parsed directly with no network call.
|
|
14
|
+
* @property src - URL of an SRT file to fetch and parse.
|
|
15
|
+
* @property subsGroups - Positions to split the cues on, each the **position** of a
|
|
16
|
+
* group's last cue — not the id written in the file. Order doesn't matter, and
|
|
17
|
+
* duplicate or out-of-range positions are ignored. Omitted, every cue belongs to one
|
|
18
|
+
* group.
|
|
19
|
+
* @property timecodeMs - Current media position in milliseconds. Drives the `--prev`
|
|
20
|
+
* and `--curr` modifiers. While it is `undefined`, no group is rendered.
|
|
21
|
+
* @property isEnded - When `true`, the last group is the current one whatever
|
|
22
|
+
* `timecodeMs` says, so the closing subtitles stay up once playback is over.
|
|
23
|
+
* @property onLoaded - Called with the raw SRT string once fetched. Only fires for
|
|
24
|
+
* `src`, since the other two sources fetch nothing.
|
|
25
|
+
* @property onParsed - Called with the cues after they changed. Never on mount.
|
|
26
|
+
* @property onLoadFailed - Called with an `Error` when the fetch fails, the response
|
|
27
|
+
* is not ok, or the parse throws.
|
|
47
28
|
* @property className - Optional additional class name(s) applied to the root element.
|
|
48
|
-
* @property children - React children rendered inside the root element, after the subtitle groups.
|
|
49
29
|
*/
|
|
50
|
-
export type Props =
|
|
51
|
-
|
|
30
|
+
export type Props = WithClassName<{
|
|
31
|
+
cues?: SrtCue[];
|
|
52
32
|
srtFileContent?: string;
|
|
33
|
+
src?: string;
|
|
53
34
|
subsGroups?: number[];
|
|
54
35
|
timecodeMs?: number;
|
|
55
36
|
isEnded?: boolean;
|
|
56
37
|
onLoaded?: (subs: string) => void;
|
|
57
|
-
onParsed?: (subs:
|
|
38
|
+
onParsed?: (subs: SrtCue[]) => void;
|
|
58
39
|
onLoadFailed?: (error: Error) => void;
|
|
59
|
-
}
|
|
40
|
+
}>;
|
|
60
41
|
/**
|
|
61
|
-
* Subtitle synchronization component.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
42
|
+
* Subtitle synchronization component. Takes an SRT source — parsed cues, a raw
|
|
43
|
+
* string, or a URL to fetch — and renders its cues in groups, marking where the
|
|
44
|
+
* media currently is.
|
|
64
45
|
*
|
|
65
|
-
* ###
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* - `
|
|
69
|
-
* - `
|
|
46
|
+
* ### CSS elements
|
|
47
|
+
* - `lm-subtitles` — the root. Carries `--loading` while a fetch is in flight, and
|
|
48
|
+
* `--error` once one failed. Both are cleared when a new source starts loading.
|
|
49
|
+
* - `lm-subtitles__group` — one per group. Carries `--curr` when the reader is in it.
|
|
50
|
+
* - `lm-subtitles__sub` — one per cue. Carries `--prev` once the playhead has passed
|
|
51
|
+
* its start, and `--curr` while the playhead is inside its interval.
|
|
70
52
|
*
|
|
71
|
-
* ###
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* - `data-sub-pos` — the subtitle's numeric ID from the SRT source.
|
|
53
|
+
* ### Data attributes
|
|
54
|
+
* On a group: `data-start-sub-pos` and `data-end-sub-pos`, its bounds as positions
|
|
55
|
+
* in the cue array. On a cue: `data-sub-pos`, its position, and `data-sub-id`, the
|
|
56
|
+
* number written in the file.
|
|
76
57
|
*
|
|
77
58
|
* @param props - Component properties.
|
|
78
59
|
* @see {@link Props}
|
|
79
|
-
* @returns A root `<div>`
|
|
80
|
-
*
|
|
60
|
+
* @returns A root `<div>` holding the subtitle groups, empty while there is no
|
|
61
|
+
* timecode or no cue.
|
|
62
|
+
*
|
|
63
|
+
* @remarks
|
|
64
|
+
* Uncontrolled only. The cues are state, fed by whichever source is set: there is no
|
|
65
|
+
* controlled variant, because a consumer holding its own cues passes them as `cues`
|
|
66
|
+
* and owns them outright.
|
|
67
|
+
*
|
|
68
|
+
* Cues are rendered back to back with no separator — the space between two of them
|
|
69
|
+
* is presentation, and belongs in a stylesheet (`lm-subtitles__sub + …::before`, a
|
|
70
|
+
* `word-spacing`, whatever suits) rather than in the text content, where it would
|
|
71
|
+
* come back out of `textContent`.
|
|
81
72
|
*/
|
|
82
73
|
export declare const Subtitles: FunctionComponent<Props>;
|