@design-edito/tools 0.5.31 → 0.5.32

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 (66) hide show
  1. package/agnostic/arrays/index.d.ts +3 -3
  2. package/agnostic/arrays/index.js +3 -3
  3. package/agnostic/colors/index.d.ts +3 -3
  4. package/agnostic/colors/index.js +3 -3
  5. package/agnostic/css/index.d.ts +3 -3
  6. package/agnostic/css/index.js +3 -3
  7. package/agnostic/errors/index.d.ts +1 -1
  8. package/agnostic/errors/index.js +1 -1
  9. package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +8 -8
  10. package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +8 -8
  11. package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +3 -3
  12. package/agnostic/html/hyper-json/smart-tags/isolated/index.js +3 -3
  13. package/agnostic/html/index.d.ts +3 -3
  14. package/agnostic/html/index.js +3 -3
  15. package/agnostic/index.d.ts +3 -3
  16. package/agnostic/index.js +3 -3
  17. package/agnostic/misc/index.d.ts +3 -3
  18. package/agnostic/misc/index.js +3 -3
  19. package/agnostic/numbers/index.d.ts +3 -3
  20. package/agnostic/numbers/index.js +3 -3
  21. package/agnostic/objects/index.d.ts +3 -3
  22. package/agnostic/objects/index.js +3 -3
  23. package/agnostic/sanitization/index.d.ts +1 -1
  24. package/agnostic/sanitization/index.js +1 -1
  25. package/agnostic/strings/index.d.ts +3 -3
  26. package/agnostic/strings/index.js +3 -3
  27. package/agnostic/time/dates/index.d.ts +1 -1
  28. package/agnostic/time/dates/index.js +1 -1
  29. package/components/Gallery/index.d.ts +45 -41
  30. package/components/Gallery/index.js +89 -50
  31. package/components/Gallery/utils.d.ts +36 -1
  32. package/components/Gallery/utils.js +57 -15
  33. package/components/index.d.ts +6 -7
  34. package/components/index.js +6 -7
  35. package/components/public-classnames.d.ts +0 -1
  36. package/components/public-classnames.js +0 -1
  37. package/node/@aws-s3/storage/directory/index.d.ts +1 -1
  38. package/node/@aws-s3/storage/directory/index.js +1 -1
  39. package/node/@aws-s3/storage/file/index.d.ts +1 -1
  40. package/node/@aws-s3/storage/file/index.js +1 -1
  41. package/node/@google-cloud/storage/directory/index.d.ts +1 -1
  42. package/node/@google-cloud/storage/directory/index.js +1 -1
  43. package/node/@google-cloud/storage/file/index.d.ts +2 -2
  44. package/node/@google-cloud/storage/file/index.js +2 -2
  45. package/node/cloud-storage/operations/index.d.ts +2 -2
  46. package/node/cloud-storage/operations/index.js +2 -2
  47. package/node/files/index.d.ts +1 -1
  48. package/node/files/index.js +1 -1
  49. package/node/ftps/directory/index.d.ts +1 -1
  50. package/node/ftps/directory/index.js +1 -1
  51. package/node/images/index.d.ts +1 -1
  52. package/node/images/index.js +1 -1
  53. package/node/images/transform/operations/index.d.ts +5 -5
  54. package/node/images/transform/operations/index.js +5 -5
  55. package/node/index.d.ts +2 -2
  56. package/node/index.js +2 -2
  57. package/node/process/index.d.ts +1 -1
  58. package/node/process/index.js +1 -1
  59. package/node/sftp/index.d.ts +1 -1
  60. package/node/sftp/index.js +1 -1
  61. package/package.json +1 -8
  62. package/components/GalleryAlt/index.d.ts +0 -76
  63. package/components/GalleryAlt/index.js +0 -188
  64. package/components/GalleryAlt/styles.module.css +0 -33
  65. package/components/GalleryAlt/utils.d.ts +0 -36
  66. package/components/GalleryAlt/utils.js +0 -58
package/node/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- export * as awsS3 from './@aws-s3/index.js'
2
1
  export * as designEdito from './@design-edito/index.js'
3
2
  export * as express from './@express/index.js'
4
3
  export * as googleCloud from './@google-cloud/index.js'
4
+ export * as awsS3 from './@aws-s3/index.js'
5
5
  export * as cloudStorage from './cloud-storage/index.js'
