@design-edito/tools 0.5.6 → 0.5.8
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/arrays/index.d.ts +2 -2
- package/agnostic/arrays/index.js +2 -2
- package/agnostic/colors/index.d.ts +2 -2
- package/agnostic/colors/index.js +2 -2
- package/agnostic/css/index.d.ts +1 -1
- package/agnostic/css/index.js +1 -1
- package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +10 -10
- package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +10 -10
- 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/index.d.ts +5 -4
- package/agnostic/index.js +5 -4
- package/agnostic/misc/index.d.ts +3 -3
- package/agnostic/misc/index.js +3 -3
- package/agnostic/numbers/index.d.ts +1 -1
- package/agnostic/numbers/index.js +1 -1
- package/agnostic/objects/index.d.ts +2 -2
- package/agnostic/objects/index.js +2 -2
- package/agnostic/strings/index.d.ts +1 -1
- package/agnostic/strings/index.js +1 -1
- package/agnostic/subtitles/index.d.ts +2 -0
- package/agnostic/subtitles/index.js +2 -0
- package/agnostic/subtitles/parse-srt/index.d.ts +17 -0
- package/agnostic/subtitles/parse-srt/index.js +67 -0
- package/agnostic/subtitles/parse-srt/index.test.d.ts +1 -0
- package/agnostic/subtitles/parse-srt/index.test.js +94 -0
- package/agnostic/subtitles/parse-srt/types.d.ts +22 -0
- package/agnostic/subtitles/parse-srt/types.js +1 -0
- package/agnostic/subtitles/srt-timecode-to-ms/index.d.ts +14 -0
- package/agnostic/subtitles/srt-timecode-to-ms/index.js +36 -0
- package/agnostic/subtitles/srt-timecode-to-ms/index.test.d.ts +1 -0
- package/agnostic/subtitles/srt-timecode-to-ms/index.test.js +44 -0
- package/components/BeforeAfter/index.controlled.d.ts +5 -4
- package/components/BeforeAfter/index.controlled.js +6 -6
- package/components/Drawer/index.d.ts +3 -1
- package/components/Drawer/index.js +5 -2
- package/components/Iframe/index.d.ts +4 -0
- package/components/Iframe/index.js +8 -1
- package/components/Input/index.d.ts +4 -0
- package/components/Input/index.js +9 -1
- package/components/ListLoader/index.controlled.d.ts +11 -3
- package/components/ListLoader/index.controlled.js +6 -3
- package/components/ListLoader/index.d.ts +1 -1
- package/components/ListLoader/index.js +2 -2
- package/components/ResizeObserver/index.d.ts +6 -1
- package/components/ResizeObserver/index.js +7 -2
- package/components/Scrllgngn/index.d.ts +37 -7
- package/components/Scrllgngn/index.js +81 -24
- package/components/Scrllgngn/styles.module.css +30 -5
- package/components/Scrllgngn/utils.d.ts +87 -4
- package/components/Scrllgngn/utils.js +142 -8
- package/components/ScrollListener/index.d.ts +12 -3
- package/components/ScrollListener/index.js +12 -3
- package/components/Select/index.d.ts +4 -0
- package/components/Select/index.js +9 -1
- package/components/Sequencer/index.d.ts +10 -1
- package/components/Sequencer/index.js +7 -3
- package/components/ShadowRoot/index.d.ts +4 -0
- package/components/ShadowRoot/index.js +5 -1
- package/components/Subtitles/index.d.ts +53 -62
- package/components/Subtitles/index.js +90 -158
- package/components/Subtitles/utils.d.ts +60 -0
- package/components/Subtitles/utils.js +76 -0
- package/components/Textarea/index.d.ts +4 -0
- package/components/Textarea/index.js +9 -1
- package/components/Video/index.controlled.d.ts +13 -2
- package/components/Video/index.controlled.js +37 -6
- package/components/Video/index.d.ts +47 -9
- package/components/Video/index.js +71 -20
- package/components/Video/utils.d.ts +14 -0
- package/components/Video/utils.js +18 -0
- package/components/index.d.ts +5 -5
- package/components/index.js +5 -5
- package/components/utils/types.d.ts +32 -0
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/node/@aws-s3/storage/directory/index.d.ts +1 -1
- package/node/@aws-s3/storage/directory/index.js +1 -1
- package/node/@aws-s3/storage/file/index.d.ts +1 -1
- package/node/@aws-s3/storage/file/index.js +1 -1
- package/node/@google-cloud/storage/directory/index.d.ts +1 -1
- package/node/@google-cloud/storage/directory/index.js +1 -1
- package/node/@google-cloud/storage/file/index.d.ts +3 -3
- package/node/@google-cloud/storage/file/index.js +3 -3
- package/node/cloud-storage/operations/index.d.ts +2 -2
- package/node/cloud-storage/operations/index.js +2 -2
- package/node/images/index.d.ts +1 -1
- package/node/images/index.js +1 -1
- package/node/images/transform/operations/index.d.ts +5 -5
- package/node/images/transform/operations/index.js +5 -5
- 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 +26 -1
|
@@ -8,7 +8,7 @@ import { ResizeObserverComponent } from '../ResizeObserver/index.js';
|
|
|
8
8
|
import { subscribe, unsubscribe } from '../ScrollListener/utils.js';
|
|
9
9
|
import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
|
|
10
10
|
import { scrllgngn as publicClassName } from '../public-classnames.js';
|
|
11
|
-
import { blockDisplayZones, consolidateStickyBlocks, contextsAreEqual, lazyLoadedBlocks, measurePages, sameDiscretePart, scrollBlocksOf, scrollKey, stickyKey, toScreenCssProps, toTrackedBlockContext, toTrackedBlockCssProps, toTrackedBlockDataAttributes } from './utils.js';
|
|
11
|
+
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
12
|
import cssModule from './styles.module.css';
|
|
13
13
|
/**
|
|
14
14
|
* Scrollytelling engine component. Orchestrates layered sticky blocks (`back`
|
|
@@ -45,6 +45,16 @@ import cssModule from './styles.module.css';
|
|
|
45
45
|
* `-width` and `-height` for the component's own stylesheet. They are internal:
|
|
46
46
|
* do not read or override them.
|
|
47
47
|
*
|
|
48
|
+
* ### Visible zone
|
|
49
|
+
* Set any of the `viewportOffset*` props and the component stops treating the whole
|
|
50
|
+
* screen as available: the fixed layers are inset by those edges, the top / content
|
|
51
|
+
* / bottom detection observes the reduced box, and `thresholdOffsetPercent` reads as
|
|
52
|
+
* a percentage of it. With none of them set, everything behaves exactly as before.
|
|
53
|
+
*
|
|
54
|
+
* The four edges are echoed as `--lm-scrllgngn-viewport-offset-top`, `-right`,
|
|
55
|
+
* `-bottom` and `-left`, carrying the length as authored — pixels for a number, the
|
|
56
|
+
* string untouched otherwise.
|
|
57
|
+
*
|
|
48
58
|
* ### Sticky block elements
|
|
49
59
|
* Each lazy-loaded sticky block receives:
|
|
50
60
|
* - `--active` modifier when the block's page range includes the current page.
|
|
@@ -76,16 +86,24 @@ import cssModule from './styles.module.css';
|
|
|
76
86
|
*
|
|
77
87
|
* @param props - Component properties.
|
|
78
88
|
* @see {@link Props}
|
|
79
|
-
* @returns A div wrapping the full scrollytelling structure:
|
|
80
|
-
* back-blocks layer, front-blocks layer, paginated scrolling
|
|
81
|
-
* bottom-bound sentinel.
|
|
89
|
+
* @returns A div wrapping the full scrollytelling structure: visible zone probe,
|
|
90
|
+
* top-bound sentinel, back-blocks layer, front-blocks layer, paginated scrolling
|
|
91
|
+
* content, and bottom-bound sentinel.
|
|
82
92
|
*
|
|
83
93
|
* @remarks
|
|
94
|
+
* A `viewportOffset*` given as a CSS length is resolved by CSS, not parsed: a hidden
|
|
95
|
+
* probe is inset by the four of them and observed, so `var()`, `clamp()` and
|
|
96
|
+
* breakpoint-dependent values all work, and any restyling that changes them is
|
|
97
|
+
* picked up as a resize. A change that moves the zone without altering its size —
|
|
98
|
+
* a top offset traded for an equal bottom one — is caught instead by a re-read every
|
|
99
|
+
* 100ms, which runs only while the component is on screen and only reaches state
|
|
100
|
+
* when the zone really moved.
|
|
101
|
+
*
|
|
84
102
|
* Tracking costs nothing until a block asks for it: with no `onScrolled` anywhere,
|
|
85
103
|
* the component never joins the shared scroll listener. Once it does, each frame
|
|
86
104
|
* measures only the pages the displayed tracked blocks span, not the whole sequence.
|
|
87
105
|
*/
|
|
88
|
-
export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadDistance = 2, forceStickBlocks, onPageChanged, onContentVisibilityChanged, className }) => {
|
|
106
|
+
export const Scrllgngn = ({ pages, thresholdOffsetPercent, viewportOffsetTop, viewportOffsetRight, viewportOffsetBottom, viewportOffsetLeft, stickyBlocksLazyLoadDistance = 2, forceStickBlocks, onPageChanged, onContentVisibilityChanged, className }) => {
|
|
89
107
|
// State
|
|
90
108
|
const [topVisible, setTopVis] = useState(false);
|
|
91
109
|
const [contentVisible, setCntVis] = useState(false);
|
|
@@ -93,6 +111,7 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
|
|
|
93
111
|
const [currentPagePos, setCurrentPagePos] = useState(0);
|
|
94
112
|
const [stickyBlocks, setStickyBlocks] = useState(new Map());
|
|
95
113
|
const [partialBoundingRect, setPartialBoundingRect] = useState();
|
|
114
|
+
const [visibleZone, setVisibleZone] = useState();
|
|
96
115
|
// Scroll tracking. Everything the per-frame pass reads lives in a ref: it is
|
|
97
116
|
// registered once with the shared scroll listener, and writes straight to the DOM
|
|
98
117
|
// rather than through state, which would re-render the whole sequence per frame.
|
|
@@ -103,6 +122,8 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
|
|
|
103
122
|
const trackedWrappersRef = useRef(new Map());
|
|
104
123
|
const lastContextsRef = useRef(new Map());
|
|
105
124
|
const currentPagePosRef = useRef(currentPagePos);
|
|
125
|
+
const visibleZoneRef = useRef(visibleZone);
|
|
126
|
+
const probeRef = useRef(null);
|
|
106
127
|
// Sticky blocks calculations
|
|
107
128
|
useEffect(() => {
|
|
108
129
|
setStickyBlocks(consolidateStickyBlocks(pages));
|
|
@@ -139,7 +160,18 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
|
|
|
139
160
|
useEffect(() => {
|
|
140
161
|
trackedBlocksRef.current = trackedBlocks;
|
|
141
162
|
currentPagePosRef.current = currentPagePos;
|
|
163
|
+
visibleZoneRef.current = visibleZone;
|
|
142
164
|
});
|
|
165
|
+
// Re-reads the probe, and only disturbs the render if the zone actually moved.
|
|
166
|
+
const readVisibleZone = useCallback(() => {
|
|
167
|
+
const probe = probeRef.current;
|
|
168
|
+
if (probe === null)
|
|
169
|
+
return;
|
|
170
|
+
const zone = toVisibleZoneRect(probe.getBoundingClientRect());
|
|
171
|
+
if (visibleZonesAreEqual(zone, visibleZoneRef.current))
|
|
172
|
+
return;
|
|
173
|
+
setVisibleZone(zone);
|
|
174
|
+
}, []);
|
|
143
175
|
// The per-frame pass: measure only the pages the displayed tracked blocks span,
|
|
144
176
|
// then hand each block its context — once, and only when something moved.
|
|
145
177
|
const handleScrolled = useCallback(() => {
|
|
@@ -172,7 +204,8 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
|
|
|
172
204
|
for (const pagePos of block.displayZone)
|
|
173
205
|
pagesToMeasure.add(pagePos);
|
|
174
206
|
}
|
|
175
|
-
const
|
|
207
|
+
const thresholdY = toThresholdY(visibleZoneRef.current, thresholdOffsetPercent);
|
|
208
|
+
const metrics = measurePages(pageElementsRef.current, pagesToMeasure, thresholdY);
|
|
176
209
|
for (const [key, block, page] of pending) {
|
|
177
210
|
const context = toTrackedBlockContext(block.displayZone, page, metrics);
|
|
178
211
|
const lastContext = lastContexts.get(key);
|
|
@@ -209,6 +242,17 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
|
|
|
209
242
|
});
|
|
210
243
|
return () => unsubscribe(subscriptionId);
|
|
211
244
|
}, [hasTrackedBlocks, handleScrolled]);
|
|
245
|
+
// Fx. dep. isOnScreen, readVisibleZone - Catch what the probe's ResizeObserver
|
|
246
|
+
// cannot: a restyle that moves the visible zone without resizing it, say a top
|
|
247
|
+
// offset traded for an equal bottom one. Only runs while the component is on
|
|
248
|
+
// screen, and only touches state when the zone actually moved.
|
|
249
|
+
const isOnScreen = topVisible || contentVisible || bottomVisible;
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
if (!isOnScreen)
|
|
252
|
+
return;
|
|
253
|
+
const interval = window.setInterval(readVisibleZone, visibleZonePollInterval);
|
|
254
|
+
return () => window.clearInterval(interval);
|
|
255
|
+
}, [isOnScreen, readVisibleZone]);
|
|
212
256
|
// Handlers
|
|
213
257
|
useChangeDispatch(currentPagePos, pagePos => onPageChanged?.(pagePos, pages?.[pagePos]));
|
|
214
258
|
useChangeDispatch(contentVisible, onContentVisibilityChanged);
|
|
@@ -232,6 +276,10 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
|
|
|
232
276
|
else
|
|
233
277
|
trackedWrappersRef.current.set(key, element);
|
|
234
278
|
};
|
|
279
|
+
const handleProbeResized = ({ entry, boundingClientRect }) => {
|
|
280
|
+
probeRef.current = entry.target;
|
|
281
|
+
setVisibleZone(toVisibleZoneRect(boundingClientRect));
|
|
282
|
+
};
|
|
235
283
|
const handleResize = ({ boundingClientRect }) => {
|
|
236
284
|
if (partialBoundingRect === undefined
|
|
237
285
|
|| boundingClientRect.left !== partialBoundingRect.left
|
|
@@ -250,22 +298,31 @@ export const Scrllgngn = ({ pages, thresholdOffsetPercent, stickyBlocksLazyLoadD
|
|
|
250
298
|
'force-stick-blocks-after': forceStickBlocks === 'after',
|
|
251
299
|
'force-stick-blocks-both': forceStickBlocks === 'both'
|
|
252
300
|
}), className);
|
|
253
|
-
const customCssProps =
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
'
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
301
|
+
const customCssProps = {
|
|
302
|
+
...toScreenCssProps(partialBoundingRect, visibleZone),
|
|
303
|
+
...toViewportOffsetCssProps({
|
|
304
|
+
top: viewportOffsetTop,
|
|
305
|
+
right: viewportOffsetRight,
|
|
306
|
+
bottom: viewportOffsetBottom,
|
|
307
|
+
left: viewportOffsetLeft
|
|
308
|
+
})
|
|
309
|
+
};
|
|
310
|
+
const visibleZoneRootMargin = toVisibleZoneRootMargin(visibleZone);
|
|
311
|
+
return _jsxs("div", { ref: rootRef, className: rootClss, "data-current-page-pos": currentPagePos, "data-current-page-id": pages?.[currentPagePos]?.id, style: { ...customCssProps }, children: [_jsx(ResizeObserverComponent, { className: c('viewport-probe'), onResized: handleProbeResized }), _jsxs(ResizeObserverComponent, { onResized: handleResize, children: [_jsx("div", { className: c('top-bound'), children: _jsx(IntersectionObserverComponent, { rootMargin: visibleZoneRootMargin, onIntersected: handleTopBoundDetect }) }), _jsx("div", { className: c('back-blocks'), children: lazyLoadedBackBlocks.map(([blockId, block], blockPos) => {
|
|
312
|
+
const isActive = block.displayOnPages.includes(currentPagePos);
|
|
313
|
+
const blockClss = c('back-block', {
|
|
314
|
+
active: isActive,
|
|
315
|
+
'lazy-loaded': !isActive
|
|
316
|
+
});
|
|
317
|
+
return _jsx("div", { ref: registerWrapper(stickyKey(blockId)), className: blockClss, style: { zIndex: blockPos }, children: block.children }, blockId);
|
|
318
|
+
}) }), _jsx("div", { className: c('front-blocks'), children: lazyLoadedFrontBlocks.map(([blockId, block], blockPos) => {
|
|
319
|
+
const isActive = block.displayOnPages.includes(currentPagePos);
|
|
320
|
+
const blockClss = c('front-block', {
|
|
321
|
+
active: isActive,
|
|
322
|
+
'lazy-loaded': !isActive
|
|
323
|
+
});
|
|
324
|
+
return _jsx("div", { ref: registerWrapper(stickyKey(blockId)), className: blockClss, style: { zIndex: blockPos }, children: block.children }, blockId);
|
|
325
|
+
}) }), _jsx("div", { className: c('scrolling-content'), children: _jsx(IntersectionObserverComponent, { rootMargin: visibleZoneRootMargin, onIntersected: handleCntDetect, children: _jsx(Paginator, { thresholdOffsetPercent: toPaginatorThresholdPercent(visibleZone, thresholdOffsetPercent), onPagesChanged: handlePagesChanged, onPageElementsChanged: handlePageElementsChanged, children: pages?.map((page, pagePos) => {
|
|
326
|
+
return _jsx(_Fragment, { children: scrollBlocksOf(page).map((block, blockPos) => _jsx("div", { ref: registerWrapper(scrollKey(pagePos, blockPos)), className: c('scroll-block'), children: block.children }, blockPos)) });
|
|
327
|
+
}) }) }) }), _jsx("div", { className: c('bottom-bound'), children: _jsx(IntersectionObserverComponent, { rootMargin: visibleZoneRootMargin, onIntersected: handleBtmBoundDetect }) })] })] });
|
|
271
328
|
};
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
.root {
|
|
2
2
|
position: relative;
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
The visible zone's height, derived from the offsets rather than measured: CSS
|
|
6
|
+
already knows them, and reading them back from JS would only add a frame of lag.
|
|
7
|
+
*/
|
|
8
|
+
--PRIVATE-viewport-height: calc(
|
|
9
|
+
100vh
|
|
10
|
+
- var(--PRIVATE-viewport-offset-top)
|
|
11
|
+
- var(--PRIVATE-viewport-offset-bottom)
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
Measured, never seen: its box is the visible zone, and observing it is how a CSS
|
|
17
|
+
length written by the consumer — a var, a clamp, a breakpoint — reaches JavaScript.
|
|
18
|
+
*/
|
|
19
|
+
.viewport-probe {
|
|
20
|
+
position: fixed;
|
|
21
|
+
top: var(--PRIVATE-viewport-offset-top);
|
|
22
|
+
right: var(--PRIVATE-viewport-offset-right);
|
|
23
|
+
bottom: var(--PRIVATE-viewport-offset-bottom);
|
|
24
|
+
left: var(--PRIVATE-viewport-offset-left);
|
|
25
|
+
visibility: hidden;
|
|
26
|
+
pointer-events: none;
|
|
27
|
+
z-index: -1;
|
|
3
28
|
}
|
|
4
29
|
|
|
5
30
|
/* MAIN LAYOUTS (back / front blocks, scrolling content) */
|
|
@@ -30,7 +55,7 @@
|
|
|
30
55
|
top: 0;
|
|
31
56
|
left: 0;
|
|
32
57
|
right: 0;
|
|
33
|
-
height:
|
|
58
|
+
height: var(--PRIVATE-viewport-height);
|
|
34
59
|
}
|
|
35
60
|
|
|
36
61
|
/*
|
|
@@ -51,20 +76,20 @@ Force stick blocks option
|
|
|
51
76
|
.root.root--bottom-visible.root--content-visible.root--force-stick-blocks-both .back-blocks,
|
|
52
77
|
.root.root--bottom-visible.root--content-visible.root--force-stick-blocks-both .front-blocks {
|
|
53
78
|
position: fixed;
|
|
54
|
-
top:
|
|
79
|
+
top: var(--PRIVATE-viewport-offset-top);
|
|
55
80
|
left: var(--PRIVATE-left);
|
|
56
81
|
width: var(--PRIVATE-width);
|
|
57
|
-
height:
|
|
82
|
+
height: var(--PRIVATE-viewport-height);
|
|
58
83
|
}
|
|
59
84
|
|
|
60
85
|
/* Scrllgngn leaving the viewport, blocks not fixed but have offset */
|
|
61
86
|
.root.root--bottom-visible.root--content-visible .back-blocks,
|
|
62
87
|
.root.root--bottom-visible.root--content-visible .front-blocks {
|
|
63
88
|
position: absolute;
|
|
64
|
-
top: calc(var(--PRIVATE-height) -
|
|
89
|
+
top: calc(var(--PRIVATE-height) - var(--PRIVATE-viewport-height));
|
|
65
90
|
left: 0;
|
|
66
91
|
right: 0;
|
|
67
|
-
height:
|
|
92
|
+
height: var(--PRIVATE-viewport-height);
|
|
68
93
|
}
|
|
69
94
|
|
|
70
95
|
/* INDIVIDUAL FIXED BLOCKS */
|
|
@@ -48,10 +48,16 @@ export declare function lazyLoadedBlocks(blocks: Map<string, ConsolidatedStickyB
|
|
|
48
48
|
* never inherit the same names from an ancestor component. `initial` leaves them
|
|
49
49
|
* behaving exactly as an unset variable would, without opening the hole.
|
|
50
50
|
*
|
|
51
|
+
* The public set describes the component's own box and nothing else. The private
|
|
52
|
+
* one is narrowed to what the fixed layers may actually occupy — the component's
|
|
53
|
+
* box **intersected** with the visible zone — so a viewport offset pushes them off
|
|
54
|
+
* whatever covers that edge without the stylesheet doing arithmetic.
|
|
55
|
+
*
|
|
51
56
|
* @param rect - The measured bounding rect, absent until the first resize lands.
|
|
57
|
+
* @param zone - The measured visible zone, absent until the probe reports.
|
|
52
58
|
* @returns The custom properties, keyed by their full name.
|
|
53
59
|
*/
|
|
54
|
-
export declare function toScreenCssProps(rect?: ScreenRect): Record<string, string>;
|
|
60
|
+
export declare function toScreenCssProps(rect?: ScreenRect, zone?: VisibleZoneRect): Record<string, string>;
|
|
55
61
|
/**
|
|
56
62
|
* Where one tracked block stands in the scroll, as handed to its `onScrolled`.
|
|
57
63
|
*
|
|
@@ -119,11 +125,11 @@ export declare function contiguousRunContaining(zone: number[], page: number): n
|
|
|
119
125
|
*
|
|
120
126
|
* @param pageElements - Every page slot, indexed by position.
|
|
121
127
|
* @param pagePositions - The positions worth measuring.
|
|
122
|
-
* @param
|
|
123
|
-
*
|
|
128
|
+
* @param thresholdY - The threshold line, in pixels from the viewport top. See
|
|
129
|
+
* {@link toThresholdY}.
|
|
124
130
|
* @returns The metrics of each measurable page, keyed by position.
|
|
125
131
|
*/
|
|
126
|
-
export declare function measurePages(pageElements: HTMLElement[], pagePositions: Iterable<number>,
|
|
132
|
+
export declare function measurePages(pageElements: HTMLElement[], pagePositions: Iterable<number>, thresholdY: number): Map<number, PageScrollMetrics>;
|
|
127
133
|
/**
|
|
128
134
|
* Assembles what one tracked block is told about the scroll.
|
|
129
135
|
*
|
|
@@ -185,3 +191,80 @@ export declare function scrollKey(pagePos: number, blockPos: number): string;
|
|
|
185
191
|
* @returns Its scroll blocks, in order. `depth` left out means `'scroll'`.
|
|
186
192
|
*/
|
|
187
193
|
export declare function scrollBlocksOf(page: PropsPage): PropsBlock[];
|
|
194
|
+
/** How much of each viewport edge is covered by something else, in pixels. */
|
|
195
|
+
export type VisibleZoneRect = {
|
|
196
|
+
top: number;
|
|
197
|
+
right: number;
|
|
198
|
+
bottom: number;
|
|
199
|
+
left: number;
|
|
200
|
+
width: number;
|
|
201
|
+
height: number;
|
|
202
|
+
};
|
|
203
|
+
/** A length as authored: a bare number means pixels, a string is CSS's problem. */
|
|
204
|
+
export type ViewportOffset = number | string;
|
|
205
|
+
/** The four edges, as authored on the component. */
|
|
206
|
+
export type ViewportOffsets = {
|
|
207
|
+
top?: ViewportOffset;
|
|
208
|
+
right?: ViewportOffset;
|
|
209
|
+
bottom?: ViewportOffset;
|
|
210
|
+
left?: ViewportOffset;
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Builds the custom properties carrying the visible zone's insets.
|
|
214
|
+
*
|
|
215
|
+
* They are emitted whatever the props hold, `0px` standing in for an edge left
|
|
216
|
+
* unset: the probe and the fixed layers position themselves against them, and an
|
|
217
|
+
* absent name would let an enclosing component's value inherit through.
|
|
218
|
+
*
|
|
219
|
+
* @param offsets - The edges as authored.
|
|
220
|
+
* @returns The custom properties, keyed by their full name.
|
|
221
|
+
*/
|
|
222
|
+
export declare function toViewportOffsetCssProps(offsets: ViewportOffsets): Record<string, string>;
|
|
223
|
+
/**
|
|
224
|
+
* Reads the visible zone off the probe element.
|
|
225
|
+
*
|
|
226
|
+
* The probe is fixed and inset by the four offsets, so its rect *is* the zone —
|
|
227
|
+
* which is how a `var()`, a `clamp()` or a breakpoint-dependent length reaches
|
|
228
|
+
* JavaScript without anyone parsing a CSS length.
|
|
229
|
+
*
|
|
230
|
+
* @param probeRect - The probe's bounding rect.
|
|
231
|
+
* @returns The zone, as insets from each viewport edge plus its own size.
|
|
232
|
+
*/
|
|
233
|
+
export declare function toVisibleZoneRect(probeRect: DOMRect): VisibleZoneRect;
|
|
234
|
+
/**
|
|
235
|
+
* Shrinks an {@link IntersectionObserver} root down to the visible zone.
|
|
236
|
+
*
|
|
237
|
+
* @param zone - The measured zone, absent until the probe reports.
|
|
238
|
+
* @returns A `rootMargin`, or `undefined` to leave the observer on the full
|
|
239
|
+
* viewport.
|
|
240
|
+
*/
|
|
241
|
+
export declare function toVisibleZoneRootMargin(zone?: VisibleZoneRect): string | undefined;
|
|
242
|
+
/**
|
|
243
|
+
* Places the threshold line, in pixels from the viewport top.
|
|
244
|
+
*
|
|
245
|
+
* `thresholdOffsetPercent` reads as a percentage **of the visible zone**, not of
|
|
246
|
+
* the viewport: a threshold at 50% has to sit halfway down what the reader can
|
|
247
|
+
* actually see, or it lands under whatever covers the top of the screen.
|
|
248
|
+
*
|
|
249
|
+
* @param zone - The measured zone, absent until the probe reports.
|
|
250
|
+
* @param thresholdOffsetPercent - The offset as authored.
|
|
251
|
+
* @returns The line's distance from the viewport top.
|
|
252
|
+
*/
|
|
253
|
+
export declare function toThresholdY(zone?: VisibleZoneRect, thresholdOffsetPercent?: number): number;
|
|
254
|
+
/**
|
|
255
|
+
* Restates the threshold as the percentage of the **viewport** an
|
|
256
|
+
* {@link IntersectionObserver} needs, since that is the only frame `rootMargin`
|
|
257
|
+
* knows.
|
|
258
|
+
*
|
|
259
|
+
* With no offsets set the zone spans the viewport and the number comes back
|
|
260
|
+
* unchanged, so nothing moves for a component that never asked for any of this.
|
|
261
|
+
*
|
|
262
|
+
* @param zone - The measured zone, absent until the probe reports.
|
|
263
|
+
* @param thresholdOffsetPercent - The offset as authored.
|
|
264
|
+
* @returns The equivalent viewport percentage, to hand to the internal `Paginator`.
|
|
265
|
+
*/
|
|
266
|
+
export declare function toPaginatorThresholdPercent(zone?: VisibleZoneRect, thresholdOffsetPercent?: number): number | undefined;
|
|
267
|
+
/** How often the visible zone is re-read while the component is on screen, in ms. */
|
|
268
|
+
export declare const visibleZonePollInterval = 100;
|
|
269
|
+
/** Whether two zones hold the same six measurements. */
|
|
270
|
+
export declare function visibleZonesAreEqual(a?: VisibleZoneRect, b?: VisibleZoneRect): boolean;
|
|
@@ -75,10 +75,16 @@ export function lazyLoadedBlocks(blocks, depth, currentPagePos, lazyLoadDistance
|
|
|
75
75
|
* never inherit the same names from an ancestor component. `initial` leaves them
|
|
76
76
|
* behaving exactly as an unset variable would, without opening the hole.
|
|
77
77
|
*
|
|
78
|
+
* The public set describes the component's own box and nothing else. The private
|
|
79
|
+
* one is narrowed to what the fixed layers may actually occupy — the component's
|
|
80
|
+
* box **intersected** with the visible zone — so a viewport offset pushes them off
|
|
81
|
+
* whatever covers that edge without the stylesheet doing arithmetic.
|
|
82
|
+
*
|
|
78
83
|
* @param rect - The measured bounding rect, absent until the first resize lands.
|
|
84
|
+
* @param zone - The measured visible zone, absent until the probe reports.
|
|
79
85
|
* @returns The custom properties, keyed by their full name.
|
|
80
86
|
*/
|
|
81
|
-
export function toScreenCssProps(rect) {
|
|
87
|
+
export function toScreenCssProps(rect, zone) {
|
|
82
88
|
const publicProps = rect === undefined
|
|
83
89
|
? {}
|
|
84
90
|
: {
|
|
@@ -91,14 +97,30 @@ export function toScreenCssProps(rect) {
|
|
|
91
97
|
'--lm-scrllgngn-screen-height': `${rect.height}px`,
|
|
92
98
|
'--lm-scrllgngn-screen-height-raw': `${rect.height}`
|
|
93
99
|
};
|
|
100
|
+
const inZone = toZoneClampedRect(rect, zone);
|
|
94
101
|
return {
|
|
95
102
|
...publicProps,
|
|
96
|
-
'--PRIVATE-left': toPrivateLength(
|
|
97
|
-
'--PRIVATE-right': toPrivateLength(
|
|
98
|
-
'--PRIVATE-width': toPrivateLength(
|
|
103
|
+
'--PRIVATE-left': toPrivateLength(inZone?.left),
|
|
104
|
+
'--PRIVATE-right': toPrivateLength(inZone?.right),
|
|
105
|
+
'--PRIVATE-width': toPrivateLength(inZone?.width),
|
|
99
106
|
'--PRIVATE-height': toPrivateLength(rect?.height)
|
|
100
107
|
};
|
|
101
108
|
}
|
|
109
|
+
/** The component's box, cut down to the horizontal span of the visible zone. */
|
|
110
|
+
function toZoneClampedRect(rect, zone) {
|
|
111
|
+
if (rect === undefined)
|
|
112
|
+
return undefined;
|
|
113
|
+
if (zone === undefined)
|
|
114
|
+
return rect;
|
|
115
|
+
const left = Math.max(rect.left, zone.left);
|
|
116
|
+
const right = Math.min(rect.right, window.innerWidth - zone.right);
|
|
117
|
+
return {
|
|
118
|
+
left,
|
|
119
|
+
right,
|
|
120
|
+
width: Math.max(right - left, 0),
|
|
121
|
+
height: rect.height
|
|
122
|
+
};
|
|
123
|
+
}
|
|
102
124
|
function toPrivateLength(value) {
|
|
103
125
|
return value === undefined ? 'initial' : `${value}px`;
|
|
104
126
|
}
|
|
@@ -155,12 +177,11 @@ export function contiguousRunContaining(zone, page) {
|
|
|
155
177
|
*
|
|
156
178
|
* @param pageElements - Every page slot, indexed by position.
|
|
157
179
|
* @param pagePositions - The positions worth measuring.
|
|
158
|
-
* @param
|
|
159
|
-
*
|
|
180
|
+
* @param thresholdY - The threshold line, in pixels from the viewport top. See
|
|
181
|
+
* {@link toThresholdY}.
|
|
160
182
|
* @returns The metrics of each measurable page, keyed by position.
|
|
161
183
|
*/
|
|
162
|
-
export function measurePages(pageElements, pagePositions,
|
|
163
|
-
const thresholdY = window.innerHeight * (thresholdOffsetPercent ?? 0) / 100;
|
|
184
|
+
export function measurePages(pageElements, pagePositions, thresholdY) {
|
|
164
185
|
const measured = new Map();
|
|
165
186
|
for (const pagePos of pagePositions) {
|
|
166
187
|
const element = pageElements[pagePos];
|
|
@@ -293,3 +314,116 @@ export function scrollKey(pagePos, blockPos) {
|
|
|
293
314
|
export function scrollBlocksOf(page) {
|
|
294
315
|
return page.blocks?.filter(block => block.depth === 'scroll' || block.depth === undefined) ?? [];
|
|
295
316
|
}
|
|
317
|
+
function toCssLength(offset) {
|
|
318
|
+
if (offset === undefined)
|
|
319
|
+
return '0px';
|
|
320
|
+
return typeof offset === 'number' ? `${offset}px` : offset;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Builds the custom properties carrying the visible zone's insets.
|
|
324
|
+
*
|
|
325
|
+
* They are emitted whatever the props hold, `0px` standing in for an edge left
|
|
326
|
+
* unset: the probe and the fixed layers position themselves against them, and an
|
|
327
|
+
* absent name would let an enclosing component's value inherit through.
|
|
328
|
+
*
|
|
329
|
+
* @param offsets - The edges as authored.
|
|
330
|
+
* @returns The custom properties, keyed by their full name.
|
|
331
|
+
*/
|
|
332
|
+
export function toViewportOffsetCssProps(offsets) {
|
|
333
|
+
const top = toCssLength(offsets.top);
|
|
334
|
+
const right = toCssLength(offsets.right);
|
|
335
|
+
const bottom = toCssLength(offsets.bottom);
|
|
336
|
+
const left = toCssLength(offsets.left);
|
|
337
|
+
return {
|
|
338
|
+
'--lm-scrllgngn-viewport-offset-top': top,
|
|
339
|
+
'--lm-scrllgngn-viewport-offset-right': right,
|
|
340
|
+
'--lm-scrllgngn-viewport-offset-bottom': bottom,
|
|
341
|
+
'--lm-scrllgngn-viewport-offset-left': left,
|
|
342
|
+
'--PRIVATE-viewport-offset-top': top,
|
|
343
|
+
'--PRIVATE-viewport-offset-right': right,
|
|
344
|
+
'--PRIVATE-viewport-offset-bottom': bottom,
|
|
345
|
+
'--PRIVATE-viewport-offset-left': left
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Reads the visible zone off the probe element.
|
|
350
|
+
*
|
|
351
|
+
* The probe is fixed and inset by the four offsets, so its rect *is* the zone —
|
|
352
|
+
* which is how a `var()`, a `clamp()` or a breakpoint-dependent length reaches
|
|
353
|
+
* JavaScript without anyone parsing a CSS length.
|
|
354
|
+
*
|
|
355
|
+
* @param probeRect - The probe's bounding rect.
|
|
356
|
+
* @returns The zone, as insets from each viewport edge plus its own size.
|
|
357
|
+
*/
|
|
358
|
+
export function toVisibleZoneRect(probeRect) {
|
|
359
|
+
return {
|
|
360
|
+
top: probeRect.top,
|
|
361
|
+
right: window.innerWidth - probeRect.right,
|
|
362
|
+
bottom: window.innerHeight - probeRect.bottom,
|
|
363
|
+
left: probeRect.left,
|
|
364
|
+
width: probeRect.width,
|
|
365
|
+
height: probeRect.height
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Shrinks an {@link IntersectionObserver} root down to the visible zone.
|
|
370
|
+
*
|
|
371
|
+
* @param zone - The measured zone, absent until the probe reports.
|
|
372
|
+
* @returns A `rootMargin`, or `undefined` to leave the observer on the full
|
|
373
|
+
* viewport.
|
|
374
|
+
*/
|
|
375
|
+
export function toVisibleZoneRootMargin(zone) {
|
|
376
|
+
if (zone === undefined)
|
|
377
|
+
return undefined;
|
|
378
|
+
return `${-zone.top}px ${-zone.right}px ${-zone.bottom}px ${-zone.left}px`;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Places the threshold line, in pixels from the viewport top.
|
|
382
|
+
*
|
|
383
|
+
* `thresholdOffsetPercent` reads as a percentage **of the visible zone**, not of
|
|
384
|
+
* the viewport: a threshold at 50% has to sit halfway down what the reader can
|
|
385
|
+
* actually see, or it lands under whatever covers the top of the screen.
|
|
386
|
+
*
|
|
387
|
+
* @param zone - The measured zone, absent until the probe reports.
|
|
388
|
+
* @param thresholdOffsetPercent - The offset as authored.
|
|
389
|
+
* @returns The line's distance from the viewport top.
|
|
390
|
+
*/
|
|
391
|
+
export function toThresholdY(zone, thresholdOffsetPercent) {
|
|
392
|
+
const percent = thresholdOffsetPercent ?? 0;
|
|
393
|
+
if (zone === undefined)
|
|
394
|
+
return window.innerHeight * percent / 100;
|
|
395
|
+
return zone.top + zone.height * percent / 100;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Restates the threshold as the percentage of the **viewport** an
|
|
399
|
+
* {@link IntersectionObserver} needs, since that is the only frame `rootMargin`
|
|
400
|
+
* knows.
|
|
401
|
+
*
|
|
402
|
+
* With no offsets set the zone spans the viewport and the number comes back
|
|
403
|
+
* unchanged, so nothing moves for a component that never asked for any of this.
|
|
404
|
+
*
|
|
405
|
+
* @param zone - The measured zone, absent until the probe reports.
|
|
406
|
+
* @param thresholdOffsetPercent - The offset as authored.
|
|
407
|
+
* @returns The equivalent viewport percentage, to hand to the internal `Paginator`.
|
|
408
|
+
*/
|
|
409
|
+
export function toPaginatorThresholdPercent(zone, thresholdOffsetPercent) {
|
|
410
|
+
if (zone === undefined)
|
|
411
|
+
return thresholdOffsetPercent;
|
|
412
|
+
const viewportHeight = window.innerHeight;
|
|
413
|
+
if (viewportHeight === 0)
|
|
414
|
+
return thresholdOffsetPercent;
|
|
415
|
+
return toThresholdY(zone, thresholdOffsetPercent) / viewportHeight * 100;
|
|
416
|
+
}
|
|
417
|
+
/** How often the visible zone is re-read while the component is on screen, in ms. */
|
|
418
|
+
export const visibleZonePollInterval = 100;
|
|
419
|
+
/** Whether two zones hold the same six measurements. */
|
|
420
|
+
export function visibleZonesAreEqual(a, b) {
|
|
421
|
+
if (a === undefined || b === undefined)
|
|
422
|
+
return a === b;
|
|
423
|
+
return a.top === b.top
|
|
424
|
+
&& a.right === b.right
|
|
425
|
+
&& a.bottom === b.bottom
|
|
426
|
+
&& a.left === b.left
|
|
427
|
+
&& a.width === b.width
|
|
428
|
+
&& a.height === b.height;
|
|
429
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type FunctionComponent, type PropsWithChildren } from 'react';
|
|
2
|
-
import type { WithClassName } from '../utils/types.js';
|
|
2
|
+
import type { WithClassName, WithViewportObservation } from '../utils/types.js';
|
|
3
3
|
import { type ScrollState } from './utils.js';
|
|
4
4
|
/**
|
|
5
5
|
* Props for the {@link ScrollListener} component.
|
|
@@ -17,22 +17,31 @@ import { type ScrollState } from './utils.js';
|
|
|
17
17
|
* it has fully left it. Never on mount.
|
|
18
18
|
* @property onScrollDirectionChanged - Called after the document scroll
|
|
19
19
|
* direction changed, with `'up'` or `'down'`. Never on mount.
|
|
20
|
+
* @property threshold - How much of the component has to be in view before it
|
|
21
|
+
* counts as visible, forwarded to the internal {@link IntersectionObserver}.
|
|
22
|
+
* @property root - The observer's root. Defaults to the viewport.
|
|
23
|
+
* @property rootMargin - Grows or shrinks that root before measuring.
|
|
20
24
|
* @property className - Optional additional class name(s) applied to the root element.
|
|
21
25
|
* @property children - React nodes rendered inside the scroll listener container.
|
|
22
26
|
*/
|
|
23
|
-
export type Props = PropsWithChildren<WithClassName<{
|
|
27
|
+
export type Props = PropsWithChildren<WithClassName<WithViewportObservation<{
|
|
24
28
|
startOnVisible?: boolean;
|
|
25
29
|
stopOnHidden?: boolean;
|
|
26
30
|
onScrollStateChanged?: (scrollState?: ScrollState) => void;
|
|
27
31
|
onVisibilityChanged?: (isVisible: boolean) => void;
|
|
28
32
|
onScrollProgressChanged?: (progress: number) => void;
|
|
29
33
|
onScrollDirectionChanged?: (direction: 'up' | 'down') => void;
|
|
30
|
-
}
|
|
34
|
+
}>>>;
|
|
31
35
|
/**
|
|
32
36
|
* Exposes scroll metrics — both the document's and its own — as CSS custom
|
|
33
37
|
* properties on its root element, so scroll-driven styling needs no JavaScript
|
|
34
38
|
* of its own.
|
|
35
39
|
*
|
|
40
|
+
* ### Root element modifiers
|
|
41
|
+
* - `--measured` — a first measurement landed, so the properties below are set.
|
|
42
|
+
* - `--scrolling-up` / `--scrolling-down` — the document's last known direction.
|
|
43
|
+
* Neither is present before the first scroll.
|
|
44
|
+
*
|
|
36
45
|
* ### CSS custom properties on the root element
|
|
37
46
|
* Each measurement comes as a `px` length under its bare name, and as a `-raw`
|
|
38
47
|
* twin holding the plain number:
|
|
@@ -12,6 +12,11 @@ import cssModule from './styles.module.css';
|
|
|
12
12
|
* properties on its root element, so scroll-driven styling needs no JavaScript
|
|
13
13
|
* of its own.
|
|
14
14
|
*
|
|
15
|
+
* ### Root element modifiers
|
|
16
|
+
* - `--measured` — a first measurement landed, so the properties below are set.
|
|
17
|
+
* - `--scrolling-up` / `--scrolling-down` — the document's last known direction.
|
|
18
|
+
* Neither is present before the first scroll.
|
|
19
|
+
*
|
|
15
20
|
* ### CSS custom properties on the root element
|
|
16
21
|
* Each measurement comes as a `px` length under its bare name, and as a `-raw`
|
|
17
22
|
* twin holding the plain number:
|
|
@@ -45,7 +50,7 @@ import cssModule from './styles.module.css';
|
|
|
45
50
|
* for everyone, each element only for itself. The listeners exist only while at
|
|
46
51
|
* least one instance is tracking.
|
|
47
52
|
*/
|
|
48
|
-
export const ScrollListener = ({ startOnVisible, stopOnHidden, onScrollStateChanged, onVisibilityChanged, onScrollProgressChanged, onScrollDirectionChanged, className, children }) => {
|
|
53
|
+
export const ScrollListener = ({ startOnVisible, stopOnHidden, threshold, root, rootMargin, onScrollStateChanged, onVisibilityChanged, onScrollProgressChanged, onScrollDirectionChanged, className, children }) => {
|
|
49
54
|
// State & refs
|
|
50
55
|
const [subscriberId] = useState(() => randomHash(6));
|
|
51
56
|
const [scrollState, setScrollState] = useState();
|
|
@@ -92,9 +97,13 @@ export const ScrollListener = ({ startOnVisible, stopOnHidden, onScrollStateChan
|
|
|
92
97
|
}, [startOnVisible, stopOnHidden, onVisibilityChanged]);
|
|
93
98
|
// Rendering
|
|
94
99
|
const c = clss(publicClassName, { cssModule });
|
|
95
|
-
const rootClss = mergeClassNames(c(
|
|
100
|
+
const rootClss = mergeClassNames(c(null, {
|
|
101
|
+
measured: scrollState !== undefined,
|
|
102
|
+
'scrolling-up': scrollDirection === 'up',
|
|
103
|
+
'scrolling-down': scrollDirection === 'down'
|
|
104
|
+
}), className);
|
|
96
105
|
const customProps = scrollState === undefined
|
|
97
106
|
? {}
|
|
98
107
|
: toScrollCssProps(scrollState);
|
|
99
|
-
return _jsx("div", { className: rootClss, ref: rootRef, style: { ...customProps }, children: _jsx(IntersectionObserverComponent, { onIntersected: handleIntersection, children: children }) });
|
|
108
|
+
return _jsx("div", { className: rootClss, ref: rootRef, style: { ...customProps }, children: _jsx(IntersectionObserverComponent, { threshold: threshold, root: root, rootMargin: rootMargin, onIntersected: handleIntersection, children: children }) });
|
|
100
109
|
};
|
|
@@ -24,6 +24,10 @@ export type Props = SelectHTMLAttributes<HTMLSelectElement> & PropsWithChildren<
|
|
|
24
24
|
* A stable auto-generated `id` is created on mount and used to associate the
|
|
25
25
|
* rendered label through the `htmlFor` attribute.
|
|
26
26
|
*
|
|
27
|
+
* ### Root element modifiers
|
|
28
|
+
* - `--controlled` — the value comes from the parent, not from inside.
|
|
29
|
+
* - `--empty` / `--filled` — whether the current value is the empty string.
|
|
30
|
+
*
|
|
27
31
|
* ### CSS elements
|
|
28
32
|
* - `label`
|
|
29
33
|
* - `error`
|
|
@@ -15,6 +15,10 @@ import cssModule from './styles.module.css';
|
|
|
15
15
|
* A stable auto-generated `id` is created on mount and used to associate the
|
|
16
16
|
* rendered label through the `htmlFor` attribute.
|
|
17
17
|
*
|
|
18
|
+
* ### Root element modifiers
|
|
19
|
+
* - `--controlled` — the value comes from the parent, not from inside.
|
|
20
|
+
* - `--empty` / `--filled` — whether the current value is the empty string.
|
|
21
|
+
*
|
|
18
22
|
* ### CSS elements
|
|
19
23
|
* - `label`
|
|
20
24
|
* - `error`
|
|
@@ -44,6 +48,10 @@ export const Select = ({ label, error, value, defaultValue, onChange, className,
|
|
|
44
48
|
};
|
|
45
49
|
// Rendering
|
|
46
50
|
const c = clss(publicClassName, { cssModule });
|
|
47
|
-
const rootClss = mergeClassNames(c(
|
|
51
|
+
const rootClss = mergeClassNames(c(null, {
|
|
52
|
+
controlled: isControlled,
|
|
53
|
+
empty: currentValue === '',
|
|
54
|
+
filled: currentValue !== ''
|
|
55
|
+
}), className);
|
|
48
56
|
return _jsxs(_Fragment, { children: [isNotFalsy(label) && _jsx("label", { className: c('label'), htmlFor: id, children: label }), _jsx("select", { ...rest, id: id, className: rootClss, value: currentValue, onChange: handleChange, children: children }), isNotFalsy(error) && _jsx("span", { className: c('error'), children: error })] });
|
|
49
57
|
};
|