@design-edito/tools 0.5.4 → 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.
Files changed (53) hide show
  1. package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +1 -1
  2. package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +1 -1
  3. package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +1 -1
  4. package/agnostic/html/hyper-json/smart-tags/isolated/index.js +1 -1
  5. package/agnostic/index.d.ts +2 -2
  6. package/agnostic/index.js +2 -2
  7. package/agnostic/misc/logs/index.d.ts +1 -1
  8. package/agnostic/misc/logs/index.js +1 -1
  9. package/agnostic/numbers/index.d.ts +1 -1
  10. package/agnostic/numbers/index.js +1 -1
  11. package/agnostic/strings/index.d.ts +1 -1
  12. package/agnostic/strings/index.js +1 -1
  13. package/components/Clippable/index.d.ts +6 -2
  14. package/components/Clippable/index.js +8 -4
  15. package/components/Drawer/index.d.ts +6 -0
  16. package/components/Drawer/index.js +3 -1
  17. package/components/Gallery/index.d.ts +6 -0
  18. package/components/Gallery/index.js +3 -1
  19. package/components/Scrllgngn/index.d.ts +3 -0
  20. package/components/Scrllgngn/index.js +2 -1
  21. package/components/ScrollListener/index.d.ts +7 -0
  22. package/components/ScrollListener/index.js +22 -2
  23. package/components/ScrollListener/utils.d.ts +8 -0
  24. package/components/ScrollListener/utils.js +11 -0
  25. package/components/Sequencer/index.d.ts +8 -0
  26. package/components/Sequencer/index.js +19 -2
  27. package/components/Theatre/index.d.ts +16 -6
  28. package/components/Theatre/index.js +15 -8
  29. package/components/index.d.ts +6 -6
  30. package/components/index.js +6 -6
  31. package/index.d.ts +1 -1
  32. package/index.js +1 -1
  33. package/node/@aws-s3/storage/directory/index.d.ts +2 -2
  34. package/node/@aws-s3/storage/directory/index.js +2 -2
  35. package/node/@aws-s3/storage/index.d.ts +1 -1
  36. package/node/@aws-s3/storage/index.js +1 -1
  37. package/node/@google-cloud/storage/directory/index.d.ts +2 -2
  38. package/node/@google-cloud/storage/directory/index.js +2 -2
  39. package/node/@google-cloud/storage/file/index.d.ts +2 -2
  40. package/node/@google-cloud/storage/file/index.js +2 -2
  41. package/node/cloud-storage/operations/index.d.ts +2 -2
  42. package/node/cloud-storage/operations/index.js +2 -2
  43. package/node/ftps/file/index.d.ts +2 -2
  44. package/node/ftps/file/index.js +2 -2
  45. package/node/images/transform/operations/index.d.ts +2 -2
  46. package/node/images/transform/operations/index.js +2 -2
  47. package/node/process/index.d.ts +1 -1
  48. package/node/process/index.js +1 -1
  49. package/node/sftp/file/index.d.ts +2 -2
  50. package/node/sftp/file/index.js +2 -2
  51. package/node/sftp/index.d.ts +1 -1
  52. package/node/sftp/index.js +1 -1
  53. package/package.json +1 -1
@@ -16,8 +16,8 @@ export * as if from './if/index.js'
16
16
  export * as initialize from './initialize/index.js'
17
17
  export * as join from './join/index.js'
18
18
  export * as length from './length/index.js'
19
- export * as negate from './negate/index.js'
20
19
  export * as map from './map/index.js'
20
+ export * as negate from './negate/index.js'
21
21
  export * as notrailing from './notrailing/index.js'
22
22
  export * as or from './or/index.js'
23
23
  export * as pickrandom from './pickrandom/index.js'
@@ -16,8 +16,8 @@ export * as if from './if/index.js'
16
16
  export * as initialize from './initialize/index.js'
17
17
  export * as join from './join/index.js'
18
18
  export * as length from './length/index.js'
19
- export * as negate from './negate/index.js'
20
19
  export * as map from './map/index.js'
