@design-edito/tools 0.5.38 → 0.5.39

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 (120) hide show
  1. package/agnostic/@react/effects/index.d.ts +1 -0
  2. package/agnostic/@react/effects/index.js +1 -0
  3. package/agnostic/@react/effects/use-state-change-dispatch/index.d.ts +22 -0
  4. package/agnostic/@react/effects/use-state-change-dispatch/index.js +31 -0
  5. package/agnostic/@react/index.d.ts +1 -0
  6. package/agnostic/@react/index.js +1 -0
  7. package/agnostic/arrays/index.d.ts +2 -1
  8. package/agnostic/arrays/index.js +2 -1
  9. package/agnostic/arrays/integers-between/index.d.ts +8 -0
  10. package/agnostic/arrays/integers-between/index.js +11 -0
  11. package/agnostic/arrays/integers-between/index.test.d.ts +1 -0
  12. package/agnostic/arrays/integers-between/index.test.js +16 -0
  13. package/agnostic/colors/index.d.ts +1 -1
  14. package/agnostic/colors/index.js +1 -1
  15. package/agnostic/css/index.d.ts +1 -0
  16. package/agnostic/css/index.js +1 -0
  17. package/agnostic/css/merge-class-names/index.d.ts +8 -0
  18. package/agnostic/css/merge-class-names/index.js +23 -0
  19. package/agnostic/css/merge-class-names/index.test.d.ts +1 -0
  20. package/agnostic/css/merge-class-names/index.test.js +52 -0
  21. package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +9 -9
  22. package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +9 -9
  23. package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +1 -1
  24. package/agnostic/html/hyper-json/smart-tags/isolated/index.js +1 -1
  25. package/agnostic/html/index.d.ts +2 -2
  26. package/agnostic/html/index.js +2 -2
  27. package/agnostic/index.d.ts +1 -0
  28. package/agnostic/index.js +1 -0
  29. package/agnostic/misc/index.d.ts +1 -1
  30. package/agnostic/misc/index.js +1 -1
  31. package/agnostic/numbers/index.d.ts +1 -1
  32. package/agnostic/numbers/index.js +1 -1
  33. package/agnostic/objects/index.d.ts +1 -1
  34. package/agnostic/objects/index.js +1 -1
  35. package/agnostic/time/index.d.ts +1 -1
  36. package/agnostic/time/index.js +1 -1
  37. package/components/BeforeAfter/index.controlled.js +1 -1
  38. package/components/BeforeAfter/index.js +2 -2
  39. package/components/Button/index.js +1 -1
  40. package/components/Clippable/index.js +1 -1
  41. package/components/Disclaimer/index.d.ts +2 -2
  42. package/components/Disclaimer/index.js +4 -3
  43. package/components/Drawer/index.js +4 -3
  44. package/components/EventListener/index.js +1 -1
  45. package/components/Gallery/index.js +5 -4
  46. package/components/Iframe/index.js +1 -1
  47. package/components/Image/index.d.ts +1 -1
  48. package/components/Image/index.js +3 -2
  49. package/components/Input/index.js +1 -1
  50. package/components/IntersectionObserver/index.js +1 -1
  51. package/components/JsonEditor/index.js +1 -1
  52. package/components/JsonEditor/types.d.ts +0 -6
  53. package/components/JsonEditor/utils.d.ts +0 -5
  54. package/components/JsonEditor/utils.js +0 -5
  55. package/components/Lightbox/index.d.ts +12 -12
  56. package/components/Lightbox/index.js +37 -35
  57. package/components/Lightbox/store.d.ts +9 -43
  58. package/components/Lightbox/store.js +51 -73
  59. package/components/Lightbox/types.d.ts +16 -0
  60. package/components/Lightbox/types.js +1 -0
  61. package/components/ListLoader/index.controlled.js +19 -13
  62. package/components/ListLoader/index.js +19 -3
  63. package/components/Overlayer/index.js +1 -1
  64. package/components/Paginator/index.js +4 -3
  65. package/components/ResizeObserver/index.js +1 -1
  66. package/components/Scrllgngn/index.d.ts +4 -4
  67. package/components/Scrllgngn/index.js +4 -3
  68. package/components/Scrllgngn/types.d.ts +55 -0
  69. package/components/Scrllgngn/types.js +24 -0
  70. package/components/Scrllgngn/utils.d.ts +2 -38
  71. package/components/ScrollListener/index.d.ts +1 -6
  72. package/components/ScrollListener/index.js +10 -9
  73. package/components/ScrollListener/types.d.ts +0 -3
  74. package/components/Select/index.js +1 -1
  75. package/components/Sequencer/index.d.ts +2 -9
  76. package/components/Sequencer/index.js +26 -128
  77. package/components/Sequencer/types.d.ts +30 -0
  78. package/components/Sequencer/types.js +30 -0
  79. package/components/Sequencer/utils.d.ts +4 -0
  80. package/components/Sequencer/utils.js +25 -0
  81. package/components/ShadowRoot/index.js +1 -1
  82. package/components/Subtitles/index.js +3 -5
  83. package/components/Subtitles/utils.d.ts +0 -51
  84. package/components/Subtitles/utils.js +0 -41
  85. package/components/Textarea/index.js +1 -1
  86. package/components/UIModule/index.js +5 -4
  87. package/components/Video/index.controlled.d.ts +1 -7
  88. package/components/Video/index.controlled.js +13 -12
  89. package/components/Video/index.d.ts +0 -8
  90. package/components/Video/index.js +11 -15
  91. package/components/Video/types.d.ts +33 -0
  92. package/components/Video/utils.d.ts +1 -67
  93. package/components/Video/utils.js +25 -44
  94. package/components/index.d.ts +2 -2
  95. package/components/index.js +2 -2
  96. package/components/utils/index.d.ts +1 -44
  97. package/components/utils/index.js +1 -83
  98. package/components/utils/viewport-behaviours/index.d.ts +8 -14
  99. package/components/utils/viewport-behaviours/index.js +15 -37
  100. package/components/utils/viewport-behaviours/types.d.ts +3 -9
  101. package/components/utils/viewport-behaviours/types.js +3 -4
  102. package/node/@aws-s3/storage/directory/index.d.ts +1 -1
  103. package/node/@aws-s3/storage/directory/index.js +1 -1
  104. package/node/@aws-s3/storage/file/index.d.ts +1 -1
  105. package/node/@aws-s3/storage/file/index.js +1 -1
  106. package/node/cloud-storage/operations/index.d.ts +1 -1
  107. package/node/cloud-storage/operations/index.js +1 -1
  108. package/node/encryption/@aes-256-gcm/index.d.ts +1 -1
  109. package/node/encryption/@aes-256-gcm/index.js +1 -1
  110. package/node/ftps/directory/index.d.ts +1 -1
  111. package/node/ftps/directory/index.js +1 -1
  112. package/node/images/index.d.ts +1 -1
  113. package/node/images/index.js +1 -1
  114. package/node/images/transform/operations/index.d.ts +3 -3
  115. package/node/images/transform/operations/index.js +3 -3
  116. package/node/index.d.ts +2 -2
  117. package/node/index.js +2 -2
  118. package/package.json +44 -1
  119. package/components/ListLoader/utils.d.ts +0 -8
  120. package/components/ListLoader/utils.js +0 -10
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { integersBetween } from '../../agnostic/arrays/integers-between/index.js';
2
3
  import { clss } from '../../agnostic/css/clss/index.js';
