compute-scroll-into-view 3.0.2 → 3.1.0

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 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});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,o,i,r,d)=>i<e&&r>t||i>e&&r<t?0:i<=e&&d<=n||r>=t&&d>=n?i-e-l:r>t&&d<n||i<e&&d>n?r-t+o:0,o=e=>{let t=e.parentElement;return null==t?e.getRootNode().host||null:t};exports.compute=(t,i)=>{var r,d,h,u;if("undefined"==typeof document)return[];let{scrollMode:s,block:f,inline:c,boundary:a,skipOverflowHiddenElements:m}=i,g="function"==typeof a?a:e=>e!==a;if(!e(t))throw new TypeError("Invalid target");let p=document.scrollingElement||document.documentElement,w=[],W=t;for(;e(W)&&g(W);){if(W=o(W),W===p){w.push(W);break}null!=W&&W===document.body&&n(W)&&!n(document.documentElement)||null!=W&&n(W,m)&&w.push(W)}let b=null!=(d=null==(r=window.visualViewport)?void 0:r.width)?d:innerWidth,H=null!=(u=null==(h=window.visualViewport)?void 0:h.height)?u:innerHeight,{scrollX:v,scrollY:y}=window,{height:M,width:E,top:x,right:I,bottom:C,left:R}=t.getBoundingClientRect(),T="start"===f||"nearest"===f?x:"end"===f?C:x+M/2,V="center"===c?R+E/2:"end"===c?I:R,k=[];for(let e=0;e<w.length;e++){let t=w[e],{height:n,width:o,top:i,right:r,bottom:d,left:h}=t.getBoundingClientRect();if("if-needed"===s&&x>=0&&R>=0&&C<=H&&I<=b&&x>=i&&C<=d&&R>=h&&I<=r)return k;let u=getComputedStyle(t),a=parseInt(u.borderLeftWidth,10),m=parseInt(u.borderTopWidth,10),g=parseInt(u.borderRightWidth,10),W=parseInt(u.borderBottomWidth,10),B=0,D=0,j="offsetWidth"in t?t.offsetWidth-t.clientWidth-a-g:0,L="offsetHeight"in t?t.offsetHeight-t.clientHeight-m-W:0,O="offsetWidth"in t?0===t.offsetWidth?0:o/t.offsetWidth:0,S="offsetHeight"in t?0===t.offsetHeight?0:n/t.offsetHeight:0;if(p===t)B="start"===f?T:"end"===f?T-H:"nearest"===f?l(y,y+H,H,m,W,y+T,y+T+M,M):T-H/2,D="start"===c?V:"center"===c?V-b/2:"end"===c?V-b:l(v,v+b,b,a,g,v+V,v+V+E,E),B=Math.max(0,B+y),D=Math.max(0,D+v);else{B="start"===f?T-i-m:"end"===f?T-d+W+L:"nearest"===f?l(i,d,n,m,W+L,T,T+M,M):T-(i+n/2)+L/2,D="start"===c?V-h-a:"center"===c?V-(h+o/2)+j/2:"end"===c?V-r+g+j:l(h,r,o,a,g+j,V,V+E,E);let{scrollLeft:e,scrollTop:u}=t;B=Math.max(0,Math.min(u+B/S,t.scrollHeight-n/S+L)),D=Math.max(0,Math.min(e+D/O,t.scrollWidth-o/O+j)),T+=u-B,V+=e-D}k.push({el:t,top:B,left:D})}return k};//# sourceMappingURL=index.cjs.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const t=t=>"object"==typeof t&&null!=t&&1===t.nodeType,e=(t,e)=>(!e||"hidden"!==t)&&("visible"!==t&&"clip"!==t),o=(t,o)=>{if(t.clientHeight<t.scrollHeight||t.clientWidth<t.scrollWidth){const n=getComputedStyle(t,null);return e(n.overflowY,o)||e(n.overflowX,o)||(t=>{const e=(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},n=(t,e,o,n,l,r,i,s)=>r<t&&i>e||r>t&&i<e?0:r<=t&&s<=o||i>=e&&s>=o?r-t-n:i>e&&s<o||r<t&&s>o?i-e+l:0,l=t=>{const e=t.parentElement;return null==e?t.getRootNode().host||null:e};exports.compute=(e,r)=>{var i,s,d,c;if("undefined"==typeof document)return[];const{scrollMode:h,block:u,inline:f,boundary:a,skipOverflowHiddenElements:g}=r,p="function"==typeof a?a:t=>t!==a;if(!t(e))throw new TypeError("Invalid target");const m=document.scrollingElement||document.documentElement,w=[];let W=e;for(;t(W)&&p(W);){if(W=l(W),W===m){w.push(W);break}null!=W&&W===document.body&&o(W)&&!o(document.documentElement)||null!=W&&o(W,g)&&w.push(W)}const b=null!=(s=null==(i=window.visualViewport)?void 0:i.width)?s:innerWidth,H=null!=(c=null==(d=window.visualViewport)?void 0:d.height)?c:innerHeight,{scrollX:y,scrollY:M}=window,{height:v,width:E,top:x,right:C,bottom:I,left:R}=e.getBoundingClientRect(),{top:T,right:B,bottom:F,left:V}=(t=>{const e=window.getComputedStyle(t);return{top:parseFloat(e.scrollMarginTop)||0,right:parseFloat(e.scrollMarginRight)||0,bottom:parseFloat(e.scrollMarginBottom)||0,left:parseFloat(e.scrollMarginLeft)||0}})(e);let k="start"===u||"nearest"===u?x-T:"end"===u?I+F:x+v/2-T+F,D="center"===f?R+E/2-V+B:"end"===f?C+B:R-V;const L=[];for(let t=0;t<w.length;t++){const e=w[t],{height:o,width:l,top:r,right:i,bottom:s,left:d}=e.getBoundingClientRect();if("if-needed"===h&&x>=0&&R>=0&&I<=H&&C<=b&&x>=r&&I<=s&&R>=d&&C<=i)return L;const c=getComputedStyle(e),a=parseInt(c.borderLeftWidth,10),g=parseInt(c.borderTopWidth,10),p=parseInt(c.borderRightWidth,10),W=parseInt(c.borderBottomWidth,10);let T=0,B=0;const F="offsetWidth"in e?e.offsetWidth-e.clientWidth-a-p:0,V="offsetHeight"in e?e.offsetHeight-e.clientHeight-g-W:0,S="offsetWidth"in e?0===e.offsetWidth?0:l/e.offsetWidth:0,j="offsetHeight"in e?0===e.offsetHeight?0:o/e.offsetHeight:0;if(m===e)T="start"===u?k:"end"===u?k-H:"nearest"===u?n(M,M+H,H,g,W,M+k,M+k+v,v):k-H/2,B="start"===f?D:"center"===f?D-b/2:"end"===f?D-b:n(y,y+b,b,a,p,y+D,y+D+E,E),T=Math.max(0,T+M),B=Math.max(0,B+y);else{T="start"===u?k-r-g:"end"===u?k-s+W+V:"nearest"===u?n(r,s,o,g,W+V,k,k+v,v):k-(r+o/2)+V/2,B="start"===f?D-d-a:"center"===f?D-(d+l/2)+F/2:"end"===f?D-i+p+F:n(d,i,l,a,p+F,D,D+E,E);const{scrollLeft:t,scrollTop:c}=e;T=0===j?0:Math.max(0,Math.min(c+T/j,e.scrollHeight-o/j+V)),B=0===S?0:Math.max(0,Math.min(t+B/S,e.scrollWidth-l/S+F)),k+=c-T,D+=t-B}L.push({el:e,top:T,left:B})}return L};//# 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 /**\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 let compute = (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 let { scrollX, scrollY } = window\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 scrollY,\n scrollY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n scrollY + targetBlock,\n scrollY + 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 scrollX,\n scrollX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n scrollX + targetInline,\n scrollX + 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 + scrollY)\n inlineScroll = Math.max(0, inlineScroll + scrollX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\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":["Object","defineProperty","exports","value","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","compute","target","options","_a","_b","_c","_d","document","scrollMode","block","inline","boundary","checkBoundary","node","TypeError","scrollingElement","documentElement","frames","cursor","push","body","viewportWidth","window","visualViewport","width","innerWidth","viewportHeight","height","innerHeight","scrollX","scrollY","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\nconst isElement = (el: any): el is Element =>\n typeof el === 'object' && el != null && el.nodeType === 1\n\nconst 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\nconst 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\nconst 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\nconst 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 */\nconst 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\nconst 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\nconst getScrollMargins = (target: Element) => {\n const computedStyle = window.getComputedStyle(target)\n return {\n top: parseFloat(computedStyle.scrollMarginTop) || 0,\n right: parseFloat(computedStyle.scrollMarginRight) || 0,\n bottom: parseFloat(computedStyle.scrollMarginBottom) || 0,\n left: parseFloat(computedStyle.scrollMarginLeft) || 0,\n }\n}\n\n/** @public */\nexport const compute = (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 const { scrollX, scrollY } = window\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 const {\n top: marginTop,\n right: marginRight,\n bottom: marginBottom,\n left: marginLeft,\n } = getScrollMargins(target)\n\n // These values mutate as we loop through and generate scroll coordinates\n let targetBlock: number =\n block === 'start' || block === 'nearest'\n ? targetTop - marginTop\n : block === 'end'\n ? targetBottom + marginBottom\n : targetTop + targetHeight / 2 - marginTop + marginBottom // block === 'center\n let targetInline: number =\n inline === 'center'\n ? targetLeft + targetWidth / 2 - marginLeft + marginRight\n : inline === 'end'\n ? targetRight + marginRight\n : targetLeft - marginLeft // 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 scrollY,\n scrollY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n scrollY + targetBlock,\n scrollY + 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 scrollX,\n scrollX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n scrollX + targetInline,\n scrollX + 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 + scrollY)\n inlineScroll = Math.max(0, inlineScroll + scrollX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\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 =\n scaleY === 0\n ? 0\n : Math.max(\n 0,\n Math.min(\n scrollTop + blockScroll / scaleY,\n frame.scrollHeight - height / scaleY + scrollbarHeight\n )\n )\n inlineScroll =\n scaleX === 0\n ? 0\n : 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":["Object","defineProperty","exports","value","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","compute","target","options","_a","_b","_c","_d","document","scrollMode","block","inline","boundary","checkBoundary","node","TypeError","scrollingElement","documentElement","frames","cursor","push","body","viewportWidth","window","visualViewport","width","innerWidth","viewportHeight","height","innerHeight","scrollX","scrollY","targetHeight","targetWidth","top","targetTop","right","targetRight","bottom","targetBottom","left","targetLeft","getBoundingClientRect","marginTop","marginRight","marginBottom","marginLeft","computedStyle","parseFloat","scrollMarginTop","scrollMarginRight","scrollMarginBottom","scrollMarginLeft","getScrollMargins","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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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** @public */
2
- export declare let compute: (
2
+ export declare const compute: (
3
3
  target: Element,
4
4
  options: Options
5
5
  ) => ScrollAction[]
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- let t=t=>"object"==typeof t&&null!=t&&1===t.nodeType,e=(t,e)=>(!e||"hidden"!==t)&&("visible"!==t&&"clip"!==t),n=(t,n)=>{if(t.clientHeight<t.scrollHeight||t.clientWidth<t.scrollWidth){let l=getComputedStyle(t,null);return e(l.overflowY,n)||e(l.overflowX,n)||(t=>{let e=(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},l=(t,e,n,l,i,o,r,d)=>o<t&&r>e||o>t&&r<e?0:o<=t&&d<=n||r>=e&&d>=n?o-t-l:r>e&&d<n||o<t&&d>n?r-e+i:0,i=t=>{let e=t.parentElement;return null==e?t.getRootNode().host||null:e},o=(e,o)=>{var r,d,h,f;if("undefined"==typeof document)return[];let{scrollMode:u,block:s,inline:c,boundary:a,skipOverflowHiddenElements:g}=o,m="function"==typeof a?a:t=>t!==a;if(!t(e))throw new TypeError("Invalid target");let p=document.scrollingElement||document.documentElement,w=[],W=e;for(;t(W)&&m(W);){if(W=i(W),W===p){w.push(W);break}null!=W&&W===document.body&&n(W)&&!n(document.documentElement)||null!=W&&n(W,g)&&w.push(W)}let H=null!=(d=null==(r=window.visualViewport)?void 0:r.width)?d:innerWidth,b=null!=(f=null==(h=window.visualViewport)?void 0:h.height)?f:innerHeight,{scrollX:v,scrollY:y}=window,{height:E,width:M,top:x,right:I,bottom:C,left:R}=e.getBoundingClientRect(),T="start"===s||"nearest"===s?x:"end"===s?C:x+E/2,V="center"===c?R+M/2:"end"===c?I:R,k=[];for(let t=0;t<w.length;t++){let e=w[t],{height:n,width:i,top:o,right:r,bottom:d,left:h}=e.getBoundingClientRect();if("if-needed"===u&&x>=0&&R>=0&&C<=b&&I<=H&&x>=o&&C<=d&&R>=h&&I<=r)return k;let f=getComputedStyle(e),a=parseInt(f.borderLeftWidth,10),g=parseInt(f.borderTopWidth,10),m=parseInt(f.borderRightWidth,10),W=parseInt(f.borderBottomWidth,10),B=0,D=0,L="offsetWidth"in e?e.offsetWidth-e.clientWidth-a-m:0,S="offsetHeight"in e?e.offsetHeight-e.clientHeight-g-W:0,X="offsetWidth"in e?0===e.offsetWidth?0:i/e.offsetWidth:0,Y="offsetHeight"in e?0===e.offsetHeight?0:n/e.offsetHeight:0;if(p===e)B="start"===s?T:"end"===s?T-b:"nearest"===s?l(y,y+b,b,g,W,y+T,y+T+E,E):T-b/2,D="start"===c?V:"center"===c?V-H/2:"end"===c?V-H:l(v,v+H,H,a,m,v+V,v+V+M,M),B=Math.max(0,B+y),D=Math.max(0,D+v);else{B="start"===s?T-o-g:"end"===s?T-d+W+S:"nearest"===s?l(o,d,n,g,W+S,T,T+E,E):T-(o+n/2)+S/2,D="start"===c?V-h-a:"center"===c?V-(h+i/2)+L/2:"end"===c?V-r+m+L:l(h,r,i,a,m+L,V,V+M,M);let{scrollLeft:t,scrollTop:f}=e;B=Math.max(0,Math.min(f+B/Y,e.scrollHeight-n/Y+S)),D=Math.max(0,Math.min(t+D/X,e.scrollWidth-i/X+L)),T+=f-B,V+=t-D}k.push({el:e,top:B,left:D})}return k};export{o as compute};//# sourceMappingURL=index.js.map
1
+ const t=t=>"object"==typeof t&&null!=t&&1===t.nodeType,e=(t,e)=>(!e||"hidden"!==t)&&("visible"!==t&&"clip"!==t),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)||(t=>{const e=(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},o=(t,e,n,o,l,r,i,s)=>r<t&&i>e||r>t&&i<e?0:r<=t&&s<=n||i>=e&&s>=n?r-t-o:i>e&&s<n||r<t&&s>n?i-e+l:0,l=t=>{const e=t.parentElement;return null==e?t.getRootNode().host||null:e},r=(e,r)=>{var i,s,d,h;if("undefined"==typeof document)return[];const{scrollMode:c,block:f,inline:u,boundary:a,skipOverflowHiddenElements:g}=r,p="function"==typeof a?a:t=>t!==a;if(!t(e))throw new TypeError("Invalid target");const m=document.scrollingElement||document.documentElement,w=[];let W=e;for(;t(W)&&p(W);){if(W=l(W),W===m){w.push(W);break}null!=W&&W===document.body&&n(W)&&!n(document.documentElement)||null!=W&&n(W,g)&&w.push(W)}const b=null!=(s=null==(i=window.visualViewport)?void 0:i.width)?s:innerWidth,H=null!=(h=null==(d=window.visualViewport)?void 0:d.height)?h:innerHeight,{scrollX:y,scrollY:M}=window,{height:v,width:E,top:x,right:C,bottom:I,left:R}=e.getBoundingClientRect(),{top:T,right:B,bottom:F,left:V}=(t=>{const e=window.getComputedStyle(t);return{top:parseFloat(e.scrollMarginTop)||0,right:parseFloat(e.scrollMarginRight)||0,bottom:parseFloat(e.scrollMarginBottom)||0,left:parseFloat(e.scrollMarginLeft)||0}})(e);let k="start"===f||"nearest"===f?x-T:"end"===f?I+F:x+v/2-T+F,D="center"===u?R+E/2-V+B:"end"===u?C+B:R-V;const L=[];for(let t=0;t<w.length;t++){const e=w[t],{height:n,width:l,top:r,right:i,bottom:s,left:d}=e.getBoundingClientRect();if("if-needed"===c&&x>=0&&R>=0&&I<=H&&C<=b&&x>=r&&I<=s&&R>=d&&C<=i)return L;const h=getComputedStyle(e),a=parseInt(h.borderLeftWidth,10),g=parseInt(h.borderTopWidth,10),p=parseInt(h.borderRightWidth,10),W=parseInt(h.borderBottomWidth,10);let T=0,B=0;const F="offsetWidth"in e?e.offsetWidth-e.clientWidth-a-p:0,V="offsetHeight"in e?e.offsetHeight-e.clientHeight-g-W:0,S="offsetWidth"in e?0===e.offsetWidth?0:l/e.offsetWidth:0,X="offsetHeight"in e?0===e.offsetHeight?0:n/e.offsetHeight:0;if(m===e)T="start"===f?k:"end"===f?k-H:"nearest"===f?o(M,M+H,H,g,W,M+k,M+k+v,v):k-H/2,B="start"===u?D:"center"===u?D-b/2:"end"===u?D-b:o(y,y+b,b,a,p,y+D,y+D+E,E),T=Math.max(0,T+M),B=Math.max(0,B+y);else{T="start"===f?k-r-g:"end"===f?k-s+W+V:"nearest"===f?o(r,s,n,g,W+V,k,k+v,v):k-(r+n/2)+V/2,B="start"===u?D-d-a:"center"===u?D-(d+l/2)+F/2:"end"===u?D-i+p+F:o(d,i,l,a,p+F,D,D+E,E);const{scrollLeft:t,scrollTop:h}=e;T=0===X?0:Math.max(0,Math.min(h+T/X,e.scrollHeight-n/X+V)),B=0===S?0:Math.max(0,Math.min(t+B/S,e.scrollWidth-l/S+F)),k+=h-T,D+=t-B}L.push({el:e,top:T,left:B})}return L};export{r as compute};//# 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 /**\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 let compute = (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 let { scrollX, scrollY } = window\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 scrollY,\n scrollY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n scrollY + targetBlock,\n scrollY + 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 scrollX,\n scrollX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n scrollX + targetInline,\n scrollX + 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 + scrollY)\n inlineScroll = Math.max(0, inlineScroll + scrollX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\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","compute","target","options","_a","_b","_c","_d","document","scrollMode","block","inline","boundary","checkBoundary","node","TypeError","scrollingElement","documentElement","frames","cursor","push","body","viewportWidth","window","visualViewport","width","innerWidth","viewportHeight","height","innerHeight","scrollX","scrollY","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.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\nconst isElement = (el: any): el is Element =>\n typeof el === 'object' && el != null && el.nodeType === 1\n\nconst 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\nconst 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\nconst 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\nconst 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 */\nconst 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\nconst 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\nconst getScrollMargins = (target: Element) => {\n const computedStyle = window.getComputedStyle(target)\n return {\n top: parseFloat(computedStyle.scrollMarginTop) || 0,\n right: parseFloat(computedStyle.scrollMarginRight) || 0,\n bottom: parseFloat(computedStyle.scrollMarginBottom) || 0,\n left: parseFloat(computedStyle.scrollMarginLeft) || 0,\n }\n}\n\n/** @public */\nexport const compute = (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 const { scrollX, scrollY } = window\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 const {\n top: marginTop,\n right: marginRight,\n bottom: marginBottom,\n left: marginLeft,\n } = getScrollMargins(target)\n\n // These values mutate as we loop through and generate scroll coordinates\n let targetBlock: number =\n block === 'start' || block === 'nearest'\n ? targetTop - marginTop\n : block === 'end'\n ? targetBottom + marginBottom\n : targetTop + targetHeight / 2 - marginTop + marginBottom // block === 'center\n let targetInline: number =\n inline === 'center'\n ? targetLeft + targetWidth / 2 - marginLeft + marginRight\n : inline === 'end'\n ? targetRight + marginRight\n : targetLeft - marginLeft // 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 scrollY,\n scrollY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n scrollY + targetBlock,\n scrollY + 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 scrollX,\n scrollX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n scrollX + targetInline,\n scrollX + 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 + scrollY)\n inlineScroll = Math.max(0, inlineScroll + scrollX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\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 =\n scaleY === 0\n ? 0\n : Math.max(\n 0,\n Math.min(\n scrollTop + blockScroll / scaleY,\n frame.scrollHeight - height / scaleY + scrollbarHeight\n )\n )\n inlineScroll =\n scaleX === 0\n ? 0\n : 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","compute","target","options","_a","_b","_c","_d","document","scrollMode","block","inline","boundary","checkBoundary","node","TypeError","scrollingElement","documentElement","frames","cursor","push","body","viewportWidth","window","visualViewport","width","innerWidth","viewportHeight","height","innerHeight","scrollX","scrollY","targetHeight","targetWidth","top","targetTop","right","targetRight","bottom","targetBottom","left","targetLeft","getBoundingClientRect","marginTop","marginRight","marginBottom","marginLeft","computedStyle","parseFloat","scrollMarginTop","scrollMarginRight","scrollMarginBottom","scrollMarginLeft","getScrollMargins","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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compute-scroll-into-view",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "The engine that powers scroll-into-view-if-needed",
5
5
  "keywords": [
6
6
  "if-needed",
package/src/index.ts CHANGED
@@ -75,10 +75,10 @@ export interface ScrollAction {
75
75
  }
76
76
 
77
77
  // @TODO better shadowdom test, 11 = document fragment
78
- let isElement = (el: any): el is Element =>
78
+ const isElement = (el: any): el is Element =>
79
79
  typeof el === 'object' && el != null && el.nodeType === 1
80
80
 
81
- let canOverflow = (
81
+ const canOverflow = (
82
82
  overflow: string | null,
83
83
  skipOverflowHiddenElements?: boolean
84
84
  ) => {
@@ -89,7 +89,7 @@ let canOverflow = (
89
89
  return overflow !== 'visible' && overflow !== 'clip'
90
90
  }
91
91
 
92
- let getFrameElement = (el: Element) => {
92
+ const getFrameElement = (el: Element) => {
93
93
  if (!el.ownerDocument || !el.ownerDocument.defaultView) {
94
94
  return null
95
95
  }
@@ -101,8 +101,8 @@ let getFrameElement = (el: Element) => {
101
101
  }
102
102
  }
103
103
 
104
- let isHiddenByFrame = (el: Element): boolean => {
105
- let frame = getFrameElement(el)
104
+ const isHiddenByFrame = (el: Element): boolean => {
105
+ const frame = getFrameElement(el)
106
106
  if (!frame) {
107
107
  return false
108
108
  }
@@ -112,9 +112,9 @@ let isHiddenByFrame = (el: Element): boolean => {
112
112
  )
113
113
  }
114
114
 
115
- let isScrollable = (el: Element, skipOverflowHiddenElements?: boolean) => {
115
+ const isScrollable = (el: Element, skipOverflowHiddenElements?: boolean) => {
116
116
  if (el.clientHeight < el.scrollHeight || el.clientWidth < el.scrollWidth) {
117
- let style = getComputedStyle(el, null)
117
+ const style = getComputedStyle(el, null)
118
118
  return (
119
119
  canOverflow(style.overflowY, skipOverflowHiddenElements) ||
120
120
  canOverflow(style.overflowX, skipOverflowHiddenElements) ||
@@ -133,7 +133,7 @@ let isScrollable = (el: Element, skipOverflowHiddenElements?: boolean) => {
133
133
  * │ target │ frame
134
134
  * └────────┘ ┗ ━ ━ ━ ┛
135
135
  */
136
- let alignNearest = (
136
+ const alignNearest = (
137
137
  scrollingEdgeStart: number,
138
138
  scrollingEdgeEnd: number,
139
139
  scrollingSize: number,
@@ -266,27 +266,37 @@ let alignNearest = (
266
266
  return 0
267
267
  }
268
268
 
269
- let getParentElement = (element: Node): Element | null => {
270
- let parent = element.parentElement
269
+ const getParentElement = (element: Node): Element | null => {
270
+ const parent = element.parentElement
271
271
  if (parent == null) {
272
272
  return (element.getRootNode() as ShadowRoot).host || null
273
273
  }
274
274
  return parent
275
275
  }
276
276
 
277
+ const getScrollMargins = (target: Element) => {
278
+ const computedStyle = window.getComputedStyle(target)
279
+ return {
280
+ top: parseFloat(computedStyle.scrollMarginTop) || 0,
281
+ right: parseFloat(computedStyle.scrollMarginRight) || 0,
282
+ bottom: parseFloat(computedStyle.scrollMarginBottom) || 0,
283
+ left: parseFloat(computedStyle.scrollMarginLeft) || 0,
284
+ }
285
+ }
286
+
277
287
  /** @public */
278
- export let compute = (target: Element, options: Options): ScrollAction[] => {
288
+ export const compute = (target: Element, options: Options): ScrollAction[] => {
279
289
  if (typeof document === 'undefined') {
280
290
  // If there's no DOM we assume it's not in a browser environment
281
291
  return []
282
292
  }
283
293
 
284
- let { scrollMode, block, inline, boundary, skipOverflowHiddenElements } =
294
+ const { scrollMode, block, inline, boundary, skipOverflowHiddenElements } =
285
295
  options
286
296
  // Allow using a callback to check the boundary
287
297
  // The default behavior is to check if the current target matches the boundary element or not
288
298
  // If undefined it'll check that target is never undefined (can happen as we recurse up the tree)
289
- let checkBoundary =
299
+ const checkBoundary =
290
300
  typeof boundary === 'function' ? boundary : (node: any) => node !== boundary
291
301
 
292
302
  if (!isElement(target)) {
@@ -294,10 +304,10 @@ export let compute = (target: Element, options: Options): ScrollAction[] => {
294
304
  }
295
305
 
296
306
  // Used to handle the top most element that can be scrolled
297
- let scrollingElement = document.scrollingElement || document.documentElement
307
+ const scrollingElement = document.scrollingElement || document.documentElement
298
308
 
299
309
  // Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box
300
- let frames: Element[] = []
310
+ const frames: Element[] = []
301
311
  let cursor: Element | null = target
302
312
  while (isElement(cursor) && checkBoundary(cursor)) {
303
313
  // Move cursor to parent
@@ -330,11 +340,11 @@ export let compute = (target: Element, options: Options): ScrollAction[] => {
330
340
  // and viewport dimensions on window.innerWidth/Height
331
341
  // https://www.quirksmode.org/mobile/viewports2.html
332
342
  // https://bokand.github.io/viewport/index.html
333
- let viewportWidth = window.visualViewport?.width ?? innerWidth
334
- let viewportHeight = window.visualViewport?.height ?? innerHeight
335
- let { scrollX, scrollY } = window
343
+ const viewportWidth = window.visualViewport?.width ?? innerWidth
344
+ const viewportHeight = window.visualViewport?.height ?? innerHeight
345
+ const { scrollX, scrollY } = window
336
346
 
337
- let {
347
+ const {
338
348
  height: targetHeight,
339
349
  width: targetWidth,
340
350
  top: targetTop,
@@ -342,30 +352,36 @@ export let compute = (target: Element, options: Options): ScrollAction[] => {
342
352
  bottom: targetBottom,
343
353
  left: targetLeft,
344
354
  } = target.getBoundingClientRect()
355
+ const {
356
+ top: marginTop,
357
+ right: marginRight,
358
+ bottom: marginBottom,
359
+ left: marginLeft,
360
+ } = getScrollMargins(target)
345
361
 
346
362
  // These values mutate as we loop through and generate scroll coordinates
347
363
  let targetBlock: number =
348
364
  block === 'start' || block === 'nearest'
349
- ? targetTop
365
+ ? targetTop - marginTop
350
366
  : block === 'end'
351
- ? targetBottom
352
- : targetTop + targetHeight / 2 // block === 'center
367
+ ? targetBottom + marginBottom
368
+ : targetTop + targetHeight / 2 - marginTop + marginBottom // block === 'center
353
369
  let targetInline: number =
354
370
  inline === 'center'
355
- ? targetLeft + targetWidth / 2
371
+ ? targetLeft + targetWidth / 2 - marginLeft + marginRight
356
372
  : inline === 'end'
357
- ? targetRight
358
- : targetLeft // inline === 'start || inline === 'nearest
373
+ ? targetRight + marginRight
374
+ : targetLeft - marginLeft // inline === 'start || inline === 'nearest
359
375
 
360
376
  // Collect new scroll positions
361
- let computations: ScrollAction[] = []
377
+ const computations: ScrollAction[] = []
362
378
  // 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)
363
379
  for (let index = 0; index < frames.length; index++) {
364
- let frame = frames[index]
380
+ const frame = frames[index]
365
381
 
366
382
  // @TODO add a shouldScroll hook here that allows userland code to take control
367
383
 
368
- let { height, width, top, right, bottom, left } =
384
+ const { height, width, top, right, bottom, left } =
369
385
  frame.getBoundingClientRect()
370
386
 
371
387
  // If the element is already visible we can end it here
@@ -385,25 +401,25 @@ export let compute = (target: Element, options: Options): ScrollAction[] => {
385
401
  return computations
386
402
  }
387
403
 
388
- let frameStyle = getComputedStyle(frame)
389
- let borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)
390
- let borderTop = parseInt(frameStyle.borderTopWidth as string, 10)
391
- let borderRight = parseInt(frameStyle.borderRightWidth as string, 10)
392
- let borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)
404
+ const frameStyle = getComputedStyle(frame)
405
+ const borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)
406
+ const borderTop = parseInt(frameStyle.borderTopWidth as string, 10)
407
+ const borderRight = parseInt(frameStyle.borderRightWidth as string, 10)
408
+ const borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)
393
409
 
394
410
  let blockScroll: number = 0
395
411
  let inlineScroll: number = 0
396
412
 
397
413
  // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here
398
414
  // @TODO find out if the "as HTMLElement" overrides can be dropped
399
- let scrollbarWidth =
415
+ const scrollbarWidth =
400
416
  'offsetWidth' in frame
401
417
  ? (frame as HTMLElement).offsetWidth -
402
418
  (frame as HTMLElement).clientWidth -
403
419
  borderLeft -
404
420
  borderRight
405
421
  : 0
406
- let scrollbarHeight =
422
+ const scrollbarHeight =
407
423
  'offsetHeight' in frame
408
424
  ? (frame as HTMLElement).offsetHeight -
409
425
  (frame as HTMLElement).clientHeight -
@@ -411,13 +427,13 @@ export let compute = (target: Element, options: Options): ScrollAction[] => {
411
427
  borderBottom
412
428
  : 0
413
429
 
414
- let scaleX =
430
+ const scaleX =
415
431
  'offsetWidth' in frame
416
432
  ? (frame as HTMLElement).offsetWidth === 0
417
433
  ? 0
418
434
  : width / (frame as HTMLElement).offsetWidth
419
435
  : 0
420
- let scaleY =
436
+ const scaleY =
421
437
  'offsetHeight' in frame
422
438
  ? (frame as HTMLElement).offsetHeight === 0
423
439
  ? 0
@@ -513,22 +529,28 @@ export let compute = (target: Element, options: Options): ScrollAction[] => {
513
529
  )
514
530
  }
515
531
 
516
- let { scrollLeft, scrollTop } = frame
532
+ const { scrollLeft, scrollTop } = frame
517
533
  // Ensure scroll coordinates are not out of bounds while applying scroll offsets
518
- blockScroll = Math.max(
519
- 0,
520
- Math.min(
521
- scrollTop + blockScroll / scaleY,
522
- frame.scrollHeight - height / scaleY + scrollbarHeight
523
- )
524
- )
525
- inlineScroll = Math.max(
526
- 0,
527
- Math.min(
528
- scrollLeft + inlineScroll / scaleX,
529
- frame.scrollWidth - width / scaleX + scrollbarWidth
530
- )
531
- )
534
+ blockScroll =
535
+ scaleY === 0
536
+ ? 0
537
+ : Math.max(
538
+ 0,
539
+ Math.min(
540
+ scrollTop + blockScroll / scaleY,
541
+ frame.scrollHeight - height / scaleY + scrollbarHeight
542
+ )
543
+ )
544
+ inlineScroll =
545
+ scaleX === 0
546
+ ? 0
547
+ : Math.max(
548
+ 0,
549
+ Math.min(
550
+ scrollLeft + inlineScroll / scaleX,
551
+ frame.scrollWidth - width / scaleX + scrollbarWidth
552
+ )
553
+ )
532
554
 
533
555
  // Cache the offset so that parent frames can scroll this into view correctly
534
556
  targetBlock += scrollTop - blockScroll