20
+ export * as negate from './negate/index.js'
21
21
  export * as notrailing from './notrailing/index.js'
22
22
  export * as or from './or/index.js'
23
23
  export * as pickrandom from './pickrandom/index.js'
@@ -4,9 +4,9 @@ export * as boolean from './boolean/index.js'
4
4
  export * as element from './element/index.js'
5
5
  export * as get from './get/index.js'
6
6
  export * as guess from './guess/index.js'
7
+ export * as nodelist from './nodelist/index.js'
7
8
  export * as null from './null/index.js'
8
9
  export * as number from './number/index.js'
9
- export * as nodelist from './nodelist/index.js'
10
10
  export * as record from './record/index.js'
11
11
  export * as ref from './ref/index.js'
12
12
  export * as string from './string/index.js'
@@ -4,9 +4,9 @@ export * as boolean from './boolean/index.js'
4
4
  export * as element from './element/index.js'
5
5
  export * as get from './get/index.js'
6
6
  export * as guess from './guess/index.js'
7
+ export * as nodelist from './nodelist/index.js'
7
8
  export * as null from './null/index.js'
8
9
  export * as number from './number/index.js'
9
- export * as nodelist from './nodelist/index.js'
10
10
  export * as record from './record/index.js'
11
11
  export * as ref from './ref/index.js'
12
12
  export * as string from './string/index.js'
@@ -1,6 +1,7 @@
1
1
  export * as arrays from './arrays/index.js'
2
2
  export * as booleans from './booleans/index.js'
3
3
  export * as colors from './colors/index.js'
4
+ export * as css from './css/index.js'
4
5
  export * as errors from './errors/index.js'
5
6
  export * as html from './html/index.js'
6
7
  export * as misc from './misc/index.js'
@@ -9,8 +10,7 @@ export * as objects from './objects/index.js'
9
10
  export * as optim from './optim/index.js'
10
11
  export * as random from './random/index.js'
11
12
  export * as regexps from './regexps/index.js'
12
- export * as strings from './strings/index.js'
13
13
  export * as sanitization from './sanitization/index.js'
14
+ export * as strings from './strings/index.js'
14
15
  export * as time from './time/index.js'
15
16
  export * as typescript from './typescript/index.js'
16
- export * as css from './css/index.js'
package/agnostic/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * as arrays from './arrays/index.js'
2
2
  export * as booleans from './booleans/index.js'
3
3
  export * as colors from './colors/index.js'
4
+ export * as css from './css/index.js'
4
5
  export * as errors from './errors/index.js'
5
6
  export * as html from './html/index.js'
6
7
  export * as misc from './misc/index.js'
@@ -9,8 +10,7 @@ export * as objects from './objects/index.js'
9
10
  export * as optim from './optim/index.js'
10
11
  export * as random from './random/index.js'
11
12
  export * as regexps from './regexps/index.js'
12
- export * as strings from './strings/index.js'
13
13
  export * as sanitization from './sanitization/index.js'
14
+ export * as strings from './strings/index.js'
14
15
  export * as time from './time/index.js'
15
16
  export * as typescript from './typescript/index.js'
16
- export * as css from './css/index.js'
@@ -1,3 +1,3 @@
1
+ export * as logger from './logger/index.js'
1
2
  export * as makeTextBlock from './make-text-block/index.js'
2
3
  export * as styles from './styles/index.js'
3
- export * as logger from './logger/index.js'
@@ -1,3 +1,3 @@
1
+ export * as logger from './logger/index.js'
1
2
  export * as makeTextBlock from './make-text-block/index.js'
2
3
  export * as styles from './styles/index.js'
3
- export * as logger from './logger/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'
@@ -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,5 +1,5 @@
1
- export * as matches from './matches/index.js'
2
1
  export * as charCodes from './char-codes/index.js'
2
+ export * as matches from './matches/index.js'
3
3
  export * as normalizeIndent from './normalize-indent/index.js'
4
4
  export * as parseTable from './parse-table/index.js'
5
5
  export * as replaceAll from './replace-all/index.js'
