@design-edito/tools 0.5.3 → 0.5.5
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/numbers/index.d.ts +1 -1
- package/agnostic/numbers/index.js +1 -1
- package/components/Clippable/index.d.ts +6 -2
- package/components/Clippable/index.js +8 -4
- package/components/Disclaimer/index.d.ts +5 -4
- package/components/Disclaimer/index.js +3 -3
- package/components/Drawer/index.d.ts +6 -0
- package/components/Drawer/index.js +3 -1
- package/components/Gallery/index.d.ts +6 -0
- package/components/Gallery/index.js +3 -1
- package/components/Image/index.d.ts +1 -4
- package/components/Image/index.js +3 -7
- package/components/Overlayer/index.d.ts +4 -4
- package/components/Overlayer/index.js +4 -4
- package/components/Scrllgngn/index.d.ts +3 -0
- package/components/Scrllgngn/index.js +2 -1
- package/components/ScrollListener/index.d.ts +7 -0
- package/components/ScrollListener/index.js +22 -2
- package/components/ScrollListener/utils.d.ts +8 -0
- package/components/ScrollListener/utils.js +11 -0
- package/components/Sequencer/index.d.ts +8 -0
- package/components/Sequencer/index.js +19 -2
- package/components/Theatre/index.d.ts +16 -6
- package/components/Theatre/index.js +15 -8
- package/node/images/transform/operations/index.d.ts +1 -1
- package/node/images/transform/operations/index.js +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * as absoluteModulo from './absolute-modulo/index.js'
|
|
2
2
|
export * as approximateRational from './approximate-rational/index.js'
|
|
3
3
|
export * as clamp from './clamp/index.js'
|
|
4
|
-
export * as interpolate from './interpolate/index.js'
|
|
5
4
|
export * as geometricProgressions from './geometric-progressions/index.js'
|
|
5
|
+
export * as interpolate from './interpolate/index.js'
|
|
6
6
|
export * as round from './round/index.js'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * as absoluteModulo from './absolute-modulo/index.js'
|
|
2
2
|
export * as approximateRational from './approximate-rational/index.js'
|
|
3
3
|
export * as clamp from './clamp/index.js'
|
|
4
|
-
export * as interpolate from './interpolate/index.js'
|
|
5
4
|
export * as geometricProgressions from './geometric-progressions/index.js'
|
|
5
|
+
export * as interpolate from './interpolate/index.js'
|
|
6
6
|
export * as round from './round/index.js'
|
|
@@ -10,6 +10,8 @@ import type { WithClassName } from '../utils/types.js';
|
|
|
10
10
|
* clipboard content is resolved, with the container's raw HTML.
|
|
11
11
|
* @property onClipped - Called once content has been written to the clipboard.
|
|
12
12
|
* Not called when the write fails.
|
|
13
|
+
* @property onCopyFailed - Called when the clipboard write throws, with the
|
|
14
|
+
* error. When set, the error is no longer logged to the console.
|
|
13
15
|
* @property className - Additional class name(s) applied to the root element.
|
|
14
16
|
* @property children - Content rendered inside the copyable container.
|
|
15
17
|
*/
|
|
@@ -17,6 +19,7 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
17
19
|
toClip?: string | ((curr: string | undefined) => string | undefined);
|
|
18
20
|
onCopyClicked?: (e: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>, rawContent: string | undefined) => void;
|
|
19
21
|
onClipped?: (content: string) => void;
|
|
22
|
+
onCopyFailed?: (error: unknown) => void;
|
|
20
23
|
}>>;
|
|
21
24
|
/**
|
|
22
25
|
* Clipboard-enabled container. Renders arbitrary content alongside a copy
|
|
@@ -34,7 +37,8 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
34
37
|
* @returns A copy-enabled content container.
|
|
35
38
|
*
|
|
36
39
|
* @remarks
|
|
37
|
-
* A failed clipboard write is logged
|
|
38
|
-
* neither `onClipped` nor the
|
|
40
|
+
* A failed clipboard write fires `onCopyFailed` (or is logged when that prop is
|
|
41
|
+
* unset) and leaves the component untouched: neither `onClipped` nor the
|
|
42
|
+
* `clipped` modifier fires.
|
|
39
43
|
*/
|
|
40
44
|
export declare const Clippable: FunctionComponent<Props>;
|
|
@@ -22,10 +22,11 @@ const clippedModifierDurationMs = 3000;
|
|
|
22
22
|
* @returns A copy-enabled content container.
|
|
23
23
|
*
|
|
24
24
|
* @remarks
|
|
25
|
-
* A failed clipboard write is logged
|
|
26
|
-
* neither `onClipped` nor the
|
|
25
|
+
* A failed clipboard write fires `onCopyFailed` (or is logged when that prop is
|
|
26
|
+
* unset) and leaves the component untouched: neither `onClipped` nor the
|
|
27
|
+
* `clipped` modifier fires.
|
|
27
28
|
*/
|
|
28
|
-
export const Clippable = ({ className, children, toClip, onCopyClicked, onClipped }) => {
|
|
29
|
+
export const Clippable = ({ className, children, toClip, onCopyClicked, onClipped, onCopyFailed }) => {
|
|
29
30
|
// State & refs
|
|
30
31
|
const [hasBeenRecentlyClipped, setHasBeenRecentlyClipped] = useState(false);
|
|
31
32
|
const contentRef = useRef(null);
|
|
@@ -50,8 +51,11 @@ export const Clippable = ({ className, children, toClip, onCopyClicked, onClippe
|
|
|
50
51
|
]);
|
|
51
52
|
}
|
|
52
53
|
catch (err) {
|
|
54
|
+
if (onCopyFailed !== undefined)
|
|
55
|
+
onCopyFailed(err);
|
|
53
56
|
// eslint-disable-next-line no-console
|
|
54
|
-
|
|
57
|
+
else
|
|
58
|
+
console.error(err);
|
|
55
59
|
return;
|
|
56
60
|
}
|
|
57
61
|
onClipped?.(html);
|
|
@@ -3,7 +3,9 @@ import type { WithClassName } from '../utils/types.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Props for the {@link Disclaimer} component.
|
|
5
5
|
*
|
|
6
|
-
* @property
|
|
6
|
+
* @property children - The content the disclaimer gates, rendered below the panel.
|
|
7
|
+
* @property undisclosedContent - Content displayed inside the disclaimer panel,
|
|
8
|
+
* until it is dismissed.
|
|
7
9
|
* @property togglerContent - Content rendered inside the dismiss toggler.
|
|
8
10
|
* When omitted, the toggler is not rendered.
|
|
9
11
|
* @property isOn - Controlled visibility state. When defined, the component
|
|
@@ -15,10 +17,9 @@ import type { WithClassName } from '../utils/types.js';
|
|
|
15
17
|
* @property onIsOnChanged - Called after the visibility state changed, with the
|
|
16
18
|
* new value.
|
|
17
19
|
* @property className - Optional additional class name(s) applied to the root element.
|
|
18
|
-
* @property children - The content the disclaimer gates, rendered below the panel.
|
|
19
20
|
*/
|
|
20
21
|
export type Props = PropsWithChildren<WithClassName<{
|
|
21
|
-
|
|
22
|
+
undisclosedContent?: ReactNode;
|
|
22
23
|
togglerContent?: ReactNode;
|
|
23
24
|
isOn?: boolean;
|
|
24
25
|
defaultIsOn?: boolean;
|
|
@@ -34,7 +35,7 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
34
35
|
*
|
|
35
36
|
* ### CSS elements
|
|
36
37
|
* - `panel`
|
|
37
|
-
* - `content`
|
|
38
|
+
* - `content` — wraps `undisclosedContent`.
|
|
38
39
|
* - `toggler`
|
|
39
40
|
* - `sensitive` — wraps `children`.
|
|
40
41
|
*
|
|
@@ -13,7 +13,7 @@ import cssModule from './styles.module.css';
|
|
|
13
13
|
*
|
|
14
14
|
* ### CSS elements
|
|
15
15
|
* - `panel`
|
|
16
|
-
* - `content`
|
|
16
|
+
* - `content` — wraps `undisclosedContent`.
|
|
17
17
|
* - `toggler`
|
|
18
18
|
* - `sensitive` — wraps `children`.
|
|
19
19
|
*
|
|
@@ -28,7 +28,7 @@ import cssModule from './styles.module.css';
|
|
|
28
28
|
* know a click happened at all.
|
|
29
29
|
* - `onIsOnChanged` fires in both modes too, and never on mount.
|
|
30
30
|
*/
|
|
31
|
-
export const Disclaimer = ({
|
|
31
|
+
export const Disclaimer = ({ children, undisclosedContent, togglerContent, isOn: isOnProp, defaultIsOn = true, onDismissClicked, onIsOnChanged, className }) => {
|
|
32
32
|
// State
|
|
33
33
|
const [internalIsOn, setInternalIsOn] = useState(defaultIsOn);
|
|
34
34
|
const isControlled = isOnProp !== undefined;
|
|
@@ -52,5 +52,5 @@ export const Disclaimer = ({ content, togglerContent, isOn: isOnProp, defaultIsO
|
|
|
52
52
|
const contentClss = c('content');
|
|
53
53
|
const togglerClss = c('toggler');
|
|
54
54
|
const sensitiveClss = c('sensitive');
|
|
55
|
-
return _jsxs("div", { className: rootClss, children: [_jsxs("div", { className: panelClss, children: [
|
|
55
|
+
return _jsxs("div", { className: rootClss, children: [_jsxs("div", { className: panelClss, children: [undisclosedContent !== undefined && _jsx("div", { className: contentClss, children: undisclosedContent }), togglerContent !== undefined && _jsx("div", { className: togglerClss, onClick: handleDismissClick, children: togglerContent })] }), _jsx("div", { className: sensitiveClss, children: children })] });
|
|
56
56
|
};
|
|
@@ -15,6 +15,8 @@ import type { WithClassName } from '../utils/types.js';
|
|
|
15
15
|
* drawer reacts, with the open state as it was.
|
|
16
16
|
* @property onIsOpenedChanged - Called after the open state changed, with the
|
|
17
17
|
* new value.
|
|
18
|
+
* @property onContentResized - Called after the measured content size changed,
|
|
19
|
+
* with the new width and height in pixels. Never on mount.
|
|
18
20
|
* @property className - Additional class name(s) applied to the root element.
|
|
19
21
|
* @property children - Drawer content.
|
|
20
22
|
*/
|
|
@@ -26,6 +28,10 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
26
28
|
onOpenerClicked?: (isOpened: boolean) => void;
|
|
27
29
|
onCloserClicked?: (isOpened: boolean) => void;
|
|
28
30
|
onIsOpenedChanged?: (isOpened: boolean) => void;
|
|
31
|
+
onContentResized?: (dimensions: {
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
}) => void;
|
|
29
35
|
}>>;
|
|
30
36
|
/**
|
|
31
37
|
* Drawer component supporting controlled and uncontrolled usage.
|
|
@@ -40,7 +40,7 @@ import cssModule from './styles.module.css';
|
|
|
40
40
|
* parent needs them to know a click happened at all.
|
|
41
41
|
* - `onIsOpenedChanged` fires in both modes too, and never on mount.
|
|
42
42
|
*/
|
|
43
|
-
export const Drawer = ({ openerContent, closerContent, defaultIsOpened = false, isOpened: isOpenedProp, onOpenerClicked, onCloserClicked, onIsOpenedChanged, className, children }) => {
|
|
43
|
+
export const Drawer = ({ openerContent, closerContent, defaultIsOpened = false, isOpened: isOpenedProp, onOpenerClicked, onCloserClicked, onIsOpenedChanged, onContentResized, className, children }) => {
|
|
44
44
|
// State
|
|
45
45
|
const [internalIsOpened, setInternalIsOpened] = useState(defaultIsOpened);
|
|
46
46
|
const [contentDimensions, setContentDimensions] = useState();
|
|
@@ -48,6 +48,8 @@ export const Drawer = ({ openerContent, closerContent, defaultIsOpened = false,
|
|
|
48
48
|
const isOpened = isOpenedProp ?? internalIsOpened;
|
|
49
49
|
// State dispatch
|
|
50
50
|
useChangeDispatch(isOpened, onIsOpenedChanged);
|
|
51
|
+
useChangeDispatch(contentDimensions, dimensions => { if (dimensions !== undefined)
|
|
52
|
+
onContentResized?.(dimensions); }, (a, b) => a?.width === b?.width && a?.height === b?.height);
|
|
51
53
|
// User action handlers
|
|
52
54
|
const handleOpenerClick = () => {
|
|
53
55
|
onOpenerClicked?.(isOpened);
|
|
@@ -31,6 +31,10 @@ import type { WithClassName } from '../utils/types.js';
|
|
|
31
31
|
* before the gallery reacts, with the active index as it was and the target index.
|
|
32
32
|
* @property onActiveSlotChanged - Called after the active slot changed, with
|
|
33
33
|
* the new index.
|
|
34
|
+
* @property onCanGoLeftChanged - Called after the ability to scroll further left
|
|
35
|
+
* changed, with the new value. Never on mount.
|
|
36
|
+
* @property onCanGoRightChanged - Called after the ability to scroll further
|
|
37
|
+
* right changed, with the new value. Never on mount.
|
|
34
38
|
* @property className - Optional additional class name(s) applied to the root element.
|
|
35
39
|
* @property children - Elements rendered as gallery slots. Each child is wrapped in a slot container.
|
|
36
40
|
*/
|
|
@@ -48,6 +52,8 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
48
52
|
onNextClicked?: (activePos: number) => void;
|
|
49
53
|
onPaginationClicked?: (activePos: number, targetPos: number) => void;
|
|
50
54
|
onActiveSlotChanged?: (activePos: number) => void;
|
|
55
|
+
onCanGoLeftChanged?: (canGoLeft: boolean) => void;
|
|
56
|
+
onCanGoRightChanged?: (canGoRight: boolean) => void;
|
|
51
57
|
}>>;
|
|
52
58
|
/**
|
|
53
59
|
* Horizontally scrollable gallery component with navigation controls and pagination.
|
|
@@ -23,7 +23,7 @@ function resolvePadding(side, shorthand) {
|
|
|
23
23
|
* - Previous/next navigation controls,
|
|
24
24
|
* - Pagination controls allowing direct slot activation.
|
|
25
25
|
*/
|
|
26
|
-
export const Gallery = ({ paddingLeft, paddingRight, padding, prevButtonContent, nextButtonContent, paginationContent, defaultActive, active, noSnap, onPrevClicked, onNextClicked, onPaginationClicked, onActiveSlotChanged, children, className }) => {
|
|
26
|
+
export const Gallery = ({ paddingLeft, paddingRight, padding, prevButtonContent, nextButtonContent, paginationContent, defaultActive, active, noSnap, onPrevClicked, onNextClicked, onPaginationClicked, onActiveSlotChanged, onCanGoLeftChanged, onCanGoRightChanged, children, className }) => {
|
|
27
27
|
// State & refs
|
|
28
28
|
const scrollerRef = useRef(null);
|
|
29
29
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
@@ -33,6 +33,8 @@ export const Gallery = ({ paddingLeft, paddingRight, padding, prevButtonContent,
|
|
|
33
33
|
const isControlled = active !== undefined;
|
|
34
34
|
// State dispatch
|
|
35
35
|
useChangeDispatch(activeIndex, onActiveSlotChanged);
|
|
36
|
+
useChangeDispatch(canGoLeft, onCanGoLeftChanged);
|
|
37
|
+
useChangeDispatch(canGoRight, onCanGoRightChanged);
|
|
36
38
|
// User actions handlers
|
|
37
39
|
const handlePrevClick = () => {
|
|
38
40
|
onPrevClicked?.(activeIndex);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type FunctionComponent, type ImgHTMLAttributes } from 'react';
|
|
2
|
-
import { type Props as TheatreProps } from '../Theatre/index.js';
|
|
3
2
|
import type { WithClassName } from '../utils/types.js';
|
|
4
3
|
/**
|
|
5
4
|
* Describes a single responsive image source for use in a `<picture>` element.
|
|
@@ -27,16 +26,14 @@ type SourceData = {
|
|
|
27
26
|
* - a single srcSet string,
|
|
28
27
|
* - an array of srcSet strings,
|
|
29
28
|
* - an array of {@link SourceData} objects for full `<source>` control.
|
|
30
|
-
* @property theatre - Props forwarded to the internal {@link Theatre} component.
|
|
31
29
|
* @property className - Optional additional class name(s) applied to the root element.
|
|
32
30
|
*/
|
|
33
31
|
export type Props = WithClassName<{
|
|
34
32
|
sources?: string | string[] | SourceData[];
|
|
35
|
-
theatre?: TheatreProps;
|
|
36
33
|
}> & ImgHTMLAttributes<HTMLImageElement>;
|
|
37
34
|
/**
|
|
38
35
|
* Image component. Wraps a native `<img>` (or `<picture>`) element with
|
|
39
|
-
* optional responsive sources
|
|
36
|
+
* optional responsive sources.
|
|
40
37
|
*
|
|
41
38
|
* ### CSS elements
|
|
42
39
|
* - `picture` — wrapping `<picture>` element, always rendered. Contains the
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
import { clss } from '../../agnostic/css/clss/index.js';
|
|
4
|
-
import { Theatre } from '../Theatre/index.js';
|
|
5
4
|
import { mergeClassNames } from '../utils/index.js';
|
|
6
5
|
import { image as publicClassName } from '../public-classnames.js';
|
|
7
6
|
import cssModule from './styles.module.css';
|
|
8
7
|
/**
|
|
9
8
|
* Image component. Wraps a native `<img>` (or `<picture>`) element with
|
|
10
|
-
* optional responsive sources
|
|
9
|
+
* optional responsive sources.
|
|
11
10
|
*
|
|
12
11
|
* ### CSS elements
|
|
13
12
|
* - `picture` — wrapping `<picture>` element, always rendered. Contains the
|
|
@@ -18,7 +17,7 @@ import cssModule from './styles.module.css';
|
|
|
18
17
|
* @see {@link Props}
|
|
19
18
|
* @returns A `<figure>` element containing a `<picture>` with the image.
|
|
20
19
|
*/
|
|
21
|
-
export const Image = ({ sources,
|
|
20
|
+
export const Image = ({ sources, className, ...intrinsicImgAttributes }) => {
|
|
22
21
|
// State
|
|
23
22
|
const parsedSources = useMemo(() => {
|
|
24
23
|
if (sources === undefined)
|
|
@@ -42,8 +41,5 @@ export const Image = ({ sources, theatre, className, ...intrinsicImgAttributes }
|
|
|
42
41
|
const pictureClss = c('picture');
|
|
43
42
|
const imgClss = c('image');
|
|
44
43
|
const pictureContent = _jsxs("picture", { className: pictureClss, children: [parsedSources.map((source, index) => _jsx("source", { srcSet: typeof source === 'string' ? source : source.srcSet, type: typeof source === 'string' ? undefined : source.type, media: typeof source === 'string' ? undefined : source.media, sizes: typeof source === 'string' ? undefined : source.sizes }, index)), _jsx("img", { className: imgClss, ...intrinsicImgAttributes })] });
|
|
45
|
-
|
|
46
|
-
? _jsx(Theatre, { defaultIsOn: false, ...theatre, children: pictureContent })
|
|
47
|
-
: pictureContent;
|
|
48
|
-
return _jsx("figure", { className: rootClss, children: theatricalContent });
|
|
44
|
+
return _jsx("figure", { className: rootClss, children: pictureContent });
|
|
49
45
|
};
|
|
@@ -3,7 +3,7 @@ import type { WithClassName } from '../utils/types.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Describes a single overlay positioned over the base content.
|
|
5
5
|
*
|
|
6
|
-
* @property
|
|
6
|
+
* @property children - React node rendered inside the overlay element.
|
|
7
7
|
* If falsy, the overlay is not rendered.
|
|
8
8
|
* @property xPercent - Horizontal position of the overlay anchor as a percentage
|
|
9
9
|
* of the root element's width. Defaults to `0`.
|
|
@@ -17,7 +17,7 @@ import type { WithClassName } from '../utils/types.js';
|
|
|
17
17
|
* - `number` — arbitrary percentage offset (e.g. `25` produces `-25%`).
|
|
18
18
|
*/
|
|
19
19
|
type Overlay = {
|
|
20
|
-
|
|
20
|
+
children?: ReactNode;
|
|
21
21
|
xPercent?: number;
|
|
22
22
|
yPercent?: number;
|
|
23
23
|
justify?: 'left' | 'center' | 'right' | number;
|
|
@@ -26,7 +26,7 @@ type Overlay = {
|
|
|
26
26
|
* Props for the {@link Overlayer} component.
|
|
27
27
|
*
|
|
28
28
|
* @property overlays - Array of {@link Overlay} descriptors rendered on top of
|
|
29
|
-
* the base content. Overlays with falsy `
|
|
29
|
+
* the base content. Overlays with falsy `children` are skipped.
|
|
30
30
|
* @property className - Optional additional class name(s) applied to the root element.
|
|
31
31
|
* @property children - Content rendered in the base layer, below all overlays.
|
|
32
32
|
*/
|
|
@@ -40,7 +40,7 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
40
40
|
*
|
|
41
41
|
* ### Child elements
|
|
42
42
|
* - `__base` — wrapping `<div>` that contains `children`.
|
|
43
|
-
* - `__overlay` — one `<div>` per entry in `overlays` (falsy `
|
|
43
|
+
* - `__overlay` — one `<div>` per entry in `overlays` (falsy `children` entries
|
|
44
44
|
* are omitted).
|
|
45
45
|
*
|
|
46
46
|
* ### CSS custom properties on each overlay element
|
|
@@ -11,7 +11,7 @@ import cssModule from './styles.module.css';
|
|
|
11
11
|
*
|
|
12
12
|
* ### Child elements
|
|
13
13
|
* - `__base` — wrapping `<div>` that contains `children`.
|
|
14
|
-
* - `__overlay` — one `<div>` per entry in `overlays` (falsy `
|
|
14
|
+
* - `__overlay` — one `<div>` per entry in `overlays` (falsy `children` entries
|
|
15
15
|
* are omitted).
|
|
16
16
|
*
|
|
17
17
|
* ### CSS custom properties on each overlay element
|
|
@@ -30,7 +30,7 @@ export const Overlayer = ({ overlays, children, className }) => {
|
|
|
30
30
|
const c = clss(publicClassName, { cssModule });
|
|
31
31
|
const rootClss = mergeClassNames(c(), className);
|
|
32
32
|
const baseClss = c('base');
|
|
33
|
-
return _jsxs("div", { className: rootClss, children: [_jsx("div", { className: baseClss, children: children }), overlays?.map(({
|
|
33
|
+
return _jsxs("div", { className: rootClss, children: [_jsx("div", { className: baseClss, children: children }), overlays?.map(({ children: overlayChildren, xPercent = 0, yPercent = 0, justify }, overlayPos) => {
|
|
34
34
|
const overlayClss = c('overlay');
|
|
35
35
|
let computedTranslateX;
|
|
36
36
|
if (typeof justify === 'number') {
|
|
@@ -53,8 +53,8 @@ export const Overlayer = ({ overlays, children, className }) => {
|
|
|
53
53
|
'--PRIVATE-top': `${yPercent}%`,
|
|
54
54
|
'--PRIVATE-translate-x': computedTranslateX
|
|
55
55
|
};
|
|
56
|
-
if (isFalsy(
|
|
56
|
+
if (isFalsy(overlayChildren))
|
|
57
57
|
return null;
|
|
58
|
-
return _jsx("div", { className: overlayClss, style: overlayCustomProps, children:
|
|
58
|
+
return _jsx("div", { className: overlayClss, style: overlayCustomProps, children: overlayChildren }, overlayPos);
|
|
59
59
|
})] });
|
|
60
60
|
};
|
|
@@ -74,6 +74,8 @@ export type PropsPage = {
|
|
|
74
74
|
* @property onPageChanged - Called once the current page has changed, never on
|
|
75
75
|
* mount. Receives the zero-based index of the new current page and the
|
|
76
76
|
* corresponding page definition, if available.
|
|
77
|
+
* @property onContentVisibilityChanged - Called after the scrolling content
|
|
78
|
+
* area entered or left the viewport, with the new value. Never on mount.
|
|
77
79
|
* @property className - Optional additional class name(s) applied to the root
|
|
78
80
|
* element.
|
|
79
81
|
*/
|
|
@@ -83,6 +85,7 @@ export type Props = WithClassName<{
|
|
|
83
85
|
stickyBlocksLazyLoadDistance?: number;
|
|
84
86
|
forceStickBlocks?: 'before' | 'after' | 'both' | 'none';
|
|
85
87
|
onPageChanged?: (currentPagePos: number, pageData?: PropsPage) => void;
|
|
88
|
+
onContentVisibilityChanged?: (isVisible: boolean) => void;
|
|
86
89
|
}>;
|
|
87
90
|
/**
|
|
88
91
|
* Scrollytelling engine component. Orchestrates layered sticky blocks (`back`
|
|
@@ -56,7 +56,7 @@ import cssModule from './styles.module.css';
|
|
|
56
56
|
* back-blocks layer, front-blocks layer, paginated scrolling content, and
|
|
57
57
|
* bottom-bound sentinel.
|
|
58
58
|
*/
|
|
59
|
-
export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadDistance = 2, forceStickBlocks, onPageChanged, className }) => {
|
|
59
|
+
export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadDistance = 2, forceStickBlocks, onPageChanged, onContentVisibilityChanged, className }) => {
|
|
60
60
|
// State
|
|
61
61
|
const [topVisible, setTopVis] = useState(false);
|
|
62
62
|
const [contentVisible, setCntVis] = useState(false);
|
|
@@ -72,6 +72,7 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
|
|
|
72
72
|
const lazyLoadedFrontBlocks = lazyLoadedBlocks(stickyBlocks, 'front', currentPagePos, stickyBlocksLazyLoadDistance);
|
|
73
73
|
// Handlers
|
|
74
74
|
useChangeDispatch(currentPagePos, pagePos => onPageChanged?.(pagePos, pages?.[pagePos]));
|
|
75
|
+
useChangeDispatch(contentVisible, onContentVisibilityChanged);
|
|
75
76
|
const handleTopBoundDetect = e => setTopVis(e.ioEntry?.isIntersecting ?? false);
|
|
76
77
|
const handleCntDetect = e => setCntVis(e.ioEntry?.isIntersecting ?? false);
|
|
77
78
|
const handleBtmBoundDetect = e => setBtmVis(e.ioEntry?.isIntersecting ?? false);
|
|
@@ -12,6 +12,11 @@ import { type ScrollState } from './utils.js';
|
|
|
12
12
|
* changed. Receives `undefined` until the first measurement lands.
|
|
13
13
|
* @property onVisibilityChanged - Called on every intersection change, with
|
|
14
14
|
* `true` when the component intersects the viewport.
|
|
15
|
+
* @property onScrollProgressChanged - Called after the element's vertical outer
|
|
16
|
+
* scroll progress changed: `0` when it is about to enter the viewport, `1` once
|
|
17
|
+
* it has fully left it. Never on mount.
|
|
18
|
+
* @property onScrollDirectionChanged - Called after the document scroll
|
|
19
|
+
* direction changed, with `'up'` or `'down'`. Never on mount.
|
|
15
20
|
* @property className - Optional additional class name(s) applied to the root element.
|
|
16
21
|
* @property children - React nodes rendered inside the scroll listener container.
|
|
17
22
|
*/
|
|
@@ -20,6 +25,8 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
20
25
|
stopOnHidden?: boolean;
|
|
21
26
|
onScrollStateChanged?: (scrollState?: ScrollState) => void;
|
|
22
27
|
onVisibilityChanged?: (isVisible: boolean) => void;
|
|
28
|
+
onScrollProgressChanged?: (progress: number) => void;
|
|
29
|
+
onScrollDirectionChanged?: (direction: 'up' | 'down') => void;
|
|
23
30
|
}>>;
|
|
24
31
|
/**
|
|
25
32
|
* Exposes scroll metrics — both the document's and its own — as CSS custom
|
|
@@ -5,7 +5,7 @@ import { randomHash } from '../../agnostic/random/uuid/index.js';
|
|
|
5
5
|
import { IntersectionObserverComponent } from '../IntersectionObserver/index.js';
|
|
6
6
|
import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
|
|
7
7
|
import { scrollListener as publicClassName } from '../public-classnames.js';
|
|
8
|
-
import { subscribe, toScrollCssProps, unsubscribe } from './utils.js';
|
|
8
|
+
import { getScrollProgress, subscribe, toScrollCssProps, unsubscribe } from './utils.js';
|
|
9
9
|
import cssModule from './styles.module.css';
|
|
10
10
|
/**
|
|
11
11
|
* Exposes scroll metrics — both the document's and its own — as CSS custom
|
|
@@ -45,13 +45,33 @@ 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, className, children }) => {
|
|
48
|
+
export const ScrollListener = ({ startOnVisible, stopOnHidden, onScrollStateChanged, onVisibilityChanged, onScrollProgressChanged, onScrollDirectionChanged, className, children }) => {
|
|
49
49
|
// State & refs
|
|
50
50
|
const [subscriberId] = useState(() => randomHash(6));
|
|
51
51
|
const [scrollState, setScrollState] = useState();
|
|
52
|
+
const [scrollDirection, setScrollDirection] = useState(null);
|
|
52
53
|
const rootRef = useRef(null);
|
|
54
|
+
const previousScrollYRef = useRef(null);
|
|
55
|
+
const scrollProgress = scrollState === undefined
|
|
56
|
+
? undefined
|
|
57
|
+
: getScrollProgress(scrollState).y;
|
|
53
58
|
// State dispatch
|
|
54
59
|
useChangeDispatch(scrollState, onScrollStateChanged);
|
|
60
|
+
useChangeDispatch(scrollProgress, progress => { if (progress !== undefined)
|
|
61
|
+
onScrollProgressChanged?.(progress); });
|
|
62
|
+
useChangeDispatch(scrollDirection, direction => { if (direction !== null)
|
|
63
|
+
onScrollDirectionChanged?.(direction); });
|
|
64
|
+
// Fx. dep. `scrollState` - derive the document scroll direction
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (scrollState === undefined)
|
|
67
|
+
return;
|
|
68
|
+
const y = scrollState.global.scroll.y;
|
|
69
|
+
const previous = previousScrollYRef.current;
|
|
70
|
+
previousScrollYRef.current = y;
|
|
71
|
+
if (previous === null || y === previous)
|
|
72
|
+
return;
|
|
73
|
+
setScrollDirection(y > previous ? 'down' : 'up');
|
|
74
|
+
}, [scrollState]);
|
|
55
75
|
// Fx. no dep. - track from mount, unless waiting for the component to show up.
|
|
56
76
|
// The cleanup runs whichever way the subscription was opened.
|
|
57
77
|
useEffect(() => {
|
|
@@ -79,6 +79,14 @@ export declare function subscribe(id: string, subscriber: Subscriber): void;
|
|
|
79
79
|
* @param id - The id passed to {@link subscribe}.
|
|
80
80
|
*/
|
|
81
81
|
export declare function unsubscribe(id: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* The element's outer scroll progress on each axis — `0` when it is about to
|
|
84
|
+
* enter the viewport, `1` once it has fully left it.
|
|
85
|
+
*/
|
|
86
|
+
export declare function getScrollProgress(scrollState: ScrollState): {
|
|
87
|
+
x: number;
|
|
88
|
+
y: number;
|
|
89
|
+
};
|
|
82
90
|
/**
|
|
83
91
|
* Builds the CSS custom properties exposed on a {@link ScrollListener} root.
|
|
84
92
|
*
|
|
@@ -116,6 +116,17 @@ function outerRange(offset, size, winSize) {
|
|
|
116
116
|
function progressIn(scroll, [from, to]) {
|
|
117
117
|
return (scroll - from) / Math.max(to - from, 1);
|
|
118
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* The element's outer scroll progress on each axis — `0` when it is about to
|
|
121
|
+
* enter the viewport, `1` once it has fully left it.
|
|
122
|
+
*/
|
|
123
|
+
export function getScrollProgress(scrollState) {
|
|
124
|
+
const { global: { win, scroll }, local } = scrollState;
|
|
125
|
+
return {
|
|
126
|
+
x: progressIn(scroll.x, outerRange(local.offsetX, local.width, win.width)),
|
|
127
|
+
y: progressIn(scroll.y, outerRange(local.offsetY, local.height, win.height))
|
|
128
|
+
};
|
|
129
|
+
}
|
|
119
130
|
/**
|
|
120
131
|
* Builds the CSS custom properties exposed on a {@link ScrollListener} root.
|
|
121
132
|
*
|
|
@@ -36,6 +36,11 @@ import { type Props as ControlledProps } from './index.controlled.js';
|
|
|
36
36
|
* with the new value.
|
|
37
37
|
* @property onStepChanged - Called after the forwarded step changed, with the
|
|
38
38
|
* new value.
|
|
39
|
+
* @property onLooped - Called when the step wraps around (either direction),
|
|
40
|
+
* only while `loop` is `true`.
|
|
41
|
+
* @property onReachedFirstStep - Called when the forwarded step becomes `0`.
|
|
42
|
+
* @property onReachedLastStep - Called when the forwarded step becomes the last
|
|
43
|
+
* one.
|
|
39
44
|
*/
|
|
40
45
|
export type Props = Omit<ControlledProps, 'isPlaying' | 'tempo'> & {
|
|
41
46
|
defaultStep?: number;
|
|
@@ -51,6 +56,9 @@ export type Props = Omit<ControlledProps, 'isPlaying' | 'tempo'> & {
|
|
|
51
56
|
onIntersected?: IOCompProps['onIntersected'];
|
|
52
57
|
onIsPlayingChanged?: (isPlaying: boolean) => void;
|
|
53
58
|
onStepChanged?: (step: number) => void;
|
|
59
|
+
onLooped?: () => void;
|
|
60
|
+
onReachedFirstStep?: () => void;
|
|
61
|
+
onReachedLastStep?: () => void;
|
|
54
62
|
};
|
|
55
63
|
/**
|
|
56
64
|
* Uncontrolled, self-advancing sequencer component. Drives a
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect, useCallback, Children } from 'react';
|
|
2
|
+
import { useState, useEffect, useCallback, useRef, Children } from 'react';
|
|
3
3
|
import { absoluteModulo } from '../../agnostic/numbers/absolute-modulo/index.js';
|
|
4
4
|
import { clamp } from '../../agnostic/numbers/clamp/index.js';
|
|
5
5
|
import { IntersectionObserverComponent } from '../IntersectionObserver/index.js';
|
|
@@ -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, ...controlledProps }) => {
|
|
33
|
+
export const Sequencer = ({ defaultStep, tempo = 60, play, loop, clampFirst, clampLast, resetOnVisible, resetOnHidden, playOnVisible, pauseOnHidden, onIntersected, onIsPlayingChanged, onStepChanged, onLooped, onReachedFirstStep, onReachedLastStep, ...controlledProps }) => {
|
|
34
34
|
// State
|
|
35
35
|
const { step, activateOnStep, children } = controlledProps;
|
|
36
36
|
const [internalPlay, setInternalPlay] = useState(play ?? false);
|
|
@@ -63,6 +63,23 @@ export const Sequencer = ({ defaultStep, tempo = 60, play, loop, clampFirst, cla
|
|
|
63
63
|
// State dispatch
|
|
64
64
|
useChangeDispatch(actualPlay, onIsPlayingChanged);
|
|
65
65
|
useChangeDispatch(forwardedStep, onStepChanged);
|
|
66
|
+
// Fx. dep. `forwardedStep` - loop / boundary events
|
|
67
|
+
const previousStepsRef = useRef(null);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (stepsCount <= 0)
|
|
70
|
+
return;
|
|
71
|
+
const previous = previousStepsRef.current;
|
|
72
|
+
previousStepsRef.current = { forwarded: forwardedStep, actual: actualStep };
|
|
73
|
+
if (previous === null)
|
|
74
|
+
return;
|
|
75
|
+
const lapChanged = Math.floor(actualStep / stepsCount) !== Math.floor(previous.actual / stepsCount);
|
|
76
|
+
if (loop === true && lapChanged)
|
|
77
|
+
onLooped?.();
|
|
78
|
+
if (forwardedStep === 0 && previous.forwarded !== 0)
|
|
79
|
+
onReachedFirstStep?.();
|
|
80
|
+
if (forwardedStep === stepsCount - 1 && previous.forwarded !== stepsCount - 1)
|
|
81
|
+
onReachedLastStep?.();
|
|
82
|
+
}, [forwardedStep, actualStep, stepsCount, loop]);
|
|
66
83
|
// Action handlers
|
|
67
84
|
const handleIntersection = useCallback(({ ioEntry, observer }) => {
|
|
68
85
|
onIntersected?.({ ioEntry, observer });
|
|
@@ -13,9 +13,16 @@ import type { WithClassName } from '../utils/types.js';
|
|
|
13
13
|
* open counts as a toggle.
|
|
14
14
|
* @property exitOnBgClick - When `true`, clicking the stage background — and
|
|
15
15
|
* not its content — counts as a toggle.
|
|
16
|
-
* @property
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* @property onOpenButtonClicked - Called when the open button is clicked, before
|
|
17
|
+
* the theatre reacts, with the state as it was.
|
|
18
|
+
* @property onCloseButtonClicked - Called when the close button is clicked,
|
|
19
|
+
* before the theatre reacts, with the state as it was.
|
|
20
|
+
* @property onBackgroundClicked - Called when the stage background is clicked
|
|
21
|
+
* (only while `exitOnBgClick` is `true`), before the theatre reacts, with the
|
|
22
|
+
* state as it was.
|
|
23
|
+
* @property onEscapePressed - Called when `Escape` is pressed while the stage is
|
|
24
|
+
* open (only while `exitOnEscape` is `true`), before the theatre reacts, with
|
|
25
|
+
* the state as it was.
|
|
19
26
|
* @property onIsOnChanged - Called after the theatre mode changed, with the new
|
|
20
27
|
* value.
|
|
21
28
|
* @property className - Optional additional class name(s) applied to the root element.
|
|
@@ -29,7 +36,10 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
29
36
|
defaultIsOn?: boolean;
|
|
30
37
|
exitOnEscape?: boolean;
|
|
31
38
|
exitOnBgClick?: boolean;
|
|
32
|
-
|
|
39
|
+
onOpenButtonClicked?: (isOn: boolean) => void;
|
|
40
|
+
onCloseButtonClicked?: (isOn: boolean) => void;
|
|
41
|
+
onBackgroundClicked?: (isOn: boolean) => void;
|
|
42
|
+
onEscapePressed?: (isOn: boolean) => void;
|
|
33
43
|
onIsOnChanged?: (isOn: boolean) => void;
|
|
34
44
|
}>>;
|
|
35
45
|
/**
|
|
@@ -53,8 +63,8 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
53
63
|
* @remarks
|
|
54
64
|
* - In controlled mode (`isOn` defined), the state is fully driven by the
|
|
55
65
|
* parent and internal state is never updated.
|
|
56
|
-
* -
|
|
57
|
-
*
|
|
66
|
+
* - The four toggle-source handlers fire in both modes — a controlled parent
|
|
67
|
+
* needs them to know a toggle was requested at all.
|
|
58
68
|
* - `onIsOnChanged` fires in both modes too, and never on mount.
|
|
59
69
|
*/
|
|
60
70
|
export declare const Theatre: FunctionComponent<Props>;
|
|
@@ -25,11 +25,11 @@ import cssModule from './styles.module.css';
|
|
|
25
25
|
* @remarks
|
|
26
26
|
* - In controlled mode (`isOn` defined), the state is fully driven by the
|
|
27
27
|
* parent and internal state is never updated.
|
|
28
|
-
* -
|
|
29
|
-
*
|
|
28
|
+
* - The four toggle-source handlers fire in both modes — a controlled parent
|
|
29
|
+
* needs them to know a toggle was requested at all.
|
|
30
30
|
* - `onIsOnChanged` fires in both modes too, and never on mount.
|
|
31
31
|
*/
|
|
32
|
-
export const Theatre = ({ closeBtnContent, openBtnContent, isOn: isOnProp, defaultIsOn = false, exitOnEscape, exitOnBgClick,
|
|
32
|
+
export const Theatre = ({ closeBtnContent, openBtnContent, isOn: isOnProp, defaultIsOn = false, exitOnEscape, exitOnBgClick, onOpenButtonClicked, onCloseButtonClicked, onBackgroundClicked, onEscapePressed, onIsOnChanged, children, className }) => {
|
|
33
33
|
// State & refs
|
|
34
34
|
const [internalIsOn, setInternalIsOn] = useState(defaultIsOn);
|
|
35
35
|
const stageRef = useRef(null);
|
|
@@ -39,18 +39,24 @@ export const Theatre = ({ closeBtnContent, openBtnContent, isOn: isOnProp, defau
|
|
|
39
39
|
useChangeDispatch(isOn, onIsOnChanged);
|
|
40
40
|
// User action handlers
|
|
41
41
|
const requestToggle = (targetIsOn) => {
|
|
42
|
-
onToggleClicked?.(isOn);
|
|
43
42
|
if (isControlled)
|
|
44
43
|
return;
|
|
45
44
|
setInternalIsOn(targetIsOn);
|
|
46
45
|
};
|
|
47
|
-
const
|
|
48
|
-
|
|
46
|
+
const handleOpenButtonClick = () => {
|
|
47
|
+
onOpenButtonClicked?.(isOn);
|
|
48
|
+
requestToggle(true);
|
|
49
|
+
};
|
|
50
|
+
const handleCloseButtonClick = () => {
|
|
51
|
+
onCloseButtonClicked?.(isOn);
|
|
52
|
+
requestToggle(false);
|
|
53
|
+
};
|
|
49
54
|
const handleStageBgClick = e => {
|
|
50
55
|
if (exitOnBgClick !== true)
|
|
51
56
|
return;
|
|
52
57
|
if (e.target !== stageRef.current)
|
|
53
58
|
return;
|
|
59
|
+
onBackgroundClicked?.(isOn);
|
|
54
60
|
requestToggle(false);
|
|
55
61
|
};
|
|
56
62
|
// Fx. dep. `exitOnEscape`, `isOn` - close the stage on the Escape key
|
|
@@ -60,11 +66,12 @@ export const Theatre = ({ closeBtnContent, openBtnContent, isOn: isOnProp, defau
|
|
|
60
66
|
const handleKeyDown = (e) => {
|
|
61
67
|
if (e.key !== 'Escape')
|
|
62
68
|
return;
|
|
69
|
+
onEscapePressed?.(isOn);
|
|
63
70
|
requestToggle(false);
|
|
64
71
|
};
|
|
65
72
|
window.addEventListener('keydown', handleKeyDown);
|
|
66
73
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
67
|
-
}, [exitOnEscape, isOn, isControlled]);
|
|
74
|
+
}, [exitOnEscape, isOn, isControlled, onEscapePressed]);
|
|
68
75
|
// Rendering
|
|
69
76
|
const c = clss(publicClassName, { cssModule });
|
|
70
77
|
const rootClss = mergeClassNames(c(null, {
|
|
@@ -74,5 +81,5 @@ export const Theatre = ({ closeBtnContent, openBtnContent, isOn: isOnProp, defau
|
|
|
74
81
|
const stageClss = c('stage');
|
|
75
82
|
const openBtnClss = c('open-btn');
|
|
76
83
|
const closeBtnClss = c('close-btn');
|
|
77
|
-
return _jsxs("div", { className: rootClss, children: [children, _jsx("div", { className: stageClss, onClick: handleStageBgClick, ref: stageRef, children: isOn && children }), _jsx("div", { className: closeBtnClss, onClick:
|
|
84
|
+
return _jsxs("div", { className: rootClss, children: [children, _jsx("div", { className: stageClss, onClick: handleStageBgClick, ref: stageRef, children: isOn && children }), _jsx("div", { className: closeBtnClss, onClick: handleCloseButtonClick, children: closeBtnContent }), _jsx("div", { className: openBtnClss, onClick: handleOpenButtonClick, children: openBtnContent })] });
|
|
78
85
|
};
|
|
@@ -3,8 +3,8 @@ export * as brighten from './brighten/index.js'
|
|
|
3
3
|
export * as extend from './extend/index.js'
|
|
4
4
|
export * as extract from './extract/index.js'
|
|
5
5
|
export * as flatten from './flatten/index.js'
|
|
6
|
-
export * as flip from './flip/index.js'
|
|
7
6
|
export * as flop from './flop/index.js'
|
|
7
|
+
export * as flip from './flip/index.js'
|
|
8
8
|
export * as hue from './hue/index.js'
|
|
9
9
|
export * as level from './level/index.js'
|
|
10
10
|
export * as lighten from './lighten/index.js'
|
|
@@ -3,8 +3,8 @@ export * as brighten from './brighten/index.js'
|
|
|
3
3
|
export * as extend from './extend/index.js'
|
|
4
4
|
export * as extract from './extract/index.js'
|
|
5
5
|
export * as flatten from './flatten/index.js'
|
|
6
|
-
export * as flip from './flip/index.js'
|
|
7
6
|
export * as flop from './flop/index.js'
|
|
7
|
+
export * as flip from './flip/index.js'
|
|
8
8
|
export * as hue from './hue/index.js'
|
|
9
9
|
export * as level from './level/index.js'
|
|
10
10
|
export * as lighten from './lighten/index.js'
|