@design-edito/tools 0.5.2 → 0.5.4

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 (152) hide show
  1. package/agnostic/html/deep-select/index.js +5 -3
  2. package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +9 -9
  3. package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +9 -9
  4. package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +3 -3
  5. package/agnostic/html/hyper-json/smart-tags/isolated/index.js +3 -3
  6. package/agnostic/html/index.d.ts +1 -1
  7. package/agnostic/html/index.js +1 -1
  8. package/agnostic/index.d.ts +2 -2
  9. package/agnostic/index.js +2 -2
  10. package/agnostic/misc/index.d.ts +3 -3
  11. package/agnostic/misc/index.js +3 -3
  12. package/agnostic/misc/logs/index.d.ts +1 -1
  13. package/agnostic/misc/logs/index.js +1 -1
  14. package/agnostic/numbers/index.d.ts +1 -1
  15. package/agnostic/numbers/index.js +1 -1
  16. package/agnostic/optim/index.d.ts +1 -1
  17. package/agnostic/optim/index.js +1 -1
  18. package/agnostic/strings/index.d.ts +2 -2
  19. package/agnostic/strings/index.js +2 -2
  20. package/agnostic/time/dates/format-date/index.d.ts +4 -2
  21. package/agnostic/time/dates/format-date/index.js +43 -94
  22. package/agnostic/time/dates/format-date/index.test.js +154 -0
  23. package/agnostic/time/dates/format-date/parts.d.ts +28 -0
  24. package/agnostic/time/dates/format-date/parts.js +136 -0
  25. package/agnostic/time/dates/format-date/types.d.ts +84 -0
  26. package/agnostic/time/dates/format-date/types.js +1 -0
  27. package/agnostic/time/duration/format-duration/index.d.ts +42 -0
  28. package/agnostic/time/duration/format-duration/index.js +94 -0
  29. package/agnostic/time/duration/format-duration/index.test.d.ts +1 -0
  30. package/agnostic/time/duration/format-duration/index.test.js +148 -0
  31. package/agnostic/time/duration/format-duration/parts.d.ts +32 -0
  32. package/agnostic/time/duration/format-duration/parts.js +107 -0
  33. package/agnostic/time/duration/format-duration/types.d.ts +93 -0
  34. package/agnostic/time/duration/format-duration/types.js +1 -0
  35. package/agnostic/time/duration/index.d.ts +6 -1
  36. package/agnostic/time/duration/index.js +2 -2
  37. package/agnostic/time/duration/index.test.d.ts +1 -0
  38. package/agnostic/time/duration/index.test.js +168 -0
  39. package/agnostic/time/index.d.ts +1 -1
  40. package/agnostic/time/index.js +1 -1
  41. package/components/BeforeAfter/index.controlled.d.ts +25 -25
  42. package/components/BeforeAfter/index.controlled.js +47 -53
  43. package/components/BeforeAfter/index.d.ts +20 -23
  44. package/components/BeforeAfter/index.js +48 -40
  45. package/components/Button/index.js +1 -1
  46. package/components/Clippable/index.d.ts +17 -26
  47. package/components/Clippable/index.js +21 -23
  48. package/components/Disclaimer/index.d.ts +32 -24
  49. package/components/Disclaimer/index.js +29 -24
  50. package/components/Drawer/index.d.ts +45 -25
  51. package/components/Drawer/index.js +61 -52
  52. package/components/EventListener/index.d.ts +4 -5
  53. package/components/Gallery/index.d.ts +14 -16
  54. package/components/Gallery/index.js +25 -30
  55. package/components/Iframe/index.js +2 -2
  56. package/components/Image/index.d.ts +5 -15
  57. package/components/Image/index.js +9 -34
  58. package/components/Input/index.d.ts +23 -19
  59. package/components/Input/index.js +30 -20
  60. package/components/JsonEditor/index.d.ts +177 -9
  61. package/components/JsonEditor/index.js +133 -77
  62. package/components/JsonEditor/types.d.ts +11 -0
  63. package/components/JsonEditor/types.js +1 -0
  64. package/components/JsonEditor/utils.d.ts +8 -0
  65. package/components/JsonEditor/utils.js +23 -0
  66. package/components/ListLoader/index.controlled.d.ts +5 -5
  67. package/components/ListLoader/index.controlled.js +4 -4
  68. package/components/ListLoader/index.d.ts +11 -11
  69. package/components/ListLoader/index.js +12 -12
  70. package/components/Overlayer/index.d.ts +4 -4
  71. package/components/Overlayer/index.js +6 -6
  72. package/components/Paginator/index.d.ts +7 -12
  73. package/components/Paginator/index.js +7 -11
  74. package/components/ResizeObserver/index.d.ts +14 -3
  75. package/components/ResizeObserver/index.js +49 -24
  76. package/components/Scrllgngn/index.d.ts +17 -13
  77. package/components/Scrllgngn/index.js +25 -84
  78. package/components/Scrllgngn/utils.d.ts +52 -0
  79. package/components/Scrllgngn/utils.js +102 -0
  80. package/components/ScrollListener/index.d.ts +43 -28
  81. package/components/ScrollListener/index.js +59 -79
  82. package/components/ScrollListener/utils.d.ts +37 -16
  83. package/components/ScrollListener/utils.js +141 -28
  84. package/components/Select/index.d.ts +24 -20
  85. package/components/Select/index.js +30 -20
  86. package/components/Sequencer/index.controlled.d.ts +16 -26
  87. package/components/Sequencer/index.controlled.js +15 -12
  88. package/components/Sequencer/index.d.ts +25 -30
  89. package/components/Sequencer/index.js +20 -34
  90. package/components/Subtitles/index.d.ts +29 -5
  91. package/components/Subtitles/index.js +90 -12
  92. package/components/Textarea/index.d.ts +26 -20
  93. package/components/Textarea/index.js +40 -21
  94. package/components/Theatre/index.d.ts +35 -39
  95. package/components/Theatre/index.js +46 -58
  96. package/components/UIModule/index.d.ts +25 -29
  97. package/components/UIModule/index.js +81 -110
  98. package/components/Video/index.controlled.d.ts +51 -51
  99. package/components/Video/index.controlled.js +75 -72
  100. package/components/Video/index.d.ts +12 -14
  101. package/components/Video/index.js +41 -86
  102. package/components/Video/utils.d.ts +35 -3
  103. package/components/Video/utils.js +37 -14
  104. package/components/index.d.ts +6 -6
  105. package/components/index.js +6 -6
  106. package/components/utils/index.d.ts +22 -0
  107. package/components/utils/index.js +31 -0
  108. package/index.d.ts +1 -1
  109. package/index.js +1 -1
  110. package/node/@aws-s3/storage/directory/index.d.ts +2 -2
  111. package/node/@aws-s3/storage/directory/index.js +2 -2
  112. package/node/@aws-s3/storage/file/index.d.ts +1 -1
  113. package/node/@aws-s3/storage/file/index.js +1 -1
  114. package/node/@aws-s3/storage/index.d.ts +1 -1
  115. package/node/@aws-s3/storage/index.js +1 -1
  116. package/node/@google-cloud/storage/directory/index.d.ts +2 -2
  117. package/node/@google-cloud/storage/directory/index.js +2 -2
  118. package/node/@google-cloud/storage/file/index.d.ts +2 -2
  119. package/node/@google-cloud/storage/file/index.js +2 -2
  120. package/node/@google-cloud/storage/index.d.ts +1 -1
  121. package/node/@google-cloud/storage/index.js +1 -1
  122. package/node/cloud-storage/operations/index.d.ts +2 -2
  123. package/node/cloud-storage/operations/index.js +2 -2
  124. package/node/ftps/file/index.d.ts +2 -2
  125. package/node/ftps/file/index.js +2 -2
  126. package/node/images/index.d.ts +1 -1
  127. package/node/images/index.js +1 -1
  128. package/node/images/transform/operations/index.d.ts +2 -2
  129. package/node/images/transform/operations/index.js +2 -2
  130. package/node/index.d.ts +1 -1
  131. package/node/index.js +1 -1
  132. package/node/process/index.d.ts +1 -1
  133. package/node/process/index.js +1 -1
  134. package/node/sftp/file/index.d.ts +2 -2
  135. package/node/sftp/file/index.js +2 -2
  136. package/node/sftp/index.d.ts +1 -1
  137. package/node/sftp/index.js +1 -1
  138. package/package.json +20 -5
  139. package/TODO.md +0 -269
  140. package/components/BeforeAfter/utils.d.ts +0 -4
  141. package/components/BeforeAfter/utils.js +0 -9
  142. package/components/Input/index.controlled.d.ts +0 -42
  143. package/components/Input/index.controlled.js +0 -54
  144. package/components/Select/index.controlled.d.ts +0 -43
  145. package/components/Select/index.controlled.js +0 -54
  146. package/components/Subtitles/types.d.ts +0 -24
  147. package/components/Subtitles/utils.d.ts +0 -32
  148. package/components/Subtitles/utils.js +0 -107
  149. package/components/Textarea/index.controlled.d.ts +0 -50
  150. package/components/Textarea/index.controlled.js +0 -75
  151. /package/{components/Subtitles/types.js → agnostic/time/dates/format-date/index.test.d.ts} +0 -0
  152. /package/components/ResizeObserver/{style.module.css → styles.module.css} +0 -0