@@ -1,5 +1,5 @@
1
- export * as matches from './matches/index.js'
2
1
  export * as charCodes from './char-codes/index.js'
2
+ export * as matches from './matches/index.js'
3
3
  export * as normalizeIndent from './normalize-indent/index.js'
4
4
  export * as parseTable from './parse-table/index.js'
5
5
  export * as replaceAll from './replace-all/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 and leaves the component untouched:
38
- * neither `onClipped` nor the `clipped` modifier fires.
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 and leaves the component untouched:
26
- * neither `onClipped` nor the `clipped` modifier fires.
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
- console.error(err);
57
+ else
58
+ console.error(err);
55
59
  return;
56
60
  }
57
61
  onClipped?.(html);
@@ -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);
@@ -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 onToggleClicked - Called whenever a toggle is requested: the open
17
- * or close button, the `Escape` key, or the stage background. Fires before the
18
- * theatre reacts, with the state as it was.
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
- onToggleClicked?: (isOn: boolean) => void;
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
- * - `onToggleClicked` fires in both modes, for all four toggle sources — a
57
- * controlled parent needs it to know a toggle was requested at all.
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
- * - `onToggleClicked` fires in both modes, for all four toggle sources — a
29
- * controlled parent needs it to know a toggle was requested at all.
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, onToggleClicked, onIsOnChanged, children, className }) => {
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 handleOpenBtnClick = () => requestToggle(true);
48
- const handleCloseBtnClick = () => requestToggle(false);
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: handleCloseBtnClick, children: closeBtnContent }), _jsx("div", { className: openBtnClss, onClick: handleOpenBtnClick, children: openBtnContent })] });
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
  };
@@ -1,22 +1,22 @@
1
1
  export * as beforeAfter from './BeforeAfter/index.js'
2
2
  export * as button from './Button/index.js'
3
3
  export * as clippable from './Clippable/index.js'
4
- export * as drawer from './Drawer/index.js'
5
4
  export * as disclaimer from './Disclaimer/index.js'
5
+ export * as drawer from './Drawer/index.js'
6
6
  export * as eventListener from './EventListener/index.js'
7
7
  export * as gallery from './Gallery/index.js'
8
- export * as image from './Image/index.js'
9
8
  export * as iframe from './Iframe/index.js'
10
- export * as intersectionObserver from './IntersectionObserver/index.js'
9
+ export * as image from './Image/index.js'
11
10
  export * as input from './Input/index.js'
11
+ export * as intersectionObserver from './IntersectionObserver/index.js'
12
12
  export * as jsonEditor from './JsonEditor/index.js'
13
13
  export * as listLoader from './ListLoader/index.js'
14
- export * as paginator from './Paginator/index.js'
15
14
  export * as overlayer from './Overlayer/index.js'
16
- export * as scrllgngn from './Scrllgngn/index.js'
15
+ export * as paginator from './Paginator/index.js'
17
16
  export * as resizeObserver from './ResizeObserver/index.js'
18
- export * as select from './Select/index.js'
17
+ export * as scrllgngn from './Scrllgngn/index.js'
19
18
  export * as scrollListener from './ScrollListener/index.js'
19
+ export * as select from './Select/index.js'
20
20
  export * as sequencer from './Sequencer/index.js'
21
21
  export * as shadowRoot from './ShadowRoot/index.js'
22
22
  export * as subtitles from './Subtitles/index.js'
@@ -1,22 +1,22 @@
1
1
  export * as beforeAfter from './BeforeAfter/index.js'
2
2
  export * as button from './Button/index.js'
3
3
  export * as clippable from './Clippable/index.js'
4
- export * as drawer from './Drawer/index.js'
5
4
  export * as disclaimer from './Disclaimer/index.js'
5
+ export * as drawer from './Drawer/index.js'
6
6
  export * as eventListener from './EventListener/index.js'
7
7
  export * as gallery from './Gallery/index.js'
8
- export * as image from './Image/index.js'
9
8
  export * as iframe from './Iframe/index.js'
10
- export * as intersectionObserver from './IntersectionObserver/index.js'
9
+ export * as image from './Image/index.js'
11
10
  export * as input from './Input/index.js'
