@@ -31,8 +31,7 @@ export interface CustomScrollAction {
3131export type ScrollMode = 'always' | 'if-needed'
3232
3333// Refactor to just the callback variant
34- export type CustomScrollBoundaryCallback = ( parent : Element ) => boolean
35- export type CustomScrollBoundary = Element | CustomScrollBoundaryCallback
34+ export type CustomScrollBoundary = ( parent : Element ) => boolean
3635
3736export interface Options {
3837 block ?: ScrollLogicalPosition
@@ -46,7 +45,7 @@ export interface Options {
4645let viewport : HTMLElement
4746
4847// return the current viewport depending on wether quirks mode is active or not
49- function getViewport ( ) {
48+ function getViewport ( ) {
5049 const doc = document
5150
5251 if ( ! viewport ) {
@@ -242,15 +241,6 @@ export default (target: Element, options: Options): CustomScrollAction[] => {
242241 boundary,
243242 skipOverflowHiddenElements,
244243 } = options
245- // Allow using a callback to check the boundary
246- // The default behavior is to check if the current target matches the boundary element or not
247- // If undefined it'll check that target is never undefined (can happen as we recurse up the tree)
248- const checkBoundary =
249- typeof boundary === 'function' ? boundary : ( node : any ) => node !== boundary
250-
251- if ( ! isElement ( target ) ) {
252- throw new Error ( 'Element is required in scrollIntoView' )
253- }
254244
255245 const targetRect = target . getBoundingClientRect ( )
256246
@@ -260,7 +250,10 @@ export default (target: Element, options: Options): CustomScrollAction[] => {
260250 // @TODO have a better shadowdom test here
261251 while (
262252 isElement ( ( parent = target . parentNode || target . host ) ) &&
263- checkBoundary ( target )
253+ // Allow using a callback to check the boundary
254+ boundary
255+ ? boundary ( target )
256+ : true
264257 ) {
265258 if ( isScrollable ( parent , skipOverflowHiddenElements ) ) {
266259 frames . push ( parent )
@@ -282,34 +275,6 @@ export default (target: Element, options: Options): CustomScrollAction[] => {
282275 const viewportX = window . scrollX || window . pageXOffset
283276 const viewportY = window . scrollY || window . pageYOffset
284277
285- // If the element is already visible we can end it here
286- if ( scrollMode === 'if-needed' ) {
287- // @TODO optimize, as getBoundingClientRect is also called from computations loop
288- const isVisible = frames . every ( frame => {
289- const frameRect = frame . getBoundingClientRect ( )
290-
291- if ( targetRect . top < frameRect . top ) {
292- return false
293- }
294- if ( targetRect . bottom > frameRect . bottom ) {
295- return false
296- }
297-
298- if ( frame === viewport ) {
299- if ( targetRect . bottom > viewportHeight || targetRect . top < 0 ) {
300- return false
301- }
302- if ( targetRect . left > viewportWidth || targetRect . right < 0 ) {
303- return false
304- }
305- }
306- return true
307- } )
308- if ( isVisible ) {
309- return [ ]
310- }
311- }
312-
313278 // @TODO remove duplicate results
314279 // These values mutate as we loop through and generate scroll coordinates
315280 let targetBlock : number =
@@ -327,139 +292,149 @@ export default (target: Element, options: Options): CustomScrollAction[] => {
327292 ? targetRect . left + targetRect . width / 2
328293 : inline === 'end'
329294 ? targetRect . right
330- : targetRect . left // inline === 'nearest
295+ : targetRect . left // inline === 'nearest && inline === 'start
331296
332297 // Collect new scroll positions
333- const computations = frames . map (
334- ( frame ) : CustomScrollAction => {
335- const frameRect = frame . getBoundingClientRect ( )
336-
337- const frameStyle = getComputedStyle ( frame )
338- const borderLeft = parseInt ( frameStyle . borderLeftWidth as string , 10 )
339- const borderTop = parseInt ( frameStyle . borderTopWidth as string , 10 )
340- const borderRight = parseInt ( frameStyle . borderRightWidth as string , 10 )
341- const borderBottom = parseInt ( frameStyle . borderBottomWidth as string , 10 )
342- // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here
343- // @TODO find out if the "as HTMLElement" overrides can be dropped
344- const scrollbarWidth =
345- 'offsetWidth' in frame
346- ? ( frame as HTMLElement ) . offsetWidth -
347- ( frame as HTMLElement ) . clientWidth -
348- borderLeft -
349- borderRight
350- : 0
351- const scrollbarHeight =
352- 'offsetHeight' in frame
353- ? ( frame as HTMLElement ) . offsetHeight -
354- ( frame as HTMLElement ) . clientHeight -
355- borderTop -
356- borderBottom
357- : 0
358-
359- let blockScroll : number = 0
360- let inlineScroll : number = 0
361-
362- if ( block === 'start' ) {
363- blockScroll =
364- viewport === frame
365- ? viewportY + targetBlock
366- : targetBlock - frameRect . top - borderTop
367- } else if ( block === 'end' ) {
368- blockScroll =
369- viewport === frame
370- ? viewportY + ( targetBlock - viewportHeight )
371- : frame . scrollTop -
372- ( frameRect . bottom - targetBlock ) +
373- borderBottom +
374- scrollbarHeight
375- } else if ( block === 'nearest' ) {
376- blockScroll =
377- viewport === frame
378- ? viewportY +
379- alignNearest (
380- viewportY ,
381- viewportY + viewportHeight ,
382- viewportHeight ,
383- borderTop ,
384- borderBottom ,
385- viewportY + targetBlock ,
386- viewportY + targetBlock + targetRect . height ,
387- targetRect . height
388- )
389- : frame . scrollTop +
390- alignNearest (
391- frameRect . top ,
392- frameRect . bottom ,
393- frameRect . height ,
394- borderTop ,
395- borderBottom + scrollbarHeight ,
396- targetBlock ,
397- targetBlock + targetRect . height ,
398- targetRect . height
399- )
400- } else {
401- // block === 'center' is the default
402- blockScroll =
403- viewport === frame
404- ? viewportY + targetBlock - viewportHeight / 2
405- : frame . scrollTop -
406- ( frameRect . top + frameRect . height / 2 - targetBlock )
407- }
408-
409- if ( inline === 'start' ) {
410- inlineScroll =
411- viewport === frame
412- ? viewportX + targetInline
413- : frame . scrollLeft + ( targetInline - frameRect . left ) - borderLeft
414- } else if ( inline === 'center' ) {
415- inlineScroll =
416- viewport === frame
417- ? viewportX + targetInline - viewportWidth / 2
418- : frame . scrollLeft -
419- ( frameRect . left + frameRect . width / 2 - targetInline )
420- } else if ( inline === 'end' ) {
421- inlineScroll =
422- viewport === frame
423- ? viewportX + ( targetInline - viewportWidth )
424- : frame . scrollLeft -
425- ( frameRect . right - targetInline ) +
426- borderRight +
427- scrollbarWidth
428- } else {
429- // inline === 'nearest' is the default
430- inlineScroll =
431- viewport === frame
432- ? viewportX +
433- alignNearest (
434- viewportX ,
435- viewportX + viewportWidth ,
436- viewportWidth ,
437- borderLeft ,
438- borderRight ,
439- viewportX + targetInline ,
440- viewportX + targetInline + targetRect . width ,
441- targetRect . width
442- )
443- : frame . scrollLeft +
444- alignNearest (
445- frameRect . left ,
446- frameRect . right ,
447- frameRect . width ,
448- borderLeft ,
449- borderRight + scrollbarWidth ,
450- targetInline ,
451- targetInline + targetRect . width ,
452- targetRect . width
453- )
454- }
455-
456- // Cache the offset so that parent frames can scroll this into view correctly
457- targetBlock += frame . scrollTop - blockScroll
458- targetInline += frame . scrollLeft - inlineScroll
459-
460- return { el : frame , top : blockScroll , left : inlineScroll }
298+ const computations = frames . reduce < CustomScrollAction [ ] > ( ( results , frame ) => {
299+ const frameRect = frame . getBoundingClientRect ( )
300+
301+ // Handle scrollMode: 'if-needed'
302+ // If the element is already visible we can end it here
303+ if (
304+ scrollMode === 'if-needed' && frame === viewport
305+ ? targetRect . bottom > viewportHeight ||
306+ targetRect . top < 0 ||
307+ ( targetRect . left > viewportWidth || targetRect . right < 0 )
308+ : targetRect . top < frameRect . top || targetRect . bottom > frameRect . bottom
309+ ) {
310+ return [ ]
461311 }
462- )
312+
313+ const frameStyle = getComputedStyle ( frame )
314+ const borderLeft = parseInt ( frameStyle . borderLeftWidth as string , 10 )
315+ const borderTop = parseInt ( frameStyle . borderTopWidth as string , 10 )
316+ const borderRight = parseInt ( frameStyle . borderRightWidth as string , 10 )
317+ const borderBottom = parseInt ( frameStyle . borderBottomWidth as string , 10 )
318+ // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here
319+ // @TODO find out if the "as HTMLElement" overrides can be dropped
320+ const scrollbarWidth =
321+ 'offsetWidth' in frame
322+ ? ( frame as HTMLElement ) . offsetWidth -
323+ ( frame as HTMLElement ) . clientWidth -
324+ borderLeft -
325+ borderRight
326+ : 0
327+ const scrollbarHeight =
328+ 'offsetHeight' in frame
329+ ? ( frame as HTMLElement ) . offsetHeight -
330+ ( frame as HTMLElement ) . clientHeight -
331+ borderTop -
332+ borderBottom
333+ : 0
334+
335+ let blockScroll : number = 0
336+ let inlineScroll : number = 0
337+
338+ if ( block === 'start' ) {
339+ blockScroll =
340+ viewport === frame
341+ ? viewportY + targetBlock
342+ : targetBlock - frameRect . top - borderTop
343+ } else if ( block === 'end' ) {
344+ blockScroll =
345+ viewport === frame
346+ ? viewportY + ( targetBlock - viewportHeight )
347+ : frame . scrollTop -
348+ ( frameRect . bottom - targetBlock ) +
349+ borderBottom +
350+ scrollbarHeight
351+ } else if ( block === 'nearest' ) {
352+ blockScroll =
353+ viewport === frame
354+ ? viewportY +
355+ alignNearest (
356+ viewportY ,
357+ viewportY + viewportHeight ,
358+ viewportHeight ,
359+ borderTop ,
360+ borderBottom ,
361+ viewportY + targetBlock ,
362+ viewportY + targetBlock + targetRect . height ,
363+ targetRect . height
364+ )
365+ : frame . scrollTop +
366+ alignNearest (
367+ frameRect . top ,
368+ frameRect . bottom ,
369+ frameRect . height ,
370+ borderTop ,
371+ borderBottom + scrollbarHeight ,
372+ targetBlock ,
373+ targetBlock + targetRect . height ,
374+ targetRect . height
375+ )
376+ } else {
377+ // block === 'center' is the default
378+ blockScroll =
379+ viewport === frame
380+ ? viewportY + targetBlock - viewportHeight / 2
381+ : frame . scrollTop -
382+ ( frameRect . top + frameRect . height / 2 - targetBlock )
383+ }
384+
385+ if ( inline === 'start' ) {
386+ inlineScroll =
387+ viewport === frame
388+ ? viewportX + targetInline
389+ : frame . scrollLeft + ( targetInline - frameRect . left ) - borderLeft
390+ } else if ( inline === 'center' ) {
391+ inlineScroll =
392+ viewport === frame
393+ ? viewportX + targetInline - viewportWidth / 2
394+ : frame . scrollLeft -
395+ ( frameRect . left + frameRect . width / 2 - targetInline )
396+ } else if ( inline === 'end' ) {
397+ inlineScroll =
398+ viewport === frame
399+ ? viewportX + ( targetInline - viewportWidth )
400+ : frame . scrollLeft -
401+ ( frameRect . right - targetInline ) +
402+ borderRight +
403+ scrollbarWidth
404+ } else {
405+ // inline === 'nearest' is the default
406+ inlineScroll =
407+ viewport === frame
408+ ? viewportX +
409+ alignNearest (
410+ viewportX ,
411+ viewportX + viewportWidth ,
412+ viewportWidth ,
413+ borderLeft ,
414+ borderRight ,
415+ viewportX + targetInline ,
416+ viewportX + targetInline + targetRect . width ,
417+ targetRect . width
418+ )
419+ : frame . scrollLeft +
420+ alignNearest (
421+ frameRect . left ,
422+ frameRect . right ,
423+ frameRect . width ,
424+ borderLeft ,
425+ borderRight + scrollbarWidth ,
426+ targetInline ,
427+ targetInline + targetRect . width ,
428+ targetRect . width
429+ )
430+ }
431+
432+ // Cache the offset so that parent frames can scroll this into view correctly
433+ targetBlock += frame . scrollTop - blockScroll
434+ targetInline += frame . scrollLeft - inlineScroll
435+
436+ return [ ...results , { el : frame , top : blockScroll , left : inlineScroll } ]
437+ } , [ ] )
463438
464439 return computations
465440}
0 commit comments