@@ -22,12 +22,12 @@ import { type Props as ControlledProps } from './index.controlled.js';
22
22
  * from the DOM. Applies to load-button activations only, never to retries or
23
23
  * stale reloads. Ignored when `pages` is provided.
24
24
  * @property fetch - Fetches one page's items. Rejections are reported through
25
- * `onFetchError` and retried according to `fetchRetriesNb`.
25
+ * `onPageFetchFailed` and retried according to `fetchRetriesNb`.
26
26
  * @property staleAfterMs - Delay after which a loaded page is refetched, counted
27
27
  * from its own last successful load. When omitted, pages are never refreshed.
28
- * @property onFetchSuccess - Called after a page's items have been stored. Not
28
+ * @property onPageFetched - Called after a page's items have been stored. Not
29
29
  * called for a page dropped while its request was in flight.
30
- * @property onFetchError - Called on every failed attempt, not only once retries
30
+ * @property onPageFetchFailed - Called on every failed attempt, not only once retries
31
31
  * are exhausted. When omitted, failures are logged with `console.warn`.
32
32
  * @property fetchRetriesNb - Number of retries after a failed fetch. Defaults to
33
33
  * `Infinity`, so a page keeps retrying until it succeeds.
@@ -40,30 +40,30 @@ export type Props<T> = Omit<ControlledProps<T>, 'itemsPages' | 'loadingPages' |
40
40
  dropPagesFurtherThan?: number;
41
41
  fetch: (page: number) => Promise<T[]>;
42
42
  staleAfterMs?: number;
43
- onFetchSuccess?: (pagePos: number, items: T[]) => void;
44
- onFetchError?: (pagePos: number, error: Error) => void;
43
+ onPageFetched?: (pagePos: number, items: T[]) => void;
44
+ onPageFetchFailed?: (pagePos: number, error: Error) => void;
45
45
  fetchRetriesNb?: number;