11
+ export * as intersectionObserver from './IntersectionObserver/index.js'
12
12
  export * as jsonEditor from './JsonEditor/index.js'
13
13
  export * as listLoader from './ListLoader/index.js'
14
- export * as paginator from './Paginator/index.js'
15
14
  export * as overlayer from './Overlayer/index.js'
16
- export * as scrllgngn from './Scrllgngn/index.js'
15
+ export * as paginator from './Paginator/index.js'
17
16
  export * as resizeObserver from './ResizeObserver/index.js'
18
- export * as select from './Select/index.js'
17
+ export * as scrllgngn from './Scrllgngn/index.js'
19
18
  export * as scrollListener from './ScrollListener/index.js'
19
+ export * as select from './Select/index.js'
20
20
  export * as sequencer from './Sequencer/index.js'
21
21
  export * as shadowRoot from './ShadowRoot/index.js'
22
22
  export * as subtitles from './Subtitles/index.js'
package/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export * as components from './components/index.js'
2
1
  export * as agnostic from './agnostic/index.js'
2
+ export * as components from './components/index.js'
3
3
  export * as node from './node/index.js'
package/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export * as components from './components/index.js'
2
1
  export * as agnostic from './agnostic/index.js'
2
+ export * as components from './components/index.js'
3
3
  export * as node from './node/index.js'
@@ -1,4 +1,4 @@
1
- export * as list from './list/index.js'
2
1
  export * as copyDir from './copy-dir/index.js'
3
- export * as removeDir from './remove-dir/index.js'
2
+ export * as list from './list/index.js'
4
3
  export * as moveDir from './move-dir/index.js'
4
+ export * as removeDir from './remove-dir/index.js'
@@ -1,4 +1,4 @@
1
- export * as list from './list/index.js'
2
1
  export * as copyDir from './copy-dir/index.js'
3
- export * as removeDir from './remove-dir/index.js'
2
+ export * as list from './list/index.js'
4
3
  export * as moveDir from './move-dir/index.js'
4
+ export * as removeDir from './remove-dir/index.js'
@@ -1,2 +1,2 @@
1
- export * as file from './file/index.js'
2
1
  export * as directory from './directory/index.js'
2
+ export * as file from './file/index.js'
@@ -1,2 +1,2 @@
1
- export * as file from './file/index.js'
2
1
  export * as directory from './directory/index.js'
2
+ export * as file from './file/index.js'
@@ -1,4 +1,4 @@
1
- export * as list from './list/index.js'
2
1
  export * as copyDir from './copy-dir/index.js'
3
- export * as removeDir from './remove-dir/index.js'
2
+ export * as list from './list/index.js'
4
3
  export * as moveDir from './move-dir/index.js'
4
+ export * as removeDir from './remove-dir/index.js'
@@ -1,4 +1,4 @@
1
- export * as list from './list/index.js'
2
1
  export * as copyDir from './copy-dir/index.js'
3
- export * as removeDir from './remove-dir/index.js'
2
+ export * as list from './list/index.js'
4
3
  export * as moveDir from './move-dir/index.js'
4
+ export * as removeDir from './remove-dir/index.js'
@@ -6,7 +6,7 @@ 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'
8
8
  export * as remove from './remove/index.js'
9
- export * as stat from './stat/index.js'
10
9
  export * as revokeSignedUrls from './revoke-signed-urls/index.js'
11
- export * as upload from './upload/index.js'
10
+ export * as stat from './stat/index.js'
12
11
  export * as updateMetadata from './update-metadata/index.js'
12
+ export * as upload from './upload/index.js'
@@ -6,7 +6,7 @@ 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'
8
8
  export * as remove from './remove/index.js'
9
- export * as stat from './stat/index.js'
10
9
  export * as revokeSignedUrls from './revoke-signed-urls/index.js'
11
- export * as upload from './upload/index.js'
10
+ export * as stat from './stat/index.js'
12
11
  export * as updateMetadata from './update-metadata/index.js'
