compute-scroll-into-view 2.0.1 → 2.0.2

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/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";function t(t){return"object"==typeof t&&null!=t&&1===t.nodeType}function e(t,e){return(!e||"hidden"!==t)&&("visible"!==t&&"clip"!==t)}function n(t,n){if(t.clientHeight<t.scrollHeight||t.clientWidth<t.scrollWidth){const o=getComputedStyle(t,null);return e(o.overflowY,n)||e(o.overflowX,n)||function(t){const e=function(t){if(!t.ownerDocument||!t.ownerDocument.defaultView)return null;try{return t.ownerDocument.defaultView.frameElement}catch(t){return null}}(t);return!!e&&(e.clientHeight<t.scrollHeight||e.clientWidth<t.scrollWidth)}(t)}return!1}function o(t,e,n,o,i,l,r,d){return l<t&&r>e||l>t&&r<e?0:l<=t&&d<=n||r>=e&&d>=n?l-t-o:r>e&&d<n||l<t&&d>n?r-e+i:0}function i(t){const e=t.parentElement;return null==e?t.getRootNode().host||null:e}module.exports=(e,l)=>{var r,d,s,u,c,f;if("undefined"==typeof document)return[];const{scrollMode:h,block:a,inline:g,boundary:m,skipOverflowHiddenElements:p}=l,w="function"==typeof m?m:t=>t!==m;if(!t(e))throw new TypeError("Invalid target");const W=document.scrollingElement||document.documentElement,H=[];let b=e;for(;t(b)&&w(b);){if(b=i(b),b===W){H.push(b);break}null!=b&&b===document.body&&n(b)&&!n(document.documentElement)||null!=b&&n(b,p)&&H.push(b)}const v=null!=(d=null==(r=window.visualViewport)?void 0:r.width)?d:innerWidth,y=null!=(u=null==(s=window.visualViewport)?void 0:s.height)?u:innerHeight,E=null!=(c=window.scrollX)?c:pageXOffset,M=null!=(f=window.scrollY)?f:pageYOffset,{height:x,width:I,top:C,right:R,bottom:T,left:V}=e.getBoundingClientRect();let k="start"===a||"nearest"===a?C:"end"===a?T:C+x/2,B="center"===g?V+I/2:"end"===g?R:V;const D=[];for(let t=0;t<H.length;t++){const e=H[t],{height:n,width:i,top:l,right:r,bottom:d,left:s}=e.getBoundingClientRect();if("if-needed"===h&&C>=0&&V>=0&&T<=y&&R<=v&&C>=l&&T<=d&&V>=s&&R<=r)return D;const u=getComputedStyle(e),c=parseInt(u.borderLeftWidth,10),f=parseInt(u.borderTopWidth,10),m=parseInt(u.borderRightWidth,10),p=parseInt(u.borderBottomWidth,10);let w=0,b=0;const O="offsetWidth"in e?e.offsetWidth-e.clientWidth-c-m:0,X="offsetHeight"in e?e.offsetHeight-e.clientHeight-f-p:0,Y="offsetWidth"in e?0===e.offsetWidth?0:i/e.offsetWidth:0,L="offsetHeight"in e?0===e.offsetHeight?0:n/e.offsetHeight:0;if(W===e)w="start"===a?k:"end"===a?k-y:"nearest"===a?o(M,M+y,y,f,p,M+k,M+k+x,x):k-y/2,b="start"===g?B:"center"===g?B-v/2:"end"===g?B-v:o(E,E+v,v,c,m,E+B,E+B+I,I),w=Math.max(0,w+M),b=Math.max(0,b+E);else{w="start"===a?k-l-f:"end"===a?k-d+p+X:"nearest"===a?o(l,d,n,f,p+X,k,k+x,x):k-(l+n/2)+X/2,b="start"===g?B-s-c:"center"===g?B-(s+i/2)+O/2:"end"===g?B-r+m+O:o(s,r,i,c,m+O,B,B+I,I);const{scrollLeft:t,scrollTop:u}=e;w=Math.max(0,Math.min(u+w/L,e.scrollHeight-n/L+X)),b=Math.max(0,Math.min(t+b/Y,e.scrollWidth-i/Y+O)),k+=u-w,B+=t-b}D.push({el:e,top:w,left:b})}return D};
1
+ "use strict";let e=e=>"object"==typeof e&&null!=e&&1===e.nodeType,t=(e,t)=>(!t||"hidden"!==e)&&("visible"!==e&&"clip"!==e),n=(e,n)=>{if(e.clientHeight<e.scrollHeight||e.clientWidth<e.scrollWidth){let l=getComputedStyle(e,null);return t(l.overflowY,n)||t(l.overflowX,n)||(e=>{let t=(e=>{if(!e.ownerDocument||!e.ownerDocument.defaultView)return null;try{return e.ownerDocument.defaultView.frameElement}catch(e){return null}})(e);return!!t&&(t.clientHeight<e.scrollHeight||t.clientWidth<e.scrollWidth)})(e)}return!1},l=(e,t,n,l,i,o,r,d)=>o<e&&r>t||o>e&&r<t?0:o<=e&&d<=n||r>=t&&d>=n?o-e-l:r>t&&d<n||o<e&&d>n?r-t+i:0,i=e=>{let t=e.parentElement;return null==t?e.getRootNode().host||null:t};module.exports=(t,o)=>{var r,d,h,f,u,s;if("undefined"==typeof document)return[];let{scrollMode:c,block:a,inline:g,boundary:m,skipOverflowHiddenElements:p}=o,w="function"==typeof m?m:e=>e!==m;if(!e(t))throw new TypeError("Invalid target");let W=document.scrollingElement||document.documentElement,H=[],b=t;for(;e(b)&&w(b);){if(b=i(b),b===W){H.push(b);break}null!=b&&b===document.body&&n(b)&&!n(document.documentElement)||null!=b&&n(b,p)&&H.push(b)}let v=null!=(d=null==(r=window.visualViewport)?void 0:r.width)?d:innerWidth,y=null!=(f=null==(h=window.visualViewport)?void 0:h.height)?f:innerHeight,E=null!=(u=window.scrollX)?u:pageXOffset,M=null!=(s=window.scrollY)?s:pageYOffset,{height:x,width:I,top:C,right:R,bottom:T,left:V}=t.getBoundingClientRect(),k="start"===a||"nearest"===a?C:"end"===a?T:C+x/2,B="center"===g?V+I/2:"end"===g?R:V,D=[];for(let e=0;e<H.length;e++){let t=H[e],{height:n,width:i,top:o,right:r,bottom:d,left:h}=t.getBoundingClientRect();if("if-needed"===c&&C>=0&&V>=0&&T<=y&&R<=v&&C>=o&&T<=d&&V>=h&&R<=r)return D;let f=getComputedStyle(t),u=parseInt(f.borderLeftWidth,10),s=parseInt(f.borderTopWidth,10),m=parseInt(f.borderRightWidth,10),p=parseInt(f.borderBottomWidth,10),w=0,b=0,O="offsetWidth"in t?t.offsetWidth-t.clientWidth-u-m:0,X="offsetHeight"in t?t.offsetHeight-t.clientHeight-s-p:0,Y="offsetWidth"in t?0===t.offsetWidth?0:i/t.offsetWidth:0,L="offsetHeight"in t?0===t.offsetHeight?0:n/t.offsetHeight:0;if(W===t)w="start"===a?k:"end"===a?k-y:"nearest"===a?l(M,M+y,y,s,p,M+k,M+k+x,x):k-y/2,b="start"===g?B:"center"===g?B-v/2:"end"===g?B-v:l(E,E+v,v,u,m,E+B,E+B+I,I),w=Math.max(0,w+M),b=Math.max(0,b+E);else{w="start"===a?k-o-s:"end"===a?k-d+p+X:"nearest"===a?l(o,d,n,s,p+X,k,k+x,x):k-(o+n/2)+X/2,b="start"===g?B-h-u:"center"===g?B-(h+i/2)+O/2:"end"===g?B-r+m+O:l(h,r,i,u,m+O,B,B+I,I);let{scrollLeft:e,scrollTop:f}=t;w=Math.max(0,Math.min(f+w/L,t.scrollHeight-n/L+X)),b=Math.max(0,Math.min(e+b/Y,t.scrollWidth-i/Y+O)),k+=f-w,B+=e-b}D.push({el:t,top:w,left:b})}return D};
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["// Compute what scrolling needs to be done on required scrolling boxes for target to be in view\n\n// The type names here are named after the spec to make it easier to find more information around what they mean:\n// To reduce churn and reduce things that need be maintained things from the official TS DOM library is used here\n// https://drafts.csswg.org/cssom-view/\n\n// For a definition on what is \"block flow direction\" exactly, check this: https://drafts.csswg.org/css-writing-modes-4/#block-flow-direction\n\n/**\n * This new option is tracked in this PR, which is the most likely candidate at the time: https://github.com/w3c/csswg-drafts/pull/1805\n * @public\n */\nexport type ScrollMode = 'always' | 'if-needed'\n\n/** @public */\nexport interface Options {\n block?: ScrollLogicalPosition\n inline?: ScrollLogicalPosition\n scrollMode?: ScrollMode\n // Custom behavior, not in any spec\n boundary?: Element | ((parent: Element) => boolean) | null\n /**\n * New option that skips auto-scrolling all nodes with overflow: hidden set\n * See FF implementation: https://hg.mozilla.org/integration/fx-team/rev/c48c3ec05012#l7.18\n * @public\n */\n skipOverflowHiddenElements?: boolean\n}\n\n/** @public */\nexport interface ScrollAction {\n el: Element\n top: number\n left: number\n}\n\n// @TODO better shadowdom test, 11 = document fragment\nfunction isElement(el: any): el is Element {\n return typeof el === 'object' && el != null && el.nodeType === 1\n}\n\nfunction canOverflow(\n overflow: string | null,\n skipOverflowHiddenElements?: boolean\n) {\n if (skipOverflowHiddenElements && overflow === 'hidden') {\n return false\n }\n\n return overflow !== 'visible' && overflow !== 'clip'\n}\n\nfunction getFrameElement(el: Element) {\n if (!el.ownerDocument || !el.ownerDocument.defaultView) {\n return null\n }\n\n try {\n return el.ownerDocument.defaultView.frameElement\n } catch (e) {\n return null\n }\n}\n\nfunction isHiddenByFrame(el: Element): boolean {\n const frame = getFrameElement(el)\n if (!frame) {\n return false\n }\n\n return (\n frame.clientHeight < el.scrollHeight || frame.clientWidth < el.scrollWidth\n )\n}\n\nfunction isScrollable(el: Element, skipOverflowHiddenElements?: boolean) {\n if (el.clientHeight < el.scrollHeight || el.clientWidth < el.scrollWidth) {\n const style = getComputedStyle(el, null)\n return (\n canOverflow(style.overflowY, skipOverflowHiddenElements) ||\n canOverflow(style.overflowX, skipOverflowHiddenElements) ||\n isHiddenByFrame(el)\n )\n }\n\n return false\n}\n/**\n * Find out which edge to align against when logical scroll position is \"nearest\"\n * Interesting fact: \"nearest\" works similarily to \"if-needed\", if the element is fully visible it will not scroll it\n *\n * Legends:\n * ┌────────┐ ┏ ━ ━ ━ ┓\n * │ target │ frame\n * └────────┘ ┗ ━ ━ ━ ┛\n */\nfunction alignNearest(\n scrollingEdgeStart: number,\n scrollingEdgeEnd: number,\n scrollingSize: number,\n scrollingBorderStart: number,\n scrollingBorderEnd: number,\n elementEdgeStart: number,\n elementEdgeEnd: number,\n elementSize: number\n) {\n /**\n * If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B\n *\n * ┌──┐\n * ┏━│━━│━┓\n * │ │\n * ┃ │ │ ┃ do nothing\n * │ │\n * ┗━│━━│━┛\n * └──┘\n *\n * If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D\n *\n * ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐\n * │┃ ┃│ do nothing\n * └───────────┘\n * ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart < scrollingEdgeStart &&\n elementEdgeEnd > scrollingEdgeEnd) ||\n (elementEdgeStart > scrollingEdgeStart && elementEdgeEnd < scrollingEdgeEnd)\n ) {\n return 0\n }\n\n /**\n * If element edge A is outside scrolling box edge A and element height is less than scrolling box height\n *\n * ┌──┐\n * ┏━│━━│━┓ ┏━┌━━┐━┓\n * └──┘ │ │\n * from ┃ ┃ to ┃ └──┘ ┃\n *\n * ┗━ ━━ ━┛ ┗━ ━━ ━┛\n *\n * If element edge B is outside scrolling box edge B and element height is greater than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━┌━━┐━┓\n * │ │\n * from ┃ ┌──┐ ┃ to ┃ │ │ ┃\n * │ │ │ │\n * ┗━│━━│━┛ ┗━│━━│━┛\n * │ │ └──┘\n * │ │\n * └──┘\n *\n * If element edge C is outside scrolling box edge C and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * │ ┃ │ ┃ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * ┃ │ ┃ │ ┃ ┃ │\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart <= scrollingEdgeStart && elementSize <= scrollingSize) ||\n (elementEdgeEnd >= scrollingEdgeEnd && elementSize >= scrollingSize)\n ) {\n return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart\n }\n\n /**\n * If element edge B is outside scrolling box edge B and element height is less than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━ ━━ ━┓\n *\n * from ┃ ┃ to ┃ ┌──┐ ┃\n * ┌──┐ │ │\n * ┗━│━━│━┛ ┗━└━━┘━┛\n * └──┘\n *\n * If element edge A is outside scrolling box edge A and element height is greater than scrolling box height\n *\n * ┌──┐\n * │ │\n * │ │ ┌──┐\n * ┏━│━━│━┓ ┏━│━━│━┓\n * │ │ │ │\n * from ┃ └──┘ ┃ to ┃ │ │ ┃\n * │ │\n * ┗━ ━━ ━┛ ┗━└━━┘━┛\n *\n * If element edge C is outside scrolling box edge C and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * │ ┃ │ ┃ │ ┃ ┃\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * ┃ │ ┃ │ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n */\n if (\n (elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||\n (elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)\n ) {\n return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd\n }\n\n return 0\n}\n\nfunction getParentElement(element: Node): Element | null {\n const parent = element.parentElement\n if (parent == null) {\n return (element.getRootNode() as ShadowRoot).host || null\n }\n return parent\n}\n\n/** @public */\nexport default (target: Element, options: Options): ScrollAction[] => {\n if (typeof document === 'undefined') {\n // If there's no DOM we assume it's not in a browser environment\n return []\n }\n\n const { scrollMode, block, inline, boundary, skipOverflowHiddenElements } =\n options\n // Allow using a callback to check the boundary\n // The default behavior is to check if the current target matches the boundary element or not\n // If undefined it'll check that target is never undefined (can happen as we recurse up the tree)\n const checkBoundary =\n typeof boundary === 'function' ? boundary : (node: any) => node !== boundary\n\n if (!isElement(target)) {\n throw new TypeError('Invalid target')\n }\n\n // Used to handle the top most element that can be scrolled\n const scrollingElement = document.scrollingElement || document.documentElement\n\n // Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box\n const frames: Element[] = []\n let cursor: Element | null = target\n while (isElement(cursor) && checkBoundary(cursor)) {\n // Move cursor to parent\n cursor = getParentElement(cursor)\n\n // Stop when we reach the viewport\n if (cursor === scrollingElement) {\n frames.push(cursor)\n break\n }\n\n // Skip document.body if it's not the scrollingElement and documentElement isn't independently scrollable\n if (\n cursor != null &&\n cursor === document.body &&\n isScrollable(cursor) &&\n !isScrollable(document.documentElement)\n ) {\n continue\n }\n\n // Now we check if the element is scrollable, this code only runs if the loop haven't already hit the viewport or a custom boundary\n if (cursor != null && isScrollable(cursor, skipOverflowHiddenElements)) {\n frames.push(cursor)\n }\n }\n\n // Support pinch-zooming properly, making sure elements scroll into the visual viewport\n // Browsers that don't support visualViewport will report the layout viewport dimensions on document.documentElement.clientWidth/Height\n // and viewport dimensions on window.innerWidth/Height\n // https://www.quirksmode.org/mobile/viewports2.html\n // https://bokand.github.io/viewport/index.html\n const viewportWidth = window.visualViewport?.width ?? innerWidth\n const viewportHeight = window.visualViewport?.height ?? innerHeight\n\n // Newer browsers supports scroll[X|Y], page[X|Y]Offset is\n const viewportX = window.scrollX ?? pageXOffset\n const viewportY = window.scrollY ?? pageYOffset\n\n const {\n height: targetHeight,\n width: targetWidth,\n top: targetTop,\n right: targetRight,\n bottom: targetBottom,\n left: targetLeft,\n } = target.getBoundingClientRect()\n\n // These values mutate as we loop through and generate scroll coordinates\n let targetBlock: number =\n block === 'start' || block === 'nearest'\n ? targetTop\n : block === 'end'\n ? targetBottom\n : targetTop + targetHeight / 2 // block === 'center\n let targetInline: number =\n inline === 'center'\n ? targetLeft + targetWidth / 2\n : inline === 'end'\n ? targetRight\n : targetLeft // inline === 'start || inline === 'nearest\n\n // Collect new scroll positions\n const computations: ScrollAction[] = []\n // 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)\n for (let index = 0; index < frames.length; index++) {\n const frame = frames[index]\n\n // @TODO add a shouldScroll hook here that allows userland code to take control\n\n const { height, width, top, right, bottom, left } =\n frame.getBoundingClientRect()\n\n // If the element is already visible we can end it here\n // @TODO targetBlock and targetInline should be taken into account to be compliant with https://github.com/w3c/csswg-drafts/pull/1805/files#diff-3c17f0e43c20f8ecf89419d49e7ef5e0R1333\n if (\n scrollMode === 'if-needed' &&\n targetTop >= 0 &&\n targetLeft >= 0 &&\n targetBottom <= viewportHeight &&\n targetRight <= viewportWidth &&\n targetTop >= top &&\n targetBottom <= bottom &&\n targetLeft >= left &&\n targetRight <= right\n ) {\n // Break the loop and return the computations for things that are not fully visible\n return computations\n }\n\n const frameStyle = getComputedStyle(frame)\n const borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)\n const borderTop = parseInt(frameStyle.borderTopWidth as string, 10)\n const borderRight = parseInt(frameStyle.borderRightWidth as string, 10)\n const borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)\n\n let blockScroll: number = 0\n let inlineScroll: number = 0\n\n // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here\n // @TODO find out if the \"as HTMLElement\" overrides can be dropped\n const scrollbarWidth =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth -\n (frame as HTMLElement).clientWidth -\n borderLeft -\n borderRight\n : 0\n const scrollbarHeight =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight -\n (frame as HTMLElement).clientHeight -\n borderTop -\n borderBottom\n : 0\n\n const scaleX =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth === 0\n ? 0\n : width / (frame as HTMLElement).offsetWidth\n : 0\n const scaleY =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight === 0\n ? 0\n : height / (frame as HTMLElement).offsetHeight\n : 0\n\n if (scrollingElement === frame) {\n // Handle viewport logic (document.documentElement or document.body)\n\n if (block === 'start') {\n blockScroll = targetBlock\n } else if (block === 'end') {\n blockScroll = targetBlock - viewportHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n viewportY,\n viewportY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n viewportY + targetBlock,\n viewportY + targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - viewportHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline\n } else if (inline === 'center') {\n inlineScroll = targetInline - viewportWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - viewportWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n viewportX,\n viewportX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n viewportX + targetInline,\n viewportX + targetInline + targetWidth,\n targetWidth\n )\n }\n\n // Apply scroll position offsets and ensure they are within bounds\n // @TODO add more test cases to cover this 100%\n blockScroll = Math.max(0, blockScroll + viewportY)\n inlineScroll = Math.max(0, inlineScroll + viewportX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\n\n if (block === 'start') {\n blockScroll = targetBlock - top - borderTop\n } else if (block === 'end') {\n blockScroll = targetBlock - bottom + borderBottom + scrollbarHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n top,\n bottom,\n height,\n borderTop,\n borderBottom + scrollbarHeight,\n targetBlock,\n targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - (top + height / 2) + scrollbarHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline - left - borderLeft\n } else if (inline === 'center') {\n inlineScroll = targetInline - (left + width / 2) + scrollbarWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - right + borderRight + scrollbarWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n left,\n right,\n width,\n borderLeft,\n borderRight + scrollbarWidth,\n targetInline,\n targetInline + targetWidth,\n targetWidth\n )\n }\n\n const { scrollLeft, scrollTop } = frame\n // Ensure scroll coordinates are not out of bounds while applying scroll offsets\n blockScroll = Math.max(\n 0,\n Math.min(\n scrollTop + blockScroll / scaleY,\n frame.scrollHeight - height / scaleY + scrollbarHeight\n )\n )\n inlineScroll = Math.max(\n 0,\n Math.min(\n scrollLeft + inlineScroll / scaleX,\n frame.scrollWidth - width / scaleX + scrollbarWidth\n )\n )\n\n // Cache the offset so that parent frames can scroll this into view correctly\n targetBlock += scrollTop - blockScroll\n targetInline += scrollLeft - inlineScroll\n }\n\n computations.push({ el: frame, top: blockScroll, left: inlineScroll })\n }\n\n return computations\n}\n"],"names":["isElement","el","nodeType","canOverflow","overflow","skipOverflowHiddenElements","isScrollable","clientHeight","scrollHeight","clientWidth","scrollWidth","style","getComputedStyle","overflowY","overflowX","frame","ownerDocument","defaultView","frameElement","e","getFrameElement","isHiddenByFrame","alignNearest","scrollingEdgeStart","scrollingEdgeEnd","scrollingSize","scrollingBorderStart","scrollingBorderEnd","elementEdgeStart","elementEdgeEnd","elementSize","getParentElement","element","parent","parentElement","getRootNode","host","target","options","_a","_b","_c","_d","_e","_f","document","scrollMode","block","inline","boundary","checkBoundary","node","TypeError","scrollingElement","documentElement","frames","cursor","push","body","viewportWidth","window","visualViewport","width","innerWidth","viewportHeight","height","innerHeight","viewportX","scrollX","pageXOffset","viewportY","scrollY","pageYOffset","targetHeight","targetWidth","top","targetTop","right","targetRight","bottom","targetBottom","left","targetLeft","getBoundingClientRect","targetBlock","targetInline","computations","index","length","frameStyle","borderLeft","parseInt","borderLeftWidth","borderTop","borderTopWidth","borderRight","borderRightWidth","borderBottom","borderBottomWidth","blockScroll","inlineScroll","scrollbarWidth","offsetWidth","scrollbarHeight","offsetHeight","scaleX","scaleY","Math","max","scrollLeft","scrollTop","min"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["// Compute what scrolling needs to be done on required scrolling boxes for target to be in view\n\n// The type names here are named after the spec to make it easier to find more information around what they mean:\n// To reduce churn and reduce things that need be maintained things from the official TS DOM library is used here\n// https://drafts.csswg.org/cssom-view/\n\n// For a definition on what is \"block flow direction\" exactly, check this: https://drafts.csswg.org/css-writing-modes-4/#block-flow-direction\n\n/**\n * This new option is tracked in this PR, which is the most likely candidate at the time: https://github.com/w3c/csswg-drafts/pull/1805\n * @public\n */\nexport type ScrollMode = 'always' | 'if-needed'\n\n/** @public */\nexport interface Options {\n /**\n * 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.\n * This means that `block: 'start'` aligns to the top edge and `block: 'end'` to the bottom.\n * @defaultValue 'center'\n */\n block?: ScrollLogicalPosition\n /**\n * 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.\n * @defaultValue 'nearest'\n */\n inline?: ScrollLogicalPosition\n /**\n * This is a proposed addition to the spec that you can track here: https://github.com/w3c/csswg-drafts/pull/5677\n *\n * This library will be updated to reflect any changes to the spec and will provide a migration path.\n * 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).\n * @defaultValue 'always'\n */\n scrollMode?: ScrollMode\n /**\n * 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.\n * By passing a boundary you can short-circuit this loop depending on your needs:\n * \n * - Prevent the browser window from scrolling.\n * - Scroll elements into view in a list, without scrolling container elements.\n * \n * You can also pass a function to do more dynamic checks to override the scroll scoping:\n * \n * ```js\n * let actions = compute(target, {\n * boundary: (parent) => {\n * // By default `overflow: hidden` elements are allowed, only `overflow: visible | clip` is skipped as\n * // this is required by the CSSOM spec\n * if (getComputedStyle(parent)['overflow'] === 'hidden') {\n * return false\n * }\n\n * return true\n * },\n * })\n * ```\n * @defaultValue null\n */\n boundary?: Element | ((parent: Element) => boolean) | null\n /**\n * New option that skips auto-scrolling all nodes with overflow: hidden set\n * See FF implementation: https://hg.mozilla.org/integration/fx-team/rev/c48c3ec05012#l7.18\n * @defaultValue false\n * @public\n */\n skipOverflowHiddenElements?: boolean\n}\n\n/** @public */\nexport interface ScrollAction {\n el: Element\n top: number\n left: number\n}\n\n// @TODO better shadowdom test, 11 = document fragment\nlet isElement = (el: any): el is Element =>\n typeof el === 'object' && el != null && el.nodeType === 1\n\nlet canOverflow = (\n overflow: string | null,\n skipOverflowHiddenElements?: boolean\n) => {\n if (skipOverflowHiddenElements && overflow === 'hidden') {\n return false\n }\n\n return overflow !== 'visible' && overflow !== 'clip'\n}\n\nlet getFrameElement = (el: Element) => {\n if (!el.ownerDocument || !el.ownerDocument.defaultView) {\n return null\n }\n\n try {\n return el.ownerDocument.defaultView.frameElement\n } catch (e) {\n return null\n }\n}\n\nlet isHiddenByFrame = (el: Element): boolean => {\n let frame = getFrameElement(el)\n if (!frame) {\n return false\n }\n\n return (\n frame.clientHeight < el.scrollHeight || frame.clientWidth < el.scrollWidth\n )\n}\n\nlet isScrollable = (el: Element, skipOverflowHiddenElements?: boolean) => {\n if (el.clientHeight < el.scrollHeight || el.clientWidth < el.scrollWidth) {\n let style = getComputedStyle(el, null)\n return (\n canOverflow(style.overflowY, skipOverflowHiddenElements) ||\n canOverflow(style.overflowX, skipOverflowHiddenElements) ||\n isHiddenByFrame(el)\n )\n }\n\n return false\n}\n/**\n * Find out which edge to align against when logical scroll position is \"nearest\"\n * Interesting fact: \"nearest\" works similarily to \"if-needed\", if the element is fully visible it will not scroll it\n *\n * Legends:\n * ┌────────┐ ┏ ━ ━ ━ ┓\n * │ target │ frame\n * └────────┘ ┗ ━ ━ ━ ┛\n */\nlet alignNearest = (\n scrollingEdgeStart: number,\n scrollingEdgeEnd: number,\n scrollingSize: number,\n scrollingBorderStart: number,\n scrollingBorderEnd: number,\n elementEdgeStart: number,\n elementEdgeEnd: number,\n elementSize: number\n) => {\n /**\n * If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B\n *\n * ┌──┐\n * ┏━│━━│━┓\n * │ │\n * ┃ │ │ ┃ do nothing\n * │ │\n * ┗━│━━│━┛\n * └──┘\n *\n * If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D\n *\n * ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐\n * │┃ ┃│ do nothing\n * └───────────┘\n * ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart < scrollingEdgeStart &&\n elementEdgeEnd > scrollingEdgeEnd) ||\n (elementEdgeStart > scrollingEdgeStart && elementEdgeEnd < scrollingEdgeEnd)\n ) {\n return 0\n }\n\n /**\n * If element edge A is outside scrolling box edge A and element height is less than scrolling box height\n *\n * ┌──┐\n * ┏━│━━│━┓ ┏━┌━━┐━┓\n * └──┘ │ │\n * from ┃ ┃ to ┃ └──┘ ┃\n *\n * ┗━ ━━ ━┛ ┗━ ━━ ━┛\n *\n * If element edge B is outside scrolling box edge B and element height is greater than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━┌━━┐━┓\n * │ │\n * from ┃ ┌──┐ ┃ to ┃ │ │ ┃\n * │ │ │ │\n * ┗━│━━│━┛ ┗━│━━│━┛\n * │ │ └──┘\n * │ │\n * └──┘\n *\n * If element edge C is outside scrolling box edge C and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * │ ┃ │ ┃ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * ┃ │ ┃ │ ┃ ┃ │\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart <= scrollingEdgeStart && elementSize <= scrollingSize) ||\n (elementEdgeEnd >= scrollingEdgeEnd && elementSize >= scrollingSize)\n ) {\n return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart\n }\n\n /**\n * If element edge B is outside scrolling box edge B and element height is less than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━ ━━ ━┓\n *\n * from ┃ ┃ to ┃ ┌──┐ ┃\n * ┌──┐ │ │\n * ┗━│━━│━┛ ┗━└━━┘━┛\n * └──┘\n *\n * If element edge A is outside scrolling box edge A and element height is greater than scrolling box height\n *\n * ┌──┐\n * │ │\n * │ │ ┌──┐\n * ┏━│━━│━┓ ┏━│━━│━┓\n * │ │ │ │\n * from ┃ └──┘ ┃ to ┃ │ │ ┃\n * │ │\n * ┗━ ━━ ━┛ ┗━└━━┘━┛\n *\n * If element edge C is outside scrolling box edge C and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * │ ┃ │ ┃ │ ┃ ┃\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * ┃ │ ┃ │ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n */\n if (\n (elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||\n (elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)\n ) {\n return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd\n }\n\n return 0\n}\n\nlet getParentElement = (element: Node): Element | null => {\n let parent = element.parentElement\n if (parent == null) {\n return (element.getRootNode() as ShadowRoot).host || null\n }\n return parent\n}\n\n/** @public */\nexport default (target: Element, options: Options): ScrollAction[] => {\n if (typeof document === 'undefined') {\n // If there's no DOM we assume it's not in a browser environment\n return []\n }\n\n let { scrollMode, block, inline, boundary, skipOverflowHiddenElements } =\n options\n // Allow using a callback to check the boundary\n // The default behavior is to check if the current target matches the boundary element or not\n // If undefined it'll check that target is never undefined (can happen as we recurse up the tree)\n let checkBoundary =\n typeof boundary === 'function' ? boundary : (node: any) => node !== boundary\n\n if (!isElement(target)) {\n throw new TypeError('Invalid target')\n }\n\n // Used to handle the top most element that can be scrolled\n let scrollingElement = document.scrollingElement || document.documentElement\n\n // Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box\n let frames: Element[] = []\n let cursor: Element | null = target\n while (isElement(cursor) && checkBoundary(cursor)) {\n // Move cursor to parent\n cursor = getParentElement(cursor)\n\n // Stop when we reach the viewport\n if (cursor === scrollingElement) {\n frames.push(cursor)\n break\n }\n\n // Skip document.body if it's not the scrollingElement and documentElement isn't independently scrollable\n if (\n cursor != null &&\n cursor === document.body &&\n isScrollable(cursor) &&\n !isScrollable(document.documentElement)\n ) {\n continue\n }\n\n // Now we check if the element is scrollable, this code only runs if the loop haven't already hit the viewport or a custom boundary\n if (cursor != null && isScrollable(cursor, skipOverflowHiddenElements)) {\n frames.push(cursor)\n }\n }\n\n // Support pinch-zooming properly, making sure elements scroll into the visual viewport\n // Browsers that don't support visualViewport will report the layout viewport dimensions on document.documentElement.clientWidth/Height\n // and viewport dimensions on window.innerWidth/Height\n // https://www.quirksmode.org/mobile/viewports2.html\n // https://bokand.github.io/viewport/index.html\n let viewportWidth = window.visualViewport?.width ?? innerWidth\n let viewportHeight = window.visualViewport?.height ?? innerHeight\n\n // Newer browsers supports scroll[X|Y], page[X|Y]Offset is\n let viewportX = window.scrollX ?? pageXOffset\n let viewportY = window.scrollY ?? pageYOffset\n\n let {\n height: targetHeight,\n width: targetWidth,\n top: targetTop,\n right: targetRight,\n bottom: targetBottom,\n left: targetLeft,\n } = target.getBoundingClientRect()\n\n // These values mutate as we loop through and generate scroll coordinates\n let targetBlock: number =\n block === 'start' || block === 'nearest'\n ? targetTop\n : block === 'end'\n ? targetBottom\n : targetTop + targetHeight / 2 // block === 'center\n let targetInline: number =\n inline === 'center'\n ? targetLeft + targetWidth / 2\n : inline === 'end'\n ? targetRight\n : targetLeft // inline === 'start || inline === 'nearest\n\n // Collect new scroll positions\n let computations: ScrollAction[] = []\n // 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)\n for (let index = 0; index < frames.length; index++) {\n let frame = frames[index]\n\n // @TODO add a shouldScroll hook here that allows userland code to take control\n\n let { height, width, top, right, bottom, left } =\n frame.getBoundingClientRect()\n\n // If the element is already visible we can end it here\n // @TODO targetBlock and targetInline should be taken into account to be compliant with https://github.com/w3c/csswg-drafts/pull/1805/files#diff-3c17f0e43c20f8ecf89419d49e7ef5e0R1333\n if (\n scrollMode === 'if-needed' &&\n targetTop >= 0 &&\n targetLeft >= 0 &&\n targetBottom <= viewportHeight &&\n targetRight <= viewportWidth &&\n targetTop >= top &&\n targetBottom <= bottom &&\n targetLeft >= left &&\n targetRight <= right\n ) {\n // Break the loop and return the computations for things that are not fully visible\n return computations\n }\n\n let frameStyle = getComputedStyle(frame)\n let borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)\n let borderTop = parseInt(frameStyle.borderTopWidth as string, 10)\n let borderRight = parseInt(frameStyle.borderRightWidth as string, 10)\n let borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)\n\n let blockScroll: number = 0\n let inlineScroll: number = 0\n\n // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here\n // @TODO find out if the \"as HTMLElement\" overrides can be dropped\n let scrollbarWidth =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth -\n (frame as HTMLElement).clientWidth -\n borderLeft -\n borderRight\n : 0\n let scrollbarHeight =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight -\n (frame as HTMLElement).clientHeight -\n borderTop -\n borderBottom\n : 0\n\n let scaleX =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth === 0\n ? 0\n : width / (frame as HTMLElement).offsetWidth\n : 0\n let scaleY =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight === 0\n ? 0\n : height / (frame as HTMLElement).offsetHeight\n : 0\n\n if (scrollingElement === frame) {\n // Handle viewport logic (document.documentElement or document.body)\n\n if (block === 'start') {\n blockScroll = targetBlock\n } else if (block === 'end') {\n blockScroll = targetBlock - viewportHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n viewportY,\n viewportY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n viewportY + targetBlock,\n viewportY + targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - viewportHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline\n } else if (inline === 'center') {\n inlineScroll = targetInline - viewportWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - viewportWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n viewportX,\n viewportX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n viewportX + targetInline,\n viewportX + targetInline + targetWidth,\n targetWidth\n )\n }\n\n // Apply scroll position offsets and ensure they are within bounds\n // @TODO add more test cases to cover this 100%\n blockScroll = Math.max(0, blockScroll + viewportY)\n inlineScroll = Math.max(0, inlineScroll + viewportX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\n\n if (block === 'start') {\n blockScroll = targetBlock - top - borderTop\n } else if (block === 'end') {\n blockScroll = targetBlock - bottom + borderBottom + scrollbarHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n top,\n bottom,\n height,\n borderTop,\n borderBottom + scrollbarHeight,\n targetBlock,\n targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - (top + height / 2) + scrollbarHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline - left - borderLeft\n } else if (inline === 'center') {\n inlineScroll = targetInline - (left + width / 2) + scrollbarWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - right + borderRight + scrollbarWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n left,\n right,\n width,\n borderLeft,\n borderRight + scrollbarWidth,\n targetInline,\n targetInline + targetWidth,\n targetWidth\n )\n }\n\n let { scrollLeft, scrollTop } = frame\n // Ensure scroll coordinates are not out of bounds while applying scroll offsets\n blockScroll = Math.max(\n 0,\n Math.min(\n scrollTop + blockScroll / scaleY,\n frame.scrollHeight - height / scaleY + scrollbarHeight\n )\n )\n inlineScroll = Math.max(\n 0,\n Math.min(\n scrollLeft + inlineScroll / scaleX,\n frame.scrollWidth - width / scaleX + scrollbarWidth\n )\n )\n\n // Cache the offset so that parent frames can scroll this into view correctly\n targetBlock += scrollTop - blockScroll\n targetInline += scrollLeft - inlineScroll\n }\n\n computations.push({ el: frame, top: blockScroll, left: inlineScroll })\n }\n\n return computations\n}\n"],"names":["isElement","el","nodeType","canOverflow","overflow","skipOverflowHiddenElements","isScrollable","clientHeight","scrollHeight","clientWidth","scrollWidth","style","getComputedStyle","overflowY","overflowX","frame","ownerDocument","defaultView","frameElement","e","getFrameElement","isHiddenByFrame","alignNearest","scrollingEdgeStart","scrollingEdgeEnd","scrollingSize","scrollingBorderStart","scrollingBorderEnd","elementEdgeStart","elementEdgeEnd","elementSize","getParentElement","element","parent","parentElement","getRootNode","host","target","options","_a","_b","_c","_d","_e","_f","document","scrollMode","block","inline","boundary","checkBoundary","node","TypeError","scrollingElement","documentElement","frames","cursor","push","body","viewportWidth","window","visualViewport","width","innerWidth","viewportHeight","height","innerHeight","viewportX","scrollX","pageXOffset","viewportY","scrollY","pageYOffset","targetHeight","targetWidth","top","targetTop","right","targetRight","bottom","targetBottom","left","targetLeft","getBoundingClientRect","targetBlock","targetInline","computations","index","length","frameStyle","borderLeft","parseInt","borderLeftWidth","borderTop","borderTopWidth","borderRight","borderRightWidth","borderBottom","borderBottomWidth","blockScroll","inlineScroll","scrollbarWidth","offsetWidth","scrollbarHeight","offsetHeight","scaleX","scaleY","Math","max","scrollLeft","scrollTop","min"],"mappings":"aA6EA,IAAIA,EAAaC,GACD,iBAAPA,GAAyB,MAANA,GAA8B,IAAhBA,EAAGC,SAEzCC,EAAc,CAChBC,EACAC,MAEIA,GAA2C,WAAbD,KAId,YAAbA,GAAuC,SAAbA,GA0B/BE,EAAe,CAACL,EAAaI,KAC/B,GAAIJ,EAAGM,aAAeN,EAAGO,cAAgBP,EAAGQ,YAAcR,EAAGS,YAAa,CACpE,IAAAC,EAAQC,iBAAiBX,EAAI,MAE/B,OAAAE,EAAYQ,EAAME,UAAWR,IAC7BF,EAAYQ,EAAMG,UAAWT,IAhBZJ,KACjB,IAAAc,EAbiBd,KACrB,IAAKA,EAAGe,gBAAkBf,EAAGe,cAAcC,YAClC,OAAA,KAGL,IACK,OAAAhB,EAAGe,cAAcC,YAAYC,YAGtC,OAFSC,GACA,OAAA,IACT,GAIYC,CAAgBnB,GAC5B,QAAKc,IAKHA,EAAMR,aAAeN,EAAGO,cAAgBO,EAAMN,YAAcR,EAAGS,YAAA,EAU7DW,CAAgBpB,EAEpB,CAEO,OAAA,CAAA,EAWLqB,EAAe,CACjBC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,IAsBGF,EAAmBL,GAClBM,EAAiBL,GAClBI,EAAmBL,GAAsBM,EAAiBL,EAEpD,EA2CNI,GAAoBL,GAAsBO,GAAeL,GACzDI,GAAkBL,GAAoBM,GAAeL,EAE/CG,EAAmBL,EAAqBG,EA4C9CG,EAAiBL,GAAoBM,EAAcL,GACnDG,EAAmBL,GAAsBO,EAAcL,EAEjDI,EAAiBL,EAAmBG,EAGtC,EAGLI,EAAoBC,IACtB,IAAIC,EAASD,EAAQE,cACrB,OAAc,MAAVD,EACMD,EAAQG,cAA6BC,MAAQ,KAEhDH,CAAA,iBAIM,CAACI,EAAiBC,KArRjC,IAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAAAC,EAsRM,GAAoB,oBAAbC,SAET,MAAO,GAGT,IAAIC,WAAEA,EAAYC,MAAAA,EAAAC,OAAOA,EAAQC,SAAAA,EAAA5C,2BAAUA,GACzCiC,EAIEY,EACkB,mBAAbD,EAA0BA,EAAYE,GAAcA,IAASF,EAElE,IAACjD,EAAUqC,GACP,MAAA,IAAIe,UAAU,kBAIlB,IAAAC,EAAmBR,SAASQ,kBAAoBR,SAASS,gBAGzDC,EAAoB,GACpBC,EAAyBnB,EAC7B,KAAOrC,EAAUwD,IAAWN,EAAcM,IAAS,CAKjD,GAHAA,EAASzB,EAAiByB,GAGtBA,IAAWH,EAAkB,CAC/BE,EAAOE,KAAKD,GACZ,KACF,CAIY,MAAVA,GACAA,IAAWX,SAASa,MACpBpD,EAAakD,KACZlD,EAAauC,SAASS,kBAMX,MAAVE,GAAkBlD,EAAakD,EAAQnD,IACzCkD,EAAOE,KAAKD,EAEhB,CAOA,IAAIG,EAAgB,OAAAnB,EAAA,OAAAD,EAAAqB,OAAOC,qBAAP,EAAAtB,EAAuBuB,OAAStB,EAAAuB,WAChDC,EAAiB,OAAAtB,EAAA,OAAAD,EAAAmB,OAAOC,qBAAP,EAAApB,EAAuBwB,QAAUvB,EAAAwB,YAGlDC,EAAY,OAAAxB,EAAOiB,OAAAQ,SAAWzB,EAAA0B,YAC9BC,EAAY,OAAA1B,EAAOgB,OAAAW,SAAW3B,EAAA4B,aAGhCP,OAAQQ,EACRX,MAAOY,EACPC,IAAKC,EACLC,MAAOC,EACPC,OAAQC,EACRC,KAAMC,GACJ7C,EAAO8C,wBAGPC,EACQ,UAAVrC,GAA+B,YAAVA,EACjB6B,EACU,QAAV7B,EACAiC,EACAJ,EAAYH,EAAe,EAC7BY,EACS,WAAXrC,EACIkC,EAAaR,EAAc,EAChB,QAAX1B,EACA8B,EACAI,EAGFI,EAA+B,GAEnC,IAAA,IAASC,EAAQ,EAAGA,EAAQhC,EAAOiC,OAAQD,IAAS,CAClD,IAAIxE,EAAQwC,EAAOgC,IAIftB,OAAEA,QAAQH,EAAOa,IAAAA,EAAAE,MAAKA,SAAOE,EAAQE,KAAAA,GACvClE,EAAMoE,wBAIR,GACiB,cAAfrC,GACA8B,GAAa,GACbM,GAAc,GACdF,GAAgBhB,GAChBc,GAAenB,GACfiB,GAAaD,GACbK,GAAgBD,GAChBG,GAAcD,GACdH,GAAeD,EAGR,OAAAS,EAGL,IAAAG,EAAa7E,iBAAiBG,GAC9B2E,EAAaC,SAASF,EAAWG,gBAA2B,IAC5DC,EAAYF,SAASF,EAAWK,eAA0B,IAC1DC,EAAcJ,SAASF,EAAWO,iBAA4B,IAC9DC,EAAeN,SAASF,EAAWS,kBAA6B,IAEhEC,EAAsB,EACtBC,EAAuB,EAIvBC,EACF,gBAAiBtF,EACZA,EAAsBuF,YACtBvF,EAAsBN,YACvBiF,EACAK,EACA,EACFQ,EACF,iBAAkBxF,EACbA,EAAsByF,aACtBzF,EAAsBR,aACvBsF,EACAI,EACA,EAEFQ,EACF,gBAAiB1F,EAC0B,IAAtCA,EAAsBuF,YACrB,EACAxC,EAAS/C,EAAsBuF,YACjC,EACFI,EACF,iBAAkB3F,EAC0B,IAAvCA,EAAsByF,aACrB,EACAvC,EAAUlD,EAAsByF,aAClC,EAEN,GAAInD,IAAqBtC,EAIPoF,EADF,UAAVpD,EACYqC,EACK,QAAVrC,EACKqC,EAAcpB,EACT,YAAVjB,EACKzB,EACZgD,EACAA,EAAYN,EACZA,EACA6B,EACAI,EACA3B,EAAYc,EACZd,EAAYc,EAAcX,EAC1BA,GAIYW,EAAcpB,EAAiB,EAI9BoC,EADF,UAAXpD,EACaqC,EACK,WAAXrC,EACMqC,EAAe1B,EAAgB,EAC1B,QAAXX,EACMqC,EAAe1B,EAGfrC,EACb6C,EACAA,EAAYR,EACZA,EACA+B,EACAK,EACA5B,EAAYkB,EACZlB,EAAYkB,EAAeX,EAC3BA,GAMJyB,EAAcQ,KAAKC,IAAI,EAAGT,EAAc7B,GACxC8B,EAAeO,KAAKC,IAAI,EAAGR,EAAejC,OACrC,CAIHgC,EADY,UAAVpD,EACYqC,EAAcT,EAAMkB,EACf,QAAV9C,EACKqC,EAAcL,EAASkB,EAAeM,EACjC,YAAVxD,EACKzB,EACZqD,EACAI,EACAd,EACA4B,EACAI,EAAeM,EACfnB,EACAA,EAAcX,EACdA,GAIYW,GAAeT,EAAMV,EAAS,GAAKsC,EAAkB,EAInEH,EADa,UAAXpD,EACaqC,EAAeJ,EAAOS,EACjB,WAAX1C,EACMqC,GAAgBJ,EAAOnB,EAAQ,GAAKuC,EAAiB,EAChD,QAAXrD,EACMqC,EAAeR,EAAQkB,EAAcM,EAGrC/E,EACb2D,EACAJ,EACAf,EACA4B,EACAK,EAAcM,EACdhB,EACAA,EAAeX,EACfA,GAIA,IAAAmC,WAAEA,EAAYC,UAAAA,GAAc/F,EAEhCoF,EAAcQ,KAAKC,IACjB,EACAD,KAAKI,IACHD,EAAYX,EAAcO,EAC1B3F,EAAMP,aAAeyD,EAASyC,EAASH,IAG3CH,EAAeO,KAAKC,IAClB,EACAD,KAAKI,IACHF,EAAaT,EAAeK,EAC5B1F,EAAML,YAAcoD,EAAQ2C,EAASJ,IAKzCjB,GAAe0B,EAAYX,EAC3Bd,GAAgBwB,EAAaT,CAC/B,CAEad,EAAA7B,KAAK,CAAExD,GAAIc,EAAO4D,IAAKwB,EAAalB,KAAMmB,GACzD,CAEO,OAAAd,CAAA"}
package/dist/index.d.ts CHANGED
@@ -4,13 +4,54 @@ export default _default
4
4
 