46
46
  fetchRetriesDelayMs?: number;
47
47
  };
48
48
  /**
49
49
  * Paginated list abstraction. Fetches the pages it is asked for, keeps them
50
- * fresh, and drives a {@link ListLoaderControlled} with the result.
50
+ * fresh, and drives a {@link ControlledListLoader} with the result.
51
51
  *
52
52
  * @param props - Component properties.
53
53
  * @see {@link Props}
54
- * @see {@link ListLoaderControlled} for the rendered markup and CSS elements.
55
- * @returns A {@link ListLoaderControlled} fed with the loaded pages.
54
+ * @see {@link ControlledListLoader} for the rendered markup and CSS elements.
55
+ * @returns A {@link ControlledListLoader} fed with the loaded pages.
56
56
  *
57
57
  * @remarks
58
58
  * - In controlled mode (`pages` defined), the page set is entirely driven by the
59
59
  * parent. `fillGaps` and `dropPagesFurtherThan` are inert, and load buttons only
60
- * report through `onLoadPageClick`.
60
+ * report through `onLoadPageClicked`.
61
61
  * - In uncontrolled mode, internal state is initialized from `defaultPage` and load
62
- * buttons extend the set themselves. `onLoadPageClick` still fires, after the
62
+ * buttons extend the set themselves. `onLoadPageClicked` still fires, after the
63
63
  * internal state has been updated.
64
64
  * - A page removed from the effective set is dropped from memory, and a request
65
65
  * still in flight for it is discarded on arrival rather than re-inserted.
66
66
  * - No page outside `firstPagePos`–`lastPagePos` is ever fetched, whichever mode
67
67
  * is in use.
68
68
  */
69
- export declare const ListLoader: <T>({ className, pages, defaultPage, fillGaps, dropPagesFurtherThan, autoLoadPrevWhenVisible, autoLoadNextWhenVisible, firstPagePos, lastPagePos, fetch, filter, display, getIdentifier, onLoadPageClick, staleAfterMs, onFetchSuccess, onFetchError, fetchRetriesNb, fetchRetriesDelayMs }: Props<T>) => ReactNode;
69
+ export declare const ListLoader: <T>({ className, pages, defaultPage, fillGaps, dropPagesFurtherThan, autoLoadPrevWhenVisible, autoLoadNextWhenVisible, firstPagePos, lastPagePos, fetch, filter, display, getIdentifier, onLoadPageClicked, staleAfterMs, onPageFetched, onPageFetchFailed, fetchRetriesNb, fetchRetriesDelayMs }: Props<T>) => ReactNode;
@@ -2,30 +2,30 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { toError } from '../../agnostic/misc/cast/index.js';
4
4
  import { clamp } from '../../agnostic/numbers/clamp/index.js';
5
- import { ListLoaderControlled } from './index.controlled.js';
5
+ import { ControlledListLoader } from './index.controlled.js';
6
6
  import { rangeBetween } from './utils.js';
7
7
  /**
8
8
  * Paginated list abstraction. Fetches the pages it is asked for, keeps them
9
- * fresh, and drives a {@link ListLoaderControlled} with the result.
9
+ * fresh, and drives a {@link ControlledListLoader} with the result.
10
10
  *
11
11
  * @param props - Component properties.
12
12
  * @see {@link Props}
13
- * @see {@link ListLoaderControlled} for the rendered markup and CSS elements.
14
- * @returns A {@link ListLoaderControlled} fed with the loaded pages.
13
+ * @see {@link ControlledListLoader} for the rendered markup and CSS elements.
14
+ * @returns A {@link ControlledListLoader} fed with the loaded pages.
15
15
  *
16
16
  * @remarks
17
17
  * - In controlled mode (`pages` defined), the page set is entirely driven by the
18
18
  * parent. `fillGaps` and `dropPagesFurtherThan` are inert, and load buttons only
19
- * report through `onLoadPageClick`.
19
+ * report through `onLoadPageClicked`.
20
20
  * - In uncontrolled mode, internal state is initialized from `defaultPage` and load
21
- * buttons extend the set themselves. `onLoadPageClick` still fires, after the
21
+ * buttons extend the set themselves. `onLoadPageClicked` still fires, after the
22
22
  * internal state has been updated.
23
23
  * - A page removed from the effective set is dropped from memory, and a request
24
24
  * still in flight for it is discarded on arrival rather than re-inserted.
25
25
  * - No page outside `firstPagePos`–`lastPagePos` is ever fetched, whichever mode
26
26
  * is in use.
27
27
  */