12
+ export * as upload from './upload/index.js'
@@ -1,7 +1,7 @@
1
- export * as copyFile from './copy-file/index.js'
2
1
  export * as copyDir from './copy-dir/index.js'
3
- export * as existsFile from './exists-file/index.js'
2
+ export * as copyFile from './copy-file/index.js'
4
3
  export * as downloadFile from './download-file/index.js'
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
7
  export * as moveFile from './move-file/index.js'
@@ -1,7 +1,7 @@
1
- export * as copyFile from './copy-file/index.js'
2
1
  export * as copyDir from './copy-dir/index.js'
3
- export * as existsFile from './exists-file/index.js'
2
+ export * as copyFile from './copy-file/index.js'
4
3
  export * as downloadFile from './download-file/index.js'
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
7
  export * as moveFile from './move-file/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 move from './move/index.js'
4
3
  export * as exists from './exists/index.js'
5
- export * as stat from './stat/index.js'
4
+ export * as move from './move/index.js'
6
5
  export * as remove from './remove/index.js'
6
+ export * as stat from './stat/index.js'
7
7
  export * as upload from './upload/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 move from './move/index.js'
4
3
  export * as exists from './exists/index.js'
5
- export * as stat from './stat/index.js'
4
+ export * as move from './move/index.js'
6
5
  export * as remove from './remove/index.js'
6
+ export * as stat from './stat/index.js'
7
7
  export * as upload from './upload/index.js'
@@ -3,13 +3,13 @@ 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'
11
11
  export * as normalize from './normalize/index.js'
12
+ export * as overlay from './overlay/index.js'
12
13
  export * as resize from './resize/index.js'
13
14
  export * as rotate from './rotate/index.js'
14
- export * as overlay from './overlay/index.js'
15
15
  export * as saturate from './saturate/index.js'
@@ -3,13 +3,13 @@ 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'
11
11
  export * as normalize from './normalize/index.js'
12
+ export * as overlay from './overlay/index.js'
12
13
  export * as resize from './resize/index.js'
13
14
  export * as rotate from './rotate/index.js'
14
- export * as overlay from './overlay/index.js'
15
15
  export * as saturate from './saturate/index.js'
@@ -1,3 +1,3 @@
1
1
  export * as onExit from './on-exit/index.js'
2
- export * as spawner from './spawner/index.js'
3
2
  export * as promptContinue from './prompt-continue/index.js'
3
+ export * as spawner from './spawner/index.js'
@@ -1,3 +1,3 @@
1
1
  export * as onExit from './on-exit/index.js'
2
- export * as spawner from './spawner/index.js'
3
2
  export * as promptContinue from './prompt-continue/index.js'
3
+ export * as spawner from './spawner/index.js'
@@ -1,7 +1,7 @@
1
- export * as download from './download/index.js'
2
1
  export * as copy from './copy/index.js'
3
- export * as move from './move/index.js'
2
+ export * as download from './download/index.js'
4
3
  export * as exists from './exists/index.js'
4
+ export * as move from './move/index.js'
5
5
  export * as remove from './remove/index.js'
6
6
  export * as stat from './stat/index.js'
7
7
  export * as upload from './upload/index.js'
@@ -1,7 +1,7 @@
1
- export * as download from './download/index.js'
2
1
  export * as copy from './copy/index.js'
3
- export * as move from './move/index.js'
2
+ export * as download from './download/index.js'
4
3
  export * as exists from './exists/index.js'
4
+ export * as move from './move/index.js'
5
5
  export * as remove from './remove/index.js'
6
6
  export * as stat from './stat/index.js'
7
7
  export * as upload from './upload/index.js'
@@ -1,2 +1,2 @@
1
- export * as file from './file/index.js'
2
1
  export * as directory from './directory/index.js'
2
+ export * as file from './file/index.js'
@@ -1,2 +1,2 @@
1
- export * as file from './file/index.js'
2
1
  export * as directory from './directory/index.js'
2
+ export * as file from './file/index.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@design-edito/tools",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "",
5
5
  "author": "Maxime Fabas",
6
6
  "license": "ISC",