6
6
  export * as encryption from './encryption/index.js'
7
- export * as files from './files/index.js'
8
7
  export * as ftps from './ftps/index.js'
9
8
  export * as images from './images/index.js'
10
9
  export * as process from './process/index.js'
11
10
  export * as sftp from './sftp/index.js'
11
+ export * as files from './files/index.js'
package/node/index.js CHANGED
@@ -1,11 +1,11 @@
1
- export * as awsS3 from './@aws-s3/index.js'
2
1
  export * as designEdito from './@design-edito/index.js'
3
2
  export * as express from './@express/index.js'
4
3
  export * as googleCloud from './@google-cloud/index.js'
4
+ export * as awsS3 from './@aws-s3/index.js'
5
5
  export * as cloudStorage from './cloud-storage/index.js'
6
6
  export * as encryption from './encryption/index.js'
7
- export * as files from './files/index.js'
8
7
  export * as ftps from './ftps/index.js'
9
8
  export * as images from './images/index.js'
10
9
  export * as process from './process/index.js'
11
10
  export * as sftp from './sftp/index.js'
11
+ export * as files from './files/index.js'
@@ -1,3 +1,3 @@
1
1
  export * as onExit from './on-exit/index.js'
2
- export * as spawner from './spawner/index.js'
3
2
  export * as promptContinue from './prompt-continue/index.js'
3
+ export * as spawner from './spawner/index.js'
@@ -1,3 +1,3 @@
1
1
  export * as onExit from './on-exit/index.js'
2
- export * as spawner from './spawner/index.js'
3
2
  export * as promptContinue from './prompt-continue/index.js'
3
+ export * as spawner from './spawner/index.js'
@@ -1,2 +1,2 @@
1
- export * as directory from './directory/index.js'
2
1
  export * as file from './file/index.js'
2
+ export * as directory from './directory/index.js'
@@ -1,2 +1,2 @@
1
- export * as directory from './directory/index.js'
2
1
  export * as file from './file/index.js'
2
+ export * as directory from './directory/index.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@design-edito/tools",
3
- "version": "0.5.31",
3
+ "version": "0.5.32",
4
4
  "description": "",
5
5
  "author": "Maxime Fabas",
6
6
  "license": "ISC",
@@ -1453,13 +1453,6 @@
1453
1453
  "./components/Gallery/index.js": {
1454
1454
  "import": "./components/Gallery/index.js"
1455
1455
  },
