@design-edito/tools 0.5.5 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agnostic/colors/index.d.ts +2 -2
- package/agnostic/colors/index.js +2 -2
- package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +6 -6
- package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +6 -6
- package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +2 -2
- package/agnostic/html/hyper-json/smart-tags/isolated/index.js +2 -2
- package/agnostic/html/index.d.ts +1 -1
- package/agnostic/html/index.js +1 -1
- package/agnostic/misc/index.d.ts +1 -1
- package/agnostic/misc/index.js +1 -1
- package/agnostic/numbers/index.d.ts +1 -1
- package/agnostic/numbers/index.js +1 -1
- package/agnostic/objects/index.d.ts +1 -1
- package/agnostic/objects/index.js +1 -1
- package/agnostic/strings/index.d.ts +1 -1
- package/agnostic/strings/index.js +1 -1
- package/components/Paginator/index.d.ts +5 -0
- package/components/Paginator/index.js +2 -1
- package/components/Scrllgngn/index.d.ts +32 -2
- package/components/Scrllgngn/index.js +158 -11
- package/components/Scrllgngn/utils.d.ts +138 -3
- package/components/Scrllgngn/utils.js +197 -4
- package/node/@aws-s3/storage/directory/index.d.ts +1 -1
- package/node/@aws-s3/storage/directory/index.js +1 -1
- package/node/@google-cloud/storage/file/index.d.ts +2 -2
- package/node/@google-cloud/storage/file/index.js +2 -2
- package/node/cloud-storage/operations/index.d.ts +1 -1
- package/node/cloud-storage/operations/index.js +1 -1
- package/node/images/index.d.ts +1 -1
- package/node/images/index.js +1 -1
- package/node/images/transform/operations/index.d.ts +1 -1
- package/node/images/transform/operations/index.js +1 -1
- package/node/index.d.ts +1 -1
- package/node/index.js +1 -1
- package/node/sftp/file/index.d.ts +1 -1
- package/node/sftp/file/index.js +1 -1
- package/package.json +1 -1
|
@@ -4,9 +4,9 @@ export * as convert from './convert/index.js'
|
|
|
4
4
|
export * as distance from './distance/index.js'
|
|
5
5
|
export * as grayscale from './grayscale/index.js'
|
|
6
6
|
export * as invert from './invert/index.js'
|
|
7
|
-
export * as lerp from './lerp/index.js'
|
|
8
7
|
export * as luminance from './luminance/index.js'
|
|
8
|
+
export * as lerp from './lerp/index.js'
|
|
9
9
|
export * as palette from './palette/index.js'
|
|
10
|
-
export * as rotate from './rotate/index.js'
|
|
11
10
|
export * as tidy from './tidy/index.js'
|
|
11
|
+
export * as rotate from './rotate/index.js'
|
|
12
12
|
export * as typechecks from './typechecks/index.js'
|
package/agnostic/colors/index.js
CHANGED
|
@@ -4,9 +4,9 @@ export * as convert from './convert/index.js'
|
|
|
4
4
|
export * as distance from './distance/index.js'
|
|
5
5
|
export * as grayscale from './grayscale/index.js'
|
|
6
6
|
export * as invert from './invert/index.js'
|
|
7
|
-
export * as lerp from './lerp/index.js'
|
|
8
7
|
export * as luminance from './luminance/index.js'
|
|
8
|
+
export * as lerp from './lerp/index.js'
|
|
9
9
|
export * as palette from './palette/index.js'
|
|
10
|
-
export * as rotate from './rotate/index.js'
|
|
11
10
|
export * as tidy from './tidy/index.js'
|
|
11
|
+
export * as rotate from './rotate/index.js'
|
|
12
12
|
export * as typechecks from './typechecks/index.js'
|
|
@@ -2,9 +2,9 @@ export * as add from './add/index.js'
|
|
|
2
2
|
export * as addclass from './addclass/index.js'
|
|
3
3
|
export * as and from './and/index.js'
|
|
4
4
|
export * as append from './append/index.js'
|
|
5
|
+
export * as clone from './clone/index.js'
|
|
5
6
|
export * as at from './at/index.js'
|
|
6
7
|
export * as call from './call/index.js'
|
|
7
|
-
export * as clone from './clone/index.js'
|
|
8
8
|
export * as deleteproperties from './deleteproperties/index.js'
|
|
9
9
|
export * as equals from './equals/index.js'
|
|
10
10
|
export * as getattribute from './getattribute/index.js'
|
|
@@ -12,22 +12,22 @@ export * as getproperties from './getproperties/index.js'
|
|
|
12
12
|
export * as getproperty from './getproperty/index.js'
|
|
13
13
|
export * as hjparse from './hjparse/index.js'
|
|
14
14
|
export * as hjstringify from './hjstringify/index.js'
|
|
15
|
-
export * as if from './if/index.js'
|
|
16
15
|
export * as initialize from './initialize/index.js'
|
|
16
|
+
export * as if from './if/index.js'
|
|
17
17
|
export * as join from './join/index.js'
|
|
18
|
-
export * as length from './length/index.js'
|
|
19
18
|
export * as map from './map/index.js'
|
|
19
|
+
export * as length from './length/index.js'
|
|
20
20
|
export * as negate from './negate/index.js'
|
|
21
21
|
export * as notrailing from './notrailing/index.js'
|
|
22
|
-
export * as or from './or/index.js'
|
|
23
22
|
export * as pickrandom from './pickrandom/index.js'
|
|
24
23
|
export * as populate from './populate/index.js'
|
|
25
24
|
export * as print from './print/index.js'
|
|
26
|
-
export * as
|
|
25
|
+
export * as or from './or/index.js'
|
|
27
26
|
export * as pusheach from './pusheach/index.js'
|
|
28
27
|
export * as recordtoarray from './recordtoarray/index.js'
|
|
29
28
|
export * as removeattribute from './removeattribute/index.js'
|
|
30
29
|
export * as removeclass from './removeclass/index.js'
|
|
30
|
+
export * as push from './push/index.js'
|
|
31
31
|
export * as renameproperty from './renameproperty/index.js'
|
|
32
32
|
export * as replace from './replace/index.js'
|
|
33
33
|
export * as select from './select/index.js'
|
|
@@ -48,5 +48,5 @@ export * as torecord from './torecord/index.js'
|
|
|
48
48
|
export * as toref from './toref/index.js'
|
|
49
49
|
export * as tostring from './tostring/index.js'
|
|
50
50
|
export * as totext from './totext/index.js'
|
|
51
|
-
export * as transformselected from './transformselected/index.js'
|
|
52
51
|
export * as trim from './trim/index.js'
|
|
52
|
+
export * as transformselected from './transformselected/index.js'
|
|
@@ -2,9 +2,9 @@ export * as add from './add/index.js'
|
|
|
2
2
|
export * as addclass from './addclass/index.js'
|
|
3
3
|
export * as and from './and/index.js'
|
|
4
4
|
export * as append from './append/index.js'
|
|
5
|
+
export * as clone from './clone/index.js'
|
|
5
6
|
export * as at from './at/index.js'
|
|
6
7
|
export * as call from './call/index.js'
|
|
7
|
-
export * as clone from './clone/index.js'
|
|
8
8
|
export * as deleteproperties from './deleteproperties/index.js'
|
|
9
9
|
export * as equals from './equals/index.js'
|
|
10
10
|
export * as getattribute from './getattribute/index.js'
|
|
@@ -12,22 +12,22 @@ export * as getproperties from './getproperties/index.js'
|
|
|
12
12
|
export * as getproperty from './getproperty/index.js'
|
|
13
13
|
export * as hjparse from './hjparse/index.js'
|
|
14
14
|
export * as hjstringify from './hjstringify/index.js'
|
|
15
|
-
export * as if from './if/index.js'
|
|
16
15
|
export * as initialize from './initialize/index.js'
|
|
16
|
+
export * as if from './if/index.js'
|
|
17
17
|
export * as join from './join/index.js'
|
|
18
|
-
export * as length from './length/index.js'
|
|
19
18
|
export * as map from './map/index.js'
|
|
19
|
+
export * as length from './length/index.js'
|
|
20
20
|
export * as negate from './negate/index.js'
|
|
21
21
|
export * as notrailing from './notrailing/index.js'
|
|
22
|
-
export * as or from './or/index.js'
|
|
23
22
|
export * as pickrandom from './pickrandom/index.js'
|
|
24
23
|
export * as populate from './populate/index.js'
|
|
25
24
|
export * as print from './print/index.js'
|
|
26
|
-
export * as
|
|
25
|
+
export * as or from './or/index.js'
|
|
27
26
|
export * as pusheach from './pusheach/index.js'
|
|
28
27
|
export * as recordtoarray from './recordtoarray/index.js'
|
|
29
28
|
export * as removeattribute from './removeattribute/index.js'
|
|
30
29
|
export * as removeclass from './removeclass/index.js'
|
|
30
|
+
export * as push from './push/index.js'
|
|
31
31
|
export * as renameproperty from './renameproperty/index.js'
|
|
32
32
|
export * as replace from './replace/index.js'
|
|
33
33
|
export * as select from './select/index.js'
|
|
@@ -48,5 +48,5 @@ export * as torecord from './torecord/index.js'
|
|
|
48
48
|
export * as toref from './toref/index.js'
|
|
49
49
|
export * as tostring from './tostring/index.js'
|
|
50
50
|
export * as totext from './totext/index.js'
|
|
51
|
-
export * as transformselected from './transformselected/index.js'
|
|
52
51
|
export * as trim from './trim/index.js'
|
|
52
|
+
export * as transformselected from './transformselected/index.js'
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export * as any from './any/index.js'
|
|
2
|
-
export * as array from './array/index.js'
|
|
3
2
|
export * as boolean from './boolean/index.js'
|
|
3
|
+
export * as array from './array/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'
|
|
8
7
|
export * as null from './null/index.js'
|
|
8
|
+
export * as nodelist from './nodelist/index.js'
|
|
9
9
|
export * as number from './number/index.js'
|
|
10
10
|
export * as record from './record/index.js'
|
|
11
11
|
export * as ref from './ref/index.js'
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export * as any from './any/index.js'
|
|
2
|
-
export * as array from './array/index.js'
|
|
3
2
|
export * as boolean from './boolean/index.js'
|
|
3
|
+
export * as array from './array/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'
|
|
8
7
|
export * as null from './null/index.js'
|
|
8
|
+
export * as nodelist from './nodelist/index.js'
|
|
9
9
|
export * as number from './number/index.js'
|
|
10
10
|
export * as record from './record/index.js'
|
|
11
11
|
export * as ref from './ref/index.js'
|
package/agnostic/html/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ export * as insertNode from './insert-node/index.js'
|
|
|
6
6
|
export * as placeholders from './placeholders/index.js'
|
|
7
7
|
export * as replaceInElement from './replace-in-element/index.js'
|
|
8
8
|
export * as selectorToElement from './selector-to-element/index.js'
|
|
9
|
-
export * as stringToNodes from './string-to-nodes/index.js'
|
|
10
9
|
export * as watchSelection from './watch-selection/index.js'
|
|
10
|
+
export * as stringToNodes from './string-to-nodes/index.js'
|
package/agnostic/html/index.js
CHANGED
|
@@ -6,5 +6,5 @@ export * as insertNode from './insert-node/index.js'
|
|
|
6
6
|
export * as placeholders from './placeholders/index.js'
|
|
7
7
|
export * as replaceInElement from './replace-in-element/index.js'
|
|
8
8
|
export * as selectorToElement from './selector-to-element/index.js'
|
|
9
|
-
export * as stringToNodes from './string-to-nodes/index.js'
|
|
10
9
|
export * as watchSelection from './watch-selection/index.js'
|
|
10
|
+
export * as stringToNodes from './string-to-nodes/index.js'
|
package/agnostic/misc/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export * as assert from './assert/index.js'
|
|
2
|
-
export * as cast from './cast/index.js'
|
|
3
2
|
export * as connection from './connection/index.js'
|
|
4
3
|
export * as crawler from './crawler/index.js'
|
|
5
4
|
export * as crossenv from './crossenv/index.js'
|
|
6
5
|
export * as dataSize from './data-size/index.js'
|
|
7
6
|
export * as isConstructorFunction from './is-constructor-function/index.js'
|
|
8
7
|
export * as isNullish from './is-nullish/index.js'
|
|
8
|
+
export * as cast from './cast/index.js'
|
|
9
9
|
export * as logs from './logs/index.js'
|
|
10
10
|
export * as loremIpsum from './lorem-ipsum/index.js'
|
|
11
11
|
export * as normalizeExtension from './normalize-extension/index.js'
|
package/agnostic/misc/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export * as assert from './assert/index.js'
|
|
2
|
-
export * as cast from './cast/index.js'
|
|
3
2
|
export * as connection from './connection/index.js'
|
|
4
3
|
export * as crawler from './crawler/index.js'
|
|
5
4
|
export * as crossenv from './crossenv/index.js'
|
|
6
5
|
export * as dataSize from './data-size/index.js'
|
|
7
6
|
export * as isConstructorFunction from './is-constructor-function/index.js'
|
|
8
7
|
export * as isNullish from './is-nullish/index.js'
|
|
8
|
+
export * as cast from './cast/index.js'
|
|
9
9
|
export * as logs from './logs/index.js'
|
|
10
10
|
export * as loremIpsum from './lorem-ipsum/index.js'
|
|
11
11
|
export * as normalizeExtension from './normalize-extension/index.js'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * as absoluteModulo from './absolute-modulo/index.js'
|
|
2
|
-
export * as approximateRational from './approximate-rational/index.js'
|
|
3
2
|
export * as clamp from './clamp/index.js'
|
|
3
|
+
export * as approximateRational from './approximate-rational/index.js'
|
|
4
4
|
export * as geometricProgressions from './geometric-progressions/index.js'
|
|
5
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
|
-
export * as approximateRational from './approximate-rational/index.js'
|
|
3
2
|
export * as clamp from './clamp/index.js'
|
|
3
|
+
export * as approximateRational from './approximate-rational/index.js'
|
|
4
4
|
export * as geometricProgressions from './geometric-progressions/index.js'
|
|
5
5
|
export * as interpolate from './interpolate/index.js'
|
|
6
6
|
export * as round from './round/index.js'
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export * as deepGetProperty from './deep-get-property/index.js'
|
|
2
2
|
export * as enums from './enums/index.js'
|
|
3
3
|
export * as flattenGetters from './flatten-getters/index.js'
|
|
4
|
-
export * as isObject from './is-object/index.js'
|
|
5
4
|
export * as isRecord from './is-record/index.js'
|
|
5
|
+
export * as isObject from './is-object/index.js'
|
|
6
6
|
export * as recordFormat from './record-format/index.js'
|
|
7
7
|
export * as recordMap from './record-map/index.js'
|
|
8
8
|
export * as sortKeys from './sort-keys/index.js'
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export * as deepGetProperty from './deep-get-property/index.js'
|
|
2
2
|
export * as enums from './enums/index.js'
|
|
3
3
|
export * as flattenGetters from './flatten-getters/index.js'
|
|
4
|
-
export * as isObject from './is-object/index.js'
|
|
5
4
|
export * as isRecord from './is-record/index.js'
|
|
5
|
+
export * as isObject from './is-object/index.js'
|
|
6
6
|
export * as recordFormat from './record-format/index.js'
|
|
7
7
|
export * as recordMap from './record-map/index.js'
|
|
8
8
|
export * as sortKeys from './sort-keys/index.js'
|
|
@@ -2,7 +2,7 @@ export * as charCodes from './char-codes/index.js'
|
|
|
2
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
|
-
export * as replaceAll from './replace-all/index.js'
|
|
6
5
|
export * as splitTrim from './split-trim/index.js'
|
|
7
6
|
export * as toAlphanum from './to-alphanum/index.js'
|
|
8
7
|
export * as trim from './trim/index.js'
|
|
8
|
+
export * as replaceAll from './replace-all/index.js'
|
|
@@ -2,7 +2,7 @@ export * as charCodes from './char-codes/index.js'
|
|
|
2
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
|
-
export * as replaceAll from './replace-all/index.js'
|
|
6
5
|
export * as splitTrim from './split-trim/index.js'
|
|
7
6
|
export * as toAlphanum from './to-alphanum/index.js'
|
|
8
7
|
export * as trim from './trim/index.js'
|
|
8
|
+
export * as replaceAll from './replace-all/index.js'
|
|
@@ -32,6 +32,10 @@ type DirectionState = 'forwards' | 'backwards' | null;
|
|
|
32
32
|
* trigger it again.
|
|
33
33
|
* @property onPagesChanged - Called after any page's {@link PageState} changed,
|
|
34
34
|
* with a flat array of every page's state, ordered by position.
|
|
35
|
+
* @property onPageElementsChanged - Called with the page slot elements, in order,
|
|
36
|
+
* whenever the observed set changes. Unlike the other handlers it *does* fire on
|
|
37
|
+
* mount: the elements don't exist before it, so their appearance is the change.
|
|
38
|
+
* Lets a parent measure the pages without reaching into this component's DOM.
|
|
35
39
|
* @property className - Optional additional class name(s) applied to the root element.
|
|
36
40
|
* @property children - Each direct child is treated as an individual page slot.
|
|
37
41
|
*/
|
|
@@ -39,6 +43,7 @@ export type Props = PropsWithChildren<WithClassName<{
|
|
|
39
43
|
thresholdOffsetPercent?: number;
|
|
40
44
|
onDirectionChanged?: (direction: DirectionState) => void;
|
|
41
45
|
onPagesChanged?: (pages: PageState[]) => void;
|
|
46
|
+
onPageElementsChanged?: (pageElements: HTMLElement[]) => void;
|
|
42
47
|
}>>;
|
|
43
48
|
/**
|
|
44
49
|
* A scroll-driven pagination component that tracks which child page is currently
|
|
@@ -27,7 +27,7 @@ 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, onDirectionChanged, onPagesChanged, className, children }) => {
|
|
30
|
+
export const Paginator = ({ thresholdOffsetPercent, onDirectionChanged, onPagesChanged, onPageElementsChanged, className, children }) => {
|
|
31
31
|
// State, refs, effects
|
|
32
32
|
const [pagesState, setPagesState] = useState(new Map());
|
|
33
33
|
const [directionState, setDirectionState] = useState(null);
|
|
@@ -67,6 +67,7 @@ export const Paginator = ({ thresholdOffsetPercent, onDirectionChanged, onPagesC
|
|
|
67
67
|
if (pagesRef.current === null)
|
|
68
68
|
return;
|
|
69
69
|
const pages = Array.from(pagesRef.current.children);
|
|
70
|
+
onPageElementsChanged?.(pages.filter(page => page instanceof HTMLElement));
|
|
70
71
|
const observerRootMargin = `-${thresholdOffsetPercent ?? 0}%`
|
|
71
72
|
+ ' 0px'
|
|
72
73
|
+ ` -${100 - (thresholdOffsetPercent ?? 0)}%`
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { type PropsWithChildren, type FunctionComponent } from 'react';
|
|
2
2
|
import type { WithClassName } from '../utils/types.js';
|
|
3
|
+
import { type TrackedBlockContext } from './utils.js';
|
|
3
4
|
/**
|
|
4
5
|
* Common properties shared by all block types.
|
|
5
6
|
*
|
|
6
7
|
* @property id - Optional stable identifier for the block. Used to consolidate
|
|
7
8
|
* blocks with the same id across multiple pages into a single sticky block
|
|
8
9
|
* displayed across those pages.
|
|
9
|
-
* @property
|
|
10
|
+
* @property onScrolled - Called with this block's {@link TrackedBlockContext} on
|
|
11
|
+
* every frame the scroll moved it, while it is displayed. Declaring it is what
|
|
12
|
+
* turns tracking on for the block — nothing is measured for a block without it.
|
|
10
13
|
* @property children - Content rendered inside the block.
|
|
11
14
|
*/
|
|
12
15
|
export type PropsCommonBlock = PropsWithChildren<{
|
|
13
16
|
id?: string;
|
|
14
|
-
|
|
17
|
+
onScrolled?: (context: TrackedBlockContext) => void;
|
|
15
18
|
}>;
|
|
16
19
|
/**
|
|
17
20
|
* A block that scrolls with the page content.
|
|
@@ -129,10 +132,37 @@ export type Props = WithClassName<{
|
|
|
129
132
|
* - An inline `z-index` derived from the block's position in the sorted stack
|
|
130
133
|
* (overridden by {@link PropsStickyBlock.zIndex} if provided).
|
|
131
134
|
*
|
|
135
|
+
* ### Scroll block elements
|
|
136
|
+
* Each scroll block sits in its own wrapper, whatever it does — a block that isn't
|
|
137
|
+
* tracked still gets one, so the DOM keeps the same shape either way.
|
|
138
|
+
*
|
|
139
|
+
* ### Tracked block elements
|
|
140
|
+
* A block declaring {@link PropsCommonBlock.onScrolled} also carries its
|
|
141
|
+
* {@link TrackedBlockContext} on its wrapper, for scrollytelling driven in CSS
|
|
142
|
+
* alone. Written straight to the element on the frames they change, so they never
|
|
143
|
+
* re-render the sequence.
|
|
144
|
+
*
|
|
145
|
+
* Progressions, as unitless `0`–`1` ratios:
|
|
146
|
+
* - `--lm-scrllgngn-block-current-page-progression-ratio`
|
|
147
|
+
* - `--lm-scrllgngn-block-display-zone-progression-ratio`
|
|
148
|
+
* - `--lm-scrllgngn-block-contiguous-display-zone-progression-ratio`
|
|
149
|
+
*
|
|
150
|
+
* Position, which only moves with the page:
|
|
151
|
+
* - `data-current-page`
|
|
152
|
+
* - `data-display-zone` — comma-separated page positions.
|
|
153
|
+
* - `data-index-of-current-page-in-display-zone`
|
|
154
|
+
* - `data-contiguous-display-zone`
|
|
155
|
+
* - `data-index-of-current-page-in-contiguous-display-zone`
|
|
156
|
+
*
|
|
132
157
|
* @param props - Component properties.
|
|
133
158
|
* @see {@link Props}
|
|
134
159
|
* @returns A div wrapping the full scrollytelling structure: top-bound sentinel,
|
|
135
160
|
* back-blocks layer, front-blocks layer, paginated scrolling content, and
|
|
136
161
|
* bottom-bound sentinel.
|
|
162
|
+
*
|
|
163
|
+
* @remarks
|
|
164
|
+
* Tracking costs nothing until a block asks for it: with no `onScrolled` anywhere,
|
|
165
|
+
* the component never joins the shared scroll listener. Once it does, each frame
|
|
166
|
+
* measures only the pages the displayed tracked blocks span, not the whole sequence.
|
|
137
167
|
*/
|
|
138
168
|
export declare const Scrllgngn: FunctionComponent<Props>;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { clss } from '../../agnostic/css/clss/index.js';
|
|
4
|
+
import { randomHash } from '../../agnostic/random/uuid/index.js';
|
|
4
5
|
import { IntersectionObserverComponent } from '../IntersectionObserver/index.js';
|
|
5
6
|
import { Paginator } from '../Paginator/index.js';
|
|
6
7
|
import { ResizeObserverComponent } from '../ResizeObserver/index.js';
|
|
8
|
+
import { subscribe, unsubscribe } from '../ScrollListener/utils.js';
|
|
7
9
|
import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
|
|
8
10
|
import { scrllgngn as publicClassName } from '../public-classnames.js';
|
|
9
|
-
import { consolidateStickyBlocks, lazyLoadedBlocks, toScreenCssProps } from './utils.js';
|
|
11
|
+
import { blockDisplayZones, consolidateStickyBlocks, contextsAreEqual, lazyLoadedBlocks, measurePages, sameDiscretePart, scrollBlocksOf, scrollKey, stickyKey, toScreenCssProps, toTrackedBlockContext, toTrackedBlockCssProps, toTrackedBlockDataAttributes } from './utils.js';
|
|
10
12
|
import cssModule from './styles.module.css';
|
|
11
13
|
/**
|
|
12
14
|
* Scrollytelling engine component. Orchestrates layered sticky blocks (`back`
|
|
@@ -50,11 +52,38 @@ import cssModule from './styles.module.css';
|
|
|
50
52
|
* - An inline `z-index` derived from the block's position in the sorted stack
|
|
51
53
|
* (overridden by {@link PropsStickyBlock.zIndex} if provided).
|
|
52
54
|
*
|
|
55
|
+
* ### Scroll block elements
|
|
56
|
+
* Each scroll block sits in its own wrapper, whatever it does — a block that isn't
|
|
57
|
+
* tracked still gets one, so the DOM keeps the same shape either way.
|
|
58
|
+
*
|
|
59
|
+
* ### Tracked block elements
|
|
60
|
+
* A block declaring {@link PropsCommonBlock.onScrolled} also carries its
|
|
61
|
+
* {@link TrackedBlockContext} on its wrapper, for scrollytelling driven in CSS
|
|
62
|
+
* alone. Written straight to the element on the frames they change, so they never
|
|
63
|
+
* re-render the sequence.
|
|
64
|
+
*
|
|
65
|
+
* Progressions, as unitless `0`–`1` ratios:
|
|
66
|
+
* - `--lm-scrllgngn-block-current-page-progression-ratio`
|
|
67
|
+
* - `--lm-scrllgngn-block-display-zone-progression-ratio`
|
|
68
|
+
* - `--lm-scrllgngn-block-contiguous-display-zone-progression-ratio`
|
|
69
|
+
*
|
|
70
|
+
* Position, which only moves with the page:
|
|
71
|
+
* - `data-current-page`
|
|
72
|
+
* - `data-display-zone` — comma-separated page positions.
|
|
73
|
+
* - `data-index-of-current-page-in-display-zone`
|
|
74
|
+
* - `data-contiguous-display-zone`
|
|
75
|
+
* - `data-index-of-current-page-in-contiguous-display-zone`
|
|
76
|
+
*
|
|
53
77
|
* @param props - Component properties.
|
|
54
78
|
* @see {@link Props}
|
|
55
79
|
* @returns A div wrapping the full scrollytelling structure: top-bound sentinel,
|
|
56
80
|
* back-blocks layer, front-blocks layer, paginated scrolling content, and
|
|
57
81
|
* bottom-bound sentinel.
|
|
82
|
+
*
|
|
83
|
+
* @remarks
|
|
84
|
+
* Tracking costs nothing until a block asks for it: with no `onScrolled` anywhere,
|
|
85
|
+
* the component never joins the shared scroll listener. Once it does, each frame
|
|
86
|
+
* measures only the pages the displayed tracked blocks span, not the whole sequence.
|
|
58
87
|
*/
|
|
59
88
|
export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadDistance = 2, forceStickBlocks, onPageChanged, onContentVisibilityChanged, className }) => {
|
|
60
89
|
// State
|
|
@@ -64,12 +93,122 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
|
|
|
64
93
|
const [currentPagePos, setCurrentPagePos] = useState(0);
|
|
65
94
|
const [stickyBlocks, setStickyBlocks] = useState(new Map());
|
|
66
95
|
const [partialBoundingRect, setPartialBoundingRect] = useState();
|
|
96
|
+
// Scroll tracking. Everything the per-frame pass reads lives in a ref: it is
|
|
97
|
+
// registered once with the shared scroll listener, and writes straight to the DOM
|
|
98
|
+
// rather than through state, which would re-render the whole sequence per frame.
|
|
99
|
+
const rootRef = useRef(null);
|
|
100
|
+
const subscriptionIdRef = useRef(randomHash(8));
|
|
101
|
+
const pageElementsRef = useRef([]);
|
|
102
|
+
const trackedBlocksRef = useRef(new Map());
|
|
103
|
+
const trackedWrappersRef = useRef(new Map());
|
|
104
|
+
const lastContextsRef = useRef(new Map());
|
|
105
|
+
const currentPagePosRef = useRef(currentPagePos);
|
|
67
106
|
// Sticky blocks calculations
|
|
68
107
|
useEffect(() => {
|
|
69
108
|
setStickyBlocks(consolidateStickyBlocks(pages));
|
|
70
109
|
}, [pages]);
|
|
71
110
|
const lazyLoadedBackBlocks = lazyLoadedBlocks(stickyBlocks, 'back', currentPagePos, stickyBlocksLazyLoadDistance);
|
|
72
111
|
const lazyLoadedFrontBlocks = lazyLoadedBlocks(stickyBlocks, 'front', currentPagePos, stickyBlocksLazyLoadDistance);
|
|
112
|
+
// Tracked blocks, keyed the same way their wrappers are
|
|
113
|
+
const displayZones = blockDisplayZones(pages);
|
|
114
|
+
const trackedBlocks = new Map();
|
|
115
|
+
for (const [blockId, block] of stickyBlocks) {
|
|
116
|
+
if (block.onScrolled === undefined)
|
|
117
|
+
continue;
|
|
118
|
+
trackedBlocks.set(stickyKey(blockId), {
|
|
119
|
+
displayZone: block.id === undefined
|
|
120
|
+
? block.displayOnPages
|
|
121
|
+
: displayZones.get(block.id) ?? block.displayOnPages,
|
|
122
|
+
onScrolled: block.onScrolled
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
pages?.forEach((page, pagePos) => {
|
|
126
|
+
scrollBlocksOf(page).forEach((block, blockPos) => {
|
|
127
|
+
if (block.onScrolled === undefined)
|
|
128
|
+
return;
|
|
129
|
+
trackedBlocks.set(scrollKey(pagePos, blockPos), {
|
|
130
|
+
displayZone: block.id === undefined
|
|
131
|
+
? [pagePos]
|
|
132
|
+
: displayZones.get(block.id) ?? [pagePos],
|
|
133
|
+
onScrolled: block.onScrolled
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
const hasTrackedBlocks = trackedBlocks.size > 0;
|
|
138
|
+
// Fx. no dep. - Keep what the per-frame pass reads in sync with the last render
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
trackedBlocksRef.current = trackedBlocks;
|
|
141
|
+
currentPagePosRef.current = currentPagePos;
|
|
142
|
+
});
|
|
143
|
+
// The per-frame pass: measure only the pages the displayed tracked blocks span,
|
|
144
|
+
// then hand each block its context — once, and only when something moved.
|
|
145
|
+
const handleScrolled = useCallback(() => {
|
|
146
|
+
const trackedBlocks = trackedBlocksRef.current;
|
|
147
|
+
const lastContexts = lastContextsRef.current;
|
|
148
|
+
const currentPage = currentPagePosRef.current;
|
|
149
|
+
const pending = [];
|
|
150
|
+
for (const [key, block] of trackedBlocks) {
|
|
151
|
+
if (block.displayZone.includes(currentPage))
|
|
152
|
+
pending.push([key, block, currentPage]);
|
|
153
|
+
}
|
|
154
|
+
// A block that just left the current page gets one last pass, on the page it was
|
|
155
|
+
// on: its progressions are clamped, so it lands on the edge it crossed instead of
|
|
156
|
+
// freezing part-way.
|
|
157
|
+
for (const [key, lastContext] of lastContexts) {
|
|
158
|
+
const block = trackedBlocks.get(key);
|
|
159
|
+
if (block === undefined) {
|
|
160
|
+
lastContexts.delete(key);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (block.displayZone.includes(currentPage))
|
|
164
|
+
continue;
|
|
165
|
+
pending.push([key, block, lastContext.currentPage]);
|
|
166
|
+
}
|
|
167
|
+
if (pending.length === 0)
|
|
168
|
+
return;
|
|
169
|
+
const pagesToMeasure = new Set();
|
|
170
|
+
for (const [, block, page] of pending) {
|
|
171
|
+
pagesToMeasure.add(page);
|
|
172
|
+
for (const pagePos of block.displayZone)
|
|
173
|
+
pagesToMeasure.add(pagePos);
|
|
174
|
+
}
|
|
175
|
+
const metrics = measurePages(pageElementsRef.current, pagesToMeasure, thresholdOffsetPercent);
|
|
176
|
+
for (const [key, block, page] of pending) {
|
|
177
|
+
const context = toTrackedBlockContext(block.displayZone, page, metrics);
|
|
178
|
+
const lastContext = lastContexts.get(key);
|
|
179
|
+
if (contextsAreEqual(context, lastContext))
|
|
180
|
+
continue;
|
|
181
|
+
const wrapper = trackedWrappersRef.current.get(key);
|
|
182
|
+
if (wrapper !== undefined) {
|
|
183
|
+
for (const [name, value] of Object.entries(toTrackedBlockCssProps(context))) {
|
|
184
|
+
wrapper.style.setProperty(name, value);
|
|
185
|
+
}
|
|
186
|
+
// Discrete values only move with the page, so they don't need a write per frame
|
|
187
|
+
if (!sameDiscretePart(context, lastContext)) {
|
|
188
|
+
for (const [name, value] of Object.entries(toTrackedBlockDataAttributes(context))) {
|
|
189
|
+
wrapper.setAttribute(name, value);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
block.onScrolled(context);
|
|
194
|
+
if (page === currentPage)
|
|
195
|
+
lastContexts.set(key, context);
|
|
196
|
+
else
|
|
197
|
+
lastContexts.delete(key);
|
|
198
|
+
}
|
|
199
|
+
}, [thresholdOffsetPercent]);
|
|
200
|
+
// Fx. dep. hasTrackedBlocks, handleScrolled - Join the shared scroll listener, and
|
|
201
|
+
// only then: no tracked block, no measurement pass at all.
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
if (!hasTrackedBlocks)
|
|
204
|
+
return;
|
|
205
|
+
const subscriptionId = subscriptionIdRef.current;
|
|
206
|
+
subscribe(subscriptionId, {
|
|
207
|
+
rootRef,
|
|
208
|
+
onScrollStateChange: handleScrolled
|
|
209
|
+
});
|
|
210
|
+
return () => unsubscribe(subscriptionId);
|
|
211
|
+
}, [hasTrackedBlocks, handleScrolled]);
|
|
73
212
|
// Handlers
|
|
74
213
|
useChangeDispatch(currentPagePos, pagePos => onPageChanged?.(pagePos, pages?.[pagePos]));
|
|
75
214
|
useChangeDispatch(contentVisible, onContentVisibilityChanged);
|
|
@@ -82,6 +221,17 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
|
|
|
82
221
|
return;
|
|
83
222
|
setCurrentPagePos(curPagePos);
|
|
84
223
|
};
|
|
224
|
+
const handlePageElementsChanged = elements => {
|
|
225
|
+
pageElementsRef.current = elements;
|
|
226
|
+
};
|
|
227
|
+
// Registers a block wrapper under the key its context is tracked by. Blocks come
|
|
228
|
+
// and go with lazy loading, so the map is kept clean on unmount rather than grown.
|
|
229
|
+
const registerWrapper = (key) => (element) => {
|
|
230
|
+
if (element === null)
|
|
231
|
+
trackedWrappersRef.current.delete(key);
|
|
232
|
+
else
|
|
233
|
+
trackedWrappersRef.current.set(key, element);
|
|
234
|
+
};
|
|
85
235
|
const handleResize = ({ boundingClientRect }) => {
|
|
86
236
|
if (partialBoundingRect === undefined
|
|
87
237
|
|| boundingClientRect.left !== partialBoundingRect.left
|
|
@@ -101,24 +251,21 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
|
|
|
101
251
|
'force-stick-blocks-both': forceStickBlocks === 'both'
|
|
102
252
|
}), className);
|
|
103
253
|
const customCssProps = toScreenCssProps(partialBoundingRect);
|
|
104
|
-
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) => {
|
|
254
|
+
return _jsx("div", { ref: rootRef, 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(([blockId, block], blockPos) => {
|
|
105
255
|
const isActive = block.displayOnPages.includes(currentPagePos);
|
|
106
256
|
const blockClss = c('back-block', {
|
|
107
257
|
active: isActive,
|
|
108
258
|
'lazy-loaded': !isActive
|
|
109
259
|
});
|
|
110
|
-
return _jsx("div", { className: blockClss, style: { zIndex: blockPos }, children: block.children });
|
|
111
|
-
}) }), _jsx("div", { className: c('front-blocks'), children: lazyLoadedFrontBlocks.map((block, blockPos) => {
|
|
260
|
+
return _jsx("div", { ref: registerWrapper(stickyKey(blockId)), className: blockClss, style: { zIndex: blockPos }, children: block.children }, blockId);
|
|
261
|
+
}) }), _jsx("div", { className: c('front-blocks'), children: lazyLoadedFrontBlocks.map(([blockId, block], blockPos) => {
|
|
112
262
|
const isActive = block.displayOnPages.includes(currentPagePos);
|
|
113
263
|
const blockClss = c('front-block', {
|
|
114
264
|
active: isActive,
|
|
115
265
|
'lazy-loaded': !isActive
|
|
116
266
|
});
|
|
117
|
-
return _jsx("div", { className: blockClss, style: { zIndex: blockPos }, children: block.children });
|
|
118
|
-
}) }), _jsx("div", { className: c('scrolling-content'), children: _jsx(IntersectionObserverComponent, { onIntersected: handleCntDetect, children: _jsx(Paginator, { thresholdOffsetPercent: thresholdOffsetPercent, onPagesChanged: handlePagesChanged, children: pages?.map(page => {
|
|
119
|
-
|
|
120
|
-
?.filter(b => b.depth === 'scroll' || b.depth === undefined) ?? [];
|
|
121
|
-
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
122
|
-
return _jsx(_Fragment, { children: scrollBlocks.map(b => b.children) });
|
|
267
|
+
return _jsx("div", { ref: registerWrapper(stickyKey(blockId)), className: blockClss, style: { zIndex: blockPos }, children: block.children }, blockId);
|
|
268
|
+
}) }), _jsx("div", { className: c('scrolling-content'), children: _jsx(IntersectionObserverComponent, { onIntersected: handleCntDetect, children: _jsx(Paginator, { thresholdOffsetPercent: thresholdOffsetPercent, onPagesChanged: handlePagesChanged, onPageElementsChanged: handlePageElementsChanged, children: pages?.map((page, pagePos) => {
|
|
269
|
+
return _jsx(_Fragment, { children: scrollBlocksOf(page).map((block, blockPos) => _jsx("div", { ref: registerWrapper(scrollKey(pagePos, blockPos)), className: c('scroll-block'), children: block.children }, blockPos)) });
|
|
123
270
|
}) }) }) }), _jsx("div", { className: c('bottom-bound'), children: _jsx(IntersectionObserverComponent, { onIntersected: handleBtmBoundDetect }) })] }) });
|
|
124
271
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PropsPage, PropsStickyBlock } from './index.js';
|
|
1
|
+
import type { PropsBlock, PropsPage, PropsStickyBlock } from './index.js';
|
|
2
2
|
/** The subset of a bounding rect a {@link Scrllgngn} tracks and exposes. */
|
|
3
3
|
export type ScreenRect = {
|
|
4
4
|
left: number;
|
|
@@ -33,9 +33,11 @@ export declare function consolidateStickyBlocks(pages?: PropsPage[]): Map<string
|
|
|
33
33
|
* @param currentPagePos - Zero-based position of the page in view.
|
|
34
34
|
* @param lazyLoadDistance - How many pages around the current one still mount
|
|
35
35
|
* their blocks.
|
|
36
|
-
* @returns The blocks to render
|
|
36
|
+
* @returns The blocks to render with the id they were consolidated under, stacked
|
|
37
|
+
* by ascending `zIndex`. The id comes along because it is what identifies the block
|
|
38
|
+
* across renders — its own `id` is optional, a consolidation key never is.
|
|
37
39
|
*/
|
|
38
|
-
export declare function lazyLoadedBlocks(blocks: Map<string, ConsolidatedStickyBlock>, depth: 'back' | 'front', currentPagePos: number, lazyLoadDistance: number): ConsolidatedStickyBlock
|
|
40
|
+
export declare function lazyLoadedBlocks(blocks: Map<string, ConsolidatedStickyBlock>, depth: 'back' | 'front', currentPagePos: number, lazyLoadDistance: number): Array<[string, ConsolidatedStickyBlock]>;
|
|
39
41
|
/**
|
|
40
42
|
* Builds the CSS custom properties exposed on a {@link Scrllgngn} root.
|
|
41
43
|
*
|
|
@@ -50,3 +52,136 @@ export declare function lazyLoadedBlocks(blocks: Map<string, ConsolidatedStickyB
|
|
|
50
52
|
* @returns The custom properties, keyed by their full name.
|
|
51
53
|
*/
|
|
52
54
|
export declare function toScreenCssProps(rect?: ScreenRect): Record<string, string>;
|
|
55
|
+
/**
|
|
56
|
+
* Where one tracked block stands in the scroll, as handed to its `onScrolled`.
|
|
57
|
+
*
|
|
58
|
+
* Every progression is a `0`–`1` ratio measured against the threshold line, and
|
|
59
|
+
* weighted **by pixel height**: a zone of unequal pages is not the same as
|
|
60
|
+
* `(indexOfCurrentPage + currentPageProgression) / displayZone.length`.
|
|
61
|
+
*
|
|
62
|
+
* @property currentPage - Zero-based position of the page in view.
|
|
63
|
+
* @property currentPageProgression - `0` when the current page's top meets the
|
|
64
|
+
* threshold, `1` when its bottom does.
|
|
65
|
+
* @property displayZone - Every page the block's `id` appears on. A block without
|
|
66
|
+
* an `id` shows on one page, so its zone holds that page alone and its three
|
|
67
|
+
* progressions are equal.
|
|
68
|
+
* @property indexOfCurrentPageInDisplayZone - Where `currentPage` sits in
|
|
69
|
+
* `displayZone`.
|
|
70
|
+
* @property displayZoneProgression - Progression across the whole zone. On a zone
|
|
71
|
+
* like `[2, 3, 5]` it runs 0 → ~0.66 over pages 2–3, pauses while page 4 shows the
|
|
72
|
+
* block nowhere, then resumes to 1 on page 5.
|
|
73
|
+
* @property contiguousDisplayZone - The unbroken run of `displayZone` containing
|
|
74
|
+
* `currentPage` — `[2, 3]` for the zone above while on page 2 or 3.
|
|
75
|
+
* @property indexOfCurrentPageInContiguousDisplayZone - Where `currentPage` sits in
|
|
76
|
+
* `contiguousDisplayZone`.
|
|
77
|
+
* @property contiguousDisplayZoneProgression - Progression across that run alone,
|
|
78
|
+
* so it always spans a full 0 → 1.
|
|
79
|
+
*/
|
|
80
|
+
export type TrackedBlockContext = {
|
|
81
|
+
currentPage: number;
|
|
82
|
+
currentPageProgression: number;
|
|
83
|
+
displayZone: number[];
|
|
84
|
+
indexOfCurrentPageInDisplayZone: number;
|
|
85
|
+
displayZoneProgression: number;
|
|
86
|
+
contiguousDisplayZone: number[];
|
|
87
|
+
indexOfCurrentPageInContiguousDisplayZone: number;
|
|
88
|
+
contiguousDisplayZoneProgression: number;
|
|
89
|
+
};
|
|
90
|
+
/** How far the threshold line has travelled through one page, in pixels. */
|
|
91
|
+
export type PageScrollMetrics = {
|
|
92
|
+
scrolled: number;
|
|
93
|
+
height: number;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Lists the pages each block id shows on.
|
|
97
|
+
*
|
|
98
|
+
* @param pages - The pages to walk, in order.
|
|
99
|
+
* @returns Zero-based page positions per id, ordered. Blocks without an `id` are
|
|
100
|
+
* left out: they show on their own page and nowhere else.
|
|
101
|
+
*/
|
|
102
|
+
export declare function blockDisplayZones(pages?: PropsPage[]): Map<string, number[]>;
|
|
103
|
+
/**
|
|
104
|
+
* Isolates the unbroken run of pages around one page.
|
|
105
|
+
*
|
|
106
|
+
* @param zone - The pages a block shows on, in any order.
|
|
107
|
+
* @param page - The page the run must contain.
|
|
108
|
+
* @returns The run holding `page`, ascending, or an empty array when `zone` misses
|
|
109
|
+
* it entirely.
|
|
110
|
+
*/
|
|
111
|
+
export declare function contiguousRunContaining(zone: number[], page: number): number[];
|
|
112
|
+
/**
|
|
113
|
+
* Measures how far the threshold line has travelled through each of the pages
|
|
114
|
+
* asked for.
|
|
115
|
+
*
|
|
116
|
+
* Only those pages are measured, not all of them: a long sequence would otherwise
|
|
117
|
+
* pay for a forced layout per page on every frame, where the tracked blocks rarely
|
|
118
|
+
* span more than a handful.
|
|
119
|
+
*
|
|
120
|
+
* @param pageElements - Every page slot, indexed by position.
|
|
121
|
+
* @param pagePositions - The positions worth measuring.
|
|
122
|
+
* @param thresholdOffsetPercent - The same offset the internal `Paginator` uses,
|
|
123
|
+
* as a percentage of the viewport height.
|
|
124
|
+
* @returns The metrics of each measurable page, keyed by position.
|
|
125
|
+
*/
|
|
126
|
+
export declare function measurePages(pageElements: HTMLElement[], pagePositions: Iterable<number>, thresholdOffsetPercent?: number): Map<number, PageScrollMetrics>;
|
|
127
|
+
/**
|
|
128
|
+
* Assembles what one tracked block is told about the scroll.
|
|
129
|
+
*
|
|
130
|
+
* @param displayZone - The pages the block shows on.
|
|
131
|
+
* @param currentPage - Zero-based position of the page in view.
|
|
132
|
+
* @param metrics - Measurements covering at least `displayZone`.
|
|
133
|
+
* @returns The context to hand to `onScrolled`.
|
|
134
|
+
*/
|
|
135
|
+
export declare function toTrackedBlockContext(displayZone: number[], currentPage: number, metrics: Map<number, PageScrollMetrics>): TrackedBlockContext;
|
|
136
|
+
/** Whether two contexts hold the same discrete fields, ignoring the progressions. */
|
|
137
|
+
export declare function sameDiscretePart(a: TrackedBlockContext, b?: TrackedBlockContext): boolean;
|
|
138
|
+
/** Whether two contexts are equal down to the progressions. */
|
|
139
|
+
export declare function contextsAreEqual(a: TrackedBlockContext, b?: TrackedBlockContext): boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Builds the CSS custom properties carrying a tracked block's progressions.
|
|
142
|
+
*
|
|
143
|
+
* Only the continuous fields land here — the discrete ones are `data-` attributes,
|
|
144
|
+
* written by {@link toTrackedBlockDataAttributes}.
|
|
145
|
+
*
|
|
146
|
+
* @param context - The block's current context.
|
|
147
|
+
* @returns The custom properties, keyed by their full name.
|
|
148
|
+
*/
|
|
149
|
+
export declare function toTrackedBlockCssProps(context: TrackedBlockContext): Record<string, string>;
|
|
150
|
+
/**
|
|
151
|
+
* Builds the `data-` attributes carrying a tracked block's discrete position.
|
|
152
|
+
*
|
|
153
|
+
* @param context - The block's current context.
|
|
154
|
+
* @returns The attributes, keyed by their full name.
|
|
155
|
+
*/
|
|
156
|
+
export declare function toTrackedBlockDataAttributes(context: TrackedBlockContext): Record<string, string>;
|
|
157
|
+
/** What the per-frame pass needs to know about one tracked block. */
|
|
158
|
+
export type TrackedBlock = {
|
|
159
|
+
displayZone: number[];
|
|
160
|
+
onScrolled: (context: TrackedBlockContext) => void;
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Keys a tracked sticky block.
|
|
164
|
+
*
|
|
165
|
+
* Sticky and scroll blocks share one map of wrappers, and a sticky block is one
|
|
166
|
+
* element for the whole sequence where a scroll block is one per page — hence two
|
|
167
|
+
* key shapes rather than a single ambiguous one.
|
|
168
|
+
*
|
|
169
|
+
* @param blockId - The id the block was consolidated under.
|
|
170
|
+
* @returns The key its wrapper is registered as.
|
|
171
|
+
*/
|
|
172
|
+
export declare function stickyKey(blockId: string): string;
|
|
173
|
+
/**
|
|
174
|
+
* Keys a tracked scroll block.
|
|
175
|
+
*
|
|
176
|
+
* @param pagePos - Zero-based position of the page it belongs to.
|
|
177
|
+
* @param blockPos - Its position among that page's scroll blocks.
|
|
178
|
+
* @returns The key its wrapper is registered as.
|
|
179
|
+
*/
|
|
180
|
+
export declare function scrollKey(pagePos: number, blockPos: number): string;
|
|
181
|
+
/**
|
|
182
|
+
* Picks the blocks of a page that scroll with the content.
|
|
183
|
+
*
|
|
184
|
+
* @param page - The page to read.
|
|
185
|
+
* @returns Its scroll blocks, in order. `depth` left out means `'scroll'`.
|
|
186
|
+
*/
|
|
187
|
+
export declare function scrollBlocksOf(page: PropsPage): PropsBlock[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clamp } from '../../agnostic/numbers/clamp/index.js';
|
|
1
2
|
import { randomHash } from '../../agnostic/random/uuid/index.js';
|
|
2
3
|
/**
|
|
3
4
|
* Merges the sticky blocks of every page into a single entry per block id.
|
|
@@ -50,18 +51,19 @@ export function consolidateStickyBlocks(pages) {
|
|
|
50
51
|
* @param currentPagePos - Zero-based position of the page in view.
|
|
51
52
|
* @param lazyLoadDistance - How many pages around the current one still mount
|
|
52
53
|
* their blocks.
|
|
53
|
-
* @returns The blocks to render
|
|
54
|
+
* @returns The blocks to render with the id they were consolidated under, stacked
|
|
55
|
+
* by ascending `zIndex`. The id comes along because it is what identifies the block
|
|
56
|
+
* across renders — its own `id` is optional, a consolidation key never is.
|
|
54
57
|
*/
|
|
55
58
|
export function lazyLoadedBlocks(blocks, depth, currentPagePos, lazyLoadDistance) {
|
|
56
59
|
return Array
|
|
57
60
|
.from(blocks)
|
|
58
|
-
.
|
|
59
|
-
.filter(block => block.depth === depth
|
|
61
|
+
.filter(([, block]) => block.depth === depth
|
|
60
62
|
&& block.displayOnPages.some(dispPage => {
|
|
61
63
|
const absDiff = Math.abs(dispPage - currentPagePos);
|
|
62
64
|
return absDiff <= lazyLoadDistance;
|
|
63
65
|
}))
|
|
64
|
-
.sort((a, b) => (a.zIndex ?? -Infinity) - (b.zIndex ?? -Infinity));
|
|
66
|
+
.sort(([, a], [, b]) => (a.zIndex ?? -Infinity) - (b.zIndex ?? -Infinity));
|
|
65
67
|
}
|
|
66
68
|
/**
|
|
67
69
|
* Builds the CSS custom properties exposed on a {@link Scrllgngn} root.
|
|
@@ -100,3 +102,194 @@ export function toScreenCssProps(rect) {
|
|
|
100
102
|
function toPrivateLength(value) {
|
|
101
103
|
return value === undefined ? 'initial' : `${value}px`;
|
|
102
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Lists the pages each block id shows on.
|
|
107
|
+
*
|
|
108
|
+
* @param pages - The pages to walk, in order.
|
|
109
|
+
* @returns Zero-based page positions per id, ordered. Blocks without an `id` are
|
|
110
|
+
* left out: they show on their own page and nowhere else.
|
|
111
|
+
*/
|
|
112
|
+
export function blockDisplayZones(pages) {
|
|
113
|
+
const zones = new Map();
|
|
114
|
+
(pages ?? []).forEach((page, pagePos) => {
|
|
115
|
+
for (const block of page.blocks ?? []) {
|
|
116
|
+
if (block.id === undefined)
|
|
117
|
+
continue;
|
|
118
|
+
const zone = zones.get(block.id) ?? [];
|
|
119
|
+
if (!zone.includes(pagePos))
|
|
120
|
+
zone.push(pagePos);
|
|
121
|
+
zones.set(block.id, zone);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
return zones;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Isolates the unbroken run of pages around one page.
|
|
128
|
+
*
|
|
129
|
+
* @param zone - The pages a block shows on, in any order.
|
|
130
|
+
* @param page - The page the run must contain.
|
|
131
|
+
* @returns The run holding `page`, ascending, or an empty array when `zone` misses
|
|
132
|
+
* it entirely.
|
|
133
|
+
*/
|
|
134
|
+
export function contiguousRunContaining(zone, page) {
|
|
135
|
+
const ascending = [...zone].sort((a, b) => a - b);
|
|
136
|
+
let run = [];
|
|
137
|
+
for (const pagePos of ascending) {
|
|
138
|
+
const previous = run[run.length - 1];
|
|
139
|
+
if (previous !== undefined && pagePos !== previous + 1) {
|
|
140
|
+
if (run.includes(page))
|
|
141
|
+
return run;
|
|
142
|
+
run = [];
|
|
143
|
+
}
|
|
144
|
+
run.push(pagePos);
|
|
145
|
+
}
|
|
146
|
+
return run.includes(page) ? run : [];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Measures how far the threshold line has travelled through each of the pages
|
|
150
|
+
* asked for.
|
|
151
|
+
*
|
|
152
|
+
* Only those pages are measured, not all of them: a long sequence would otherwise
|
|
153
|
+
* pay for a forced layout per page on every frame, where the tracked blocks rarely
|
|
154
|
+
* span more than a handful.
|
|
155
|
+
*
|
|
156
|
+
* @param pageElements - Every page slot, indexed by position.
|
|
157
|
+
* @param pagePositions - The positions worth measuring.
|
|
158
|
+
* @param thresholdOffsetPercent - The same offset the internal `Paginator` uses,
|
|
159
|
+
* as a percentage of the viewport height.
|
|
160
|
+
* @returns The metrics of each measurable page, keyed by position.
|
|
161
|
+
*/
|
|
162
|
+
export function measurePages(pageElements, pagePositions, thresholdOffsetPercent) {
|
|
163
|
+
const thresholdY = window.innerHeight * (thresholdOffsetPercent ?? 0) / 100;
|
|
164
|
+
const measured = new Map();
|
|
165
|
+
for (const pagePos of pagePositions) {
|
|
166
|
+
const element = pageElements[pagePos];
|
|
167
|
+
if (element === undefined)
|
|
168
|
+
continue;
|
|
169
|
+
const { top, height } = element.getBoundingClientRect();
|
|
170
|
+
measured.set(pagePos, {
|
|
171
|
+
scrolled: clamp(thresholdY - top, 0, height),
|
|
172
|
+
height
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return measured;
|
|
176
|
+
}
|
|
177
|
+
/** Pixel-weighted progression across a set of pages, `0` when none is measured. */
|
|
178
|
+
function zoneProgression(zone, metrics) {
|
|
179
|
+
let scrolled = 0;
|
|
180
|
+
let height = 0;
|
|
181
|
+
for (const pagePos of zone) {
|
|
182
|
+
const pageMetrics = metrics.get(pagePos);
|
|
183
|
+
if (pageMetrics === undefined)
|
|
184
|
+
continue;
|
|
185
|
+
scrolled += pageMetrics.scrolled;
|
|
186
|
+
height += pageMetrics.height;
|
|
187
|
+
}
|
|
188
|
+
return height === 0 ? 0 : scrolled / height;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Assembles what one tracked block is told about the scroll.
|
|
192
|
+
*
|
|
193
|
+
* @param displayZone - The pages the block shows on.
|
|
194
|
+
* @param currentPage - Zero-based position of the page in view.
|
|
195
|
+
* @param metrics - Measurements covering at least `displayZone`.
|
|
196
|
+
* @returns The context to hand to `onScrolled`.
|
|
197
|
+
*/
|
|
198
|
+
export function toTrackedBlockContext(displayZone, currentPage, metrics) {
|
|
199
|
+
const contiguousDisplayZone = contiguousRunContaining(displayZone, currentPage);
|
|
200
|
+
return {
|
|
201
|
+
currentPage,
|
|
202
|
+
currentPageProgression: zoneProgression([currentPage], metrics),
|
|
203
|
+
displayZone,
|
|
204
|
+
indexOfCurrentPageInDisplayZone: displayZone.indexOf(currentPage),
|
|
205
|
+
displayZoneProgression: zoneProgression(displayZone, metrics),
|
|
206
|
+
contiguousDisplayZone,
|
|
207
|
+
indexOfCurrentPageInContiguousDisplayZone: contiguousDisplayZone.indexOf(currentPage),
|
|
208
|
+
contiguousDisplayZoneProgression: zoneProgression(contiguousDisplayZone, metrics)
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/** The fields that only move when the page does, so they can be written sparingly. */
|
|
212
|
+
function discretePart(context) {
|
|
213
|
+
return [
|
|
214
|
+
context.currentPage,
|
|
215
|
+
context.indexOfCurrentPageInDisplayZone,
|
|
216
|
+
context.indexOfCurrentPageInContiguousDisplayZone,
|
|
217
|
+
context.displayZone.join(','),
|
|
218
|
+
context.contiguousDisplayZone.join(',')
|
|
219
|
+
].join('|');
|
|
220
|
+
}
|
|
221
|
+
/** Whether two contexts hold the same discrete fields, ignoring the progressions. */
|
|
222
|
+
export function sameDiscretePart(a, b) {
|
|
223
|
+
return b !== undefined && discretePart(a) === discretePart(b);
|
|
224
|
+
}
|
|
225
|
+
/** Whether two contexts are equal down to the progressions. */
|
|
226
|
+
export function contextsAreEqual(a, b) {
|
|
227
|
+
return sameDiscretePart(a, b)
|
|
228
|
+
&& b !== undefined
|
|
229
|
+
&& a.currentPageProgression === b.currentPageProgression
|
|
230
|
+
&& a.displayZoneProgression === b.displayZoneProgression
|
|
231
|
+
&& a.contiguousDisplayZoneProgression === b.contiguousDisplayZoneProgression;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Builds the CSS custom properties carrying a tracked block's progressions.
|
|
235
|
+
*
|
|
236
|
+
* Only the continuous fields land here — the discrete ones are `data-` attributes,
|
|
237
|
+
* written by {@link toTrackedBlockDataAttributes}.
|
|
238
|
+
*
|
|
239
|
+
* @param context - The block's current context.
|
|
240
|
+
* @returns The custom properties, keyed by their full name.
|
|
241
|
+
*/
|
|
242
|
+
export function toTrackedBlockCssProps(context) {
|
|
243
|
+
return {
|
|
244
|
+
'--lm-scrllgngn-block-current-page-progression-ratio': `${context.currentPageProgression}`,
|
|
245
|
+
'--lm-scrllgngn-block-display-zone-progression-ratio': `${context.displayZoneProgression}`,
|
|
246
|
+
'--lm-scrllgngn-block-contiguous-display-zone-progression-ratio': `${context.contiguousDisplayZoneProgression}`
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Builds the `data-` attributes carrying a tracked block's discrete position.
|
|
251
|
+
*
|
|
252
|
+
* @param context - The block's current context.
|
|
253
|
+
* @returns The attributes, keyed by their full name.
|
|
254
|
+
*/
|
|
255
|
+
export function toTrackedBlockDataAttributes(context) {
|
|
256
|
+
return {
|
|
257
|
+
'data-current-page': `${context.currentPage}`,
|
|
258
|
+
'data-display-zone': context.displayZone.join(','),
|
|
259
|
+
'data-index-of-current-page-in-display-zone': `${context.indexOfCurrentPageInDisplayZone}`,
|
|
260
|
+
'data-contiguous-display-zone': context.contiguousDisplayZone.join(','),
|
|
261
|
+
'data-index-of-current-page-in-contiguous-display-zone': `${context.indexOfCurrentPageInContiguousDisplayZone}`
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Keys a tracked sticky block.
|
|
266
|
+
*
|
|
267
|
+
* Sticky and scroll blocks share one map of wrappers, and a sticky block is one
|
|
268
|
+
* element for the whole sequence where a scroll block is one per page — hence two
|
|
269
|
+
* key shapes rather than a single ambiguous one.
|
|
270
|
+
*
|
|
271
|
+
* @param blockId - The id the block was consolidated under.
|
|
272
|
+
* @returns The key its wrapper is registered as.
|
|
273
|
+
*/
|
|
274
|
+
export function stickyKey(blockId) {
|
|
275
|
+
return `sticky:${blockId}`;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Keys a tracked scroll block.
|
|
279
|
+
*
|
|
280
|
+
* @param pagePos - Zero-based position of the page it belongs to.
|
|
281
|
+
* @param blockPos - Its position among that page's scroll blocks.
|
|
282
|
+
* @returns The key its wrapper is registered as.
|
|
283
|
+
*/
|
|
284
|
+
export function scrollKey(pagePos, blockPos) {
|
|
285
|
+
return `scroll:${pagePos}:${blockPos}`;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Picks the blocks of a page that scroll with the content.
|
|
289
|
+
*
|
|
290
|
+
* @param page - The page to read.
|
|
291
|
+
* @returns Its scroll blocks, in order. `depth` left out means `'scroll'`.
|
|
292
|
+
*/
|
|
293
|
+
export function scrollBlocksOf(page) {
|
|
294
|
+
return page.blocks?.filter(block => block.depth === 'scroll' || block.depth === undefined) ?? [];
|
|
295
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export * as copy from './copy/index.js'
|
|
2
|
-
export * as download from './download/index.js'
|
|
3
2
|
export * as exists from './exists/index.js'
|
|
3
|
+
export * as download from './download/index.js'
|
|
4
4
|
export * as generateSignedUrl from './generate-signed-url/index.js'
|
|
5
5
|
export * as getMetadata from './get-metadata/index.js'
|
|
6
6
|
export * as getPermissions from './get-permissions/index.js'
|
|
7
|
-
export * as move from './move/index.js'
|
|
8
7
|
export * as remove from './remove/index.js'
|
|
8
|
+
export * as move from './move/index.js'
|
|
9
9
|
export * as revokeSignedUrls from './revoke-signed-urls/index.js'
|
|
10
10
|
export * as stat from './stat/index.js'
|
|
11
11
|
export * as updateMetadata from './update-metadata/index.js'
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export * as copy from './copy/index.js'
|
|
2
|
-
export * as download from './download/index.js'
|
|
3
2
|
export * as exists from './exists/index.js'
|
|
3
|
+
export * as download from './download/index.js'
|
|
4
4
|
export * as generateSignedUrl from './generate-signed-url/index.js'
|
|
5
5
|
export * as getMetadata from './get-metadata/index.js'
|
|
6
6
|
export * as getPermissions from './get-permissions/index.js'
|
|
7
|
-
export * as move from './move/index.js'
|
|
8
7
|
export * as remove from './remove/index.js'
|
|
8
|
+
export * as move from './move/index.js'
|
|
9
9
|
export * as revokeSignedUrls from './revoke-signed-urls/index.js'
|
|
10
10
|
export * as stat from './stat/index.js'
|
|
11
11
|
export * as updateMetadata from './update-metadata/index.js'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * as copyDir from './copy-dir/index.js'
|
|
2
2
|
export * as copyFile from './copy-file/index.js'
|
|
3
|
-
export * as downloadFile from './download-file/index.js'
|
|
4
3
|
export * as existsFile from './exists-file/index.js'
|
|
4
|
+
export * as downloadFile from './download-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 copyDir from './copy-dir/index.js'
|
|
2
2
|
export * as copyFile from './copy-file/index.js'
|
|
3
|
-
export * as downloadFile from './download-file/index.js'
|
|
4
3
|
export * as existsFile from './exists-file/index.js'
|
|
4
|
+
export * as downloadFile from './download-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'
|
package/node/images/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export * as create from './create/index.js'
|
|
2
1
|
export * as format from './format/index.js'
|
|
2
|
+
export * as create from './create/index.js'
|
|
3
3
|
export * as metadata from './metadata/index.js'
|
|
4
4
|
export * as transform from './transform/index.js'
|
|
5
5
|
export * as utils from './utils/index.js'
|
package/node/images/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export * as create from './create/index.js'
|
|
2
1
|
export * as format from './format/index.js'
|
|
2
|
+
export * as create from './create/index.js'
|
|
3
3
|
export * as metadata from './metadata/index.js'
|
|
4
4
|
export * as transform from './transform/index.js'
|
|
5
5
|
export * as utils from './utils/index.js'
|
|
@@ -3,8 +3,8 @@ export * as brighten from './brighten/index.js'
|
|
|
3
3
|
export * as extend from './extend/index.js'
|
|
4
4
|
export * as extract from './extract/index.js'
|
|
5
5
|
export * as flatten from './flatten/index.js'
|
|
6
|
-
export * as flop from './flop/index.js'
|
|
7
6
|
export * as flip from './flip/index.js'
|
|
7
|
+
export * as flop from './flop/index.js'
|
|
8
8
|
export * as hue from './hue/index.js'
|
|
9
9
|
export * as level from './level/index.js'
|
|
10
10
|
export * as lighten from './lighten/index.js'
|
|
@@ -3,8 +3,8 @@ export * as brighten from './brighten/index.js'
|
|
|
3
3
|
export * as extend from './extend/index.js'
|
|
4
4
|
export * as extract from './extract/index.js'
|
|
5
5
|
export * as flatten from './flatten/index.js'
|
|
6
|
-
export * as flop from './flop/index.js'
|
|
7
6
|
export * as flip from './flip/index.js'
|
|
7
|
+
export * as flop from './flop/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'
|
package/node/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * as awsS3 from './@aws-s3/index.js'
|
|
2
|
-
export * as designEdito from './@design-edito/index.js'
|
|
3
2
|
export * as express from './@express/index.js'
|
|
3
|
+
export * as designEdito from './@design-edito/index.js'
|
|
4
4
|
export * as googleCloud from './@google-cloud/index.js'
|
|
5
5
|
export * as cloudStorage from './cloud-storage/index.js'
|
|
6
6
|
export * as encryption from './encryption/index.js'
|
package/node/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * as awsS3 from './@aws-s3/index.js'
|
|
2
|
-
export * as designEdito from './@design-edito/index.js'
|
|
3
2
|
export * as express from './@express/index.js'
|
|
3
|
+
export * as designEdito from './@design-edito/index.js'
|
|
4
4
|
export * as googleCloud from './@google-cloud/index.js'
|
|
5
5
|
export * as cloudStorage from './cloud-storage/index.js'
|
|
6
6
|
export * as encryption from './encryption/index.js'
|
package/node/sftp/file/index.js
CHANGED