4
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
3
5
  import { IntersectionObserverComponent } from '../IntersectionObserver/index.js';
4
- import { mergeClassNames } from '../utils/index.js';
5
6
  import { listLoader as publicClassName } from '../public-classnames.js';
6
- import { rangeBetween } from './utils.js';
7
7
  import cssModule from './styles.module.css';
8
8
  /**
9
9
  * Presentational layer of the paginated list. Renders already-loaded pages as a
@@ -65,21 +65,27 @@ export const ControlledListLoader = ({ className, pages, firstPagePos, lastPageP
65
65
  const prevPage = loadedPages[pos - 1];
66
66
  if (prevPage === undefined)
67
67
  return [];
68
- return rangeBetween(prevPage + 1, pagePos - 1);
68
+ return integersBetween(prevPage + 1, pagePos - 1);
69
69
  });
70
- const slots = [
71
- ...firstLoadedPage !== undefined && firstLoadedPage > firstPagePos
72
- ? [{ pagePos: firstLoadedPage - 1, kind: 'prev' }]
73
- : [],
74
- ...loadedPages.map(pagePos => ({ pagePos, kind: 'items' })),
75
- ...gapPages.map(pagePos => ({ pagePos, kind: 'gap' })),
76
- ...lastLoadedPage !== undefined && lastLoadedPage < lastPagePos
77
- ? [{ pagePos: lastLoadedPage + 1, kind: 'next' }]
78
- : []
79
- ].sort((a, b) => a.pagePos - b.pagePos);
80
70
  const sortedLoadingPages = [...loadingPages ?? []]
81
71
  .filter(pagePos => inBoundsPages.includes(pagePos))
82
72
  .sort((a, b) => a - b);
73
+ const hasNothing = loadedPages.length === 0 && sortedLoadingPages.length === 0;
74
+ const slots = hasNothing
75
+ ? [{
76
+ pagePos: [...inBoundsPages].sort((a, b) => a - b)[0] ?? firstPagePos,
77
+ kind: 'gap'
78
+ }]
79
+ : [
80
+ ...firstLoadedPage !== undefined && firstLoadedPage > firstPagePos
81
+ ? [{ pagePos: firstLoadedPage - 1, kind: 'prev' }]
82
+ : [],
83
+ ...loadedPages.map(pagePos => ({ pagePos, kind: 'items' })),
84
+ ...gapPages.map(pagePos => ({ pagePos, kind: 'gap' })),
85
+ ...lastLoadedPage !== undefined && lastLoadedPage < lastPagePos
86
+ ? [{ pagePos: lastLoadedPage + 1, kind: 'next' }]
87
+ : []
88
+ ].sort((a, b) => a.pagePos - b.pagePos);
83
89
  const c = clss(publicClassName, { cssModule });
84
90
  const rootClss = mergeClassNames(c(null, { loading: sortedLoadingPages.length > 0 }), className);
85
91
  const renderLoadButton = (pagePos, kind) => {
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { integersBetween } from '../../agnostic/arrays/integers-between/index.js';
3
4
  import { toError } from '../../agnostic/misc/cast/index.js';
4
5
  import { clamp } from '../../agnostic/numbers/clamp/index.js';
5
6
  import { ControlledListLoader } from './index.controlled.js';
6
- import { rangeBetween } from './utils.js';
7
7
  /**
8
8
  * Paginated list abstraction. Fetches the pages it is asked for, keeps them
9
9
  * fresh, and drives a {@link ControlledListLoader} with the result.
@@ -32,6 +32,7 @@ export const ListLoader = ({ className, pages, defaultPage, fillGaps = true, dro
32
32
  clamp(defaultPage ?? firstPagePos, firstPagePos, lastPagePos)
33
33
  ]);
34
34
  const requestedPages = useRef(new Set());
35
+ const retryTimeouts = useRef(new Set());
35
36
  // Single source of truth for what should be loaded. Memoized so the effects
36
37
  // below keep a stable dependency when the page set has not actually changed.
37
38
  const currentPages = useMemo(() => (pages ?? internalPages).filter(page => page >= firstPagePos && page <= lastPagePos), [pages, internalPages, firstPagePos, lastPagePos]);
@@ -45,6 +46,11 @@ export const ListLoader = ({ className, pages, defaultPage, fillGaps = true, dro
45
46
  return kept.length === next.length ? next : kept;
46
47
  });
47
48
  }
49
+ const gaveUp = requestedPages.current.has(pagePos)
50
+ && !itemsPages.has(pagePos)
51
+ && !loadingPages.has(pagePos);
52
+ if (gaveUp)
53
+ loadPage(pagePos);
48
54
  onLoadPageClicked?.(pagePos);
49
55
  };
50
56
  const storePage = (page, items) => setItemsPages(curr => {
@@ -85,9 +91,19 @@ export const ListLoader = ({ className, pages, defaultPage, fillGaps = true, dro
85
91
  reportFetchError(page, err);
86
92
  if (retriesLeft <= 0)
87
93
  return setPageLoading(page, false);
88
- window.setTimeout(() => loadPage(page, retriesLeft - 1), fetchRetriesDelayMs);
94
+ const timeout = window.setTimeout(() => {
95
+ retryTimeouts.current.delete(timeout);
96
+ loadPage(page, retriesLeft - 1);
97
+ }, fetchRetriesDelayMs);
98
+ retryTimeouts.current.add(timeout);
89
99
  });
90
100
  };
101
+ // Fx. dep. none - Emptying `requestedPages` is what stops a fetch already in flight.
102
+ useEffect(() => () => {
103
+ retryTimeouts.current.forEach(timeout => { window.clearTimeout(timeout); });
104
+ retryTimeouts.current.clear();
105
+ requestedPages.current.clear();
106
+ }, []);
91
107
  // Fx. dep. `currentPages` - Drops everything held for pages no longer wanted.
92
108
  // The updaters return `curr` untouched when nothing was removed, so an unstable
93
109
  // `pages` prop cannot spin the render loop.
@@ -112,7 +128,7 @@ export const ListLoader = ({ className, pages, defaultPage, fillGaps = true, dro
112
128
  setInternalPages(curr => {
113
129
  if (curr.length === 0)
114
130
  return curr;
115
- const missing = rangeBetween(Math.min(...curr), Math.max(...curr))
131
+ const missing = integersBetween(Math.min(...curr), Math.max(...curr))
116
132
  .filter(page => !curr.includes(page));
117
133
  if (missing.length === 0)
118
134
  return curr;
@@ -1,7 +1,7 @@
1
1
  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
- import { mergeClassNames } from '../utils/index.js';
4
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
5
5
  import { overlayer as publicClassName } from '../public-classnames.js';
6
6
  import cssModule from './styles.module.css';
7
7
  /**
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, Children, useState } from 'react';
3
+ import { useStateChangeDispatch } from '../../agnostic/@react/effects/use-state-change-dispatch/index.js';
3
4
  import { clss } from '../../agnostic/css/clss/index.js';
4
- import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
5
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
5
6
  import { paginator as publicClassName } from '../public-classnames.js';
6
7
  import cssModule from './styles.module.css';
7
8
  /**
@@ -63,10 +64,10 @@ export const Paginator = ({ thresholdOffsetPercent, onDirectionChanged, onPagesC
63
64
  console.warn('Paginator: thresholdOffsetPercent must be a number between 0 and 100, received', thresholdOffsetPercent, `— clamped to ${thresholdPercent}.`);
64
65
  }, [thresholdOffsetPercent, thresholdPercent]);
65
66
  // State dispatch
66
- useChangeDispatch(pagesState, pages => onPagesChanged?.(Array
67
+ useStateChangeDispatch(pagesState, pages => onPagesChanged?.(Array
67
68
  .from(pages)
68
69
  .map(([, pageState]) => pageState)));
69
- useChangeDispatch(directionState, onDirectionChanged);
70
+ useStateChangeDispatch(directionState, onDirectionChanged);
70
71
  // Catch scroll direction listening on scroll events
71
72
  useEffect(() => {
72
73
  let lastCall = 0;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useRef, useEffect, useState } from 'react';
3
3
  import { clss } from '../../agnostic/css/clss/index.js';
4
- import { mergeClassNames } from '../utils/index.js';
4
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
5
5
  import { resizeObserver as publicClassName } from '../public-classnames.js';
6
6
  import cssModule from './styles.module.css';
7
7
  /**
@@ -1,6 +1,6 @@
1
- import { type PropsWithChildren, type FunctionComponent } from 'react';
1
+ import { type FunctionComponent, type PropsWithChildren } from 'react';
2
2
  import type { WithClassName } from '../utils/types.js';
3
- import { type TrackedBlockContext, type ViewportOffset } from './utils.js';
3
+ import type { ForceStickMode, StickyDepth, TrackedBlockContext, ViewportOffset } from './types.js';
4
4
  /**
5
5
  * Common properties shared by all block types.
6
6
  *
@@ -34,7 +34,7 @@ export type PropsScrollBlock = PropsCommonBlock & {
34
34
  * stacked in the order they appear across pages (ascending).
35
35
  */