28
- export const ListLoader = ({ className, pages, defaultPage, fillGaps = true, dropPagesFurtherThan, autoLoadPrevWhenVisible, autoLoadNextWhenVisible, firstPagePos, lastPagePos, fetch, filter, display, getIdentifier, onLoadPageClick, staleAfterMs, onFetchSuccess, onFetchError, fetchRetriesNb = Infinity, fetchRetriesDelayMs = 1000 }) => {
28
+ export const ListLoader = ({ className, pages, defaultPage, fillGaps = true, dropPagesFurtherThan, autoLoadPrevWhenVisible, autoLoadNextWhenVisible, firstPagePos, lastPagePos, fetch, filter, display, getIdentifier, onLoadPageClicked, staleAfterMs, onPageFetched, onPageFetchFailed, fetchRetriesNb = Infinity, fetchRetriesDelayMs = 1000 }) => {
29
29
  const [itemsPages, setItemsPages] = useState(new Map());
30
30
  const [loadingPages, setLoadingPages] = useState(new Set());
31
31
  const [internalPages, setInternalPages] = useState([
@@ -45,7 +45,7 @@ export const ListLoader = ({ className, pages, defaultPage, fillGaps = true, dro
45
45
  return kept.length === next.length ? next : kept;
46
46
  });
47
47
  }
48
- onLoadPageClick?.(pagePos);
48
+ onLoadPageClicked?.(pagePos);
49
49
  };
50
50
  const storePage = (page, items) => setItemsPages(curr => {
51
51
  const next = new Map(curr);
@@ -62,8 +62,8 @@ export const ListLoader = ({ className, pages, defaultPage, fillGaps = true, dro
62
62
  });
63
63
  const reportFetchError = (page, err) => {
64
64
  const error = toError(err);
65
- if (onFetchError !== undefined)
66
- return onFetchError(page, error);
65
+ if (onPageFetchFailed !== undefined)
66
+ return onPageFetchFailed(page, error);
67
67
  // eslint-disable-next-line no-console
68
68
  console.warn(`ListLoader failed to fetch page ${page}`, error);
69
69
  };
@@ -78,7 +78,7 @@ export const ListLoader = ({ className, pages, defaultPage, fillGaps = true, dro
78
78
  if (!requestedPages.current.has(page))
79
79
  return;
80
80
  storePage(page, items);
81
- onFetchSuccess?.(page, items);
81
+ onPageFetched?.(page, items);
82
82
  setPageLoading(page, false);
83
83
  })
84
84
  .catch((err) => {
@@ -142,5 +142,5 @@ export const ListLoader = ({ className, pages, defaultPage, fillGaps = true, dro
142
142
  const controlledItemsPages = new Map(Array
143
143
  .from(itemsPages)
144
144
  .map(([page, { items }]) => [page, items]));
145
- return _jsx(ListLoaderControlled, { className: className, pages: currentPages, firstPagePos: firstPagePos, lastPagePos: lastPagePos, itemsPages: controlledItemsPages, filter: filter, display: display, getIdentifier: getIdentifier, loadingPages: Array.from(loadingPages), onLoadPageClick: handleLoadClick, autoLoadPrevWhenVisible: autoLoadPrevWhenVisible, autoLoadNextWhenVisible: autoLoadNextWhenVisible });
145
+ return _jsx(ControlledListLoader, { className: className, pages: currentPages, firstPagePos: firstPagePos, lastPagePos: lastPagePos, itemsPages: controlledItemsPages, filter: filter, display: display, getIdentifier: getIdentifier, loadingPages: Array.from(loadingPages), onLoadPageClicked: handleLoadClick, autoLoadPrevWhenVisible: autoLoadPrevWhenVisible, autoLoadNextWhenVisible: autoLoadNextWhenVisible });
146
146
  };
@@ -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 content - React node rendered inside the overlay element.
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
- content?: ReactNode;
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 `content` are skipped.
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 `content` entries
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
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { clss } from '../../agnostic/css/clss/index.js';
3
3
  import { isFalsy } from '../../agnostic/booleans/is-falsy/index.js';
4
4
  import { mergeClassNames } from '../utils/index.js';
5
- import { overlayer as publicClassname } from '../public-classnames.js';
5
+ import { overlayer as publicClassName } from '../public-classnames.js';
6
6
  import cssModule from './styles.module.css';
7
7
  /**
8
8
  * Absolute positioning overlay component. Renders a base content layer and
@@ -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 `content` entries
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
@@ -27,10 +27,10 @@ import cssModule from './styles.module.css';
27
27
  * @returns A root `<div>` containing the base layer and all positioned overlay elements.
28
28
  */
29
29
  export const Overlayer = ({ overlays, children, className }) => {
30
- const c = clss(publicClassname, { cssModule });
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(({ content, xPercent = 0, yPercent = 0, justify }) => {
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(content))
56
+ if (isFalsy(overlayChildren))
57
57
  return null;
58
- return _jsx("div", { className: overlayClss, style: overlayCustomProps, children: content });
58
+ return _jsx("div", { className: overlayClss, style: overlayCustomProps, children: overlayChildren }, overlayPos);
59
59
  })] });
60
60
  };
@@ -27,23 +27,18 @@ type DirectionState = 'forwards' | 'backwards' | null;
27
27
  * the {@link IntersectionObserver} root margin. Determines how far into the viewport
28
28
  * a page must be before it is considered `'curr'`. Defaults to `0`.
29
29
  *
30
- * @property stateHandlers - Callbacks called after the internal state changed
31
- * @property stateHandlers.directionChanged - Callback invoked when the scroll direction changes.
32
- * Receives the new {@link DirectionState}. Only fires when the direction actually
33
- * changes repeated scrolls in the same direction do not trigger it again.
34
- *
35
- * @property stateHandlers.pageChanged - Callback invoked whenever any page's {@link PageState}
36
- * changes. Receives a flat array of all pages' states, ordered by position.
37
- *
30
+ * @property onDirectionChanged - Called after the scroll direction changed, with
31
+ * the new {@link DirectionState}. Repeated scrolls in the same direction do not
32
+ * trigger it again.
33
+ * @property onPagesChanged - Called after any page's {@link PageState} changed,
34
+ * with a flat array of every page's state, ordered by position.
38
35
  * @property className - Optional additional class name(s) applied to the root element.
39
36
  * @property children - Each direct child is treated as an individual page slot.
40
37
  */
41
38
  export type Props = PropsWithChildren<WithClassName<{
42
39
  thresholdOffsetPercent?: number;
43
- stateHandlers?: {
44
- directionChanged?: (direction: DirectionState) => void;
45
- pageChanged?: (pages: PageState[]) => void;
46
- };
40
+ onDirectionChanged?: (direction: DirectionState) => void;
41
+ onPagesChanged?: (pages: PageState[]) => void;
47
42
  }>>;
48
43
  /**
49
44
  * A scroll-driven pagination component that tracks which child page is currently
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, Children, useState } from 'react';
3
3
  import { clss } from '../../agnostic/css/clss/index.js';
4
- import { mergeClassNames } from '../utils/index.js';
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
7
  /**
@@ -27,21 +27,17 @@ import cssModule from './styles.module.css';
27
27
  * - `currCount` on each {@link PageState} increments each time a page transitions
28
28
  * into the `'curr'` position, making it useful as a re-entry counter.
29
29
  */
30
- export const Paginator = ({ thresholdOffsetPercent, stateHandlers, className, children }) => {
30
+ export const Paginator = ({ thresholdOffsetPercent, onDirectionChanged, onPagesChanged, className, children }) => {
31
31
  // State, refs, effects
32
32
  const [pagesState, setPagesState] = useState(new Map());
33
33
  const [directionState, setDirectionState] = useState(null);
34
34
  const pagesRef = useRef(null);
35
35
  const directionRef = useRef(null);
36
- // State change handlers
37
- useEffect(() => {
38
- stateHandlers?.pageChanged?.(Array
39
- .from(pagesState)
40
- .map(([, state]) => state));
41
- }, [pagesState]);
42
- useEffect(() => {
43
- stateHandlers?.directionChanged?.(directionState);
44
- }, [directionState]);
36
+ // State dispatch
37
+ useChangeDispatch(pagesState, pages => onPagesChanged?.(Array
38
+ .from(pages)
39
+ .map(([, pageState]) => pageState)));
40
+ useChangeDispatch(directionState, onDirectionChanged);
45
41
  // Catch scroll direction listening on scroll events
46
42
  useEffect(() => {
47
43
  let lastCall = 0;
@@ -16,9 +16,20 @@ export type Props = PropsWithChildren<WithClassName<{
16
16
  onResized?: (entry: ROEntryWithBoundingRect) => void;
17
17
  }>>;
18
18
  /**
19
- * Component that observes its own size changes and exposes the dimensions.
20
- * Updates are exposed both via data attributes (e.g., `data-width`) and CSS custom properties
21
- * (e.g., `--<prefix>-width`, `--<prefix>-width-px`) for styling or scripting purposes.
19
+ * Component that observes its own size changes and exposes the measured content
20
+ * rect on its root element. Both sets stay absent until the first measurement
21
+ * lands.
22
+ *
23
+ * ### Data attributes
24
+ * `data-x`, `data-y`, `data-top`, `data-left`, `data-bottom`, `data-right`,
25
+ * `data-width` and `data-height` — the content rect, as plain numbers.
26
+ *
27
+ * ### CSS custom properties
28
+ * The same eight measurements, each exposed twice: `--lm-resize-observer-width`
29
+ * carries the ready-to-use `px` length, `--lm-resize-observer-width-raw` the
30
+ * plain number for `calc()`. Same pattern for `-x`, `-y`, `-top`, `-left`,
31
+ * `-bottom`, `-right` and `-height`.
32
+ *
22
33
  * @param props - Component properties
23
34
  * @see {@link Props}
24
35
  * @returns A div wrapping `children`, with resize observation applied.
@@ -3,11 +3,22 @@ import { useRef, useEffect, useState } from 'react';
3
3
  import { clss } from '../../agnostic/css/clss/index.js';
4
4
  import { mergeClassNames } from '../utils/index.js';
5
5
  import { resizeObserver as publicClassName } from '../public-classnames.js';
6
- import cssModule from './style.module.css';
6
+ import cssModule from './styles.module.css';
7
7
  /**
8
- * Component that observes its own size changes and exposes the dimensions.
9
- * Updates are exposed both via data attributes (e.g., `data-width`) and CSS custom properties
10
- * (e.g., `--<prefix>-width`, `--<prefix>-width-px`) for styling or scripting purposes.
8
+ * Component that observes its own size changes and exposes the measured content
9
+ * rect on its root element. Both sets stay absent until the first measurement
10
+ * lands.
11
+ *
12
+ * ### Data attributes
13
+ * `data-x`, `data-y`, `data-top`, `data-left`, `data-bottom`, `data-right`,
14
+ * `data-width` and `data-height` — the content rect, as plain numbers.
15
+ *
16
+ * ### CSS custom properties
17
+ * The same eight measurements, each exposed twice: `--lm-resize-observer-width`
18
+ * carries the ready-to-use `px` length, `--lm-resize-observer-width-raw` the
19
+ * plain number for `calc()`. Same pattern for `-x`, `-y`, `-top`, `-left`,
20
+ * `-bottom`, `-right` and `-height`.
21
+ *
11
22
  * @param props - Component properties
12
23
  * @see {@link Props}
13
24
  * @returns A div wrapping `children`, with resize observation applied.
@@ -43,27 +54,41 @@ export const ResizeObserverComponent = ({ className, onResized, children }) => {
43
54
  return () => observerRef.current?.disconnect();
44
55
  }, [onResized]);
45
56
  // Data attributes, CSS custom props & Rendering
46
- const { x, y, top, left, bottom, right, width, height } = roEntry?.entry.contentRect ?? {};
47
- const contentRect = { x, y, top, left, bottom, right, width, height };
48
- const dataAttributes = Object
49
- .entries(contentRect)
50
- .reduce((acc, [key, val]) => {
51
- if (val === undefined)
52
- return acc;
53
- return { ...acc, [`data-${key}`]: val.toString() };
54
- }, {});
55
- const cssCustomProps = Object
56
- .entries(contentRect)
57
- .reduce((acc, [key, val]) => {
58
- if (val === undefined)
59
- return acc;
60
- return {
61
- ...acc,
62
- [`--${publicClassName}-${key}`]: val.toString(),
63
- [`--${publicClassName}-${key}-px`]: `${val.toString()}px`
57
+ const contentRect = roEntry?.entry.contentRect;
58
+ let dataAttributes = {};
59
+ let cssCustomProps = {};
60
+ if (contentRect !== undefined) {
61
+ const { x, y, top, left, bottom, right, width, height } = contentRect;
62
+ dataAttributes = {
63
+ 'data-x': `${x}`,
64
+ 'data-y': `${y}`,
65
+ 'data-top': `${top}`,
66
+ 'data-left': `${left}`,
67
+ 'data-bottom': `${bottom}`,
68
+ 'data-right': `${right}`,
69
+ 'data-width': `${width}`,
70
+ 'data-height': `${height}`
64
71
  };
65
- }, {});
72
+ cssCustomProps = {
73
+ '--lm-resize-observer-x': `${x}px`,
74
+ '--lm-resize-observer-x-raw': `${x}`,
75
+ '--lm-resize-observer-y': `${y}px`,
76
+ '--lm-resize-observer-y-raw': `${y}`,
77
+ '--lm-resize-observer-top': `${top}px`,
78
+ '--lm-resize-observer-top-raw': `${top}`,
79
+ '--lm-resize-observer-left': `${left}px`,
80
+ '--lm-resize-observer-left-raw': `${left}`,
81
+ '--lm-resize-observer-bottom': `${bottom}px`,
82
+ '--lm-resize-observer-bottom-raw': `${bottom}`,
83
+ '--lm-resize-observer-right': `${right}px`,
84
+ '--lm-resize-observer-right-raw': `${right}`,
85
+ '--lm-resize-observer-width': `${width}px`,
86
+ '--lm-resize-observer-width-raw': `${width}`,
87
+ '--lm-resize-observer-height': `${height}px`,
88
+ '--lm-resize-observer-height-raw': `${height}`
89
+ };
90
+ }
66
91
  const c = clss(publicClassName, { cssModule });
67
- const rootClss = mergeClassNames(c(null), className);
92
+ const rootClss = mergeClassNames(c(), className);
68
93
  return _jsx("div", { ...dataAttributes, className: rootClss, ref: rootRef, style: { ...cssCustomProps }, children: children });
69
94
  };
@@ -71,10 +71,8 @@ export type PropsPage = {
71
71
  * - `'after'` — forces blocks from pages after the current one.
72
72
  * - `'both'` — forces blocks on both sides.
73
73
  * - `'none'` — no forcing (default behaviour).
74
- * @property stateHandlers - Optional callbacks invoked in response to
75
- * component state changes.
76
- * @property stateHandlers.pageChanged - Called whenever the current page
77
- * changes. Receives the zero-based index of the current page and the
74
+ * @property onPageChanged - Called once the current page has changed, never on
75
+ * mount. Receives the zero-based index of the new current page and the
78
76
  * corresponding page definition, if available.
79
77
  * @property className - Optional additional class name(s) applied to the root
80
78
  * element.
@@ -84,9 +82,7 @@ export type Props = WithClassName<{
84
82
  thresholdOffsetPercent?: number;
85
83
  stickyBlocksLazyLoadDistance?: number;
86
84
  forceStickBlocks?: 'before' | 'after' | 'both' | 'none';
87
- stateHandlers?: {
88
- pageChanged?: (currentPagePos: number, pageData?: PropsPage) => void;
89
- };
85
+ onPageChanged?: (currentPagePos: number, pageData?: PropsPage) => void;
90
86
  }>;
91
87
  /**
92
88
  * Scrollytelling engine component. Orchestrates layered sticky blocks (`back`
@@ -104,16 +100,24 @@ export type Props = WithClassName<{
104
100
  * - `--force-stick-blocks-both` — when `forceStickBlocks === 'both'`.
105
101
  *
106
102
  * ### Data attributes
107
- * - `data-current-page-pos` zero-based index of the page currently in view,
108
- * updated on every page change reported by the internal {@link Paginator}.
103
+ * Both are updated on every page change reported by the internal
104
+ * {@link Paginator}.
105
+ * - `data-current-page-pos` — zero-based index of the page currently in view.
106
+ * - `data-current-page-id` — that page's {@link PropsPage.id}. Absent while the
107
+ * current page carries no id, since the id is optional.
109
108
  *
110
109
  * ### CSS custom properties
111
110
  * Exposed on the root element and updated on resize via the internal
112
111
  * {@link ResizeObserverComponent}:
113
- * - `--scrllgngn-screen-left` / `--PRIVATE-left` — left edge of the bounding rect (px).
114
- * - `--scrllgngn-screen-right` / `--PRIVATE-right` — right edge (px).
115
- * - `--scrllgngn-screen-width` / `--PRIVATE-width` — total width (px).
116
- * - `--scrllgngn-screen-height` / `--PRIVATE-height` — total height (px).
112
+ * - `--lm-scrllgngn-screen-left` / `--lm-scrllgngn-screen-left-raw` — left edge
113
+ * of the bounding rect.
114
+ * - `--lm-scrllgngn-screen-right` / `--lm-scrllgngn-screen-right-raw` — right edge.
115
+ * - `--lm-scrllgngn-screen-width` / `--lm-scrllgngn-screen-width-raw` — total width.
116
+ * - `--lm-scrllgngn-screen-height` / `--lm-scrllgngn-screen-height-raw` — total height.
117
+ *
118
+ * The same four measurements are also exposed as `--PRIVATE-left`, `-right`,
119
+ * `-width` and `-height` for the component's own stylesheet. They are internal:
120
+ * do not read or override them.
117
121
  *
118
122
  * ### Sticky block elements
119
123
  * Each lazy-loaded sticky block receives:
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from 'react';
3
3
  import { clss } from '../../agnostic/css/clss/index.js';
4
- import { randomHash } from '../../agnostic/random/uuid/index.js';
5
4
  import { IntersectionObserverComponent } from '../IntersectionObserver/index.js';
6
5
  import { Paginator } from '../Paginator/index.js';
7
6
  import { ResizeObserverComponent } from '../ResizeObserver/index.js';
8
- import { mergeClassNames } from '../utils/index.js';
7
+ import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
9
8
  import { scrllgngn as publicClassName } from '../public-classnames.js';
9
+ import { consolidateStickyBlocks, lazyLoadedBlocks, toScreenCssProps } from './utils.js';
10
10
  import cssModule from './styles.module.css';
11
11
  /**
12
12
  * Scrollytelling engine component. Orchestrates layered sticky blocks (`back`
@@ -24,16 +24,24 @@ import cssModule from './styles.module.css';
24
24
  * - `--force-stick-blocks-both` — when `forceStickBlocks === 'both'`.
25
25
  *
26
26
  * ### Data attributes
27
- * - `data-current-page-pos` zero-based index of the page currently in view,
28
- * updated on every page change reported by the internal {@link Paginator}.
27
+ * Both are updated on every page change reported by the internal
28
+ * {@link Paginator}.
29
+ * - `data-current-page-pos` — zero-based index of the page currently in view.
30
+ * - `data-current-page-id` — that page's {@link PropsPage.id}. Absent while the
31
+ * current page carries no id, since the id is optional.
29
32
  *
30
33
  * ### CSS custom properties
31
34
  * Exposed on the root element and updated on resize via the internal
32
35
  * {@link ResizeObserverComponent}:
33
- * - `--scrllgngn-screen-left` / `--PRIVATE-left` — left edge of the bounding rect (px).
34
- * - `--scrllgngn-screen-right` / `--PRIVATE-right` — right edge (px).
35
- * - `--scrllgngn-screen-width` / `--PRIVATE-width` — total width (px).
36
- * - `--scrllgngn-screen-height` / `--PRIVATE-height` — total height (px).
36
+ * - `--lm-scrllgngn-screen-left` / `--lm-scrllgngn-screen-left-raw` — left edge
37
+ * of the bounding rect.
38
+ * - `--lm-scrllgngn-screen-right` / `--lm-scrllgngn-screen-right-raw` — right edge.
39
+ * - `--lm-scrllgngn-screen-width` / `--lm-scrllgngn-screen-width-raw` — total width.
40
+ * - `--lm-scrllgngn-screen-height` / `--lm-scrllgngn-screen-height-raw` — total height.
41
+ *
42
+ * The same four measurements are also exposed as `--PRIVATE-left`, `-right`,
43
+ * `-width` and `-height` for the component's own stylesheet. They are internal:
44
+ * do not read or override them.
37
45
  *
38
46
  * ### Sticky block elements
39
47
  * Each lazy-loaded sticky block receives:
@@ -48,7 +56,7 @@ import cssModule from './styles.module.css';
48
56
  * back-blocks layer, front-blocks layer, paginated scrolling content, and
49
57
  * bottom-bound sentinel.
50
58
  */
51
- export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadDistance = 2, forceStickBlocks, stateHandlers, className }) => {
59
+ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadDistance = 2, forceStickBlocks, onPageChanged, className }) => {
52
60
  // State
53
61
  const [topVisible, setTopVis] = useState(false);
54
62
  const [contentVisible, setCntVis] = useState(false);
@@ -58,69 +66,20 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
58
66
  const [partialBoundingRect, setPartialBoundingRect] = useState();
59
67
  // Sticky blocks calculations
60
68
  useEffect(() => {
61
- const consolidatedBlocks = new Map();
62
- for (const page of pages ?? []) {
63
- const pageIndex = pages?.indexOf(page) ?? -1;
64
- for (const block of page.blocks ?? []) {
65
- const blockId = block.id ?? randomHash(12);
66
- const found = consolidatedBlocks.get(blockId);
67
- if (found !== undefined)
68
- consolidatedBlocks.set(blockId, {
69
- ...found,
70
- ...block,
71
- displayOnPages: [
72
- ...found.displayOnPages,
73
- pageIndex
74
- ]
75
- });
76
- else
77
- consolidatedBlocks.set(blockId, {
78
- ...block,
79
- displayOnPages: [pageIndex]
80
- });
81
- }
82
- }
83
- const consolidatedStickyBlocks = new Map(Array
84
- .from(consolidatedBlocks)
85
- .filter((e) => {
86
- const block = e[1];
87
- return block.depth === 'back'
88
- || block.depth === 'front';
89
- }));
90
- setStickyBlocks(consolidatedStickyBlocks);
69
+ setStickyBlocks(consolidateStickyBlocks(pages));
91
70
  }, [pages]);
92
- const lazyLoadedBackBlocks = Array
93
- .from(stickyBlocks)
94
- .filter(([, block]) => block.depth === 'back'
95
- && block.displayOnPages.some(dispPage => {
96
- const absDiff = Math.abs(dispPage - currentPagePos);
97
- return absDiff <= stickyBlocksLazyLoadDistance;
98
- })).map(([, block]) => block)
99
- .sort((a, b) => {
100
- return (a.zIndex ?? -Infinity) - (b.zIndex ?? -Infinity);
101
- });
102
- const lazyLoadedFrontBlocks = Array
103
- .from(stickyBlocks)
104
- .filter(([, block]) => block.depth === 'front'
105
- && block.displayOnPages.some(dispPage => {
106
- const absDiff = Math.abs(dispPage - currentPagePos);
107
- return absDiff <= stickyBlocksLazyLoadDistance;
108
- })).map(([, block]) => block)
109
- .sort((a, b) => {
110
- return (a.zIndex ?? -Infinity) - (b.zIndex ?? -Infinity);
111
- });
71
+ const lazyLoadedBackBlocks = lazyLoadedBlocks(stickyBlocks, 'back', currentPagePos, stickyBlocksLazyLoadDistance);
72
+ const lazyLoadedFrontBlocks = lazyLoadedBlocks(stickyBlocks, 'front', currentPagePos, stickyBlocksLazyLoadDistance);
112
73
  // Handlers
74
+ useChangeDispatch(currentPagePos, pagePos => onPageChanged?.(pagePos, pages?.[pagePos]));
113
75
  const handleTopBoundDetect = e => setTopVis(e.ioEntry?.isIntersecting ?? false);
114
76
  const handleCntDetect = e => setCntVis(e.ioEntry?.isIntersecting ?? false);
115
77
  const handleBtmBoundDetect = e => setBtmVis(e.ioEntry?.isIntersecting ?? false);
116
- const handlePageChange = statePages => {
78
+ const handlePagesChanged = statePages => {
117
79
  const curPagePos = statePages.findIndex(page => page.position === 'curr');
118
80
  if (curPagePos === -1)
119
81
  return;
120
- if (curPagePos === currentPagePos)
121
- return;
122
82
  setCurrentPagePos(curPagePos);
123
- stateHandlers?.pageChanged?.(curPagePos, pages?.[curPagePos]);
124
83
  };
125
84
  const handleResize = ({ boundingClientRect }) => {
126
85
  if (partialBoundingRect === undefined
@@ -140,24 +99,8 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
140
99
  'force-stick-blocks-after': forceStickBlocks === 'after',
141
100
  'force-stick-blocks-both': forceStickBlocks === 'both'
142
101
  }), className);
143
- const customCssProps = {};
144
- if (partialBoundingRect?.left !== undefined) {
145
- customCssProps[`--${publicClassName}-screen-left`] = `${partialBoundingRect.left}px`;
146
- customCssProps['--PRIVATE-left'] = `${partialBoundingRect.left}px`;
147
- }
148
- if (partialBoundingRect?.right !== undefined) {
149
- customCssProps[`--${publicClassName}-screen-right`] = `${partialBoundingRect.right}px`;
150
- customCssProps['--PRIVATE-right'] = `${partialBoundingRect.right}px`;
151
- }
152
- if (partialBoundingRect?.width !== undefined) {
153
- customCssProps[`--${publicClassName}-screen-width`] = `${partialBoundingRect.width}px`;
154
- customCssProps['--PRIVATE-width'] = `${partialBoundingRect.width}px`;
155
- }
156
- if (partialBoundingRect?.height !== undefined) {
157
- customCssProps[`--${publicClassName}-screen-height`] = `${partialBoundingRect.height}px`;
158
- customCssProps['--PRIVATE-height'] = `${partialBoundingRect.height}px`;
159
- }
160
- return _jsx("div", { className: rootClss, "data-current-page-pos": currentPagePos, style: { ...customCssProps }, children: _jsxs(ResizeObserverComponent, { onResized: handleResize, children: [_jsx("div", { className: c('top-bound'), children: _jsx(IntersectionObserverComponent, { onIntersected: handleTopBoundDetect }) }), _jsx("div", { className: c('back-blocks'), children: lazyLoadedBackBlocks.map((block, blockPos) => {
102
+ const customCssProps = toScreenCssProps(partialBoundingRect);
103
+ return _jsx("div", { className: rootClss, "data-current-page-pos": currentPagePos, "data-current-page-id": pages?.[currentPagePos]?.id, style: { ...customCssProps }, children: _jsxs(ResizeObserverComponent, { onResized: handleResize, children: [_jsx("div", { className: c('top-bound'), children: _jsx(IntersectionObserverComponent, { onIntersected: handleTopBoundDetect }) }), _jsx("div", { className: c('back-blocks'), children: lazyLoadedBackBlocks.map((block, blockPos) => {
161
104
  const isActive = block.displayOnPages.includes(currentPagePos);
162
105
  const blockClss = c('back-block', {
163
106
  active: isActive,
@@ -171,9 +114,7 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
171
114
  'lazy-loaded': !isActive
172
115
  });
173
116
  return _jsx("div", { className: blockClss, style: { zIndex: blockPos }, children: block.children });
174
- }) }), _jsx("div", { className: c('scrolling-content'), children: _jsx(IntersectionObserverComponent, { onIntersected: handleCntDetect, children: _jsx(Paginator, { thresholdOffsetPercent: thresholdOffsetPercent, stateHandlers: {
175
- pageChanged: handlePageChange
176
- }, children: pages?.map(page => {
117
+ }) }), _jsx("div", { className: c('scrolling-content'), children: _jsx(IntersectionObserverComponent, { onIntersected: handleCntDetect, children: _jsx(Paginator, { thresholdOffsetPercent: thresholdOffsetPercent, onPagesChanged: handlePagesChanged, children: pages?.map(page => {
177
118
  const scrollBlocks = page.blocks
178
119
  ?.filter(b => b.depth === 'scroll' || b.depth === undefined) ?? [];
179
120
  // eslint-disable-next-line @typescript-eslint/promise-function-async