5
5
  /** @public */
6
6
  export declare interface Options {
7
+ /**
8
+ * 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.
9
+ * This means that `block: 'start'` aligns to the top edge and `block: 'end'` to the bottom.
10
+ * @defaultValue 'center'
11
+ */
7
12
  block?: ScrollLogicalPosition
13
+ /**
14
+ * 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.
15
+ * @defaultValue 'nearest'
16
+ */
8
17
  inline?: ScrollLogicalPosition
18
+ /**
19
+ * This is a proposed addition to the spec that you can track here: https://github.com/w3c/csswg-drafts/pull/5677
20
+ *
21
+ * This library will be updated to reflect any changes to the spec and will provide a migration path.
22
+ * 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).
23
+ * @defaultValue 'always'
24
+ */
9
25
  scrollMode?: ScrollMode
26
+ /**
27
+ * 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.
28
+ * By passing a boundary you can short-circuit this loop depending on your needs:
29
+ *
30
+ * - Prevent the browser window from scrolling.
31
+ * - Scroll elements into view in a list, without scrolling container elements.
32
+ *
33
+ * You can also pass a function to do more dynamic checks to override the scroll scoping:
34
+ *
35
+ * ```js
36
+ * let actions = compute(target, {
37
+ * boundary: (parent) => {
38
+ * // By default `overflow: hidden` elements are allowed, only `overflow: visible | clip` is skipped as
39
+ * // this is required by the CSSOM spec
40
+ * if (getComputedStyle(parent)['overflow'] === 'hidden') {
41
+ * return false
42
+ * }
43
+
44
+ * return true
45
+ * },
46
+ * })
47
+ * ```
48
+ * @defaultValue null
49
+ */
10
50
  boundary?: Element | ((parent: Element) => boolean) | null
11
51
  /**
12
52
  * New option that skips auto-scrolling all nodes with overflow: hidden set
13
53
  * See FF implementation: https://hg.mozilla.org/integration/fx-team/rev/c48c3ec05012#l7.18
54
+ * @defaultValue false
14
55
  * @public
15
56
  */
16
57
  skipOverflowHiddenElements?: boolean
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- function t(t){return"object"==typeof t&&null!=t&&1===t.nodeType}function e(t,e){return(!e||"hidden"!==t)&&("visible"!==t&&"clip"!==t)}function n(t,n){if(t.clientHeight<t.scrollHeight||t.clientWidth<t.scrollWidth){const o=getComputedStyle(t,null);return e(o.overflowY,n)||e(o.overflowX,n)||function(t){const e=function(t){if(!t.ownerDocument||!t.ownerDocument.defaultView)return null;try{return t.ownerDocument.defaultView.frameElement}catch(t){return null}}(t);return!!e&&(e.clientHeight<t.scrollHeight||e.clientWidth<t.scrollWidth)}(t)}return!1}function o(t,e,n,o,i,l,r,d){return l<t&&r>e||l>t&&r<e?0:l<=t&&d<=n||r>=e&&d>=n?l-t-o:r>e&&d<n||l<t&&d>n?r-e+i:0}function i(t){const e=t.parentElement;return null==e?t.getRootNode().host||null:e}var l=(e,l)=>{var r,d,u,f,s,c;if("undefined"==typeof document)return[];const{scrollMode:h,block:a,inline:g,boundary:m,skipOverflowHiddenElements:p}=l,w="function"==typeof m?m:t=>t!==m;if(!t(e))throw new TypeError("Invalid target");const W=document.scrollingElement||document.documentElement,H=[];let b=e;for(;t(b)&&w(b);){if(b=i(b),b===W){H.push(b);break}null!=b&&b===document.body&&n(b)&&!n(document.documentElement)||null!=b&&n(b,p)&&H.push(b)}const v=null!=(d=null==(r=window.visualViewport)?void 0:r.width)?d:innerWidth,y=null!=(f=null==(u=window.visualViewport)?void 0:u.height)?f:innerHeight,E=null!=(s=window.scrollX)?s:pageXOffset,M=null!=(c=window.scrollY)?c:pageYOffset,{height:x,width:I,top:C,right:R,bottom:T,left:V}=e.getBoundingClientRect();let k="start"===a||"nearest"===a?C:"end"===a?T:C+x/2,B="center"===g?V+I/2:"end"===g?R:V;const D=[];for(let t=0;t<H.length;t++){const e=H[t],{height:n,width:i,top:l,right:r,bottom:d,left:u}=e.getBoundingClientRect();if("if-needed"===h&&C>=0&&V>=0&&T<=y&&R<=v&&C>=l&&T<=d&&V>=u&&R<=r)return D;const f=getComputedStyle(e),s=parseInt(f.borderLeftWidth,10),c=parseInt(f.borderTopWidth,10),m=parseInt(f.borderRightWidth,10),p=parseInt(f.borderBottomWidth,10);let w=0,b=0;const O="offsetWidth"in e?e.offsetWidth-e.clientWidth-s-m:0,X="offsetHeight"in e?e.offsetHeight-e.clientHeight-c-p:0,Y="offsetWidth"in e?0===e.offsetWidth?0:i/e.offsetWidth:0,L="offsetHeight"in e?0===e.offsetHeight?0:n/e.offsetHeight:0;if(W===e)w="start"===a?k:"end"===a?k-y:"nearest"===a?o(M,M+y,y,c,p,M+k,M+k+x,x):k-y/2,b="start"===g?B:"center"===g?B-v/2:"end"===g?B-v:o(E,E+v,v,s,m,E+B,E+B+I,I),w=Math.max(0,w+M),b=Math.max(0,b+E);else{w="start"===a?k-l-c:"end"===a?k-d+p+X:"nearest"===a?o(l,d,n,c,p+X,k,k+x,x):k-(l+n/2)+X/2,b="start"===g?B-u-s:"center"===g?B-(u+i/2)+O/2:"end"===g?B-r+m+O:o(u,r,i,s,m+O,B,B+I,I);const{scrollLeft:t,scrollTop:f}=e;w=Math.max(0,Math.min(f+w/L,e.scrollHeight-n/L+X)),b=Math.max(0,Math.min(t+b/Y,e.scrollWidth-i/Y+O)),k+=f-w,B+=t-b}D.push({el:e,top:w,left:b})}return D};export{l as default};
1
+ let e=e=>"object"==typeof e&&null!=e&&1===e.nodeType,t=(e,t)=>(!t||"hidden"!==e)&&("visible"!==e&&"clip"!==e),n=(e,n)=>{if(e.clientHeight<e.scrollHeight||e.clientWidth<e.scrollWidth){let l=getComputedStyle(e,null);return t(l.overflowY,n)||t(l.overflowX,n)||(e=>{let t=(e=>{if(!e.ownerDocument||!e.ownerDocument.defaultView)return null;try{return e.ownerDocument.defaultView.frameElement}catch(e){return null}})(e);return!!t&&(t.clientHeight<e.scrollHeight||t.clientWidth<e.scrollWidth)})(e)}return!1},l=(e,t,n,l,i,o,r,d)=>o<e&&r>t||o>e&&r<t?0:o<=e&&d<=n||r>=t&&d>=n?o-e-l:r>t&&d<n||o<e&&d>n?r-t+i:0,i=e=>{let t=e.parentElement;return null==t?e.getRootNode().host||null:t};var o=(t,o)=>{var r,d,h,f,u,s;if("undefined"==typeof document)return[];let{scrollMode:a,block:c,inline:g,boundary:m,skipOverflowHiddenElements:p}=o,w="function"==typeof m?m:e=>e!==m;if(!e(t))throw new TypeError("Invalid target");let W=document.scrollingElement||document.documentElement,H=[],b=t;for(;e(b)&&w(b);){if(b=i(b),b===W){H.push(b);break}null!=b&&b===document.body&&n(b)&&!n(document.documentElement)||null!=b&&n(b,p)&&H.push(b)}let v=null!=(d=null==(r=window.visualViewport)?void 0:r.width)?d:innerWidth,y=null!=(f=null==(h=window.visualViewport)?void 0:h.height)?f:innerHeight,E=null!=(u=window.scrollX)?u:pageXOffset,M=null!=(s=window.scrollY)?s:pageYOffset,{height:x,width:I,top:C,right:R,bottom:T,left:V}=t.getBoundingClientRect(),k="start"===c||"nearest"===c?C:"end"===c?T:C+x/2,B="center"===g?V+I/2:"end"===g?R:V,D=[];for(let e=0;e<H.length;e++){let t=H[e],{height:n,width:i,top:o,right:r,bottom:d,left:h}=t.getBoundingClientRect();if("if-needed"===a&&C>=0&&V>=0&&T<=y&&R<=v&&C>=o&&T<=d&&V>=h&&R<=r)return D;let f=getComputedStyle(t),u=parseInt(f.borderLeftWidth,10),s=parseInt(f.borderTopWidth,10),m=parseInt(f.borderRightWidth,10),p=parseInt(f.borderBottomWidth,10),w=0,b=0,O="offsetWidth"in t?t.offsetWidth-t.clientWidth-u-m:0,X="offsetHeight"in t?t.offsetHeight-t.clientHeight-s-p:0,Y="offsetWidth"in t?0===t.offsetWidth?0:i/t.offsetWidth:0,L="offsetHeight"in t?0===t.offsetHeight?0:n/t.offsetHeight:0;if(W===t)w="start"===c?k:"end"===c?k-y:"nearest"===c?l(M,M+y,y,s,p,M+k,M+k+x,x):k-y/2,b="start"===g?B:"center"===g?B-v/2:"end"===g?B-v:l(E,E+v,v,u,m,E+B,E+B+I,I),w=Math.max(0,w+M),b=Math.max(0,b+E);else{w="start"===c?k-o-s:"end"===c?k-d+p+X:"nearest"===c?l(o,d,n,s,p+X,k,k+x,x):k-(o+n/2)+X/2,b="start"===g?B-h-u:"center"===g?B-(h+i/2)+O/2:"end"===g?B-r+m+O:l(h,r,i,u,m+O,B,B+I,I);let{scrollLeft:e,scrollTop:f}=t;w=Math.max(0,Math.min(f+w/L,t.scrollHeight-n/L+X)),b=Math.max(0,Math.min(e+b/Y,t.scrollWidth-i/Y+O)),k+=f-w,B+=e-b}D.push({el:t,top:w,left:b})}return D};export{o as default};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["// Compute what scrolling needs to be done on required scrolling boxes for target to be in view\n\n// The type names here are named after the spec to make it easier to find more information around what they mean:\n// To reduce churn and reduce things that need be maintained things from the official TS DOM library is used here\n// https://drafts.csswg.org/cssom-view/\n\n// For a definition on what is \"block flow direction\" exactly, check this: https://drafts.csswg.org/css-writing-modes-4/#block-flow-direction\n\n/**\n * This new option is tracked in this PR, which is the most likely candidate at the time: https://github.com/w3c/csswg-drafts/pull/1805\n * @public\n */\nexport type ScrollMode = 'always' | 'if-needed'\n\n/** @public */\nexport interface Options {\n block?: ScrollLogicalPosition\n inline?: ScrollLogicalPosition\n scrollMode?: ScrollMode\n // Custom behavior, not in any spec\n boundary?: Element | ((parent: Element) => boolean) | null\n /**\n * New option that skips auto-scrolling all nodes with overflow: hidden set\n * See FF implementation: https://hg.mozilla.org/integration/fx-team/rev/c48c3ec05012#l7.18\n * @public\n */\n skipOverflowHiddenElements?: boolean\n}\n\n/** @public */\nexport interface ScrollAction {\n el: Element\n top: number\n left: number\n}\n\n// @TODO better shadowdom test, 11 = document fragment\nfunction isElement(el: any): el is Element {\n return typeof el === 'object' && el != null && el.nodeType === 1\n}\n\nfunction canOverflow(\n overflow: string | null,\n skipOverflowHiddenElements?: boolean\n) {\n if (skipOverflowHiddenElements && overflow === 'hidden') {\n return false\n }\n\n return overflow !== 'visible' && overflow !== 'clip'\n}\n\nfunction getFrameElement(el: Element) {\n if (!el.ownerDocument || !el.ownerDocument.defaultView) {\n return null\n }\n\n try {\n return el.ownerDocument.defaultView.frameElement\n } catch (e) {\n return null\n }\n}\n\nfunction isHiddenByFrame(el: Element): boolean {\n const frame = getFrameElement(el)\n if (!frame) {\n return false\n }\n\n return (\n frame.clientHeight < el.scrollHeight || frame.clientWidth < el.scrollWidth\n )\n}\n\nfunction isScrollable(el: Element, skipOverflowHiddenElements?: boolean) {\n if (el.clientHeight < el.scrollHeight || el.clientWidth < el.scrollWidth) {\n const style = getComputedStyle(el, null)\n return (\n canOverflow(style.overflowY, skipOverflowHiddenElements) ||\n canOverflow(style.overflowX, skipOverflowHiddenElements) ||\n isHiddenByFrame(el)\n )\n }\n\n return false\n}\n/**\n * Find out which edge to align against when logical scroll position is \"nearest\"\n * Interesting fact: \"nearest\" works similarily to \"if-needed\", if the element is fully visible it will not scroll it\n *\n * Legends:\n * ┌────────┐ ┏ ━ ━ ━ ┓\n * │ target │ frame\n * └────────┘ ┗ ━ ━ ━ ┛\n */\nfunction alignNearest(\n scrollingEdgeStart: number,\n scrollingEdgeEnd: number,\n scrollingSize: number,\n scrollingBorderStart: number,\n scrollingBorderEnd: number,\n elementEdgeStart: number,\n elementEdgeEnd: number,\n elementSize: number\n) {\n /**\n * If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B\n *\n * ┌──┐\n * ┏━│━━│━┓\n * │ │\n * ┃ │ │ ┃ do nothing\n * │ │\n * ┗━│━━│━┛\n * └──┘\n *\n * If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D\n *\n * ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐\n * │┃ ┃│ do nothing\n * └───────────┘\n * ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart < scrollingEdgeStart &&\n elementEdgeEnd > scrollingEdgeEnd) ||\n (elementEdgeStart > scrollingEdgeStart && elementEdgeEnd < scrollingEdgeEnd)\n ) {\n return 0\n }\n\n /**\n * If element edge A is outside scrolling box edge A and element height is less than scrolling box height\n *\n * ┌──┐\n * ┏━│━━│━┓ ┏━┌━━┐━┓\n * └──┘ │ │\n * from ┃ ┃ to ┃ └──┘ ┃\n *\n * ┗━ ━━ ━┛ ┗━ ━━ ━┛\n *\n * If element edge B is outside scrolling box edge B and element height is greater than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━┌━━┐━┓\n * │ │\n * from ┃ ┌──┐ ┃ to ┃ │ │ ┃\n * │ │ │ │\n * ┗━│━━│━┛ ┗━│━━│━┛\n * │ │ └──┘\n * │ │\n * └──┘\n *\n * If element edge C is outside scrolling box edge C and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * │ ┃ │ ┃ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * ┃ │ ┃ │ ┃ ┃ │\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart <= scrollingEdgeStart && elementSize <= scrollingSize) ||\n (elementEdgeEnd >= scrollingEdgeEnd && elementSize >= scrollingSize)\n ) {\n return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart\n }\n\n /**\n * If element edge B is outside scrolling box edge B and element height is less than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━ ━━ ━┓\n *\n * from ┃ ┃ to ┃ ┌──┐ ┃\n * ┌──┐ │ │\n * ┗━│━━│━┛ ┗━└━━┘━┛\n * └──┘\n *\n * If element edge A is outside scrolling box edge A and element height is greater than scrolling box height\n *\n * ┌──┐\n * │ │\n * │ │ ┌──┐\n * ┏━│━━│━┓ ┏━│━━│━┓\n * │ │ │ │\n * from ┃ └──┘ ┃ to ┃ │ │ ┃\n * │ │\n * ┗━ ━━ ━┛ ┗━└━━┘━┛\n *\n * If element edge C is outside scrolling box edge C and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * │ ┃ │ ┃ │ ┃ ┃\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * ┃ │ ┃ │ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n */\n if (\n (elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||\n (elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)\n ) {\n return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd\n }\n\n return 0\n}\n\nfunction getParentElement(element: Node): Element | null {\n const parent = element.parentElement\n if (parent == null) {\n return (element.getRootNode() as ShadowRoot).host || null\n }\n return parent\n}\n\n/** @public */\nexport default (target: Element, options: Options): ScrollAction[] => {\n if (typeof document === 'undefined') {\n // If there's no DOM we assume it's not in a browser environment\n return []\n }\n\n const { scrollMode, block, inline, boundary, skipOverflowHiddenElements } =\n options\n // Allow using a callback to check the boundary\n // The default behavior is to check if the current target matches the boundary element or not\n // If undefined it'll check that target is never undefined (can happen as we recurse up the tree)\n const checkBoundary =\n typeof boundary === 'function' ? boundary : (node: any) => node !== boundary\n\n if (!isElement(target)) {\n throw new TypeError('Invalid target')\n }\n\n // Used to handle the top most element that can be scrolled\n const scrollingElement = document.scrollingElement || document.documentElement\n\n // Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box\n const frames: Element[] = []\n let cursor: Element | null = target\n while (isElement(cursor) && checkBoundary(cursor)) {\n // Move cursor to parent\n cursor = getParentElement(cursor)\n\n // Stop when we reach the viewport\n if (cursor === scrollingElement) {\n frames.push(cursor)\n break\n }\n\n // Skip document.body if it's not the scrollingElement and documentElement isn't independently scrollable\n if (\n cursor != null &&\n cursor === document.body &&\n isScrollable(cursor) &&\n !isScrollable(document.documentElement)\n ) {\n continue\n }\n\n // Now we check if the element is scrollable, this code only runs if the loop haven't already hit the viewport or a custom boundary\n if (cursor != null && isScrollable(cursor, skipOverflowHiddenElements)) {\n frames.push(cursor)\n }\n }\n\n // Support pinch-zooming properly, making sure elements scroll into the visual viewport\n // Browsers that don't support visualViewport will report the layout viewport dimensions on document.documentElement.clientWidth/Height\n // and viewport dimensions on window.innerWidth/Height\n // https://www.quirksmode.org/mobile/viewports2.html\n // https://bokand.github.io/viewport/index.html\n const viewportWidth = window.visualViewport?.width ?? innerWidth\n const viewportHeight = window.visualViewport?.height ?? innerHeight\n\n // Newer browsers supports scroll[X|Y], page[X|Y]Offset is\n const viewportX = window.scrollX ?? pageXOffset\n const viewportY = window.scrollY ?? pageYOffset\n\n const {\n height: targetHeight,\n width: targetWidth,\n top: targetTop,\n right: targetRight,\n bottom: targetBottom,\n left: targetLeft,\n } = target.getBoundingClientRect()\n\n // These values mutate as we loop through and generate scroll coordinates\n let targetBlock: number =\n block === 'start' || block === 'nearest'\n ? targetTop\n : block === 'end'\n ? targetBottom\n : targetTop + targetHeight / 2 // block === 'center\n let targetInline: number =\n inline === 'center'\n ? targetLeft + targetWidth / 2\n : inline === 'end'\n ? targetRight\n : targetLeft // inline === 'start || inline === 'nearest\n\n // Collect new scroll positions\n const computations: ScrollAction[] = []\n // 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)\n for (let index = 0; index < frames.length; index++) {\n const frame = frames[index]\n\n // @TODO add a shouldScroll hook here that allows userland code to take control\n\n const { height, width, top, right, bottom, left } =\n frame.getBoundingClientRect()\n\n // If the element is already visible we can end it here\n // @TODO targetBlock and targetInline should be taken into account to be compliant with https://github.com/w3c/csswg-drafts/pull/1805/files#diff-3c17f0e43c20f8ecf89419d49e7ef5e0R1333\n if (\n scrollMode === 'if-needed' &&\n targetTop >= 0 &&\n targetLeft >= 0 &&\n targetBottom <= viewportHeight &&\n targetRight <= viewportWidth &&\n targetTop >= top &&\n targetBottom <= bottom &&\n targetLeft >= left &&\n targetRight <= right\n ) {\n // Break the loop and return the computations for things that are not fully visible\n return computations\n }\n\n const frameStyle = getComputedStyle(frame)\n const borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)\n const borderTop = parseInt(frameStyle.borderTopWidth as string, 10)\n const borderRight = parseInt(frameStyle.borderRightWidth as string, 10)\n const borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)\n\n let blockScroll: number = 0\n let inlineScroll: number = 0\n\n // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here\n // @TODO find out if the \"as HTMLElement\" overrides can be dropped\n const scrollbarWidth =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth -\n (frame as HTMLElement).clientWidth -\n borderLeft -\n borderRight\n : 0\n const scrollbarHeight =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight -\n (frame as HTMLElement).clientHeight -\n borderTop -\n borderBottom\n : 0\n\n const scaleX =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth === 0\n ? 0\n : width / (frame as HTMLElement).offsetWidth\n : 0\n const scaleY =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight === 0\n ? 0\n : height / (frame as HTMLElement).offsetHeight\n : 0\n\n if (scrollingElement === frame) {\n // Handle viewport logic (document.documentElement or document.body)\n\n if (block === 'start') {\n blockScroll = targetBlock\n } else if (block === 'end') {\n blockScroll = targetBlock - viewportHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n viewportY,\n viewportY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n viewportY + targetBlock,\n viewportY + targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - viewportHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline\n } else if (inline === 'center') {\n inlineScroll = targetInline - viewportWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - viewportWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n viewportX,\n viewportX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n viewportX + targetInline,\n viewportX + targetInline + targetWidth,\n targetWidth\n )\n }\n\n // Apply scroll position offsets and ensure they are within bounds\n // @TODO add more test cases to cover this 100%\n blockScroll = Math.max(0, blockScroll + viewportY)\n inlineScroll = Math.max(0, inlineScroll + viewportX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\n\n if (block === 'start') {\n blockScroll = targetBlock - top - borderTop\n } else if (block === 'end') {\n blockScroll = targetBlock - bottom + borderBottom + scrollbarHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n top,\n bottom,\n height,\n borderTop,\n borderBottom + scrollbarHeight,\n targetBlock,\n targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - (top + height / 2) + scrollbarHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline - left - borderLeft\n } else if (inline === 'center') {\n inlineScroll = targetInline - (left + width / 2) + scrollbarWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - right + borderRight + scrollbarWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n left,\n right,\n width,\n borderLeft,\n borderRight + scrollbarWidth,\n targetInline,\n targetInline + targetWidth,\n targetWidth\n )\n }\n\n const { scrollLeft, scrollTop } = frame\n // Ensure scroll coordinates are not out of bounds while applying scroll offsets\n blockScroll = Math.max(\n 0,\n Math.min(\n scrollTop + blockScroll / scaleY,\n frame.scrollHeight - height / scaleY + scrollbarHeight\n )\n )\n inlineScroll = Math.max(\n 0,\n Math.min(\n scrollLeft + inlineScroll / scaleX,\n frame.scrollWidth - width / scaleX + scrollbarWidth\n )\n )\n\n // Cache the offset so that parent frames can scroll this into view correctly\n targetBlock += scrollTop - blockScroll\n targetInline += scrollLeft - inlineScroll\n }\n\n computations.push({ el: frame, top: blockScroll, left: inlineScroll })\n }\n\n return computations\n}\n"],"names":["isElement","el","nodeType","canOverflow","overflow","skipOverflowHiddenElements","isScrollable","clientHeight","scrollHeight","clientWidth","scrollWidth","style","getComputedStyle","overflowY","overflowX","frame","ownerDocument","defaultView","frameElement","e","getFrameElement","isHiddenByFrame","alignNearest","scrollingEdgeStart","scrollingEdgeEnd","scrollingSize","scrollingBorderStart","scrollingBorderEnd","elementEdgeStart","elementEdgeEnd","elementSize","getParentElement","element","parent","parentElement","getRootNode","host","index","target","options","_a","_b","_c","_d","_e","_f","document","scrollMode","block","inline","boundary","checkBoundary","node","TypeError","scrollingElement","documentElement","frames","cursor","push","body","viewportWidth","window","visualViewport","width","innerWidth","viewportHeight","height","innerHeight","viewportX","scrollX","pageXOffset","viewportY","scrollY","pageYOffset","targetHeight","targetWidth","top","targetTop","right","targetRight","bottom","targetBottom","left","targetLeft","getBoundingClientRect","targetBlock","targetInline","computations","length","frameStyle","borderLeft","parseInt","borderLeftWidth","borderTop","borderTopWidth","borderRight","borderRightWidth","borderBottom","borderBottomWidth","blockScroll","inlineScroll","scrollbarWidth","offsetWidth","scrollbarHeight","offsetHeight","scaleX","scaleY","Math","max","scrollLeft","scrollTop","min"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["// Compute what scrolling needs to be done on required scrolling boxes for target to be in view\n\n// The type names here are named after the spec to make it easier to find more information around what they mean:\n// To reduce churn and reduce things that need be maintained things from the official TS DOM library is used here\n// https://drafts.csswg.org/cssom-view/\n\n// For a definition on what is \"block flow direction\" exactly, check this: https://drafts.csswg.org/css-writing-modes-4/#block-flow-direction\n\n/**\n * This new option is tracked in this PR, which is the most likely candidate at the time: https://github.com/w3c/csswg-drafts/pull/1805\n * @public\n */\nexport type ScrollMode = 'always' | 'if-needed'\n\n/** @public */\nexport interface Options {\n /**\n * 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.\n * This means that `block: 'start'` aligns to the top edge and `block: 'end'` to the bottom.\n * @defaultValue 'center'\n */\n block?: ScrollLogicalPosition\n /**\n * 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.\n * @defaultValue 'nearest'\n */\n inline?: ScrollLogicalPosition\n /**\n * This is a proposed addition to the spec that you can track here: https://github.com/w3c/csswg-drafts/pull/5677\n *\n * This library will be updated to reflect any changes to the spec and will provide a migration path.\n * 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).\n * @defaultValue 'always'\n */\n scrollMode?: ScrollMode\n /**\n * 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.\n * By passing a boundary you can short-circuit this loop depending on your needs:\n * \n * - Prevent the browser window from scrolling.\n * - Scroll elements into view in a list, without scrolling container elements.\n * \n * You can also pass a function to do more dynamic checks to override the scroll scoping:\n * \n * ```js\n * let actions = compute(target, {\n * boundary: (parent) => {\n * // By default `overflow: hidden` elements are allowed, only `overflow: visible | clip` is skipped as\n * // this is required by the CSSOM spec\n * if (getComputedStyle(parent)['overflow'] === 'hidden') {\n * return false\n * }\n\n * return true\n * },\n * })\n * ```\n * @defaultValue null\n */\n boundary?: Element | ((parent: Element) => boolean) | null\n /**\n * New option that skips auto-scrolling all nodes with overflow: hidden set\n * See FF implementation: https://hg.mozilla.org/integration/fx-team/rev/c48c3ec05012#l7.18\n * @defaultValue false\n * @public\n */\n skipOverflowHiddenElements?: boolean\n}\n\n/** @public */\nexport interface ScrollAction {\n el: Element\n top: number\n left: number\n}\n\n// @TODO better shadowdom test, 11 = document fragment\nlet isElement = (el: any): el is Element =>\n typeof el === 'object' && el != null && el.nodeType === 1\n\nlet canOverflow = (\n overflow: string | null,\n skipOverflowHiddenElements?: boolean\n) => {\n if (skipOverflowHiddenElements && overflow === 'hidden') {\n return false\n }\n\n return overflow !== 'visible' && overflow !== 'clip'\n}\n\nlet getFrameElement = (el: Element) => {\n if (!el.ownerDocument || !el.ownerDocument.defaultView) {\n return null\n }\n\n try {\n return el.ownerDocument.defaultView.frameElement\n } catch (e) {\n return null\n }\n}\n\nlet isHiddenByFrame = (el: Element): boolean => {\n let frame = getFrameElement(el)\n if (!frame) {\n return false\n }\n\n return (\n frame.clientHeight < el.scrollHeight || frame.clientWidth < el.scrollWidth\n )\n}\n\nlet isScrollable = (el: Element, skipOverflowHiddenElements?: boolean) => {\n if (el.clientHeight < el.scrollHeight || el.clientWidth < el.scrollWidth) {\n let style = getComputedStyle(el, null)\n return (\n canOverflow(style.overflowY, skipOverflowHiddenElements) ||\n canOverflow(style.overflowX, skipOverflowHiddenElements) ||\n isHiddenByFrame(el)\n )\n }\n\n return false\n}\n/**\n * Find out which edge to align against when logical scroll position is \"nearest\"\n * Interesting fact: \"nearest\" works similarily to \"if-needed\", if the element is fully visible it will not scroll it\n *\n * Legends:\n * ┌────────┐ ┏ ━ ━ ━ ┓\n * │ target │ frame\n * └────────┘ ┗ ━ ━ ━ ┛\n */\nlet alignNearest = (\n scrollingEdgeStart: number,\n scrollingEdgeEnd: number,\n scrollingSize: number,\n scrollingBorderStart: number,\n scrollingBorderEnd: number,\n elementEdgeStart: number,\n elementEdgeEnd: number,\n elementSize: number\n) => {\n /**\n * If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B\n *\n * ┌──┐\n * ┏━│━━│━┓\n * │ │\n * ┃ │ │ ┃ do nothing\n * │ │\n * ┗━│━━│━┛\n * └──┘\n *\n * If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D\n *\n * ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐\n * │┃ ┃│ do nothing\n * └───────────┘\n * ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart < scrollingEdgeStart &&\n elementEdgeEnd > scrollingEdgeEnd) ||\n (elementEdgeStart > scrollingEdgeStart && elementEdgeEnd < scrollingEdgeEnd)\n ) {\n return 0\n }\n\n /**\n * If element edge A is outside scrolling box edge A and element height is less than scrolling box height\n *\n * ┌──┐\n * ┏━│━━│━┓ ┏━┌━━┐━┓\n * └──┘ │ │\n * from ┃ ┃ to ┃ └──┘ ┃\n *\n * ┗━ ━━ ━┛ ┗━ ━━ ━┛\n *\n * If element edge B is outside scrolling box edge B and element height is greater than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━┌━━┐━┓\n * │ │\n * from ┃ ┌──┐ ┃ to ┃ │ │ ┃\n * │ │ │ │\n * ┗━│━━│━┛ ┗━│━━│━┛\n * │ │ └──┘\n * │ │\n * └──┘\n *\n * If element edge C is outside scrolling box edge C and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * │ ┃ │ ┃ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * ┃ │ ┃ │ ┃ ┃ │\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart <= scrollingEdgeStart && elementSize <= scrollingSize) ||\n (elementEdgeEnd >= scrollingEdgeEnd && elementSize >= scrollingSize)\n ) {\n return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart\n }\n\n /**\n * If element edge B is outside scrolling box edge B and element height is less than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━ ━━ ━┓\n *\n * from ┃ ┃ to ┃ ┌──┐ ┃\n * ┌──┐ │ │\n * ┗━│━━│━┛ ┗━└━━┘━┛\n * └──┘\n *\n * If element edge A is outside scrolling box edge A and element height is greater than scrolling box height\n *\n * ┌──┐\n * │ │\n * │ │ ┌──┐\n * ┏━│━━│━┓ ┏━│━━│━┓\n * │ │ │ │\n * from ┃ └──┘ ┃ to ┃ │ │ ┃\n * │ │\n * ┗━ ━━ ━┛ ┗━└━━┘━┛\n *\n * If element edge C is outside scrolling box edge C and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * │ ┃ │ ┃ │ ┃ ┃\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * ┃ │ ┃ │ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n */\n if (\n (elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||\n (elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)\n ) {\n return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd\n }\n\n return 0\n}\n\nlet getParentElement = (element: Node): Element | null => {\n let parent = element.parentElement\n if (parent == null) {\n return (element.getRootNode() as ShadowRoot).host || null\n }\n return parent\n}\n\n/** @public */\nexport default (target: Element, options: Options): ScrollAction[] => {\n if (typeof document === 'undefined') {\n // If there's no DOM we assume it's not in a browser environment\n return []\n }\n\n let { scrollMode, block, inline, boundary, skipOverflowHiddenElements } =\n options\n // Allow using a callback to check the boundary\n // The default behavior is to check if the current target matches the boundary element or not\n // If undefined it'll check that target is never undefined (can happen as we recurse up the tree)\n let checkBoundary =\n typeof boundary === 'function' ? boundary : (node: any) => node !== boundary\n\n if (!isElement(target)) {\n throw new TypeError('Invalid target')\n }\n\n // Used to handle the top most element that can be scrolled\n let scrollingElement = document.scrollingElement || document.documentElement\n\n // Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box\n let frames: Element[] = []\n let cursor: Element | null = target\n while (isElement(cursor) && checkBoundary(cursor)) {\n // Move cursor to parent\n cursor = getParentElement(cursor)\n\n // Stop when we reach the viewport\n if (cursor === scrollingElement) {\n frames.push(cursor)\n break\n }\n\n // Skip document.body if it's not the scrollingElement and documentElement isn't independently scrollable\n if (\n cursor != null &&\n cursor === document.body &&\n isScrollable(cursor) &&\n !isScrollable(document.documentElement)\n ) {\n continue\n }\n\n // Now we check if the element is scrollable, this code only runs if the loop haven't already hit the viewport or a custom boundary\n if (cursor != null && isScrollable(cursor, skipOverflowHiddenElements)) {\n frames.push(cursor)\n }\n }\n\n // Support pinch-zooming properly, making sure elements scroll into the visual viewport\n // Browsers that don't support visualViewport will report the layout viewport dimensions on document.documentElement.clientWidth/Height\n // and viewport dimensions on window.innerWidth/Height\n // https://www.quirksmode.org/mobile/viewports2.html\n // https://bokand.github.io/viewport/index.html\n let viewportWidth = window.visualViewport?.width ?? innerWidth\n let viewportHeight = window.visualViewport?.height ?? innerHeight\n\n // Newer browsers supports scroll[X|Y], page[X|Y]Offset is\n let viewportX = window.scrollX ?? pageXOffset\n let viewportY = window.scrollY ?? pageYOffset\n\n let {\n height: targetHeight,\n width: targetWidth,\n top: targetTop,\n right: targetRight,\n bottom: targetBottom,\n left: targetLeft,\n } = target.getBoundingClientRect()\n\n // These values mutate as we loop through and generate scroll coordinates\n let targetBlock: number =\n block === 'start' || block === 'nearest'\n ? targetTop\n : block === 'end'\n ? targetBottom\n : targetTop + targetHeight / 2 // block === 'center\n let targetInline: number =\n inline === 'center'\n ? targetLeft + targetWidth / 2\n : inline === 'end'\n ? targetRight\n : targetLeft // inline === 'start || inline === 'nearest\n\n // Collect new scroll positions\n let computations: ScrollAction[] = []\n // 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)\n for (let index = 0; index < frames.length; index++) {\n let frame = frames[index]\n\n // @TODO add a shouldScroll hook here that allows userland code to take control\n\n let { height, width, top, right, bottom, left } =\n frame.getBoundingClientRect()\n\n // If the element is already visible we can end it here\n // @TODO targetBlock and targetInline should be taken into account to be compliant with https://github.com/w3c/csswg-drafts/pull/1805/files#diff-3c17f0e43c20f8ecf89419d49e7ef5e0R1333\n if (\n scrollMode === 'if-needed' &&\n targetTop >= 0 &&\n targetLeft >= 0 &&\n targetBottom <= viewportHeight &&\n targetRight <= viewportWidth &&\n targetTop >= top &&\n targetBottom <= bottom &&\n targetLeft >= left &&\n targetRight <= right\n ) {\n // Break the loop and return the computations for things that are not fully visible\n return computations\n }\n\n let frameStyle = getComputedStyle(frame)\n let borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)\n let borderTop = parseInt(frameStyle.borderTopWidth as string, 10)\n let borderRight = parseInt(frameStyle.borderRightWidth as string, 10)\n let borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)\n\n let blockScroll: number = 0\n let inlineScroll: number = 0\n\n // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here\n // @TODO find out if the \"as HTMLElement\" overrides can be dropped\n let scrollbarWidth =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth -\n (frame as HTMLElement).clientWidth -\n borderLeft -\n borderRight\n : 0\n let scrollbarHeight =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight -\n (frame as HTMLElement).clientHeight -\n borderTop -\n borderBottom\n : 0\n\n let scaleX =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth === 0\n ? 0\n : width / (frame as HTMLElement).offsetWidth\n : 0\n let scaleY =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight === 0\n ? 0\n : height / (frame as HTMLElement).offsetHeight\n : 0\n\n if (scrollingElement === frame) {\n // Handle viewport logic (document.documentElement or document.body)\n\n if (block === 'start') {\n blockScroll = targetBlock\n } else if (block === 'end') {\n blockScroll = targetBlock - viewportHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n viewportY,\n viewportY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n viewportY + targetBlock,\n viewportY + targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - viewportHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline\n } else if (inline === 'center') {\n inlineScroll = targetInline - viewportWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - viewportWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n viewportX,\n viewportX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n viewportX + targetInline,\n viewportX + targetInline + targetWidth,\n targetWidth\n )\n }\n\n // Apply scroll position offsets and ensure they are within bounds\n // @TODO add more test cases to cover this 100%\n blockScroll = Math.max(0, blockScroll + viewportY)\n inlineScroll = Math.max(0, inlineScroll + viewportX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\n\n if (block === 'start') {\n blockScroll = targetBlock - top - borderTop\n } else if (block === 'end') {\n blockScroll = targetBlock - bottom + borderBottom + scrollbarHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n top,\n bottom,\n height,\n borderTop,\n borderBottom + scrollbarHeight,\n targetBlock,\n targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - (top + height / 2) + scrollbarHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline - left - borderLeft\n } else if (inline === 'center') {\n inlineScroll = targetInline - (left + width / 2) + scrollbarWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - right + borderRight + scrollbarWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n left,\n right,\n width,\n borderLeft,\n borderRight + scrollbarWidth,\n targetInline,\n targetInline + targetWidth,\n targetWidth\n )\n }\n\n let { scrollLeft, scrollTop } = frame\n // Ensure scroll coordinates are not out of bounds while applying scroll offsets\n blockScroll = Math.max(\n 0,\n Math.min(\n scrollTop + blockScroll / scaleY,\n frame.scrollHeight - height / scaleY + scrollbarHeight\n )\n )\n inlineScroll = Math.max(\n 0,\n Math.min(\n scrollLeft + inlineScroll / scaleX,\n frame.scrollWidth - width / scaleX + scrollbarWidth\n )\n )\n\n // Cache the offset so that parent frames can scroll this into view correctly\n targetBlock += scrollTop - blockScroll\n targetInline += scrollLeft - inlineScroll\n }\n\n computations.push({ el: frame, top: blockScroll, left: inlineScroll })\n }\n\n return computations\n}\n"],"names":["isElement","el","nodeType","canOverflow","overflow","skipOverflowHiddenElements","isScrollable","clientHeight","scrollHeight","clientWidth","scrollWidth","style","getComputedStyle","overflowY","overflowX","frame","ownerDocument","defaultView","frameElement","e","getFrameElement","isHiddenByFrame","alignNearest","scrollingEdgeStart","scrollingEdgeEnd","scrollingSize","scrollingBorderStart","scrollingBorderEnd","elementEdgeStart","elementEdgeEnd","elementSize","getParentElement","element","parent","parentElement","getRootNode","host","index","target","options","_a","_b","_c","_d","_e","_f","document","scrollMode","block","inline","boundary","checkBoundary","node","TypeError","scrollingElement","documentElement","frames","cursor","push","body","viewportWidth","window","visualViewport","width","innerWidth","viewportHeight","height","innerHeight","viewportX","scrollX","pageXOffset","viewportY","scrollY","pageYOffset","targetHeight","targetWidth","top","targetTop","right","targetRight","bottom","targetBottom","left","targetLeft","getBoundingClientRect","targetBlock","targetInline","computations","length","frameStyle","borderLeft","parseInt","borderLeftWidth","borderTop","borderTopWidth","borderRight","borderRightWidth","borderBottom","borderBottomWidth","blockScroll","inlineScroll","scrollbarWidth","offsetWidth","scrollbarHeight","offsetHeight","scaleX","scaleY","Math","max","scrollLeft","scrollTop","min"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compute-scroll-into-view",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "The engine that powers scroll-into-view-if-needed",
5
5
  "keywords": [
6
6
  "if-needed",
@@ -33,7 +33,6 @@
33
33
  },
34
34
  "main": "./dist/index.cjs",
35
35
  "module": "./dist/index.js",
36
- "source": "./src/index.ts",
37
36
  "typings": "./dist/index.d.ts",
38
37
  "files": [
39
38
  "dist",
package/src/index.ts CHANGED
@@ -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,14 +75,13 @@ 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
  }
@@ -50,7 +89,7 @@ function canOverflow(
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,12 +281,12 @@ 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)) {
@@ -255,10 +294,10 @@ export default (target: Element, options: Options): ScrollAction[] => {
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,25 +388,25 @@ 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 -
@@ -375,13 +414,13 @@ export default (target: Element, options: Options): ScrollAction[] => {
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,