1456
- "./components/GalleryAlt": {
1457
- "import": "./components/GalleryAlt/index.js",
1458
- "types": "./components/GalleryAlt/index.d.ts"
1459
- },
1460
- "./components/GalleryAlt/index.js": {
1461
- "import": "./components/GalleryAlt/index.js"
1462
- },
1463
1456
  "./components/Iframe": {
1464
1457
  "import": "./components/Iframe/index.js",
1465
1458
  "types": "./components/Iframe/index.d.ts"
@@ -1,76 +0,0 @@
1
- import { type FunctionComponent, type PropsWithChildren, type ReactNode } from 'react';
2
- import type { WithClassName } from '../utils/types.js';
3
- /**
4
- * Props for the GalleryAlt component. Same shape as {@link Gallery}, so the two swap.
5
- *
6
- * @property paddingLeft - Left padding applied to the first slot. A number is pixels.
7
- * Falls back to `padding`, then `0px`.
8
- * @property paddingRight - Right padding applied to the last slot. Same rules.
9
- * @property padding - Shorthand for both ends, used when a side is not set.
10
- * @property prevButtonContent - Content of the "previous" control. Defaults to `"prev"`.
11
- * @property nextButtonContent - Content of the "next" control. Defaults to `"next"`.
12
- * @property paginationContent - Content of each pagination item: a node used for all of
13
- * them, a function of the page index, or undefined to show the index.
14
- * @property defaultActive - Slot to scroll to at mount, in uncontrolled mode.
15
- * @property active - Controlled index. When set, the active slot is this one and hand
16
- * scrolling is disabled.
17
- * @property noSnap - Frees the scroll from snapping to a slot.
18
- * @property onPrevClicked - Fires before the gallery reacts, with the index as it was.
19
- * @property onNextClicked - Fires before the gallery reacts, with the index as it was.
20
- * @property onPaginationClicked - Fires before the gallery reacts, with the index as it
21
- * was and the one aimed at.
22
- * @property onActiveSlotChanged - Fires after the active slot changed, with the new index.
23
- * @property onCanGoLeftChanged - Fires after the ability to scroll further left changed.
24
- * Never on mount.
25
- * @property onCanGoRightChanged - Same, to the right.
26
- * @property className - Added to the root element.
27
- * @property children - One slot per child.
28
- */
29
- export type Props = PropsWithChildren<WithClassName<{
30
- paddingLeft?: string | number;
31
- paddingRight?: string | number;
32
- padding?: string | number;
33
- prevButtonContent?: ReactNode;
34
- nextButtonContent?: ReactNode;
35
- paginationContent?: ReactNode | ((page: number) => ReactNode);
36
- defaultActive?: number;
37
- active?: number;
38
- noSnap?: boolean;
39
- onPrevClicked?: (activePos: number) => void;
40
- onNextClicked?: (activePos: number) => void;
41
- onPaginationClicked?: (activePos: number, targetPos: number) => void;
42
- onActiveSlotChanged?: (activePos: number) => void;
43
- onCanGoLeftChanged?: (canGoLeft: boolean) => void;
44
- onCanGoRightChanged?: (canGoRight: boolean) => void;
45
- }>>;
46
- /**
47
- * Horizontally scrollable gallery, told apart from {@link Gallery} by how it locates the
48
- * active slot.
49
- *
50
- * Geometry is measured **once per layout change** into a table of scroll positions, one
51
- * per slot, and the boundaries between them. A scroll event then only has to place a
52
- * number between two others — no element is measured while the reader scrolls.
53
- *
54
- * The same table is what the controls scroll to, so reading the active slot and moving
55
- * to one cannot disagree, with or without snapping.
56
- *
57
- * ### CSS elements
58
- * - `scroller`
59
- * - `slot`, `slot--active`
60
- * - `actions`, `prev`, `next`
61
- * - `pagination`, `page`, `page--active`
62
- *
63
- * Root modifiers: `controlled`, `no-snap`, `measured`, `at-first`, `at-last`,
64
- * `can-go-left`, `can-go-right`.
65
- *
66
- * @param props - Component properties.
67
- * @see {@link Props}
68
- * @returns A container wrapping the scroller, the two controls and the pagination.
69
- *
70
- * @remarks
71
- * Nothing is measured while the reader scrolls: the table is rebuilt only when a box
72
- * changes size or a slot is added, watched through a `ResizeObserver`. Until the first
73
- * measurement lands, the root carries no `measured` modifier and every slot position
74
- * reads as `0`.
75
- */
76
- export declare const GalleryAlt: FunctionComponent<Props>;
@@ -1,188 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Children, useCallback, useEffect, useRef, useState } from 'react';
3
- import { clss } from '../../agnostic/css/clss/index.js';
4
- import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
5
- import { galleryAlt as publicClassName } from '../public-classnames.js';
6
- import { EMPTY_GEOMETRY, measureGeometry, readActiveIndex, readReach } from './utils.js';
7
- import cssModule from './styles.module.css';
8
- /** Resolves one side's padding to a CSS length, falling back to the shorthand then to `0px`. */
9
- function resolvePadding(side, shorthand) {
10
- const value = side ?? shorthand ?? 0;
11
- return typeof value === 'number' ? `${value}px` : value;
12
- }
13
- /**
14
- * Horizontally scrollable gallery, told apart from {@link Gallery} by how it locates the
15
- * active slot.
16
- *
17
- * Geometry is measured **once per layout change** into a table of scroll positions, one
18
- * per slot, and the boundaries between them. A scroll event then only has to place a
19
- * number between two others — no element is measured while the reader scrolls.
20
- *
21
- * The same table is what the controls scroll to, so reading the active slot and moving
22
- * to one cannot disagree, with or without snapping.
23
- *
24
- * ### CSS elements
25
- * - `scroller`
26
- * - `slot`, `slot--active`
27
- * - `actions`, `prev`, `next`
28
- * - `pagination`, `page`, `page--active`
29
- *
30
- * Root modifiers: `controlled`, `no-snap`, `measured`, `at-first`, `at-last`,
31
- * `can-go-left`, `can-go-right`.
32
- *
33
- * @param props - Component properties.
34
- * @see {@link Props}
35
- * @returns A container wrapping the scroller, the two controls and the pagination.
36
- *
37
- * @remarks
38
- * Nothing is measured while the reader scrolls: the table is rebuilt only when a box
39
- * changes size or a slot is added, watched through a `ResizeObserver`. Until the first
40
- * measurement lands, the root carries no `measured` modifier and every slot position
41
- * reads as `0`.
42
- */
43
- export const GalleryAlt = ({ paddingLeft, paddingRight, padding, prevButtonContent, nextButtonContent, paginationContent, defaultActive, active, noSnap, onPrevClicked, onNextClicked, onPaginationClicked, onActiveSlotChanged, onCanGoLeftChanged, onCanGoRightChanged, children, className }) => {
44
- // State & refs
45
- const scrollerRef = useRef(null);
46
- const geometryRef = useRef(EMPTY_GEOMETRY);
47
- const [scrolledIndex, setScrolledIndex] = useState(0);
48
- const [isMeasured, setIsMeasured] = useState(false);
49
- const [canGoLeft, setCanGoLeft] = useState(false);
50
- const [canGoRight, setCanGoRight] = useState(false);
51
- const childrenCount = Children.count(children);
52
- const isControlled = active !== undefined;
53
- // Controlled, the prop is the answer: the class names and `data-active` say where the
54
- // gallery was told to be, rather than where a smooth scroll has got to so far.
55
- const activeIndex = active ?? scrolledIndex;
56
- // State dispatch
57
- useChangeDispatch(activeIndex, onActiveSlotChanged);
58
- useChangeDispatch(canGoLeft, onCanGoLeftChanged);
59
- useChangeDispatch(canGoRight, onCanGoRightChanged);
60
- const readScroll = useCallback(() => {
61
- const scrollerElt = scrollerRef.current;
62
- if (scrollerElt === null)
63
- return;
64
- const { scrollLeft } = scrollerElt;
65
- const geometry = geometryRef.current;
66
- setScrolledIndex(readActiveIndex(geometry, scrollLeft));
67
- const { canGoLeft, canGoRight } = readReach(geometry, scrollLeft);
68
- setCanGoLeft(canGoLeft);
69
- setCanGoRight(canGoRight);
70
- }, []);
71
- const measure = useCallback(() => {
72
- const scrollerElt = scrollerRef.current;
73
- if (scrollerElt === null)
74
- return;
75
- geometryRef.current = measureGeometry(scrollerElt);
76
- setIsMeasured(true);
77
- readScroll();
78
- }, [readScroll]);
79
- const scrollToSlot = useCallback((pos, smooth = true) => {
80
- const scrollerElt = scrollerRef.current;
81
- const offset = geometryRef.current.offsets[pos];
82
- if (scrollerElt === null || offset === undefined)
83
- return;
84
- scrollerElt.scrollTo({ left: offset, behavior: smooth ? 'smooth' : 'instant' });
85
- }, []);
86
- // User actions handlers
87
- const handlePrevClick = () => {
88
- onPrevClicked?.(activeIndex);
89
- if (isControlled)
90
- return;
91
- scrollToSlot(activeIndex - 1);
92
- };
93
- const handleNextClick = () => {
94
- onNextClicked?.(activeIndex);
95
- if (isControlled)
96
- return;
97
- scrollToSlot(activeIndex + 1);
98
- };
99
- const handlePaginationClick = (pos) => {
100
- onPaginationClicked?.(activeIndex, pos);
101
- if (isControlled)
102
- return;
103
- scrollToSlot(pos);
104
- };
105
- // Fx. dep. childrenCount - Measure, and measure again whenever a box changes size.
106
- // Watching the slots and not only the scroller is what makes a late image or font
107
- // land correctly, where a delay after mount could only guess.
108
- useEffect(() => {
109
- const scrollerElt = scrollerRef.current;
110
- if (scrollerElt === null)
111
- return;
112
- measure();
113
- const observer = new ResizeObserver(measure);
114
- observer.observe(scrollerElt);
115
- Array.from(scrollerElt.children).forEach(child => observer.observe(child));
116
- return () => observer.disconnect();
117
- }, [childrenCount, measure]);
118
- // Fx. no dep. - One read per frame while scrolling, and it touches no element.
119
- useEffect(() => {
120
- const scrollerElt = scrollerRef.current;
121
- if (scrollerElt === null)
122
- return;
123
- let animationFrame = null;
124
- const onScroll = () => {
125
- if (animationFrame !== null)
126
- return;
127
- animationFrame = requestAnimationFrame(() => {
128
- animationFrame = null;
129
- readScroll();
130
- });
131
- };
132
- scrollerElt.addEventListener('scroll', onScroll, { passive: true });
133
- return () => {
134
- scrollerElt.removeEventListener('scroll', onScroll);
135
- if (animationFrame !== null)
136
- cancelAnimationFrame(animationFrame);
137
- };
138
- }, [readScroll]);
139
- // Fx. no dep. - Place the gallery where it starts, once the table exists.
140
- useEffect(() => {
141
- const toActivate = active ?? defaultActive;
142
- if (toActivate === undefined)
143
- return;
144
- scrollToSlot(toActivate, false);
145
- }, []);
146
- // Fx. dep. active - Follow the controlled index.
147
- useEffect(() => {
148
- if (active === undefined)
149
- return;
150
- scrollToSlot(active);
151
- }, [active]);
152
- // Rendering
153
- const c = clss(publicClassName, { cssModule });
154
- const rootClss = mergeClassNames(c(null, {
155
- 'controlled': isControlled,
156
- 'no-snap': noSnap === true,
157
- 'measured': isMeasured,
158
- 'at-first': activeIndex === 0,
159
- 'at-last': activeIndex === childrenCount - 1,
160
- 'can-go-left': canGoLeft,
161
- 'can-go-right': canGoRight
162
- }), className);
163
- const scrollerClss = c('scroller');
164
- const actionsClss = c('actions');
165
- const prevBtnClss = c('prev');
166
- const nextBtnClss = c('next');
167
- const paginationClss = c('pagination');
168
- const dataAttributes = { 'data-active': `${activeIndex}` };
169
- const actualPaddingLeft = resolvePadding(paddingLeft, padding);
170
- const actualPaddingRight = resolvePadding(paddingRight, padding);
171
- return _jsxs("div", { className: rootClss, ...dataAttributes, children: [_jsx("div", { className: scrollerClss, ref: scrollerRef, children: Children.map(children, (child, pos) => {
172
- const slotClss = c('slot', { active: pos === activeIndex });
173
- const style = {
174
- 'margin-left': pos === 0
175
- ? actualPaddingLeft
176
- : undefined,
177
- 'margin-right': pos === childrenCount - 1
178
- ? actualPaddingRight
179
- : undefined
180
- };
181
- return _jsx("div", { className: slotClss, style: { ...style }, "data-slot": pos, children: child }, pos);
182
- }) }), _jsxs("div", { className: actionsClss, children: [_jsx("div", { className: prevBtnClss, onClick: handlePrevClick, children: prevButtonContent ?? 'prev' }), _jsx("div", { className: nextBtnClss, onClick: handleNextClick, children: nextButtonContent ?? 'next' })] }), _jsx("div", { className: paginationClss, children: Children.map(children, (_, pos) => {
183
- const pageClss = c('page', { active: pos === activeIndex });
184
- return _jsx("div", { className: pageClss, "data-page": pos, onClick: () => handlePaginationClick(pos), children: typeof paginationContent === 'function'
185
- ? paginationContent(pos)
186
- : paginationContent ?? pos });
187
- }) })] });
188
- };
@@ -1,33 +0,0 @@
1
- .scroller {
2
- display: flex;
3
- overflow-x: auto;
4
- scroll-snap-type: x mandatory;
5
- scroll-behavior: smooth;
6
- -webkit-overflow-scrolling: touch;
7
- scrollbar-width: none;
8
- -ms-overflow-style: none;
9
- scrollbar-color: transparent transparent;
10
- }
11
-
12
- .scroller::-webkit-scrollbar {
13
- display: none;
14
- height: 0;
15
- }
16
-
17
- .scroller::-webkit-scrollbar-track,
18
- .scroller::-webkit-scrollbar-thumb {
19
- background: transparent;
20
- }
21
-
22
- .root.root--controlled .scroller {
23
- overflow-x: hidden;
24
- }
25
-
26
- .root.root--no-snap .scroller {
27
- scroll-snap-type: unset;
28
- scroll-behavior: unset;
29
- }
30
-
31
- .slot {
32
- scroll-snap-align: center;
33
- }
@@ -1,36 +0,0 @@
1
- /** The layout, measured once, so that reading the active slot touches no element. */
2
- export type Geometry = {
3
- /** Scroll positions that bring each slot to the middle, clamped to the track. */
4
- offsets: number[];
5
- /** Midpoints between consecutive slot centres, in content coordinates. */
6
- boundaries: number[];
7
- /** Half the viewport, kept here rather than read again on every scroll. */
8
- halfViewport: number;
9
- /** The far end of the track. */
10
- maxScroll: number;
11
- };
12
- export declare const EMPTY_GEOMETRY: Geometry;
13
- /**
14
- * Everything the gallery needs to know about its own layout.
15
- *
16
- * Positions are taken against the scroller's own box rather than `offsetLeft`, which is
17
- * relative to whichever ancestor happens to be positioned — so a gallery nested anywhere
18
- * measures the same.
19
- *
20
- * **Boundaries come from slot centres, and scroll positions are clamped.** Keeping them
21
- * apart is what avoids ties: two slots at the same end of the track share a scroll
22
- * position, but never a centre, so every slot owns a range of its own.
23
- */
24
- export declare function measureGeometry(scroller: HTMLElement): Geometry;
25
- /**
26
- * Which slot the middle of the viewport is nearest — numbers only, whatever the count.
27
- *
28
- * Same answer as walking the slots and keeping the closest centre, which is what the
29
- * table was built from.
30
- */
31
- export declare function readActiveIndex(geometry: Geometry, scrollLeft: number): number;
32
- /** Whether there is track left on each side, within {@link EDGE_TOLERANCE}. */
33
- export declare function readReach(geometry: Geometry, scrollLeft: number): {
34
- canGoLeft: boolean;
35
- canGoRight: boolean;
36
- };
@@ -1,58 +0,0 @@
1
- /** A tolerance in px, so a sub-pixel scroll position does not read as « can still go ». */
2
- const EDGE_TOLERANCE = 1;
3
- export const EMPTY_GEOMETRY = {
4
- offsets: [],
5
- boundaries: [],
6
- halfViewport: 0,
7
- maxScroll: 0
8
- };
9
- /**
10
- * Everything the gallery needs to know about its own layout.
11
- *
12
- * Positions are taken against the scroller's own box rather than `offsetLeft`, which is
13
- * relative to whichever ancestor happens to be positioned — so a gallery nested anywhere
14
- * measures the same.
15
- *
16
- * **Boundaries come from slot centres, and scroll positions are clamped.** Keeping them
17
- * apart is what avoids ties: two slots at the same end of the track share a scroll
18
- * position, but never a centre, so every slot owns a range of its own.
19
- */
20
- export function measureGeometry(scroller) {
21
- const { clientWidth, scrollWidth, scrollLeft } = scroller;
22
- const halfViewport = clientWidth / 2;
23
- const maxScroll = Math.max(0, scrollWidth - clientWidth);
24
- const scrollerLeft = scroller.getBoundingClientRect().left;
25
- const centers = Array.from(scroller.children).map(child => {
26
- const rect = child.getBoundingClientRect();
27
- return rect.left - scrollerLeft + scrollLeft + rect.width / 2;
28
- });
29
- return {
30
- offsets: centers.map(center => Math.min(Math.max(center - halfViewport, 0), maxScroll)),
31
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- i is in range by construction, centers[i] sits before the one being mapped
32
- boundaries: centers.slice(1).map((center, i) => (centers[i] + center) / 2),
33
- halfViewport,
34
- maxScroll
35
- };
36
- }
37
- /**
38
- * Which slot the middle of the viewport is nearest — numbers only, whatever the count.
39
- *
40
- * Same answer as walking the slots and keeping the closest centre, which is what the
41
- * table was built from.
42
- */
43
- export function readActiveIndex(geometry, scrollLeft) {
44
- const { boundaries, halfViewport } = geometry;
45
- const viewportCenter = scrollLeft + halfViewport;
46
- let index = 0;
47
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- index is bounded by the loop condition
48
- while (index < boundaries.length && viewportCenter > boundaries[index])
49
- index += 1;
50
- return index;
51
- }
52
- /** Whether there is track left on each side, within {@link EDGE_TOLERANCE}. */
53
- export function readReach(geometry, scrollLeft) {
54
- return {
55
- canGoLeft: scrollLeft > EDGE_TOLERANCE,
56
- canGoRight: scrollLeft < geometry.maxScroll - EDGE_TOLERANCE
57
- };
58
- }