@@ -14,14 +14,54 @@ export type ScrollMode = 'always' | 'if-needed'
14
14
15
15
/** @public */
16
16
export interface Options {
17
+ /**
18
+ * Control the logical scroll position on the y-axis. The spec states that the `block` direction is related to the [writing-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode), but this is not implemented yet in this library.
19
+ * This means that `block: 'start'` aligns to the top edge and `block: 'end'` to the bottom.
20
+ * @defaultValue 'center'
21
+ */
17
22
block ?: ScrollLogicalPosition
23
+ /**
24
+ * Like `block` this is affected by the [writing-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode). In left-to-right pages `inline: 'start'` will align to the left edge. In right-to-left it should be flipped. This will be supported in a future release.
25
+ * @defaultValue 'nearest'
26
+ */
18
27
inline ?: ScrollLogicalPosition
28
+ /**
29
+ * This is a proposed addition to the spec that you can track here: https://github.com/w3c/csswg-drafts/pull/5677
30
+ *
31
+ * This library will be updated to reflect any changes to the spec and will provide a migration path.
32
+ * To be backwards compatible with `Element.scrollIntoViewIfNeeded` if something is not 100% visible it will count as "needs scrolling". If you need a different visibility ratio your best option would be to implement an [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).
33
+ * @defaultValue 'always'
34
+ */
19
35
scrollMode ?: ScrollMode
20
- // Custom behavior, not in any spec
36
+ /**
37
+ * By default there is no boundary. All the parent elements of your target is checked until it reaches the viewport ([`document.scrollingElement`](https://developer.mozilla.org/en-US/docs/Web/API/document/scrollingElement)) when calculating layout and what to scroll.
38
+ * By passing a boundary you can short-circuit this loop depending on your needs:
39
+ *
40
+ * - Prevent the browser window from scrolling.
41
+ * - Scroll elements into view in a list, without scrolling container elements.
42
+ *
43
+ * You can also pass a function to do more dynamic checks to override the scroll scoping:
44
+ *
45
+ * ```js
46
+ * let actions = compute(target, {
47
+ * boundary: (parent) => {
48
+ * // By default `overflow: hidden` elements are allowed, only `overflow: visible | clip` is skipped as
49
+ * // this is required by the CSSOM spec
50
+ * if (getComputedStyle(parent)['overflow'] === 'hidden') {
51
+ * return false
52
+ * }
53
+
54
+ * return true
55
+ * },
56
+ * })
57
+ * ```
58
+ * @defaultValue null
59
+ */
21
60
boundary ?: Element | ( ( parent : Element ) => boolean ) | null
22
61
/**
23
62
* New option that skips auto-scrolling all nodes with overflow: hidden set
24
63
* See FF implementation: https://hg.mozilla.org/integration/fx-team/rev/c48c3ec05012#l7.18
64
+ * @defaultValue false
25
65
* @public
26
66
*/
27
67
skipOverflowHiddenElements ?: boolean
@@ -35,22 +75,21 @@ export interface ScrollAction {
35
75
}
36
76
37
77
// @TODO better shadowdom test, 11 = document fragment
38
- function isElement ( el : any ) : el is Element {
39
- return typeof el === 'object' && el != null && el . nodeType === 1
40
- }
78
+ let isElement = ( el : any ) : el is Element =>
79
+ typeof el === 'object' && el != null && el . nodeType === 1
41
80
42
- function canOverflow (
81
+ let canOverflow = (
43
82
overflow : string | null ,
44
83
skipOverflowHiddenElements ?: boolean
45
- ) {
84
+ ) => {
46
85
if ( skipOverflowHiddenElements && overflow === 'hidden' ) {
47
86
return false
48
87
}
49
88
50
89
return overflow !== 'visible' && overflow !== 'clip'
51
90
}
52
91
53
- function getFrameElement ( el : Element ) {
92
+ let getFrameElement = ( el : Element ) => {
54
93
if ( ! el . ownerDocument || ! el . ownerDocument . defaultView ) {
55
94
return null
56
95
}
@@ -62,8 +101,8 @@ function getFrameElement(el: Element) {
62
101
}
63
102
}
64
103
65
- function isHiddenByFrame ( el : Element ) : boolean {
66
- const frame = getFrameElement ( el )
104
+ let isHiddenByFrame = ( el : Element ) : boolean => {
105
+ let frame = getFrameElement ( el )
67
106
if ( ! frame ) {
68
107
return false
69
108
}
@@ -73,9 +112,9 @@ function isHiddenByFrame(el: Element): boolean {
73
112
)
74
113
}
75
114
76
- function isScrollable ( el : Element , skipOverflowHiddenElements ?: boolean ) {
115
+ let isScrollable = ( el : Element , skipOverflowHiddenElements ?: boolean ) => {
77
116
if ( el . clientHeight < el . scrollHeight || el . clientWidth < el . scrollWidth ) {
78
- const style = getComputedStyle ( el , null )
117
+ let style = getComputedStyle ( el , null )
79
118
return (
80
119
canOverflow ( style . overflowY , skipOverflowHiddenElements ) ||
81
120
canOverflow ( style . overflowX , skipOverflowHiddenElements ) ||
@@ -94,7 +133,7 @@ function isScrollable(el: Element, skipOverflowHiddenElements?: boolean) {
94
133
* │ target │ frame
95
134
* └────────┘ ┗ ━ ━ ━ ┛
96
135
*/
97
- function alignNearest (
136
+ let alignNearest = (
98
137
scrollingEdgeStart : number ,
99
138
scrollingEdgeEnd : number ,
100
139
scrollingSize : number ,
@@ -103,7 +142,7 @@ function alignNearest(
103
142
elementEdgeStart : number ,
104
143
elementEdgeEnd : number ,
105
144
elementSize : number
106
- ) {
145
+ ) => {
107
146
/**
108
147
* If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B
109
148
*
@@ -227,8 +266,8 @@ function alignNearest(
227
266
return 0
228
267
}
229
268
230
- function getParentElement ( element : Node ) : Element | null {
231
- const parent = element . parentElement
269
+ let getParentElement = ( element : Node ) : Element | null => {
270
+ let parent = element . parentElement
232
271
if ( parent == null ) {
233
272
return ( element . getRootNode ( ) as ShadowRoot ) . host || null
234
273
}
@@ -242,23 +281,23 @@ export default (target: Element, options: Options): ScrollAction[] => {
242
281
return [ ]
243
282
}
244
283
245
- const { scrollMode, block, inline, boundary, skipOverflowHiddenElements } =
284
+ let { scrollMode, block, inline, boundary, skipOverflowHiddenElements } =
246
285
options
247
286
// Allow using a callback to check the boundary
248
287
// The default behavior is to check if the current target matches the boundary element or not
249
288
// If undefined it'll check that target is never undefined (can happen as we recurse up the tree)
250
- const checkBoundary =
289
+ let checkBoundary =
251
290
typeof boundary === 'function' ? boundary : ( node : any ) => node !== boundary
252
291
253
292
if ( ! isElement ( target ) ) {
254
293
throw new TypeError ( 'Invalid target' )
255
294
}
256
295
257
296
// Used to handle the top most element that can be scrolled
258
- const scrollingElement = document . scrollingElement || document . documentElement
297
+ let scrollingElement = document . scrollingElement || document . documentElement
259
298
260
299
// Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box
261
- const frames : Element [ ] = [ ]
300
+ let frames : Element [ ] = [ ]
262
301
let cursor : Element | null = target
263
302
while ( isElement ( cursor ) && checkBoundary ( cursor ) ) {
264
303
// Move cursor to parent
@@ -291,14 +330,14 @@ export default (target: Element, options: Options): ScrollAction[] => {
291
330
// and viewport dimensions on window.innerWidth/Height
292
331
// https://www.quirksmode.org/mobile/viewports2.html
293
332
// https://bokand.github.io/viewport/index.html
294
- const viewportWidth = window . visualViewport ?. width ?? innerWidth
295
- const viewportHeight = window . visualViewport ?. height ?? innerHeight
333
+ let viewportWidth = window . visualViewport ?. width ?? innerWidth
334
+ let viewportHeight = window . visualViewport ?. height ?? innerHeight
296
335
297
336
// Newer browsers supports scroll[X|Y], page[X|Y]Offset is
298
- const viewportX = window . scrollX ?? pageXOffset
299
- const viewportY = window . scrollY ?? pageYOffset
337
+ let viewportX = window . scrollX ?? pageXOffset
338
+ let viewportY = window . scrollY ?? pageYOffset
300
339
301
- const {
340
+ let {
302
341
height : targetHeight ,
303
342
width : targetWidth ,
304
343
top : targetTop ,
@@ -322,14 +361,14 @@ export default (target: Element, options: Options): ScrollAction[] => {
322
361
: targetLeft // inline === 'start || inline === 'nearest
323
362
324
363
// Collect new scroll positions
325
- const computations : ScrollAction [ ] = [ ]
364
+ let computations : ScrollAction [ ] = [ ]
326
365
// In chrome there's no longer a difference between caching the `frames.length` to a var or not, so we don't in this case (size > speed anyways)
327
366
for ( let index = 0 ; index < frames . length ; index ++ ) {
328
- const frame = frames [ index ]
367
+ let frame = frames [ index ]
329
368
330
369
// @TODO add a shouldScroll hook here that allows userland code to take control
331
370
332
- const { height, width, top, right, bottom, left } =
371
+ let { height, width, top, right, bottom, left } =
333
372
frame . getBoundingClientRect ( )
334
373
335
374
// If the element is already visible we can end it here
@@ -349,39 +388,39 @@ export default (target: Element, options: Options): ScrollAction[] => {
349
388
return computations
350
389
}
351
390
352
- const frameStyle = getComputedStyle ( frame )
353
- const borderLeft = parseInt ( frameStyle . borderLeftWidth as string , 10 )
354
- const borderTop = parseInt ( frameStyle . borderTopWidth as string , 10 )
355
- const borderRight = parseInt ( frameStyle . borderRightWidth as string , 10 )
356
- const borderBottom = parseInt ( frameStyle . borderBottomWidth as string , 10 )
391
+ let frameStyle = getComputedStyle ( frame )
392
+ let borderLeft = parseInt ( frameStyle . borderLeftWidth as string , 10 )
393
+ let borderTop = parseInt ( frameStyle . borderTopWidth as string , 10 )
394
+ let borderRight = parseInt ( frameStyle . borderRightWidth as string , 10 )
395
+ let borderBottom = parseInt ( frameStyle . borderBottomWidth as string , 10 )
357
396
358
397
let blockScroll : number = 0
359
398
let inlineScroll : number = 0
360
399
361
400
// The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here
362
401
// @TODO find out if the "as HTMLElement" overrides can be dropped
363
- const scrollbarWidth =
402
+ let scrollbarWidth =
364
403
'offsetWidth' in frame
365
404
? ( frame as HTMLElement ) . offsetWidth -
366
405
( frame as HTMLElement ) . clientWidth -
367
406
borderLeft -
368
407
borderRight
369
408
: 0
370
- const scrollbarHeight =
409
+ let scrollbarHeight =
371
410
'offsetHeight' in frame
372
411
? ( frame as HTMLElement ) . offsetHeight -
373
412
( frame as HTMLElement ) . clientHeight -
374
413
borderTop -
375
414
borderBottom
376
415
: 0
377
416
378
- const scaleX =
417
+ let scaleX =
379
418
'offsetWidth' in frame
380
419
? ( frame as HTMLElement ) . offsetWidth === 0
381
420
? 0
382
421
: width / ( frame as HTMLElement ) . offsetWidth
383
422
: 0
384
- const scaleY =
423
+ let scaleY =
385
424
'offsetHeight' in frame
386
425
? ( frame as HTMLElement ) . offsetHeight === 0
387
426
? 0
@@ -478,7 +517,7 @@ export default (target: Element, options: Options): ScrollAction[] => {
478
517
)
479
518
}
480
519
481
- const { scrollLeft, scrollTop } = frame
520
+ let { scrollLeft, scrollTop } = frame
482
521
// Ensure scroll coordinates are not out of bounds while applying scroll offsets
483
522
blockScroll = Math . max (
484
523
0 ,
0 commit comments