@design-edito/tools 0.5.7 → 0.5.9
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/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 +18 -18
- package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +18 -18
- 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 +2 -2
- package/agnostic/index.js +2 -2
- package/agnostic/misc/index.d.ts +1 -1
- package/agnostic/misc/index.js +1 -1
- package/agnostic/misc/logs/index.d.ts +1 -1
- package/agnostic/misc/logs/index.js +1 -1
- package/agnostic/objects/index.d.ts +2 -2
- package/agnostic/objects/index.js +2 -2
- 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/time/dates/index.d.ts +1 -1
- package/agnostic/time/dates/index.js +1 -1
- package/agnostic/time/index.d.ts +1 -1
- package/agnostic/time/index.js +1 -1
- package/components/BeforeAfter/index.controlled.d.ts +5 -4
- package/components/BeforeAfter/index.controlled.js +6 -6
- package/components/Drawer/index.d.ts +3 -1
- package/components/Drawer/index.js +5 -2
- package/components/Iframe/index.d.ts +4 -0
- package/components/Iframe/index.js +8 -1
- package/components/Input/index.d.ts +4 -0
- package/components/Input/index.js +9 -1
- package/components/ListLoader/index.controlled.d.ts +3 -0
- package/components/ListLoader/index.controlled.js +4 -1
- package/components/Paginator/index.d.ts +7 -2
- package/components/Paginator/index.js +43 -6
- package/components/ResizeObserver/index.d.ts +6 -1
- package/components/ResizeObserver/index.js +7 -2
- package/components/ScrollListener/index.d.ts +5 -0
- package/components/ScrollListener/index.js +10 -1
- package/components/Select/index.d.ts +4 -0
- package/components/Select/index.js +9 -1
- package/components/ShadowRoot/index.d.ts +4 -0
- package/components/ShadowRoot/index.js +5 -1
- package/components/Textarea/index.d.ts +4 -0
- package/components/Textarea/index.js +9 -1
- package/components/Video/index.controlled.d.ts +15 -4
- package/components/Video/index.controlled.js +37 -9
- package/components/Video/index.d.ts +5 -3
- package/components/Video/index.js +14 -4
- package/components/Video/utils.d.ts +11 -0
- package/components/Video/utils.js +14 -5
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/node/@google-cloud/storage/file/index.d.ts +1 -1
- package/node/@google-cloud/storage/file/index.js +1 -1
- package/node/cloud-storage/operations/index.d.ts +2 -2
- package/node/cloud-storage/operations/index.js +2 -2
- package/node/files/index.d.ts +1 -1
- package/node/files/index.js +1 -1
- package/node/images/transform/operations/index.d.ts +2 -2
- package/node/images/transform/operations/index.js +2 -2
- package/node/index.d.ts +2 -2
- package/node/index.js +2 -2
- package/package.json +1 -1
|
@@ -4,6 +4,18 @@ import { clss } from '../../agnostic/css/clss/index.js';
|
|
|
4
4
|
import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
|
|
5
5
|
import { paginator as publicClassName } from '../public-classnames.js';
|
|
6
6
|
import cssModule from './styles.module.css';
|
|
7
|
+
/**
|
|
8
|
+
* Brings `thresholdOffsetPercent` back into the 0–100 range `rootMargin` accepts.
|
|
9
|
+
*
|
|
10
|
+
* Outside it the margin comes out as `--20%`, which the `IntersectionObserver`
|
|
11
|
+
* constructor rejects by throwing — taking the whole component down with it over a
|
|
12
|
+
* single setting. A non-finite value falls back to `0` for the same reason.
|
|
13
|
+
*/
|
|
14
|
+
function toThresholdPercent(percent) {
|
|
15
|
+
if (percent === undefined || !Number.isFinite(percent))
|
|
16
|
+
return 0;
|
|
17
|
+
return Math.min(100, Math.max(0, percent));
|
|
18
|
+
}
|
|
7
19
|
/**
|
|
8
20
|
* A scroll-driven pagination component that tracks which child page is currently
|
|
9
21
|
* visible in the viewport and the direction of scroll.
|
|
@@ -23,7 +35,10 @@ import cssModule from './styles.module.css';
|
|
|
23
35
|
* when the direction actually changes, using an internal ref to avoid stale
|
|
24
36
|
* closure comparisons.
|
|
25
37
|
* - Page visibility is tracked via a single {@link IntersectionObserver} instance
|
|
26
|
-
* that is recreated when `thresholdOffsetPercent` or
|
|
38
|
+
* that is recreated when `thresholdOffsetPercent` or the **number** of children
|
|
39
|
+
* changes. Not on `children` itself: a parent building its child list inline hands
|
|
40
|
+
* over a new array on every render, which would tear down and rebuild one observer
|
|
41
|
+
* per page each time, for nothing.
|
|
27
42
|
* - `currCount` on each {@link PageState} increments each time a page transitions
|
|
28
43
|
* into the `'curr'` position, making it useful as a re-entry counter.
|
|
29
44
|
*/
|
|
@@ -33,6 +48,20 @@ export const Paginator = ({ thresholdOffsetPercent, onDirectionChanged, onPagesC
|
|
|
33
48
|
const [directionState, setDirectionState] = useState(null);
|
|
34
49
|
const pagesRef = useRef(null);
|
|
35
50
|
const directionRef = useRef(null);
|
|
51
|
+
const childrenArr = Children.toArray(children);
|
|
52
|
+
const pagesCount = childrenArr.length;
|
|
53
|
+
const thresholdPercent = toThresholdPercent(thresholdOffsetPercent);
|
|
54
|
+
// Fx. dep. thresholdOffsetPercent, thresholdPercent - Says it once per offending
|
|
55
|
+
// value rather than on every render. Clamping without a word would turn a caller's
|
|
56
|
+
// bug into a scrolling glitch, hunted for somewhere else entirely.
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (thresholdOffsetPercent === undefined)
|
|
59
|
+
return;
|
|
60
|
+
if (thresholdPercent === thresholdOffsetPercent)
|
|
61
|
+
return;
|
|
62
|
+
// eslint-disable-next-line no-console
|
|
63
|
+
console.warn('Paginator: thresholdOffsetPercent must be a number between 0 and 100, received', thresholdOffsetPercent, `— clamped to ${thresholdPercent}.`);
|
|
64
|
+
}, [thresholdOffsetPercent, thresholdPercent]);
|
|
36
65
|
// State dispatch
|
|
37
66
|
useChangeDispatch(pagesState, pages => onPagesChanged?.(Array
|
|
38
67
|
.from(pages)
|
|
@@ -62,15 +91,24 @@ export const Paginator = ({ thresholdOffsetPercent, onDirectionChanged, onPagesC
|
|
|
62
91
|
window.removeEventListener('resize', handleScroll);
|
|
63
92
|
};
|
|
64
93
|
}, []);
|
|
65
|
-
//
|
|
94
|
+
// Fx. dep. pagesCount - The page slots are this component's own divs, reused as
|
|
95
|
+
// long as their number holds, so the set only changes when the count does.
|
|
66
96
|
useEffect(() => {
|
|
67
97
|
if (pagesRef.current === null)
|
|
68
98
|
return;
|
|
69
99
|
const pages = Array.from(pagesRef.current.children);
|
|
70
100
|
onPageElementsChanged?.(pages.filter(page => page instanceof HTMLElement));
|
|
71
|
-
|
|
101
|
+
}, [pagesCount]);
|
|
102
|
+
// Detect active pages with Intersection Observer
|
|
103
|
+
// Fx. dep. thresholdPercent, pagesCount - Same reasoning: rebuilding one observer
|
|
104
|
+
// per page on every render would cost a full measurement pass for an unchanged set.
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (pagesRef.current === null)
|
|
107
|
+
return;
|
|
108
|
+
const pages = Array.from(pagesRef.current.children);
|
|
109
|
+
const observerRootMargin = `-${thresholdPercent}%`
|
|
72
110
|
+ ' 0px'
|
|
73
|
-
+ ` -${100 -
|
|
111
|
+
+ ` -${100 - thresholdPercent}%`
|
|
74
112
|
+ ' 0px';
|
|
75
113
|
const observer = new IntersectionObserver(entries => {
|
|
76
114
|
setPagesState(prevState => {
|
|
@@ -100,7 +138,7 @@ export const Paginator = ({ thresholdOffsetPercent, onDirectionChanged, onPagesC
|
|
|
100
138
|
observer.observe(page);
|
|
101
139
|
});
|
|
102
140
|
return () => observer.disconnect();
|
|
103
|
-
}, [
|
|
141
|
+
}, [thresholdPercent, pagesCount]);
|
|
104
142
|
// Rendering
|
|
105
143
|
const c = clss(publicClassName, { cssModule });
|
|
106
144
|
const rootClss = mergeClassNames(c(null, {
|
|
@@ -108,7 +146,6 @@ export const Paginator = ({ thresholdOffsetPercent, onDirectionChanged, onPagesC
|
|
|
108
146
|
backwards: directionState === 'backwards'
|
|
109
147
|
}), className);
|
|
110
148
|
const pagesClss = c('pages');
|
|
111
|
-
const childrenArr = Children.toArray(children);
|
|
112
149
|
return _jsx("div", { className: rootClss, children: _jsx("div", { className: pagesClss, ref: pagesRef, children: childrenArr.map((child, pos) => {
|
|
113
150
|
const state = pagesState.get(pos);
|
|
114
151
|
const pageClss = c('page', {
|
|
@@ -20,9 +20,14 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
20
20
|
* rect on its root element. Both sets stay absent until the first measurement
|
|
21
21
|
* lands.
|
|
22
22
|
*
|
|
23
|
+
* ### Root element modifiers
|
|
24
|
+
* - `--measured` — a first measurement landed, so the sets below are populated.
|
|
25
|
+
*
|
|
23
26
|
* ### Data attributes
|
|
24
27
|
* `data-x`, `data-y`, `data-top`, `data-left`, `data-bottom`, `data-right`,
|
|
25
|
-
* `data-width` and `data-height` — the content rect, as plain numbers.
|
|
28
|
+
* `data-width` and `data-height` — the content rect, as plain numbers. They carry
|
|
29
|
+
* what a custom property cannot reach: `content: attr(data-width)`, and attribute
|
|
30
|
+
* selectors.
|
|
26
31
|
*
|
|
27
32
|
* ### CSS custom properties
|
|
28
33
|
* The same eight measurements, each exposed twice: `--lm-resize-observer-width`
|
|
@@ -9,9 +9,14 @@ import cssModule from './styles.module.css';
|
|
|
9
9
|
* rect on its root element. Both sets stay absent until the first measurement
|
|
10
10
|
* lands.
|
|
11
11
|
*
|
|
12
|
+
* ### Root element modifiers
|
|
13
|
+
* - `--measured` — a first measurement landed, so the sets below are populated.
|
|
14
|
+
*
|
|
12
15
|
* ### Data attributes
|
|
13
16
|
* `data-x`, `data-y`, `data-top`, `data-left`, `data-bottom`, `data-right`,
|
|
14
|
-
* `data-width` and `data-height` — the content rect, as plain numbers.
|
|
17
|
+
* `data-width` and `data-height` — the content rect, as plain numbers. They carry
|
|
18
|
+
* what a custom property cannot reach: `content: attr(data-width)`, and attribute
|
|
19
|
+
* selectors.
|
|
15
20
|
*
|
|
16
21
|
* ### CSS custom properties
|
|
17
22
|
* The same eight measurements, each exposed twice: `--lm-resize-observer-width`
|
|
@@ -89,6 +94,6 @@ export const ResizeObserverComponent = ({ className, onResized, children }) => {
|
|
|
89
94
|
};
|
|
90
95
|
}
|
|
91
96
|
const c = clss(publicClassName, { cssModule });
|
|
92
|
-
const rootClss = mergeClassNames(c(), className);
|
|
97
|
+
const rootClss = mergeClassNames(c(null, { measured: contentRect !== undefined }), className);
|
|
93
98
|
return _jsx("div", { ...dataAttributes, className: rootClss, ref: rootRef, style: { ...cssCustomProps }, children: children });
|
|
94
99
|
};
|
|
@@ -37,6 +37,11 @@ export type Props = PropsWithChildren<WithClassName<WithViewportObservation<{
|
|
|
37
37
|
* properties on its root element, so scroll-driven styling needs no JavaScript
|
|
38
38
|
* of its own.
|
|
39
39
|
*
|
|
40
|
+
* ### Root element modifiers
|
|
41
|
+
* - `--measured` — a first measurement landed, so the properties below are set.
|
|
42
|
+
* - `--scrolling-up` / `--scrolling-down` — the document's last known direction.
|
|
43
|
+
* Neither is present before the first scroll.
|
|
44
|
+
*
|
|
40
45
|
* ### CSS custom properties on the root element
|
|
41
46
|
* Each measurement comes as a `px` length under its bare name, and as a `-raw`
|
|
42
47
|
* twin holding the plain number:
|
|
@@ -12,6 +12,11 @@ import cssModule from './styles.module.css';
|
|
|
12
12
|
* properties on its root element, so scroll-driven styling needs no JavaScript
|
|
13
13
|
* of its own.
|
|
14
14
|
*
|
|
15
|
+
* ### Root element modifiers
|
|
16
|
+
* - `--measured` — a first measurement landed, so the properties below are set.
|
|
17
|
+
* - `--scrolling-up` / `--scrolling-down` — the document's last known direction.
|
|
18
|
+
* Neither is present before the first scroll.
|
|
19
|
+
*
|
|
15
20
|
* ### CSS custom properties on the root element
|
|
16
21
|
* Each measurement comes as a `px` length under its bare name, and as a `-raw`
|
|
17
22
|
* twin holding the plain number:
|
|
@@ -92,7 +97,11 @@ export const ScrollListener = ({ startOnVisible, stopOnHidden, threshold, root,
|
|
|
92
97
|
}, [startOnVisible, stopOnHidden, onVisibilityChanged]);
|
|
93
98
|
// Rendering
|
|
94
99
|
const c = clss(publicClassName, { cssModule });
|
|
95
|
-
const rootClss = mergeClassNames(c(
|
|
100
|
+
const rootClss = mergeClassNames(c(null, {
|
|
101
|
+
measured: scrollState !== undefined,
|
|
102
|
+
'scrolling-up': scrollDirection === 'up',
|
|
103
|
+
'scrolling-down': scrollDirection === 'down'
|
|
104
|
+
}), className);
|
|
96
105
|
const customProps = scrollState === undefined
|
|
97
106
|
? {}
|
|
98
107
|
: toScrollCssProps(scrollState);
|
|
@@ -24,6 +24,10 @@ export type Props = SelectHTMLAttributes<HTMLSelectElement> & PropsWithChildren<
|
|
|
24
24
|
* A stable auto-generated `id` is created on mount and used to associate the
|
|
25
25
|
* rendered label through the `htmlFor` attribute.
|
|
26
26
|
*
|
|
27
|
+
* ### Root element modifiers
|
|
28
|
+
* - `--controlled` — the value comes from the parent, not from inside.
|
|
29
|
+
* - `--empty` / `--filled` — whether the current value is the empty string.
|
|
30
|
+
*
|
|
27
31
|
* ### CSS elements
|
|
28
32
|
* - `label`
|
|
29
33
|
* - `error`
|
|
@@ -15,6 +15,10 @@ import cssModule from './styles.module.css';
|
|
|
15
15
|
* A stable auto-generated `id` is created on mount and used to associate the
|
|
16
16
|
* rendered label through the `htmlFor` attribute.
|
|
17
17
|
*
|
|
18
|
+
* ### Root element modifiers
|
|
19
|
+
* - `--controlled` — the value comes from the parent, not from inside.
|
|
20
|
+
* - `--empty` / `--filled` — whether the current value is the empty string.
|
|
21
|
+
*
|
|
18
22
|
* ### CSS elements
|
|
19
23
|
* - `label`
|
|
20
24
|
* - `error`
|
|
@@ -44,6 +48,10 @@ export const Select = ({ label, error, value, defaultValue, onChange, className,
|
|
|
44
48
|
};
|
|
45
49
|
// Rendering
|
|
46
50
|
const c = clss(publicClassName, { cssModule });
|
|
47
|
-
const rootClss = mergeClassNames(c(
|
|
51
|
+
const rootClss = mergeClassNames(c(null, {
|
|
52
|
+
controlled: isControlled,
|
|
53
|
+
empty: currentValue === '',
|
|
54
|
+
filled: currentValue !== ''
|
|
55
|
+
}), className);
|
|
48
56
|
return _jsxs(_Fragment, { children: [isNotFalsy(label) && _jsx("label", { className: c('label'), htmlFor: id, children: label }), _jsx("select", { ...rest, id: id, className: rootClss, value: currentValue, onChange: handleChange, children: children }), isNotFalsy(error) && _jsx("span", { className: c('error'), children: error })] });
|
|
49
57
|
};
|
|
@@ -28,6 +28,10 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
28
28
|
* Component that creates a Shadow Root on its host element and renders
|
|
29
29
|
* its children inside that Shadow Root using a React portal.
|
|
30
30
|
*
|
|
31
|
+
* ### Root element modifiers
|
|
32
|
+
* - `--attached` — the shadow root exists and the children are rendered into it.
|
|
33
|
+
* Absent on the first render, before the host element is in the document.
|
|
34
|
+
*
|
|
31
35
|
* @param props - Component properties
|
|
32
36
|
* @see {@link Props}
|
|
33
37
|
* @returns A host `div` element that owns the created Shadow Root.
|
|
@@ -9,6 +9,10 @@ import cssModule from './styles.module.css';
|
|
|
9
9
|
* Component that creates a Shadow Root on its host element and renders
|
|
10
10
|
* its children inside that Shadow Root using a React portal.
|
|
11
11
|
*
|
|
12
|
+
* ### Root element modifiers
|
|
13
|
+
* - `--attached` — the shadow root exists and the children are rendered into it.
|
|
14
|
+
* Absent on the first render, before the host element is in the document.
|
|
15
|
+
*
|
|
12
16
|
* @param props - Component properties
|
|
13
17
|
* @see {@link Props}
|
|
14
18
|
* @returns A host `div` element that owns the created Shadow Root.
|
|
@@ -38,6 +42,6 @@ export const ShadowRootComponent = ({ mode = 'open', delegatesFocus, slotAssignm
|
|
|
38
42
|
]);
|
|
39
43
|
// Rendering
|
|
40
44
|
const c = clss(publicClassName, { cssModule });
|
|
41
|
-
const rootClss = mergeClassNames(c(), className);
|
|
45
|
+
const rootClss = mergeClassNames(c(null, { attached: shadowRoot !== null }), className);
|
|
42
46
|
return _jsx("div", { ref: hostRef, className: rootClss, children: shadowRoot !== null && createPortal(_jsxs(_Fragment, { children: [styles !== undefined && _jsx("style", { children: styles }), children] }), shadowRoot) });
|
|
43
47
|
};
|
|
@@ -26,6 +26,10 @@ export type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & WithClassName<
|
|
|
26
26
|
* A stable auto-generated `id` is created on mount and used to associate the
|
|
27
27
|
* rendered label through the `htmlFor` attribute.
|
|
28
28
|
*
|
|
29
|
+
* ### Root element modifiers
|
|
30
|
+
* - `--controlled` — the value comes from the parent, not from inside.
|
|
31
|
+
* - `--empty` / `--filled` — whether the current value is the empty string.
|
|
32
|
+
*
|
|
29
33
|
* ### CSS elements
|
|
30
34
|
* - `label`
|
|
31
35
|
* - `error`
|
|
@@ -15,6 +15,10 @@ import cssModule from './styles.module.css';
|
|
|
15
15
|
* A stable auto-generated `id` is created on mount and used to associate the
|
|
16
16
|
* rendered label through the `htmlFor` attribute.
|
|
17
17
|
*
|
|
18
|
+
* ### Root element modifiers
|
|
19
|
+
* - `--controlled` — the value comes from the parent, not from inside.
|
|
20
|
+
* - `--empty` / `--filled` — whether the current value is the empty string.
|
|
21
|
+
*
|
|
18
22
|
* ### CSS elements
|
|
19
23
|
* - `label`
|
|
20
24
|
* - `error`
|
|
@@ -53,6 +57,10 @@ export const Textarea = ({ label, error, autoHeight = false, value, defaultValue
|
|
|
53
57
|
};
|
|
54
58
|
// Rendering
|
|
55
59
|
const c = clss(publicClassName, { cssModule });
|
|
56
|
-
const rootClss = mergeClassNames(c(
|
|
60
|
+
const rootClss = mergeClassNames(c(null, {
|
|
61
|
+
controlled: isControlled,
|
|
62
|
+
empty: currentValue === '',
|
|
63
|
+
filled: currentValue !== ''
|
|
64
|
+
}), className);
|
|
57
65
|
return _jsxs(_Fragment, { children: [isNotFalsy(label) && _jsx("label", { className: c('label'), htmlFor: id, children: label }), _jsx("textarea", { ...rest, ref: textareaRef, id: id, className: rootClss, value: currentValue, onChange: handleChange }), isNotFalsy(error) && _jsx("span", { className: c('error'), children: error })] });
|
|
58
66
|
};
|
|
@@ -38,7 +38,9 @@ type TrackData = {
|
|
|
38
38
|
* @property loudBtnContent - React content for the "loud" (unmute) button.
|
|
39
39
|
* @property muteBtnContent - React content for the mute button.
|
|
40
40
|
* @property fullscreenBtnContent - React content for the fullscreen button.
|
|
41
|
-
* @property play -
|
|
41
|
+
* @property play - Whether the media should be playing. A **request**, not a
|
|
42
|
+
* setting: a browser holds a veto over playback — an unmuted media outside a user
|
|
43
|
+
* gesture is refused — so this prop asks, and `onIsPlayingChanged` answers.
|
|
42
44
|
* @property fullscreen - External control of fullscreen mode.
|
|
43
45
|
* @property volume - External control of volume (0 to 1).
|
|
44
46
|
* @property mute - External control of mute (true = muted).
|
|
@@ -67,7 +69,10 @@ type TrackData = {
|
|
|
67
69
|
* @property onTimelineClicked - Called when the timeline is clicked, before the
|
|
68
70
|
* component reacts, with the target and current times (in seconds). The component
|
|
69
71
|
* seeks to the target right after, unless the time is controlled.
|
|
70
|
-
* @property onIsPlayingChanged - Called
|
|
72
|
+
* @property onIsPlayingChanged - Called with what the **element** is doing, not
|
|
73
|
+
* with what `play` asked for. It is how a refused play surfaces, that being the one
|
|
74
|
+
* case where nothing else does: the promise rejects, no event fires, and without
|
|
75
|
+
* this a parent would go on believing a media that never started. Never on mount.
|
|
71
76
|
* @property onIsFullscreenChanged - Called once the fullscreen state has changed.
|
|
72
77
|
* @property onIsEndedChanged - Called after playback reached the end, and again
|
|
73
78
|
* once it left it — a seek back or a new play. Never on mount.
|
|
@@ -146,8 +151,14 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
146
151
|
* - `data-total-time-ms` — total duration in milliseconds.
|
|
147
152
|
*
|
|
148
153
|
* ### CSS custom properties on the root element
|
|
149
|
-
*
|
|
150
|
-
*
|
|
154
|
+
* Durations come as a `ms` time under the bare name and as a plain number under a
|
|
155
|
+
* `-raw` twin; ratios are unitless and have no twin.
|
|
156
|
+
* - `--lm-video-current-time` / `--lm-video-current-time-raw`
|
|
157
|
+
* - `--lm-video-total-time` / `--lm-video-total-time-raw`
|
|
158
|
+
* - `--lm-video-current-time-ratio` — how far through the media the playhead is.
|
|
159
|
+
* What a progress bar animates on, with no JavaScript of its own.
|
|
160
|
+
* - `--lm-video-volume-ratio`
|
|
161
|
+
* - `--lm-video-playback-rate` — a multiplier, not a ratio: `1` is normal speed.
|
|
151
162
|
*
|
|
152
163
|
* @param props - Component properties.
|
|
153
164
|
* @see {@link Props}
|
|
@@ -35,8 +35,14 @@ import { forceExitFullscreen, forceFullscreen, forceLoud, forceMute, forcePause,
|
|
|
35
35
|
* - `data-total-time-ms` — total duration in milliseconds.
|
|
36
36
|
*
|
|
37
37
|
* ### CSS custom properties on the root element
|
|
38
|
-
*
|
|
39
|
-
*
|
|
38
|
+
* Durations come as a `ms` time under the bare name and as a plain number under a
|
|
39
|
+
* `-raw` twin; ratios are unitless and have no twin.
|
|
40
|
+
* - `--lm-video-current-time` / `--lm-video-current-time-raw`
|
|
41
|
+
* - `--lm-video-total-time` / `--lm-video-total-time-raw`
|
|
42
|
+
* - `--lm-video-current-time-ratio` — how far through the media the playhead is.
|
|
43
|
+
* What a progress bar animates on, with no JavaScript of its own.
|
|
44
|
+
* - `--lm-video-volume-ratio`
|
|
45
|
+
* - `--lm-video-playback-rate` — a multiplier, not a ratio: `1` is normal speed.
|
|
40
46
|
*
|
|
41
47
|
* @param props - Component properties.
|
|
42
48
|
* @see {@link Props}
|
|
@@ -53,6 +59,10 @@ export const ControlledVideo = ({ sources, tracks, subtitles, playBtnContent, pa
|
|
|
53
59
|
// element, and the two events that turn it on and off are joined by a sync on
|
|
54
60
|
// every time update, which covers a seek away from the end.
|
|
55
61
|
const [isEnded, setIsEnded] = useState(false);
|
|
62
|
+
// What the element is actually doing, as opposed to what `play` asked for. The
|
|
63
|
+
// two part ways whenever a browser refuses a play, which it does without firing
|
|
64
|
+
// anything — see `forcePlay`.
|
|
65
|
+
const [isElementPlaying, setIsElementPlaying] = useState(false);
|
|
56
66
|
const isTimeControlled = givenCurrentTimeMs !== undefined;
|
|
57
67
|
// The parent owns the time as soon as it provides one, so that is what gets
|
|
58
68
|
// displayed — not what the element reported one render later.
|
|
@@ -86,12 +96,18 @@ export const ControlledVideo = ({ sources, tracks, subtitles, playBtnContent, pa
|
|
|
86
96
|
}, [intrinsicVideoAttributes.onTimeUpdate]);
|
|
87
97
|
const handleEndedEvent = useCallback((e) => {
|
|
88
98
|
setIsEnded(true);
|
|
99
|
+
setIsElementPlaying(false);
|
|
89
100
|
intrinsicVideoAttributes.onEnded?.(e);
|
|
90
101
|
}, [intrinsicVideoAttributes.onEnded]);
|
|
91
102
|
const handlePlayEvent = useCallback((e) => {
|
|
92
103
|
setIsEnded(false);
|
|
104
|
+
setIsElementPlaying(true);
|
|
93
105
|
intrinsicVideoAttributes.onPlay?.(e);
|
|
94
106
|
}, [intrinsicVideoAttributes.onPlay]);
|
|
107
|
+
const handlePauseEvent = useCallback((e) => {
|
|
108
|
+
setIsElementPlaying(false);
|
|
109
|
+
intrinsicVideoAttributes.onPause?.(e);
|
|
110
|
+
}, [intrinsicVideoAttributes.onPause]);
|
|
95
111
|
// Custom action handlers
|
|
96
112
|
const handlePlayButtonClick = useCallback((e) => {
|
|
97
113
|
const wasPlaying = videoRef.current?.paused === false;
|
|
@@ -159,7 +175,13 @@ export const ControlledVideo = ({ sources, tracks, subtitles, playBtnContent, pa
|
|
|
159
175
|
'data-total-time-ms': totalTimeMs
|
|
160
176
|
};
|
|
161
177
|
const rootStyles = {
|
|
162
|
-
|
|
178
|
+
'--lm-video-current-time': `${currentTimeMs}ms`,
|
|
179
|
+
'--lm-video-current-time-raw': `${currentTimeMs}`,
|
|
180
|
+
'--lm-video-total-time': `${totalTimeMs}ms`,
|
|
181
|
+
'--lm-video-total-time-raw': `${totalTimeMs}`,
|
|
182
|
+
'--lm-video-current-time-ratio': `${currentTimeRatio}`,
|
|
183
|
+
'--lm-video-volume-ratio': `${volume}`,
|
|
184
|
+
'--lm-video-playback-rate': `${playbackRate}`
|
|
163
185
|
};
|
|
164
186
|
const parsedSources = useMemo(() => {
|
|
165
187
|
if (sources === undefined)
|
|
@@ -227,12 +249,18 @@ export const ControlledVideo = ({ sources, tracks, subtitles, playBtnContent, pa
|
|
|
227
249
|
void forcePause(videoRef.current);
|
|
228
250
|
return;
|
|
229
251
|
}
|
|
230
|
-
if (play
|
|
231
|
-
void forcePlay(videoRef.current);
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
252
|
+
if (play !== true) {
|
|
234
253
|
void forcePause(videoRef.current);
|
|
254
|
+
return;
|
|
235
255
|
}
|
|
256
|
+
let isCurrent = true;
|
|
257
|
+
void forcePlay(videoRef.current).then(isNowPlaying => {
|
|
258
|
+
// A refused play fires nothing, so without this the component would go on
|
|
259
|
+
// believing a media that never started.
|
|
260
|
+
if (isCurrent && !isNowPlaying)
|
|
261
|
+
setIsElementPlaying(false);
|
|
262
|
+
});
|
|
263
|
+
return () => { isCurrent = false; };
|
|
236
264
|
}, [play, isTimeControlled]);
|
|
237
265
|
useEffect(() => {
|
|
238
266
|
forceVolume(videoRef.current, volume);
|
|
@@ -264,13 +292,13 @@ export const ControlledVideo = ({ sources, tracks, subtitles, playBtnContent, pa
|
|
|
264
292
|
}, [onFullscreenChange]);
|
|
265
293
|
// State handlers
|
|
266
294
|
useChangeDispatch(currentTimeMs, onCurrentTimeMsChanged);
|
|
267
|
-
useChangeDispatch(
|
|
295
|
+
useChangeDispatch(isElementPlaying, onIsPlayingChanged);
|
|
268
296
|
useChangeDispatch(isFullscreen, onIsFullscreenChanged);
|
|
269
297
|
useChangeDispatch(isLoud, onIsLoudChanged);
|
|
270
298
|
useChangeDispatch(isEnded, onIsEndedChanged);
|
|
271
299
|
useChangeDispatch(volume, onVolumeChanged);
|
|
272
300
|
useChangeDispatch(playbackRate, onPlaybackRateChanged);
|
|
273
|
-
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, children: [parsedSources.map((source, index) => typeof source === 'string'
|
|
301
|
+
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'
|
|
274
302
|
? _jsx("source", { src: source }, index)
|
|
275
303
|
: _jsx("source", { src: source.src, type: source.type }, index)), parsedTracks.map((track, index) => typeof track === 'string'
|
|
276
304
|
? _jsx("track", { src: track }, index)
|
|
@@ -74,8 +74,10 @@ export type Props = WithViewportObservation<Omit<ControlledProps, 'play' | 'full
|
|
|
74
74
|
* behaviour is the same as setting the `…When…` one alone.
|
|
75
75
|
*
|
|
76
76
|
* Browsers refuse an unmuted `play()` outside a user gesture, so pairing an
|
|
77
|
-
* `autoLoud…` with an `autoPlay…` will usually have the playback rejected
|
|
78
|
-
*
|
|
79
|
-
*
|
|
77
|
+
* `autoLoud…` with an `autoPlay…` will usually have the playback rejected. The
|
|
78
|
+
* refusal is caught rather than ignored — the element is read back once the attempt
|
|
79
|
+
* settles, and the play state follows what it says — so the controls stay truthful.
|
|
80
|
+
* The media still won't play, though: autoplay muted, and leave unmuting to the
|
|
81
|
+
* reader.
|
|
80
82
|
*/
|
|
81
83
|
export declare const Video: FunctionComponent<Props>;
|
|
@@ -35,9 +35,11 @@ import { ControlledVideo } from './index.controlled.js';
|
|
|
35
35
|
* behaviour is the same as setting the `…When…` one alone.
|
|
36
36
|
*
|
|
37
37
|
* Browsers refuse an unmuted `play()` outside a user gesture, so pairing an
|
|
38
|
-
* `autoLoud…` with an `autoPlay…` will usually have the playback rejected
|
|
39
|
-
*
|
|
40
|
-
*
|
|
38
|
+
* `autoLoud…` with an `autoPlay…` will usually have the playback rejected. The
|
|
39
|
+
* refusal is caught rather than ignored — the element is read back once the attempt
|
|
40
|
+
* settles, and the play state follows what it says — so the controls stay truthful.
|
|
41
|
+
* The media still won't play, though: autoplay muted, and leave unmuting to the
|
|
42
|
+
* reader.
|
|
41
43
|
*/
|
|
42
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 }) => {
|
|
43
45
|
// State & refs
|
|
@@ -87,6 +89,14 @@ export const Video = ({ loop, autoPlayWhenVisible, autoPlayOnceVisible, autoPaus
|
|
|
87
89
|
setPlay(false);
|
|
88
90
|
controlledProps.onPause?.(e);
|
|
89
91
|
}, [controlledProps.onPause]);
|
|
92
|
+
// The element's own account of whether it is playing. It covers what the `play`
|
|
93
|
+
// and `pause` events do, plus the one case they can't: a play the browser refused,
|
|
94
|
+
// which fires nothing at all. Without it the controls would stay on `--play-on`
|
|
95
|
+
// over a media that never started.
|
|
96
|
+
const handleIsPlayingChanged = useCallback((isPlaying) => {
|
|
97
|
+
setPlay(isPlaying);
|
|
98
|
+
controlledProps.onIsPlayingChanged?.(isPlaying);
|
|
99
|
+
}, [controlledProps.onIsPlayingChanged]);
|
|
90
100
|
const handleOnVolumeChangeEvent = useCallback((e) => {
|
|
91
101
|
setMute(e.currentTarget.muted);
|
|
92
102
|
setVolume(e.currentTarget.volume);
|
|
@@ -178,7 +188,7 @@ export const Video = ({ loop, autoPlayWhenVisible, autoPlayOnceVisible, autoPaus
|
|
|
178
188
|
// Render
|
|
179
189
|
const c = clss(publicClassName, { cssModule });
|
|
180
190
|
const rootClss = mergeClassNames(c(), wrapperClassName);
|
|
181
|
-
const videoContent = _jsx(ControlledVideo, { ...controlledProps, play: play && !isTimeControlled, volume: volume, mute: mute, playbackRate: playbackRate, fullscreen: fullscreen, onPlay: handleOnPlayEvent, onPause: handleOnPauseEvent, onVolumeChange: handleOnVolumeChangeEvent, onRateChange: handleOnRateChangeEvent, onLoadedMetadata: handleOnLoadedMetadataEvent, onFullscreenChange: handleFullscreenChange, onPlayButtonClicked: handlePlayButtonClick, onPauseButtonClicked: handlePauseButtonClick, onLoudButtonClicked: handleLoudButtonClick, onMuteButtonClicked: handleMuteButtonClick, onVolumeRangeChanged: handleVolumeRangeChange, onRateRangeChanged: handleRateRangeChange, onFullscreenButtonClicked: handleFullscreenButtonClick });
|
|
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 });
|
|
182
192
|
return _jsx("div", { className: rootClss, children: needsObserve
|
|
183
193
|
? _jsx(IntersectionObserverComponent, { threshold: threshold, root: root, rootMargin: rootMargin, onIntersected: onIntersected, children: videoContent })
|
|
184
194
|
: videoContent });
|
|
@@ -2,6 +2,17 @@ export declare const muteAttributeWorkaround: (video: HTMLVideoElement | null, s
|
|
|
2
2
|
export declare const forceMute: (video: HTMLVideoElement | null) => void;
|
|
3
3
|
export declare const forceLoud: (video: HTMLVideoElement | null) => void;
|
|
4
4
|
export declare const forceVolume: (video: HTMLVideoElement | null, volume: number) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Asks the element to play, and reports what came of it.
|
|
7
|
+
*
|
|
8
|
+
* A browser may refuse — an unmuted media outside a user gesture, typically — and
|
|
9
|
+
* it refuses **silently**: the promise rejects and no event fires, so this is the
|
|
10
|
+
* only moment the outcome can be known. Hence the return value, and hence no
|
|
11
|
+
* logging: a refusal is not an anomaly to print, it is an answer to hand back.
|
|
12
|
+
*
|
|
13
|
+
* @param video - The element, or `null` before it mounts.
|
|
14
|
+
* @returns Whether it is playing now.
|
|
15
|
+
*/
|
|
5
16
|
export declare const forcePlay: (video: HTMLVideoElement | null) => Promise<boolean>;
|
|
6
17
|
export declare const forcePause: (video: HTMLVideoElement | null) => boolean;
|
|
7
18
|
export declare const forcePlaybackRate: (video: HTMLVideoElement | null, rate: number) => void;
|
|
@@ -28,6 +28,17 @@ 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
42
|
export const forcePlay = async (video) => {
|
|
32
43
|
if (video === null)
|
|
33
44
|
return false;
|
|
@@ -35,13 +46,11 @@ export const forcePlay = async (video) => {
|
|
|
35
46
|
return true;
|
|
36
47
|
try {
|
|
37
48
|
await video.play();
|
|
38
|
-
return video.paused;
|
|
39
49
|
}
|
|
40
|
-
catch (
|
|
41
|
-
//
|
|
42
|
-
console.error(e);
|
|
50
|
+
catch (err) {
|
|
51
|
+
// The refusal itself is the information, and it is in `video.paused` below.
|
|
43
52
|
}
|
|
44
|
-
return
|
|
53
|
+
return !video.paused;
|
|
45
54
|
};
|
|
46
55
|
export const forcePause = (video) => {
|
|
47
56
|
if (video === null)
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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'
|
|
4
|
+
export * as generateSignedUrl from './generate-signed-url/index.js'
|
|
5
5
|
export * as getMetadata from './get-metadata/index.js'
|
|
6
6
|
export * as getPermissions from './get-permissions/index.js'
|
|
7
7
|
export * as move from './move/index.js'
|
|
@@ -1,7 +1,7 @@
|
|
|
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'
|
|
4
|
+
export * as generateSignedUrl from './generate-signed-url/index.js'
|
|
5
5
|
export * as getMetadata from './get-metadata/index.js'
|
|
6
6
|
export * as getPermissions from './get-permissions/index.js'
|
|
7
7
|
export * as move from './move/index.js'
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export * as copyDir from './copy-dir/index.js'
|
|
2
|
-
export * as copyFile from './copy-file/index.js'
|
|
3
2
|
export * as downloadFile from './download-file/index.js'
|
|
3
|
+
export * as copyFile from './copy-file/index.js'
|
|
4
4
|
export * as existsFile from './exists-file/index.js'
|
|
5
5
|
export * as listDir from './list-dir/index.js'
|
|
6
6
|
export * as moveDir from './move-dir/index.js'
|
|
7
|
-
export * as moveFile from './move-file/index.js'
|
|
8
7
|
export * as removeDir from './remove-dir/index.js'
|
|
8
|
+
export * as moveFile from './move-file/index.js'
|
|
9
9
|
export * as removeFile from './remove-file/index.js'
|
|
10
10
|
export * as statFile from './stat-file/index.js'
|
|
11
11
|
export * as uploadFile from './upload-file/index.js'
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export * as copyDir from './copy-dir/index.js'
|
|
2
|
-
export * as copyFile from './copy-file/index.js'
|
|
3
2
|
export * as downloadFile from './download-file/index.js'
|
|
3
|
+
export * as copyFile from './copy-file/index.js'
|
|
4
4
|
export * as existsFile from './exists-file/index.js'
|
|
5
5
|
export * as listDir from './list-dir/index.js'
|
|
6
6
|
export * as moveDir from './move-dir/index.js'
|
|
7
|
-
export * as moveFile from './move-file/index.js'
|
|
8
7
|
export * as removeDir from './remove-dir/index.js'
|
|
8
|
+
export * as moveFile from './move-file/index.js'
|
|
9
9
|
export * as removeFile from './remove-file/index.js'
|
|
10
10
|
export * as statFile from './stat-file/index.js'
|
|
11
11
|
export * as uploadFile from './upload-file/index.js'
|
package/node/files/index.d.ts
CHANGED
package/node/files/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export * as blur from './blur/index.js'
|
|
2
|
-
export * as brighten from './brighten/index.js'
|
|
3
2
|
export * as extend from './extend/index.js'
|
|
3
|
+
export * as brighten from './brighten/index.js'
|
|
4
4
|
export * as extract from './extract/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'
|
|
9
8
|
export * as level from './level/index.js'
|
|
9
|
+
export * as hue from './hue/index.js'
|
|
10
10
|
export * as lighten from './lighten/index.js'
|
|
11
11
|
export * as normalize from './normalize/index.js'
|
|
12
12
|
export * as overlay from './overlay/index.js'
|