36
36
  export type PropsStickyBlock = PropsCommonBlock & {
37
- depth: 'back' | 'front';
37
+ depth: StickyDepth;
38
38
  zIndex?: number;
39
39
  };
40
40
  /**
@@ -98,7 +98,7 @@ export type Props = WithClassName<{
98
98
  viewportOffsetBottom?: ViewportOffset;
99
99
  viewportOffsetLeft?: ViewportOffset;
100
100
  stickyBlocksLazyLoadDistance?: number;
101
- forceStickBlocks?: 'before' | 'after' | 'both' | 'none';
101
+ forceStickBlocks?: ForceStickMode;
102
102
  onPageChanged?: (payload: {
103
103
  currentPagePos: number;
104
104
  pageData?: PropsPage;
@@ -1,12 +1,13 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { useStateChangeDispatch } from '../../agnostic/@react/effects/use-state-change-dispatch/index.js';
3
4
  import { clss } from '../../agnostic/css/clss/index.js';
4
5
  import { randomHash } from '../../agnostic/random/uuid/index.js';
6
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
5
7
  import { IntersectionObserverComponent } from '../IntersectionObserver/index.js';
6
8
  import { Paginator } from '../Paginator/index.js';
7
9
  import { ResizeObserverComponent } from '../ResizeObserver/index.js';
8
10
  import { subscribe, unsubscribe } from '../ScrollListener/utils.js';
9
- import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
10
11
  import { scrllgngn as publicClassName } from '../public-classnames.js';
11
12
  import { blockDisplayZones, consolidateStickyBlocks, contextsAreEqual, lazyLoadedBlocks, measurePages, sameDiscretePart, scrollBlocksOf, scrollKey, stickyKey, toPaginatorThresholdPercent, toScreenCssProps, toThresholdY, toTrackedBlockContext, toTrackedBlockCssProps, toTrackedBlockDataAttributes, toViewportOffsetCssProps, toVisibleZoneRect, toVisibleZoneRootMargin, visibleZonePollInterval, visibleZonesAreEqual } from './utils.js';
12
13
  import cssModule from './styles.module.css';
@@ -254,11 +255,11 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, viewportOffsetTop, vi
254
255
  return () => window.clearInterval(interval);
255
256
  }, [isOnScreen, readVisibleZone]);
256
257
  // Handlers
257
- useChangeDispatch(currentPagePos, pagePos => onPageChanged?.({
258
+ useStateChangeDispatch(currentPagePos, pagePos => onPageChanged?.({
258
259
  currentPagePos: pagePos,
259
260
  pageData: pages?.[pagePos]
260
261
  }));
261
- useChangeDispatch(contentVisible, onContentVisibilityChanged);
262
+ useStateChangeDispatch(contentVisible, onContentVisibilityChanged);
262
263
  const handleTopBoundDetect = e => setTopVis(e.ioEntry?.isIntersecting ?? false);
263
264
  const handleCntDetect = e => setCntVis(e.ioEntry?.isIntersecting ?? false);
264
265
  const handleBtmBoundDetect = e => setBtmVis(e.ioEntry?.isIntersecting ?? false);
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The depths that lift a block out of the scroll flow.
3
+ *
4
+ * **Exported because a consumer has to validate what it is handed**, and a type gives a
5
+ * runtime validator nothing to compare against. A block declaring one of these is
6
+ * consolidated across the pages it appears on and rendered in that layer, not inline.
7
+ */
8
+ export declare const SCRLLGNGN_STICKY_DEPTHS: readonly ["back", "front"];
9
+ export type StickyDepth = typeof SCRLLGNGN_STICKY_DEPTHS[number];
10
+ /**
11
+ * Which out-of-range sticky blocks are forced into a stuck state.
12
+ *
13
+ * Exported for the same reason as {@link SCRLLGNGN_STICKY_DEPTHS}: the list is what a
14
+ * validator needs, and the day a mode is added is the day a caller writing it would
15
+ * otherwise silently get nothing.
16
+ */
17
+ export declare const SCRLLGNGN_FORCE_STICK_MODES: readonly ["before", "after", "both", "none"];
18
+ export type ForceStickMode = typeof SCRLLGNGN_FORCE_STICK_MODES[number];
19
+ /** A length as authored: a bare number means pixels, a string is CSS's problem. */
20
+ export type ViewportOffset = number | string;
21
+ /**
22
+ * Where one tracked block stands in the scroll, as handed to its `onScrolled`.
23
+ *
24
+ * Every progression is a `0`–`1` ratio measured against the threshold line, and
25
+ * weighted **by pixel height**: a zone of unequal pages is not the same as
26
+ * `(indexOfCurrentPage + currentPageProgression) / displayZone.length`.
27
+ *
28
+ * @property currentPage - Zero-based position of the page in view.
29
+ * @property currentPageProgression - `0` when the current page's top meets the
30
+ * threshold, `1` when its bottom does.
31
+ * @property displayZone - Every page the block's `id` appears on. A block without
32
+ * an `id` shows on one page, so its zone holds that page alone and its three
33
+ * progressions are equal.
34
+ * @property indexOfCurrentPageInDisplayZone - Where `currentPage` sits in
35
+ * `displayZone`.
36
+ * @property displayZoneProgression - Progression across the whole zone. On a zone
37
+ * like `[2, 3, 5]` it runs 0 → ~0.66 over pages 2–3, pauses while page 4 shows the
38
+ * block nowhere, then resumes to 1 on page 5.
39
+ * @property contiguousDisplayZone - The unbroken run of `displayZone` containing
40
+ * `currentPage` — `[2, 3]` for the zone above while on page 2 or 3.
41
+ * @property indexOfCurrentPageInContiguousDisplayZone - Where `currentPage` sits in
42
+ * `contiguousDisplayZone`.
43
+ * @property contiguousDisplayZoneProgression - Progression across that run alone,
44
+ * so it always spans a full 0 → 1.
45
+ */
46
+ export type TrackedBlockContext = {
47
+ currentPage: number;
48
+ currentPageProgression: number;
49
+ displayZone: number[];
50
+ indexOfCurrentPageInDisplayZone: number;
51
+ displayZoneProgression: number;
52
+ contiguousDisplayZone: number[];
53
+ indexOfCurrentPageInContiguousDisplayZone: number;
54
+ contiguousDisplayZoneProgression: number;
55
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The depths that lift a block out of the scroll flow.
3
+ *
4
+ * **Exported because a consumer has to validate what it is handed**, and a type gives a
5
+ * runtime validator nothing to compare against. A block declaring one of these is
6
+ * consolidated across the pages it appears on and rendered in that layer, not inline.
7
+ */
8
+ export const SCRLLGNGN_STICKY_DEPTHS = [
9
+ 'back',
10
+ 'front'
11
+ ];
12
+ /**
13
+ * Which out-of-range sticky blocks are forced into a stuck state.
14
+ *
15
+ * Exported for the same reason as {@link SCRLLGNGN_STICKY_DEPTHS}: the list is what a
16
+ * validator needs, and the day a mode is added is the day a caller writing it would
17
+ * otherwise silently get nothing.
18
+ */
19
+ export const SCRLLGNGN_FORCE_STICK_MODES = [
20
+ 'before',
21
+ 'after',
22
+ 'both',
23
+ 'none'
24
+ ];
@@ -1,4 +1,5 @@
1
1
  import type { PropsBlock, PropsPage, PropsStickyBlock } from './index.js';
2
+ import type { StickyDepth, TrackedBlockContext, ViewportOffset } from './types.js';
2
3
  /** The subset of a bounding rect a {@link Scrllgngn} tracks and exposes. */
3
4
  export type ScreenRect = {
4
5
  left: number;
@@ -37,7 +38,7 @@ export declare function consolidateStickyBlocks(pages?: PropsPage[]): Map<string
37
38
  * by ascending `zIndex`. The id comes along because it is what identifies the block
38
39
  * across renders — its own `id` is optional, a consolidation key never is.
39
40
  */
40
- export declare function lazyLoadedBlocks(blocks: Map<string, ConsolidatedStickyBlock>, depth: 'back' | 'front', currentPagePos: number, lazyLoadDistance: number): Array<[string, ConsolidatedStickyBlock]>;
41
+ export declare function lazyLoadedBlocks(blocks: Map<string, ConsolidatedStickyBlock>, depth: StickyDepth, currentPagePos: number, lazyLoadDistance: number): Array<[string, ConsolidatedStickyBlock]>;
41
42
  /**
42
43
  * Builds the CSS custom properties exposed on a {@link Scrllgngn} root.
43
44
  *
@@ -58,41 +59,6 @@ export declare function lazyLoadedBlocks(blocks: Map<string, ConsolidatedStickyB
58
59
  * @returns The custom properties, keyed by their full name.
59
60
  */
60
61
  export declare function toScreenCssProps(rect?: ScreenRect, zone?: VisibleZoneRect): Record<string, string>;
61
- /**
62
- * Where one tracked block stands in the scroll, as handed to its `onScrolled`.
63
- *
64
- * Every progression is a `0`–`1` ratio measured against the threshold line, and
65
- * weighted **by pixel height**: a zone of unequal pages is not the same as
66
- * `(indexOfCurrentPage + currentPageProgression) / displayZone.length`.
67
- *
68
- * @property currentPage - Zero-based position of the page in view.
69
- * @property currentPageProgression - `0` when the current page's top meets the
70
- * threshold, `1` when its bottom does.
71
- * @property displayZone - Every page the block's `id` appears on. A block without
72
- * an `id` shows on one page, so its zone holds that page alone and its three
73
- * progressions are equal.
74
- * @property indexOfCurrentPageInDisplayZone - Where `currentPage` sits in
75
- * `displayZone`.
76
- * @property displayZoneProgression - Progression across the whole zone. On a zone
77
- * like `[2, 3, 5]` it runs 0 → ~0.66 over pages 2–3, pauses while page 4 shows the
78
- * block nowhere, then resumes to 1 on page 5.
79
- * @property contiguousDisplayZone - The unbroken run of `displayZone` containing
80
- * `currentPage` — `[2, 3]` for the zone above while on page 2 or 3.
81
- * @property indexOfCurrentPageInContiguousDisplayZone - Where `currentPage` sits in
82
- * `contiguousDisplayZone`.
83
- * @property contiguousDisplayZoneProgression - Progression across that run alone,
84
- * so it always spans a full 0 → 1.
85
- */
86
- export type TrackedBlockContext = {
87
- currentPage: number;
88
- currentPageProgression: number;
89
- displayZone: number[];
90
- indexOfCurrentPageInDisplayZone: number;
91
- displayZoneProgression: number;
92
- contiguousDisplayZone: number[];
93
- indexOfCurrentPageInContiguousDisplayZone: number;
94
- contiguousDisplayZoneProgression: number;
95
- };
96
62
  /** How far the threshold line has travelled through one page, in pixels. */
97
63
  export type PageScrollMetrics = {
98
64
  scrolled: number;
@@ -200,8 +166,6 @@ export type VisibleZoneRect = {
200
166
  width: number;
201
167
  height: number;
202
168
  };
203
- /** A length as authored: a bare number means pixels, a string is CSS's problem. */
204
- export type ViewportOffset = number | string;
205
169
  /** The four edges, as authored on the component. */
206
170
  export type ViewportOffsets = {
207
171
  top?: ViewportOffset;
@@ -12,9 +12,6 @@ import type { ScrollListenerAction } from './types.js';
12
12
  * which is what this component has always done — a starting point and not a setting,
13
13
  * hence `default…`. `defaultTracking={false}` with `whenVisible='track'` is the old
14
14
  * `startOnVisible`, written in the shared grammar.
15
- * @property behavioursSuspended - Holds back the instructions that **start** something,
16
- * and lets through those that stop it. A gate reaches `'track'`: measuring a subtree
17
- * nobody is being shown is work done for no one.
18
15
  * @property onIsTrackingChanged - Called once the effective tracking state changed.
19
16
  * @property onScrollStateChanged - Called after the measured {@link ScrollState}
20
17
  * changed. Receives `undefined` until the first measurement lands.
@@ -42,9 +39,7 @@ export type Props = PropsWithChildren<WithClassName<{
42
39
  onScrollStateChanged?: (scrollState?: ScrollState) => void;
43
40
  onScrollProgressChanged?: (progress: number) => void;
44
41
  onScrollDirectionChanged?: (direction: 'up' | 'down') => void;
45
- }>> & ViewportBehaviours<ScrollListenerAction> & {
46
- behavioursSuspended?: boolean;
47
- };
42
+ }>> & ViewportBehaviours<ScrollListenerAction>;
48
43
  /**
49
44
  * Exposes scroll metrics — both the document's and its own — as CSS custom
50
45
  * properties on its root element, so scroll-driven styling needs no JavaScript
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from 'react';
3
+ import { useStateChangeDispatch } from '../../agnostic/@react/effects/use-state-change-dispatch/index.js';
3
4
  import { clss } from '../../agnostic/css/clss/index.js';
4
5
  import { randomHash } from '../../agnostic/random/uuid/index.js';
5
- import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
6
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
6
7
  import { useViewportBehaviours } from '../utils/viewport-behaviours/index.js';
7
8
  import { scrollListener as publicClassName } from '../public-classnames.js';
8
9
  import { getScrollProgress, subscribe, toScrollCssProps, unsubscribe } from './utils.js';
@@ -64,7 +65,7 @@ import cssModule from './styles.module.css';
64
65
  * for everyone, each element only for itself. The listeners exist only while at
65
66
  * least one instance is tracking.
66
67
  */
67
- export const ScrollListener = ({ tracking, defaultTracking, behavioursSuspended, visibilityThreshold, visibilityRoot, visibilityRootMargin, visibilityOnAfterMs, visibilityOffAfterMs, whenVisible, whenHidden, onIsTrackingChanged, onScrollStateChanged, onVisibilityChanged, onScrollProgressChanged, onScrollDirectionChanged, className, children }) => {
68
+ export const ScrollListener = ({ tracking, defaultTracking, visibilityThreshold, visibilityRoot, visibilityRootMargin, visibilityOnAfterMs, visibilityOffAfterMs, whenVisible, whenHidden, onIsTrackingChanged, onScrollStateChanged, onVisibilityChanged, onScrollProgressChanged, onScrollDirectionChanged, className, children }) => {
68
69
  // State & refs
69
70
  const [subscriberId] = useState(() => randomHash(6));
70
71
  const [internalTracking, setInternalTracking] = useState(defaultTracking ?? true);
@@ -82,8 +83,8 @@ export const ScrollListener = ({ tracking, defaultTracking, behavioursSuspended,
82
83
  // `tracking`: the effective state reads the prop first, so the internal one they write
83
84
  // is simply never consulted.
84
85
  const actions = {
85
- track: { kind: 'start', domain: 'tracking', run: () => setInternalTracking(true) },
86
- untrack: { kind: 'stop', domain: 'tracking', run: () => setInternalTracking(false) }
86
+ track: { domain: 'tracking', run: () => setInternalTracking(true) },
87
+ untrack: { domain: 'tracking', run: () => setInternalTracking(false) }
87
88
  };
88
89
  // The surrender half of the result is dropped: this component has no control a reader
89
90
  // could take over. The observer runs on the root itself — there is no inner box any
@@ -98,13 +99,13 @@ export const ScrollListener = ({ tracking, defaultTracking, behavioursSuspended,
98
99
  whenVisible,
99
100
  whenHidden,
100
101
  onVisibilityChanged
101
- }, actions, behavioursSuspended === true);
102
+ }, actions);
102
103
  // State dispatch
103
- useChangeDispatch(isTracking, onIsTrackingChanged);
104
- useChangeDispatch(scrollState, onScrollStateChanged);
105
- useChangeDispatch(scrollProgress, progress => { if (progress !== undefined)
104
+ useStateChangeDispatch(isTracking, onIsTrackingChanged);
105
+ useStateChangeDispatch(scrollState, onScrollStateChanged);
106
+ useStateChangeDispatch(scrollProgress, progress => { if (progress !== undefined)
106
107
  onScrollProgressChanged?.(progress); });
107
- useChangeDispatch(scrollDirection, direction => { if (direction !== null)
108
+ useStateChangeDispatch(scrollDirection, direction => { if (direction !== null)
108
109
  onScrollDirectionChanged?.(direction); });
109
110
  // Fx. dep. `scrollState` - derive the document scroll direction
110
111
  useEffect(() => {
@@ -24,8 +24,5 @@ export type ScrollListenerAction = ScrollListenerVerb;
24
24
  * and nothing that ever surrenders. The field is part of `ActionSpec` and is declared
25
25
  * honestly rather than split into domains no hand could ever tell apart. The consequence
26
26
  * is that `':force'` has no trigger here.
27
- *
28
- * A gate is the other brake, and it does reach `track`: measuring a subtree nobody is
29
- * being shown is work done for no one.
30
27
  */
31
28
  export type ScrollListenerDomain = 'tracking';
@@ -3,7 +3,7 @@ import { useState } from 'react';
3
3
  import { clss } from '../../agnostic/css/clss/index.js';
4
4
  import { isNotFalsy } from '../../agnostic/booleans/is-falsy/index.js';
5
5
  import { randomHash } from '../../agnostic/random/uuid/index.js';
6
- import { mergeClassNames } from '../utils/index.js';
6
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
7
7
  import { select as publicClassName } from '../public-classnames.js';
8
8
  import cssModule from './styles.module.css';
9
9
  /**
@@ -21,11 +21,6 @@ import type { SequencerAction } from './types.js';
21
21
  * the counter over from the tempo.
22
22
  * @property defaultPlay - Whether it starts advancing. `false` by default, and the
23
23
  * starting point a `'play'` instruction moves from — not a setting, hence `default…`.
24
- * @property behavioursSuspended - Holds back the instructions that **start** something,
25
- * and lets through those that stop it. For a consumer withholding the sequence behind
26
- * something — a warning to accept, typically. What was held back is replayed the moment
27
- * this goes false, so lifting the veil on an already-visible sequence does what was
28
- * asked. `':force'` does not override it.
29
24
  * @property tempo - Speed in beats per minute: one step every `60000 / tempo` ms, so `60`
30
25
  * is a step per second. Clamped to a minimum of `1`.
31
26
  * @property loop - Whether the sequence wraps round. **`false` by default**, so a
@@ -62,9 +57,7 @@ export type Props = PropsWithChildren<WithClassName<{
62
57
  onLooped?: () => void;
63
58
  onReachedFirstStep?: () => void;
64
59
  onReachedLastStep?: () => void;
65
- }>> & ViewportBehaviours<SequencerAction> & {
66
- behavioursSuspended?: boolean;
67
- };
60
+ }>> & ViewportBehaviours<SequencerAction>;
68
61
  /**
69
62
  * A sequencer over its children, which it classifies and never moves.
70
63
  *
@@ -108,7 +101,7 @@ export type Props = PropsWithChildren<WithClassName<{
108
101
  * **Nothing surrenders here.** The layer lets a reader's hand switch an instruction off
109
102
  * for the rest of the mount, and this component has no control for a hand to touch — so
110
103
  * `':force'` has nothing to override, and the single `'playback'` domain is a formality.
111
- * A gate still holds `'play'` back, which is a different brake. @see {@link SequencerDomain}
104
+ * @see {@link SequencerDomain}
112
105
  *
113
106
  * ### On the root
114
107
  * `--playing`, `--at-start`, `--at-end`, `--ended`, plus `data-step`, `data-active-step`,