compute-scroll-into-view 1.0.19 → 2.0.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/README.md CHANGED
@@ -2,7 +2,6 @@
2
2
  [![npm version](https://img.shields.io/npm/v/compute-scroll-into-view.svg?style=flat-square)](https://www.npmjs.com/package/compute-scroll-into-view)
3
3
  [![gzip size][gzip-badge]][unpkg-dist]
4
4
  [![size][size-badge]][unpkg-dist]
5
- [![module formats: umd, cjs, and es][module-formats-badge]][unpkg-dist]
6
5
  [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)](https://github.com/semantic-release/semantic-release)
7
6
 
8
7
  ![compute-scroll-into-view](https://user-images.githubusercontent.com/81981/43024153-a2cc212c-8c6d-11e8-913b-b4d62efcf105.png)
@@ -12,33 +11,41 @@ Use this if you want the smallest possible bundlesize and is ok with implementin
12
11
 
13
12
  Scrolling SVG elements are supported, as well as Shadow DOM elements. The [VisualViewport](https://developer.mozilla.org/en-US/docs/Web/API/VisualViewport) API is also supported, ensuring scrolling works properly on modern devices. Quirksmode is also supported as long as you polyfill [`document.scrollingElement`](https://developer.mozilla.org/en-US/docs/Web/API/document/scrollingElement).
14
13
 
14
+ - [Install](#install)
15
+ - [Usage](#usage)
16
+ - [API](#api)
17
+ - [compute(target, options)](#computetarget-options)
18
+ - [options](#options)
19
+ - [block](#block)
20
+ - [inline](#inline)
21
+ - [scrollMode](#scrollmode)
22
+ - [boundary](#boundary)
23
+ - [skipOverflowHiddenElements](#skipoverflowhiddenelements)
24
+
15
25
  ## Install
16
26
 
17
27
  ```bash
18
28
  npm i compute-scroll-into-view
19
29
  ```
20
30
 
21
- The UMD build is also available on [unpkg](https://unpkg.com/compute-scroll-into-view/umd/):
31
+ You can also use it from an CDN:
22
32
 
23
- ```html
24
- <script src="https://unpkg.com/compute-scroll-into-view/umd/compute-scroll-into-view.min.js"></script>
33
+ ```js
34
+ const { default: compute } = await import(
35
+ 'https://esm.sh/compute-scroll-into-view'
36
+ )
25
37
  ```
26
38
 
27
- You can find the library on `window.computeScrollIntoView`.
28
-
29
39
  ## Usage
30
40
 
31
41
  ```js
32
- // es6 import
33
- import computeScrollIntoView from 'compute-scroll-into-view'
34
- // or es5
35
- const computeScrollIntoView = require('compute-scroll-into-view')
42
+ import compute from 'compute-scroll-into-view'
36
43
 
37
44
  const node = document.getElementById('hero')
38
45
 
39
46
  // same behavior as Element.scrollIntoView({block: "nearest", inline: "nearest"})
40
47
  // see: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
41
- const actions = computeScrollIntoView(node, {
48
+ const actions = compute(node, {
42
49
  scrollMode: 'if-needed',
43
50
  block: 'nearest',
44
51
  inline: 'nearest',
@@ -46,7 +53,7 @@ const actions = computeScrollIntoView(node, {
46
53
 
47
54
  // same behavior as Element.scrollIntoViewIfNeeded(true)
48
55
  // see: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded
49
- const actions = computeScrollIntoView(node, {
56
+ const actions = compute(node, {
50
57
  scrollMode: 'if-needed',
51
58
  block: 'center',
52
59
  inline: 'center',
@@ -61,7 +68,7 @@ actions.forEach(({ el, top, left }) => {
61
68
 
62
69
  ## API
63
70
 
64
- ### computeScrollIntoView(target, options)
71
+ ### compute(target, options)
65
72
 
66
73
  ### options
67
74
 
@@ -102,7 +109,7 @@ By passing a boundary you can short-circuit this loop depending on your needs:
102
109
  You can also pass a function to do more dynamic checks to override the scroll scoping:
103
110
 
104
111
  ```js
105
- const actions = computeScrollIntoView(target, {
112
+ const actions = compute(target, {
106
113
  boundary: (parent) => {
107
114
  // By default `overflow: hidden` elements are allowed, only `overflow: visible | clip` is skipped as
108
115
  // this is required by the CSSOM spec
@@ -123,11 +130,6 @@ By default the [spec](https://drafts.csswg.org/cssom-view/#scrolling-box) states
123
130
 
124
131
  This package follows the convention [adopted by Firefox](https://hg.mozilla.org/integration/fx-team/rev/c48c3ec05012#l7.18) of setting a boolean option to _not_ scroll all nodes with `overflow: hidden` set.
125
132
 
126
- # TypeScript support
127
-
128
- This library ships with library definitions for TypeScript.
129
-
130
- [gzip-badge]: http://img.badgesize.io/https://unpkg.com/compute-scroll-into-view/umd/compute-scroll-into-view.min.js?compression=gzip&label=gzip%20size&style=flat-square
131
- [size-badge]: http://img.badgesize.io/https://unpkg.com/compute-scroll-into-view/umd/compute-scroll-into-view.min.js?label=size&style=flat-square
132
- [unpkg-dist]: https://unpkg.com/compute-scroll-into-view/umd/
133
- [module-formats-badge]: https://img.shields.io/badge/module%20formats-umd%2C%20cjs%2C%20es-green.svg?style=flat-square
133
+ [gzip-badge]: https://img.badgesize.io/https:/unpkg.com/compute-scroll-into-view/dist/index.js?label=gzip%20size&compression=gzip&style=flat-square
134
+ [size-badge]: https://img.badgesize.io/https:/unpkg.com/compute-scroll-into-view/dist/index.js?label=size&style=flat-square
135
+ [unpkg-dist]: https://unpkg.com/compute-scroll-into-view/dist/
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";function t(t){return"object"==typeof t&&null!=t&&1===t.nodeType}function e(t,e){return(!e||"hidden"!==t)&&("visible"!==t&&"clip"!==t)}function n(t,n){if(t.clientHeight<t.scrollHeight||t.clientWidth<t.scrollWidth){const o=getComputedStyle(t,null);return e(o.overflowY,n)||e(o.overflowX,n)||function(t){const e=function(t){if(!t.ownerDocument||!t.ownerDocument.defaultView)return null;try{return t.ownerDocument.defaultView.frameElement}catch(t){return null}}(t);return!!e&&(e.clientHeight<t.scrollHeight||e.clientWidth<t.scrollWidth)}(t)}return!1}function o(t,e,n,o,i,l,r,d){return l<t&&r>e||l>t&&r<e?0:l<=t&&d<=n||r>=e&&d>=n?l-t-o:r>e&&d<n||l<t&&d>n?r-e+i:0}function i(t){const e=t.parentElement;return null==e?t.getRootNode().host||null:e}module.exports=(e,l)=>{var r,d,s,u,c,f;if("undefined"==typeof document)return[];const{scrollMode:h,block:a,inline:g,boundary:m,skipOverflowHiddenElements:p}=l,w="function"==typeof m?m:t=>t!==m;if(!t(e))throw new TypeError("Invalid target");const W=document.scrollingElement||document.documentElement,H=[];let b=e;for(;t(b)&&w(b);){if(b=i(b),b===W){H.push(b);break}null!=b&&b===document.body&&n(b)&&!n(document.documentElement)||null!=b&&n(b,p)&&H.push(b)}const v=null!=(d=null==(r=window.visualViewport)?void 0:r.width)?d:innerWidth,y=null!=(u=null==(s=window.visualViewport)?void 0:s.height)?u:innerHeight,E=null!=(c=window.scrollX)?c:pageXOffset,M=null!=(f=window.scrollY)?f:pageYOffset,{height:x,width:I,top:C,right:R,bottom:T,left:V}=e.getBoundingClientRect();let k="start"===a||"nearest"===a?C:"end"===a?T:C+x/2,B="center"===g?V+I/2:"end"===g?R:V;const D=[];for(let t=0;t<H.length;t++){const e=H[t],{height:n,width:i,top:l,right:r,bottom:d,left:s}=e.getBoundingClientRect();if("if-needed"===h&&C>=0&&V>=0&&T<=y&&R<=v&&C>=l&&T<=d&&V>=s&&R<=r)return D;const u=getComputedStyle(e),c=parseInt(u.borderLeftWidth,10),f=parseInt(u.borderTopWidth,10),m=parseInt(u.borderRightWidth,10),p=parseInt(u.borderBottomWidth,10);let w=0,b=0;const O="offsetWidth"in e?e.offsetWidth-e.clientWidth-c-m:0,X="offsetHeight"in e?e.offsetHeight-e.clientHeight-f-p:0,Y="offsetWidth"in e?0===e.offsetWidth?0:i/e.offsetWidth:0,L="offsetHeight"in e?0===e.offsetHeight?0:n/e.offsetHeight:0;if(W===e)w="start"===a?k:"end"===a?k-y:"nearest"===a?o(M,M+y,y,f,p,M+k,M+k+x,x):k-y/2,b="start"===g?B:"center"===g?B-v/2:"end"===g?B-v:o(E,E+v,v,c,m,E+B,E+B+I,I),w=Math.max(0,w+M),b=Math.max(0,b+E);else{w="start"===a?k-l-f:"end"===a?k-d+p+X:"nearest"===a?o(l,d,n,f,p+X,k,k+x,x):k-(l+n/2)+X/2,b="start"===g?B-s-c:"center"===g?B-(s+i/2)+O/2:"end"===g?B-r+m+O:o(s,r,i,c,m+O,B,B+I,I);const{scrollLeft:t,scrollTop:u}=e;w=Math.max(0,Math.min(u+w/L,e.scrollHeight-n/L+X)),b=Math.max(0,Math.min(t+b/Y,e.scrollWidth-i/Y+O)),k+=u-w,B+=t-b}D.push({el:e,top:w,left:b})}return D};
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["// Compute what scrolling needs to be done on required scrolling boxes for target to be in view\n\n// The type names here are named after the spec to make it easier to find more information around what they mean:\n// To reduce churn and reduce things that need be maintained things from the official TS DOM library is used here\n// https://drafts.csswg.org/cssom-view/\n\n// For a definition on what is \"block flow direction\" exactly, check this: https://drafts.csswg.org/css-writing-modes-4/#block-flow-direction\n\n/**\n * This new option is tracked in this PR, which is the most likely candidate at the time: https://github.com/w3c/csswg-drafts/pull/1805\n * @public\n */\nexport type ScrollMode = 'always' | 'if-needed'\n\n/** @public */\nexport interface Options {\n block?: ScrollLogicalPosition\n inline?: ScrollLogicalPosition\n scrollMode?: ScrollMode\n // Custom behavior, not in any spec\n boundary?: Element | ((parent: Element) => boolean) | null\n /**\n * New option that skips auto-scrolling all nodes with overflow: hidden set\n * See FF implementation: https://hg.mozilla.org/integration/fx-team/rev/c48c3ec05012#l7.18\n * @public\n */\n skipOverflowHiddenElements?: boolean\n}\n\n/** @public */\nexport interface ScrollAction {\n el: Element\n top: number\n left: number\n}\n\n// @TODO better shadowdom test, 11 = document fragment\nfunction isElement(el: any): el is Element {\n return typeof el === 'object' && el != null && el.nodeType === 1\n}\n\nfunction canOverflow(\n overflow: string | null,\n skipOverflowHiddenElements?: boolean\n) {\n if (skipOverflowHiddenElements && overflow === 'hidden') {\n return false\n }\n\n return overflow !== 'visible' && overflow !== 'clip'\n}\n\nfunction getFrameElement(el: Element) {\n if (!el.ownerDocument || !el.ownerDocument.defaultView) {\n return null\n }\n\n try {\n return el.ownerDocument.defaultView.frameElement\n } catch (e) {\n return null\n }\n}\n\nfunction isHiddenByFrame(el: Element): boolean {\n const frame = getFrameElement(el)\n if (!frame) {\n return false\n }\n\n return (\n frame.clientHeight < el.scrollHeight || frame.clientWidth < el.scrollWidth\n )\n}\n\nfunction isScrollable(el: Element, skipOverflowHiddenElements?: boolean) {\n if (el.clientHeight < el.scrollHeight || el.clientWidth < el.scrollWidth) {\n const style = getComputedStyle(el, null)\n return (\n canOverflow(style.overflowY, skipOverflowHiddenElements) ||\n canOverflow(style.overflowX, skipOverflowHiddenElements) ||\n isHiddenByFrame(el)\n )\n }\n\n return false\n}\n/**\n * Find out which edge to align against when logical scroll position is \"nearest\"\n * Interesting fact: \"nearest\" works similarily to \"if-needed\", if the element is fully visible it will not scroll it\n *\n * Legends:\n * ┌────────┐ ┏ ━ ━ ━ ┓\n * │ target │ frame\n * └────────┘ ┗ ━ ━ ━ ┛\n */\nfunction alignNearest(\n scrollingEdgeStart: number,\n scrollingEdgeEnd: number,\n scrollingSize: number,\n scrollingBorderStart: number,\n scrollingBorderEnd: number,\n elementEdgeStart: number,\n elementEdgeEnd: number,\n elementSize: number\n) {\n /**\n * If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B\n *\n * ┌──┐\n * ┏━│━━│━┓\n * │ │\n * ┃ │ │ ┃ do nothing\n * │ │\n * ┗━│━━│━┛\n * └──┘\n *\n * If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D\n *\n * ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐\n * │┃ ┃│ do nothing\n * └───────────┘\n * ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart < scrollingEdgeStart &&\n elementEdgeEnd > scrollingEdgeEnd) ||\n (elementEdgeStart > scrollingEdgeStart && elementEdgeEnd < scrollingEdgeEnd)\n ) {\n return 0\n }\n\n /**\n * If element edge A is outside scrolling box edge A and element height is less than scrolling box height\n *\n * ┌──┐\n * ┏━│━━│━┓ ┏━┌━━┐━┓\n * └──┘ │ │\n * from ┃ ┃ to ┃ └──┘ ┃\n *\n * ┗━ ━━ ━┛ ┗━ ━━ ━┛\n *\n * If element edge B is outside scrolling box edge B and element height is greater than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━┌━━┐━┓\n * │ │\n * from ┃ ┌──┐ ┃ to ┃ │ │ ┃\n * │ │ │ │\n * ┗━│━━│━┛ ┗━│━━│━┛\n * │ │ └──┘\n * │ │\n * └──┘\n *\n * If element edge C is outside scrolling box edge C and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * │ ┃ │ ┃ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * ┃ │ ┃ │ ┃ ┃ │\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart <= scrollingEdgeStart && elementSize <= scrollingSize) ||\n (elementEdgeEnd >= scrollingEdgeEnd && elementSize >= scrollingSize)\n ) {\n return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart\n }\n\n /**\n * If element edge B is outside scrolling box edge B and element height is less than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━ ━━ ━┓\n *\n * from ┃ ┃ to ┃ ┌──┐ ┃\n * ┌──┐ │ │\n * ┗━│━━│━┛ ┗━└━━┘━┛\n * └──┘\n *\n * If element edge A is outside scrolling box edge A and element height is greater than scrolling box height\n *\n * ┌──┐\n * │ │\n * │ │ ┌──┐\n * ┏━│━━│━┓ ┏━│━━│━┓\n * │ │ │ │\n * from ┃ └──┘ ┃ to ┃ │ │ ┃\n * │ │\n * ┗━ ━━ ━┛ ┗━└━━┘━┛\n *\n * If element edge C is outside scrolling box edge C and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * │ ┃ │ ┃ │ ┃ ┃\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * ┃ │ ┃ │ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n */\n if (\n (elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||\n (elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)\n ) {\n return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd\n }\n\n return 0\n}\n\nfunction getParentElement(element: Node): Element | null {\n const parent = element.parentElement\n if (parent == null) {\n return (element.getRootNode() as ShadowRoot).host || null\n }\n return parent\n}\n\n/** @public */\nexport default (target: Element, options: Options): ScrollAction[] => {\n if (typeof document === 'undefined') {\n // If there's no DOM we assume it's not in a browser environment\n return []\n }\n\n const { scrollMode, block, inline, boundary, skipOverflowHiddenElements } =\n options\n // Allow using a callback to check the boundary\n // The default behavior is to check if the current target matches the boundary element or not\n // If undefined it'll check that target is never undefined (can happen as we recurse up the tree)\n const checkBoundary =\n typeof boundary === 'function' ? boundary : (node: any) => node !== boundary\n\n if (!isElement(target)) {\n throw new TypeError('Invalid target')\n }\n\n // Used to handle the top most element that can be scrolled\n const scrollingElement = document.scrollingElement || document.documentElement\n\n // Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box\n const frames: Element[] = []\n let cursor: Element | null = target\n while (isElement(cursor) && checkBoundary(cursor)) {\n // Move cursor to parent\n cursor = getParentElement(cursor)\n\n // Stop when we reach the viewport\n if (cursor === scrollingElement) {\n frames.push(cursor)\n break\n }\n\n // Skip document.body if it's not the scrollingElement and documentElement isn't independently scrollable\n if (\n cursor != null &&\n cursor === document.body &&\n isScrollable(cursor) &&\n !isScrollable(document.documentElement)\n ) {\n continue\n }\n\n // Now we check if the element is scrollable, this code only runs if the loop haven't already hit the viewport or a custom boundary\n if (cursor != null && isScrollable(cursor, skipOverflowHiddenElements)) {\n frames.push(cursor)\n }\n }\n\n // Support pinch-zooming properly, making sure elements scroll into the visual viewport\n // Browsers that don't support visualViewport will report the layout viewport dimensions on document.documentElement.clientWidth/Height\n // and viewport dimensions on window.innerWidth/Height\n // https://www.quirksmode.org/mobile/viewports2.html\n // https://bokand.github.io/viewport/index.html\n const viewportWidth = window.visualViewport?.width ?? innerWidth\n const viewportHeight = window.visualViewport?.height ?? innerHeight\n\n // Newer browsers supports scroll[X|Y], page[X|Y]Offset is\n const viewportX = window.scrollX ?? pageXOffset\n const viewportY = window.scrollY ?? pageYOffset\n\n const {\n height: targetHeight,\n width: targetWidth,\n top: targetTop,\n right: targetRight,\n bottom: targetBottom,\n left: targetLeft,\n } = target.getBoundingClientRect()\n\n // These values mutate as we loop through and generate scroll coordinates\n let targetBlock: number =\n block === 'start' || block === 'nearest'\n ? targetTop\n : block === 'end'\n ? targetBottom\n : targetTop + targetHeight / 2 // block === 'center\n let targetInline: number =\n inline === 'center'\n ? targetLeft + targetWidth / 2\n : inline === 'end'\n ? targetRight\n : targetLeft // inline === 'start || inline === 'nearest\n\n // Collect new scroll positions\n const computations: ScrollAction[] = []\n // In chrome there's no longer a difference between caching the `frames.length` to a var or not, so we don't in this case (size > speed anyways)\n for (let index = 0; index < frames.length; index++) {\n const frame = frames[index]\n\n // @TODO add a shouldScroll hook here that allows userland code to take control\n\n const { height, width, top, right, bottom, left } =\n frame.getBoundingClientRect()\n\n // If the element is already visible we can end it here\n // @TODO targetBlock and targetInline should be taken into account to be compliant with https://github.com/w3c/csswg-drafts/pull/1805/files#diff-3c17f0e43c20f8ecf89419d49e7ef5e0R1333\n if (\n scrollMode === 'if-needed' &&\n targetTop >= 0 &&\n targetLeft >= 0 &&\n targetBottom <= viewportHeight &&\n targetRight <= viewportWidth &&\n targetTop >= top &&\n targetBottom <= bottom &&\n targetLeft >= left &&\n targetRight <= right\n ) {\n // Break the loop and return the computations for things that are not fully visible\n return computations\n }\n\n const frameStyle = getComputedStyle(frame)\n const borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)\n const borderTop = parseInt(frameStyle.borderTopWidth as string, 10)\n const borderRight = parseInt(frameStyle.borderRightWidth as string, 10)\n const borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)\n\n let blockScroll: number = 0\n let inlineScroll: number = 0\n\n // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here\n // @TODO find out if the \"as HTMLElement\" overrides can be dropped\n const scrollbarWidth =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth -\n (frame as HTMLElement).clientWidth -\n borderLeft -\n borderRight\n : 0\n const scrollbarHeight =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight -\n (frame as HTMLElement).clientHeight -\n borderTop -\n borderBottom\n : 0\n\n const scaleX =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth === 0\n ? 0\n : width / (frame as HTMLElement).offsetWidth\n : 0\n const scaleY =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight === 0\n ? 0\n : height / (frame as HTMLElement).offsetHeight\n : 0\n\n if (scrollingElement === frame) {\n // Handle viewport logic (document.documentElement or document.body)\n\n if (block === 'start') {\n blockScroll = targetBlock\n } else if (block === 'end') {\n blockScroll = targetBlock - viewportHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n viewportY,\n viewportY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n viewportY + targetBlock,\n viewportY + targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - viewportHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline\n } else if (inline === 'center') {\n inlineScroll = targetInline - viewportWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - viewportWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n viewportX,\n viewportX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n viewportX + targetInline,\n viewportX + targetInline + targetWidth,\n targetWidth\n )\n }\n\n // Apply scroll position offsets and ensure they are within bounds\n // @TODO add more test cases to cover this 100%\n blockScroll = Math.max(0, blockScroll + viewportY)\n inlineScroll = Math.max(0, inlineScroll + viewportX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\n\n if (block === 'start') {\n blockScroll = targetBlock - top - borderTop\n } else if (block === 'end') {\n blockScroll = targetBlock - bottom + borderBottom + scrollbarHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n top,\n bottom,\n height,\n borderTop,\n borderBottom + scrollbarHeight,\n targetBlock,\n targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - (top + height / 2) + scrollbarHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline - left - borderLeft\n } else if (inline === 'center') {\n inlineScroll = targetInline - (left + width / 2) + scrollbarWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - right + borderRight + scrollbarWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n left,\n right,\n width,\n borderLeft,\n borderRight + scrollbarWidth,\n targetInline,\n targetInline + targetWidth,\n targetWidth\n )\n }\n\n const { scrollLeft, scrollTop } = frame\n // Ensure scroll coordinates are not out of bounds while applying scroll offsets\n blockScroll = Math.max(\n 0,\n Math.min(\n scrollTop + blockScroll / scaleY,\n frame.scrollHeight - height / scaleY + scrollbarHeight\n )\n )\n inlineScroll = Math.max(\n 0,\n Math.min(\n scrollLeft + inlineScroll / scaleX,\n frame.scrollWidth - width / scaleX + scrollbarWidth\n )\n )\n\n // Cache the offset so that parent frames can scroll this into view correctly\n targetBlock += scrollTop - blockScroll\n targetInline += scrollLeft - inlineScroll\n }\n\n computations.push({ el: frame, top: blockScroll, left: inlineScroll })\n }\n\n return computations\n}\n"],"names":["isElement","el","nodeType","canOverflow","overflow","skipOverflowHiddenElements","isScrollable","clientHeight","scrollHeight","clientWidth","scrollWidth","style","getComputedStyle","overflowY","overflowX","frame","ownerDocument","defaultView","frameElement","e","getFrameElement","isHiddenByFrame","alignNearest","scrollingEdgeStart","scrollingEdgeEnd","scrollingSize","scrollingBorderStart","scrollingBorderEnd","elementEdgeStart","elementEdgeEnd","elementSize","getParentElement","element","parent","parentElement","getRootNode","host","target","options","_a","_b","_c","_d","_e","_f","document","scrollMode","block","inline","boundary","checkBoundary","node","TypeError","scrollingElement","documentElement","frames","cursor","push","body","viewportWidth","window","visualViewport","width","innerWidth","viewportHeight","height","innerHeight","viewportX","scrollX","pageXOffset","viewportY","scrollY","pageYOffset","targetHeight","targetWidth","top","targetTop","right","targetRight","bottom","targetBottom","left","targetLeft","getBoundingClientRect","targetBlock","targetInline","computations","index","length","frameStyle","borderLeft","parseInt","borderLeftWidth","borderTop","borderTopWidth","borderRight","borderRightWidth","borderBottom","borderBottomWidth","blockScroll","inlineScroll","scrollbarWidth","offsetWidth","scrollbarHeight","offsetHeight","scaleX","scaleY","Math","max","scrollLeft","scrollTop","min"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /** @public */
2
+ declare const _default: (target: Element, options: Options) => ScrollAction[]
3
+ export default _default
4
+
5
+ /** @public */
6
+ export declare interface Options {
7
+ block?: ScrollLogicalPosition
8
+ inline?: ScrollLogicalPosition
9
+ scrollMode?: ScrollMode
10
+ boundary?: Element | ((parent: Element) => boolean) | null
11
+ /**
12
+ * New option that skips auto-scrolling all nodes with overflow: hidden set
13
+ * See FF implementation: https://hg.mozilla.org/integration/fx-team/rev/c48c3ec05012#l7.18
14
+ * @public
15
+ */
16
+ skipOverflowHiddenElements?: boolean
17
+ }
18
+
19
+ /** @public */
20
+ export declare interface ScrollAction {
21
+ el: Element
22
+ top: number
23
+ left: number
24
+ }
25
+
26
+ /**
27
+ * 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
28
+ * @public
29
+ */
30
+ export declare type ScrollMode = 'always' | 'if-needed'
31
+
32
+ export {}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- function t(t){return"object"==typeof t&&null!=t&&1===t.nodeType}function e(t,e){return(!e||"hidden"!==t)&&"visible"!==t&&"clip"!==t}function n(t,n){if(t.clientHeight<t.scrollHeight||t.clientWidth<t.scrollWidth){var i=getComputedStyle(t,null);return e(i.overflowY,n)||e(i.overflowX,n)||function(t){var e=function(t){if(!t.ownerDocument||!t.ownerDocument.defaultView)return null;try{return t.ownerDocument.defaultView.frameElement}catch(t){return null}}(t);return!!e&&(e.clientHeight<t.scrollHeight||e.clientWidth<t.scrollWidth)}(t)}return!1}function i(t,e,n,i,r,o,l,d){return o<t&&l>e||o>t&&l<e?0:o<=t&&d<=n||l>=e&&d>=n?o-t-i:l>e&&d<n||o<t&&d>n?l-e+r:0}module.exports=function(e,r){var o=window,l=r.scrollMode,d=r.block,f=r.inline,h=r.boundary,u=r.skipOverflowHiddenElements,s="function"==typeof h?h:function(t){return t!==h};if(!t(e))throw new TypeError("Invalid target");for(var c=document.scrollingElement||document.documentElement,a=[],g=e;t(g)&&s(g);){if((g=g.parentElement)===c){a.push(g);break}null!=g&&g===document.body&&n(g)&&!n(document.documentElement)||null!=g&&n(g,u)&&a.push(g)}for(var m=o.visualViewport?o.visualViewport.width:innerWidth,p=o.visualViewport?o.visualViewport.height:innerHeight,w=window.scrollX||pageXOffset,v=window.scrollY||pageYOffset,W=e.getBoundingClientRect(),H=W.height,b=W.width,y=W.top,E=W.right,M=W.bottom,V=W.left,x="start"===d||"nearest"===d?y:"end"===d?M:y+H/2,I="center"===f?V+b/2:"end"===f?E:V,C=[],T=0;T<a.length;T++){var k=a[T],B=k.getBoundingClientRect(),D=B.height,O=B.width,R=B.top,X=B.right,Y=B.bottom,L=B.left;if("if-needed"===l&&y>=0&&V>=0&&M<=p&&E<=m&&y>=R&&M<=Y&&V>=L&&E<=X)return C;var S=getComputedStyle(k),j=parseInt(S.borderLeftWidth,10),q=parseInt(S.borderTopWidth,10),z=parseInt(S.borderRightWidth,10),A=parseInt(S.borderBottomWidth,10),F=0,G=0,J="offsetWidth"in k?k.offsetWidth-k.clientWidth-j-z:0,K="offsetHeight"in k?k.offsetHeight-k.clientHeight-q-A:0,N="offsetWidth"in k?0===k.offsetWidth?0:O/k.offsetWidth:0,P="offsetHeight"in k?0===k.offsetHeight?0:D/k.offsetHeight:0;if(c===k)F="start"===d?x:"end"===d?x-p:"nearest"===d?i(v,v+p,p,q,A,v+x,v+x+H,H):x-p/2,G="start"===f?I:"center"===f?I-m/2:"end"===f?I-m:i(w,w+m,m,j,z,w+I,w+I+b,b),F=Math.max(0,F+v),G=Math.max(0,G+w);else{F="start"===d?x-R-q:"end"===d?x-Y+A+K:"nearest"===d?i(R,Y,D,q,A+K,x,x+H,H):x-(R+D/2)+K/2,G="start"===f?I-L-j:"center"===f?I-(L+O/2)+J/2:"end"===f?I-X+z+J:i(L,X,O,j,z+J,I,I+b,b);var Q=k.scrollLeft,U=k.scrollTop;x+=U-(F=Math.max(0,Math.min(U+F/P,k.scrollHeight-D/P+K))),I+=Q-(G=Math.max(0,Math.min(Q+G/N,k.scrollWidth-O/N+J)))}C.push({el:k,top:F,left:G})}return C};
1
+ function t(t){return"object"==typeof t&&null!=t&&1===t.nodeType}function e(t,e){return(!e||"hidden"!==t)&&("visible"!==t&&"clip"!==t)}function n(t,n){if(t.clientHeight<t.scrollHeight||t.clientWidth<t.scrollWidth){const o=getComputedStyle(t,null);return e(o.overflowY,n)||e(o.overflowX,n)||function(t){const e=function(t){if(!t.ownerDocument||!t.ownerDocument.defaultView)return null;try{return t.ownerDocument.defaultView.frameElement}catch(t){return null}}(t);return!!e&&(e.clientHeight<t.scrollHeight||e.clientWidth<t.scrollWidth)}(t)}return!1}function o(t,e,n,o,i,l,r,d){return l<t&&r>e||l>t&&r<e?0:l<=t&&d<=n||r>=e&&d>=n?l-t-o:r>e&&d<n||l<t&&d>n?r-e+i:0}function i(t){const e=t.parentElement;return null==e?t.getRootNode().host||null:e}var l=(e,l)=>{var r,d,u,f,s,c;if("undefined"==typeof document)return[];const{scrollMode:h,block:a,inline:g,boundary:m,skipOverflowHiddenElements:p}=l,w="function"==typeof m?m:t=>t!==m;if(!t(e))throw new TypeError("Invalid target");const W=document.scrollingElement||document.documentElement,H=[];let b=e;for(;t(b)&&w(b);){if(b=i(b),b===W){H.push(b);break}null!=b&&b===document.body&&n(b)&&!n(document.documentElement)||null!=b&&n(b,p)&&H.push(b)}const v=null!=(d=null==(r=window.visualViewport)?void 0:r.width)?d:innerWidth,y=null!=(f=null==(u=window.visualViewport)?void 0:u.height)?f:innerHeight,E=null!=(s=window.scrollX)?s:pageXOffset,M=null!=(c=window.scrollY)?c:pageYOffset,{height:x,width:I,top:C,right:R,bottom:T,left:V}=e.getBoundingClientRect();let k="start"===a||"nearest"===a?C:"end"===a?T:C+x/2,B="center"===g?V+I/2:"end"===g?R:V;const D=[];for(let t=0;t<H.length;t++){const e=H[t],{height:n,width:i,top:l,right:r,bottom:d,left:u}=e.getBoundingClientRect();if("if-needed"===h&&C>=0&&V>=0&&T<=y&&R<=v&&C>=l&&T<=d&&V>=u&&R<=r)return D;const f=getComputedStyle(e),s=parseInt(f.borderLeftWidth,10),c=parseInt(f.borderTopWidth,10),m=parseInt(f.borderRightWidth,10),p=parseInt(f.borderBottomWidth,10);let w=0,b=0;const O="offsetWidth"in e?e.offsetWidth-e.clientWidth-s-m:0,X="offsetHeight"in e?e.offsetHeight-e.clientHeight-c-p:0,Y="offsetWidth"in e?0===e.offsetWidth?0:i/e.offsetWidth:0,L="offsetHeight"in e?0===e.offsetHeight?0:n/e.offsetHeight:0;if(W===e)w="start"===a?k:"end"===a?k-y:"nearest"===a?o(M,M+y,y,c,p,M+k,M+k+x,x):k-y/2,b="start"===g?B:"center"===g?B-v/2:"end"===g?B-v:o(E,E+v,v,s,m,E+B,E+B+I,I),w=Math.max(0,w+M),b=Math.max(0,b+E);else{w="start"===a?k-l-c:"end"===a?k-d+p+X:"nearest"===a?o(l,d,n,c,p+X,k,k+x,x):k-(l+n/2)+X/2,b="start"===g?B-u-s:"center"===g?B-(u+i/2)+O/2:"end"===g?B-r+m+O:o(u,r,i,s,m+O,B,B+I,I);const{scrollLeft:t,scrollTop:f}=e;w=Math.max(0,Math.min(f+w/L,e.scrollHeight-n/L+X)),b=Math.max(0,Math.min(t+b/Y,e.scrollWidth-i/Y+O)),k+=f-w,B+=t-b}D.push({el:e,top:w,left:b})}return D};export{l as default};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["// Compute what scrolling needs to be done on required scrolling boxes for target to be in view\n\n// The type names here are named after the spec to make it easier to find more information around what they mean:\n// To reduce churn and reduce things that need be maintained things from the official TS DOM library is used here\n// https://drafts.csswg.org/cssom-view/\n\n// For a definition on what is \"block flow direction\" exactly, check this: https://drafts.csswg.org/css-writing-modes-4/#block-flow-direction\n\n// add support for visualViewport object currently implemented in chrome\ninterface visualViewport {\n height: number\n width: number\n}\n\ntype ScrollLogicalPosition = 'start' | 'center' | 'end' | 'nearest'\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\ntype ScrollMode = 'always' | 'if-needed'\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\ntype SkipOverflowHiddenElements = boolean\n\ninterface Options {\n block?: ScrollLogicalPosition\n inline?: ScrollLogicalPosition\n scrollMode?: ScrollMode\n boundary?: CustomScrollBoundary\n skipOverflowHiddenElements?: SkipOverflowHiddenElements\n}\n\n// Custom behavior, not in any spec\ntype CustomScrollBoundaryCallback = (parent: Element) => boolean\ntype CustomScrollBoundary = Element | CustomScrollBoundaryCallback | null\ninterface CustomScrollAction {\n el: Element\n top: number\n left: number\n}\n\n// @TODO better shadowdom test, 11 = document fragment\nfunction isElement(el: any): el is Element {\n return typeof el === 'object' && el != null && el.nodeType === 1\n}\n\nfunction canOverflow(\n overflow: string | null,\n skipOverflowHiddenElements?: boolean\n) {\n if (skipOverflowHiddenElements && overflow === 'hidden') {\n return false\n }\n\n return overflow !== 'visible' && overflow !== 'clip'\n}\n\nfunction getFrameElement(el: Element) {\n if (!el.ownerDocument || !el.ownerDocument.defaultView) {\n return null\n }\n\n try {\n return el.ownerDocument.defaultView.frameElement\n } catch (e) {\n return null\n }\n}\n\nfunction isHiddenByFrame(el: Element): boolean {\n const frame = getFrameElement(el)\n if (!frame) {\n return false\n }\n\n return (\n frame.clientHeight < el.scrollHeight || frame.clientWidth < el.scrollWidth\n )\n}\n\nfunction isScrollable(el: Element, skipOverflowHiddenElements?: boolean) {\n if (el.clientHeight < el.scrollHeight || el.clientWidth < el.scrollWidth) {\n const style = getComputedStyle(el, null)\n return (\n canOverflow(style.overflowY, skipOverflowHiddenElements) ||\n canOverflow(style.overflowX, skipOverflowHiddenElements) ||\n isHiddenByFrame(el)\n )\n }\n\n return false\n}\n/**\n * Find out which edge to align against when logical scroll position is \"nearest\"\n * Interesting fact: \"nearest\" works similarily to \"if-needed\", if the element is fully visible it will not scroll it\n *\n * Legends:\n * ┌────────┐ ┏ ━ ━ ━ ┓\n * │ target │ frame\n * └────────┘ ┗ ━ ━ ━ ┛\n */\nfunction alignNearest(\n scrollingEdgeStart: number,\n scrollingEdgeEnd: number,\n scrollingSize: number,\n scrollingBorderStart: number,\n scrollingBorderEnd: number,\n elementEdgeStart: number,\n elementEdgeEnd: number,\n elementSize: number\n) {\n /**\n * If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B\n *\n * ┌──┐\n * ┏━│━━│━┓\n * │ │\n * ┃ │ │ ┃ do nothing\n * │ │\n * ┗━│━━│━┛\n * └──┘\n *\n * If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D\n *\n * ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐\n * │┃ ┃│ do nothing\n * └───────────┘\n * ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart < scrollingEdgeStart &&\n elementEdgeEnd > scrollingEdgeEnd) ||\n (elementEdgeStart > scrollingEdgeStart && elementEdgeEnd < scrollingEdgeEnd)\n ) {\n return 0\n }\n\n /**\n * If element edge A is outside scrolling box edge A and element height is less than scrolling box height\n *\n * ┌──┐\n * ┏━│━━│━┓ ┏━┌━━┐━┓\n * └──┘ │ │\n * from ┃ ┃ to ┃ └──┘ ┃\n *\n * ┗━ ━━ ━┛ ┗━ ━━ ━┛\n *\n * If element edge B is outside scrolling box edge B and element height is greater than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━┌━━┐━┓\n * │ │\n * from ┃ ┌──┐ ┃ to ┃ │ │ ┃\n * │ │ │ │\n * ┗━│━━│━┛ ┗━│━━│━┛\n * │ │ └──┘\n * │ │\n * └──┘\n *\n * If element edge C is outside scrolling box edge C and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * │ ┃ │ ┃ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * ┃ │ ┃ │ ┃ ┃ │\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart <= scrollingEdgeStart && elementSize <= scrollingSize) ||\n (elementEdgeEnd >= scrollingEdgeEnd && elementSize >= scrollingSize)\n ) {\n return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart\n }\n\n /**\n * If element edge B is outside scrolling box edge B and element height is less than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━ ━━ ━┓\n *\n * from ┃ ┃ to ┃ ┌──┐ ┃\n * ┌──┐ │ │\n * ┗━│━━│━┛ ┗━└━━┘━┛\n * └──┘\n *\n * If element edge A is outside scrolling box edge A and element height is greater than scrolling box height\n *\n * ┌──┐\n * │ │\n * │ │ ┌──┐\n * ┏━│━━│━┓ ┏━│━━│━┓\n * │ │ │ │\n * from ┃ └──┘ ┃ to ┃ │ │ ┃\n * │ │\n * ┗━ ━━ ━┛ ┗━└━━┘━┛\n *\n * If element edge C is outside scrolling box edge C and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * │ ┃ │ ┃ │ ┃ ┃\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * ┃ │ ┃ │ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n */\n if (\n (elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||\n (elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)\n ) {\n return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd\n }\n\n return 0\n}\n\nexport default (target: Element, options: Options): CustomScrollAction[] => {\n //TODO: remove this hack when microbundle will support typescript >= 4.0\n const windowWithViewport = window as unknown as Window & {\n visualViewport: visualViewport\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 = cursor.parentElement\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 = windowWithViewport.visualViewport\n ? windowWithViewport.visualViewport.width\n : innerWidth\n const viewportHeight = windowWithViewport.visualViewport\n ? windowWithViewport.visualViewport.height\n : innerHeight\n\n // Newer browsers supports scroll[X|Y], page[X|Y]Offset is\n const viewportX = window.scrollX || pageXOffset\n const viewportY = window.scrollY || pageYOffset\n\n const {\n height: targetHeight,\n width: targetWidth,\n top: targetTop,\n right: targetRight,\n bottom: targetBottom,\n left: targetLeft,\n } = target.getBoundingClientRect()\n\n // These values mutate as we loop through and generate scroll coordinates\n let targetBlock: number =\n block === 'start' || block === 'nearest'\n ? targetTop\n : block === 'end'\n ? targetBottom\n : targetTop + targetHeight / 2 // block === 'center\n let targetInline: number =\n inline === 'center'\n ? targetLeft + targetWidth / 2\n : inline === 'end'\n ? targetRight\n : targetLeft // inline === 'start || inline === 'nearest\n\n // Collect new scroll positions\n const computations: CustomScrollAction[] = []\n // In chrome there's no longer a difference between caching the `frames.length` to a var or not, so we don't in this case (size > speed anyways)\n for (let index = 0; index < frames.length; index++) {\n const frame = frames[index]\n\n // @TODO add a shouldScroll hook here that allows userland code to take control\n\n const { height, width, top, right, bottom, left } =\n frame.getBoundingClientRect()\n\n // If the element is already visible we can end it here\n // @TODO targetBlock and targetInline should be taken into account to be compliant with https://github.com/w3c/csswg-drafts/pull/1805/files#diff-3c17f0e43c20f8ecf89419d49e7ef5e0R1333\n if (\n scrollMode === 'if-needed' &&\n targetTop >= 0 &&\n targetLeft >= 0 &&\n targetBottom <= viewportHeight &&\n targetRight <= viewportWidth &&\n targetTop >= top &&\n targetBottom <= bottom &&\n targetLeft >= left &&\n targetRight <= right\n ) {\n // Break the loop and return the computations for things that are not fully visible\n return computations\n }\n\n const frameStyle = getComputedStyle(frame)\n const borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)\n const borderTop = parseInt(frameStyle.borderTopWidth as string, 10)\n const borderRight = parseInt(frameStyle.borderRightWidth as string, 10)\n const borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)\n\n let blockScroll: number = 0\n let inlineScroll: number = 0\n\n // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here\n // @TODO find out if the \"as HTMLElement\" overrides can be dropped\n const scrollbarWidth =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth -\n (frame as HTMLElement).clientWidth -\n borderLeft -\n borderRight\n : 0\n const scrollbarHeight =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight -\n (frame as HTMLElement).clientHeight -\n borderTop -\n borderBottom\n : 0\n\n const scaleX =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth === 0\n ? 0\n : width / (frame as HTMLElement).offsetWidth\n : 0\n const scaleY =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight === 0\n ? 0\n : height / (frame as HTMLElement).offsetHeight\n : 0\n\n if (scrollingElement === frame) {\n // Handle viewport logic (document.documentElement or document.body)\n\n if (block === 'start') {\n blockScroll = targetBlock\n } else if (block === 'end') {\n blockScroll = targetBlock - viewportHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n viewportY,\n viewportY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n viewportY + targetBlock,\n viewportY + targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - viewportHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline\n } else if (inline === 'center') {\n inlineScroll = targetInline - viewportWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - viewportWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n viewportX,\n viewportX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n viewportX + targetInline,\n viewportX + targetInline + targetWidth,\n targetWidth\n )\n }\n\n // Apply scroll position offsets and ensure they are within bounds\n // @TODO add more test cases to cover this 100%\n blockScroll = Math.max(0, blockScroll + viewportY)\n inlineScroll = Math.max(0, inlineScroll + viewportX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\n\n if (block === 'start') {\n blockScroll = targetBlock - top - borderTop\n } else if (block === 'end') {\n blockScroll = targetBlock - bottom + borderBottom + scrollbarHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n top,\n bottom,\n height,\n borderTop,\n borderBottom + scrollbarHeight,\n targetBlock,\n targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - (top + height / 2) + scrollbarHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline - left - borderLeft\n } else if (inline === 'center') {\n inlineScroll = targetInline - (left + width / 2) + scrollbarWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - right + borderRight + scrollbarWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n left,\n right,\n width,\n borderLeft,\n borderRight + scrollbarWidth,\n targetInline,\n targetInline + targetWidth,\n targetWidth\n )\n }\n\n const { scrollLeft, scrollTop } = frame\n // Ensure scroll coordinates are not out of bounds while applying scroll offsets\n blockScroll = Math.max(\n 0,\n Math.min(\n scrollTop + blockScroll / scaleY,\n frame.scrollHeight - height / scaleY + scrollbarHeight\n )\n )\n inlineScroll = Math.max(\n 0,\n Math.min(\n scrollLeft + inlineScroll / scaleX,\n frame.scrollWidth - width / scaleX + scrollbarWidth\n )\n )\n\n // Cache the offset so that parent frames can scroll this into view correctly\n targetBlock += scrollTop - blockScroll\n targetInline += scrollLeft - inlineScroll\n }\n\n computations.push({ el: frame, top: blockScroll, left: inlineScroll })\n }\n\n return computations\n}\n"],"names":["isElement","el","nodeType","canOverflow","overflow","skipOverflowHiddenElements","isScrollable","clientHeight","scrollHeight","clientWidth","scrollWidth","style","getComputedStyle","overflowY","overflowX","frame","ownerDocument","defaultView","frameElement","e","getFrameElement","isHiddenByFrame","alignNearest","scrollingEdgeStart","scrollingEdgeEnd","scrollingSize","scrollingBorderStart","scrollingBorderEnd","elementEdgeStart","elementEdgeEnd","elementSize","target","options","windowWithViewport","window","scrollMode","block","inline","boundary","checkBoundary","node","TypeError","scrollingElement","document","documentElement","frames","cursor","parentElement","push","body","viewportWidth","visualViewport","width","innerWidth","viewportHeight","height","innerHeight","viewportX","scrollX","pageXOffset","viewportY","scrollY","pageYOffset","getBoundingClientRect","targetHeight","targetWidth","targetTop","top","targetRight","right","targetBottom","bottom","targetLeft","left","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 block?: ScrollLogicalPosition\n inline?: ScrollLogicalPosition\n scrollMode?: ScrollMode\n // Custom behavior, not in any spec\n boundary?: Element | ((parent: Element) => boolean) | null\n /**\n * New option that skips auto-scrolling all nodes with overflow: hidden set\n * See FF implementation: https://hg.mozilla.org/integration/fx-team/rev/c48c3ec05012#l7.18\n * @public\n */\n skipOverflowHiddenElements?: boolean\n}\n\n/** @public */\nexport interface ScrollAction {\n el: Element\n top: number\n left: number\n}\n\n// @TODO better shadowdom test, 11 = document fragment\nfunction isElement(el: any): el is Element {\n return typeof el === 'object' && el != null && el.nodeType === 1\n}\n\nfunction canOverflow(\n overflow: string | null,\n skipOverflowHiddenElements?: boolean\n) {\n if (skipOverflowHiddenElements && overflow === 'hidden') {\n return false\n }\n\n return overflow !== 'visible' && overflow !== 'clip'\n}\n\nfunction getFrameElement(el: Element) {\n if (!el.ownerDocument || !el.ownerDocument.defaultView) {\n return null\n }\n\n try {\n return el.ownerDocument.defaultView.frameElement\n } catch (e) {\n return null\n }\n}\n\nfunction isHiddenByFrame(el: Element): boolean {\n const frame = getFrameElement(el)\n if (!frame) {\n return false\n }\n\n return (\n frame.clientHeight < el.scrollHeight || frame.clientWidth < el.scrollWidth\n )\n}\n\nfunction isScrollable(el: Element, skipOverflowHiddenElements?: boolean) {\n if (el.clientHeight < el.scrollHeight || el.clientWidth < el.scrollWidth) {\n const style = getComputedStyle(el, null)\n return (\n canOverflow(style.overflowY, skipOverflowHiddenElements) ||\n canOverflow(style.overflowX, skipOverflowHiddenElements) ||\n isHiddenByFrame(el)\n )\n }\n\n return false\n}\n/**\n * Find out which edge to align against when logical scroll position is \"nearest\"\n * Interesting fact: \"nearest\" works similarily to \"if-needed\", if the element is fully visible it will not scroll it\n *\n * Legends:\n * ┌────────┐ ┏ ━ ━ ━ ┓\n * │ target │ frame\n * └────────┘ ┗ ━ ━ ━ ┛\n */\nfunction alignNearest(\n scrollingEdgeStart: number,\n scrollingEdgeEnd: number,\n scrollingSize: number,\n scrollingBorderStart: number,\n scrollingBorderEnd: number,\n elementEdgeStart: number,\n elementEdgeEnd: number,\n elementSize: number\n) {\n /**\n * If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B\n *\n * ┌──┐\n * ┏━│━━│━┓\n * │ │\n * ┃ │ │ ┃ do nothing\n * │ │\n * ┗━│━━│━┛\n * └──┘\n *\n * If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D\n *\n * ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐\n * │┃ ┃│ do nothing\n * └───────────┘\n * ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart < scrollingEdgeStart &&\n elementEdgeEnd > scrollingEdgeEnd) ||\n (elementEdgeStart > scrollingEdgeStart && elementEdgeEnd < scrollingEdgeEnd)\n ) {\n return 0\n }\n\n /**\n * If element edge A is outside scrolling box edge A and element height is less than scrolling box height\n *\n * ┌──┐\n * ┏━│━━│━┓ ┏━┌━━┐━┓\n * └──┘ │ │\n * from ┃ ┃ to ┃ └──┘ ┃\n *\n * ┗━ ━━ ━┛ ┗━ ━━ ━┛\n *\n * If element edge B is outside scrolling box edge B and element height is greater than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━┌━━┐━┓\n * │ │\n * from ┃ ┌──┐ ┃ to ┃ │ │ ┃\n * │ │ │ │\n * ┗━│━━│━┛ ┗━│━━│━┛\n * │ │ └──┘\n * │ │\n * └──┘\n *\n * If element edge C is outside scrolling box edge C and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * │ ┃ │ ┃ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * ┃ │ ┃ │ ┃ ┃ │\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart <= scrollingEdgeStart && elementSize <= scrollingSize) ||\n (elementEdgeEnd >= scrollingEdgeEnd && elementSize >= scrollingSize)\n ) {\n return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart\n }\n\n /**\n * If element edge B is outside scrolling box edge B and element height is less than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━ ━━ ━┓\n *\n * from ┃ ┃ to ┃ ┌──┐ ┃\n * ┌──┐ │ │\n * ┗━│━━│━┛ ┗━└━━┘━┛\n * └──┘\n *\n * If element edge A is outside scrolling box edge A and element height is greater than scrolling box height\n *\n * ┌──┐\n * │ │\n * │ │ ┌──┐\n * ┏━│━━│━┓ ┏━│━━│━┓\n * │ │ │ │\n * from ┃ └──┘ ┃ to ┃ │ │ ┃\n * │ │\n * ┗━ ━━ ━┛ ┗━└━━┘━┛\n *\n * If element edge C is outside scrolling box edge C and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * │ ┃ │ ┃ │ ┃ ┃\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * ┃ │ ┃ │ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n */\n if (\n (elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||\n (elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)\n ) {\n return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd\n }\n\n return 0\n}\n\nfunction getParentElement(element: Node): Element | null {\n const parent = element.parentElement\n if (parent == null) {\n return (element.getRootNode() as ShadowRoot).host || null\n }\n return parent\n}\n\n/** @public */\nexport default (target: Element, options: Options): ScrollAction[] => {\n if (typeof document === 'undefined') {\n // If there's no DOM we assume it's not in a browser environment\n return []\n }\n\n const { scrollMode, block, inline, boundary, skipOverflowHiddenElements } =\n options\n // Allow using a callback to check the boundary\n // The default behavior is to check if the current target matches the boundary element or not\n // If undefined it'll check that target is never undefined (can happen as we recurse up the tree)\n const checkBoundary =\n typeof boundary === 'function' ? boundary : (node: any) => node !== boundary\n\n if (!isElement(target)) {\n throw new TypeError('Invalid target')\n }\n\n // Used to handle the top most element that can be scrolled\n const scrollingElement = document.scrollingElement || document.documentElement\n\n // Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box\n const frames: Element[] = []\n let cursor: Element | null = target\n while (isElement(cursor) && checkBoundary(cursor)) {\n // Move cursor to parent\n cursor = getParentElement(cursor)\n\n // Stop when we reach the viewport\n if (cursor === scrollingElement) {\n frames.push(cursor)\n break\n }\n\n // Skip document.body if it's not the scrollingElement and documentElement isn't independently scrollable\n if (\n cursor != null &&\n cursor === document.body &&\n isScrollable(cursor) &&\n !isScrollable(document.documentElement)\n ) {\n continue\n }\n\n // Now we check if the element is scrollable, this code only runs if the loop haven't already hit the viewport or a custom boundary\n if (cursor != null && isScrollable(cursor, skipOverflowHiddenElements)) {\n frames.push(cursor)\n }\n }\n\n // Support pinch-zooming properly, making sure elements scroll into the visual viewport\n // Browsers that don't support visualViewport will report the layout viewport dimensions on document.documentElement.clientWidth/Height\n // and viewport dimensions on window.innerWidth/Height\n // https://www.quirksmode.org/mobile/viewports2.html\n // https://bokand.github.io/viewport/index.html\n const viewportWidth = window.visualViewport?.width ?? innerWidth\n const viewportHeight = window.visualViewport?.height ?? innerHeight\n\n // Newer browsers supports scroll[X|Y], page[X|Y]Offset is\n const viewportX = window.scrollX ?? pageXOffset\n const viewportY = window.scrollY ?? pageYOffset\n\n const {\n height: targetHeight,\n width: targetWidth,\n top: targetTop,\n right: targetRight,\n bottom: targetBottom,\n left: targetLeft,\n } = target.getBoundingClientRect()\n\n // These values mutate as we loop through and generate scroll coordinates\n let targetBlock: number =\n block === 'start' || block === 'nearest'\n ? targetTop\n : block === 'end'\n ? targetBottom\n : targetTop + targetHeight / 2 // block === 'center\n let targetInline: number =\n inline === 'center'\n ? targetLeft + targetWidth / 2\n : inline === 'end'\n ? targetRight\n : targetLeft // inline === 'start || inline === 'nearest\n\n // Collect new scroll positions\n const computations: ScrollAction[] = []\n // In chrome there's no longer a difference between caching the `frames.length` to a var or not, so we don't in this case (size > speed anyways)\n for (let index = 0; index < frames.length; index++) {\n const frame = frames[index]\n\n // @TODO add a shouldScroll hook here that allows userland code to take control\n\n const { height, width, top, right, bottom, left } =\n frame.getBoundingClientRect()\n\n // If the element is already visible we can end it here\n // @TODO targetBlock and targetInline should be taken into account to be compliant with https://github.com/w3c/csswg-drafts/pull/1805/files#diff-3c17f0e43c20f8ecf89419d49e7ef5e0R1333\n if (\n scrollMode === 'if-needed' &&\n targetTop >= 0 &&\n targetLeft >= 0 &&\n targetBottom <= viewportHeight &&\n targetRight <= viewportWidth &&\n targetTop >= top &&\n targetBottom <= bottom &&\n targetLeft >= left &&\n targetRight <= right\n ) {\n // Break the loop and return the computations for things that are not fully visible\n return computations\n }\n\n const frameStyle = getComputedStyle(frame)\n const borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)\n const borderTop = parseInt(frameStyle.borderTopWidth as string, 10)\n const borderRight = parseInt(frameStyle.borderRightWidth as string, 10)\n const borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)\n\n let blockScroll: number = 0\n let inlineScroll: number = 0\n\n // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here\n // @TODO find out if the \"as HTMLElement\" overrides can be dropped\n const scrollbarWidth =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth -\n (frame as HTMLElement).clientWidth -\n borderLeft -\n borderRight\n : 0\n const scrollbarHeight =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight -\n (frame as HTMLElement).clientHeight -\n borderTop -\n borderBottom\n : 0\n\n const scaleX =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth === 0\n ? 0\n : width / (frame as HTMLElement).offsetWidth\n : 0\n const scaleY =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight === 0\n ? 0\n : height / (frame as HTMLElement).offsetHeight\n : 0\n\n if (scrollingElement === frame) {\n // Handle viewport logic (document.documentElement or document.body)\n\n if (block === 'start') {\n blockScroll = targetBlock\n } else if (block === 'end') {\n blockScroll = targetBlock - viewportHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n viewportY,\n viewportY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n viewportY + targetBlock,\n viewportY + targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - viewportHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline\n } else if (inline === 'center') {\n inlineScroll = targetInline - viewportWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - viewportWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n viewportX,\n viewportX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n viewportX + targetInline,\n viewportX + targetInline + targetWidth,\n targetWidth\n )\n }\n\n // Apply scroll position offsets and ensure they are within bounds\n // @TODO add more test cases to cover this 100%\n blockScroll = Math.max(0, blockScroll + viewportY)\n inlineScroll = Math.max(0, inlineScroll + viewportX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\n\n if (block === 'start') {\n blockScroll = targetBlock - top - borderTop\n } else if (block === 'end') {\n blockScroll = targetBlock - bottom + borderBottom + scrollbarHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n top,\n bottom,\n height,\n borderTop,\n borderBottom + scrollbarHeight,\n targetBlock,\n targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - (top + height / 2) + scrollbarHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline - left - borderLeft\n } else if (inline === 'center') {\n inlineScroll = targetInline - (left + width / 2) + scrollbarWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - right + borderRight + scrollbarWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n left,\n right,\n width,\n borderLeft,\n borderRight + scrollbarWidth,\n targetInline,\n targetInline + targetWidth,\n targetWidth\n )\n }\n\n const { scrollLeft, scrollTop } = frame\n // Ensure scroll coordinates are not out of bounds while applying scroll offsets\n blockScroll = Math.max(\n 0,\n Math.min(\n scrollTop + blockScroll / scaleY,\n frame.scrollHeight - height / scaleY + scrollbarHeight\n )\n )\n inlineScroll = Math.max(\n 0,\n Math.min(\n scrollLeft + inlineScroll / scaleX,\n frame.scrollWidth - width / scaleX + scrollbarWidth\n )\n )\n\n // Cache the offset so that parent frames can scroll this into view correctly\n targetBlock += scrollTop - blockScroll\n targetInline += scrollLeft - inlineScroll\n }\n\n computations.push({ el: frame, top: blockScroll, left: inlineScroll })\n }\n\n return computations\n}\n"],"names":["isElement","el","nodeType","canOverflow","overflow","skipOverflowHiddenElements","isScrollable","clientHeight","scrollHeight","clientWidth","scrollWidth","style","getComputedStyle","overflowY","overflowX","frame","ownerDocument","defaultView","frameElement","e","getFrameElement","isHiddenByFrame","alignNearest","scrollingEdgeStart","scrollingEdgeEnd","scrollingSize","scrollingBorderStart","scrollingBorderEnd","elementEdgeStart","elementEdgeEnd","elementSize","getParentElement","element","parent","parentElement","getRootNode","host","index","target","options","_a","_b","_c","_d","_e","_f","document","scrollMode","block","inline","boundary","checkBoundary","node","TypeError","scrollingElement","documentElement","frames","cursor","push","body","viewportWidth","window","visualViewport","width","innerWidth","viewportHeight","height","innerHeight","viewportX","scrollX","pageXOffset","viewportY","scrollY","pageYOffset","targetHeight","targetWidth","top","targetTop","right","targetRight","bottom","targetBottom","left","targetLeft","getBoundingClientRect","targetBlock","targetInline","computations","length","frameStyle","borderLeft","parseInt","borderLeftWidth","borderTop","borderTopWidth","borderRight","borderRightWidth","borderBottom","borderBottomWidth","blockScroll","inlineScroll","scrollbarWidth","offsetWidth","scrollbarHeight","offsetHeight","scaleX","scaleY","Math","max","scrollLeft","scrollTop","min"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compute-scroll-into-view",
3
- "version": "1.0.19",
3
+ "version": "2.0.0",
4
4
  "description": "The engine that powers scroll-into-view-if-needed",
5
5
  "keywords": [
6
6
  "if-needed",
@@ -20,72 +20,61 @@
20
20
  "license": "MIT",
21
21
  "author": "Cody Olsen",
22
22
  "sideEffects": false,
23
+ "type": "module",
23
24
  "exports": {
24
25
  ".": {
25
- "types": "./typings/index.d.ts",
26
+ "types": "./dist/index.d.ts",
26
27
  "source": "./src/index.ts",
27
- "import": "./dist/index.mjs",
28
- "require": "./dist/index.js",
29
- "default": "./dist/index.mjs"
28
+ "import": "./dist/index.js",
29
+ "require": "./dist/index.cjs",
30
+ "default": "./dist/index.js"
30
31
  },
31
32
  "./package.json": "./package.json"
32
33
  },
33
- "main": "dist/index.js",
34
- "umd:main": "umd/compute-scroll-into-view.min.js",
35
- "module": "dist/index.mjs",
36
- "source": "src/index.ts",
37
- "typings": "typings/index.d.ts",
34
+ "main": "./dist/index.cjs",
35
+ "module": "./dist/index.js",
36
+ "source": "./src/index.ts",
37
+ "typings": "./dist/index.d.ts",
38
38
  "files": [
39
39
  "dist",
40
- "typings",
41
- "umd/compute-scroll-into-view.min.js",
42
- "umd/compute-scroll-into-view.min.js.map"
40
+ "src"
43
41
  ],
44
42
  "scripts": {
45
- "prebuild": "rimraf 'dist' 'umd'",
46
- "build": "npm run build:dist && npm run build:umd",
47
- "build:dist": "microbundle -f cjs,es",
48
- "build:umd": "microbundle -f umd -o umd",
43
+ "prebuild": "npx rimraf 'dist'",
44
+ "build": "pkg build --strict",
49
45
  "prepublishOnly": "npm run build",
50
- "test": "jest -c integration/jest.config.js",
51
- "typecheck": "tsc --noEmit"
52
- },
53
- "husky": {
54
- "hooks": {
55
- "pre-commit": "lint-staged"
56
- }
57
- },
58
- "lint-staged": {
59
- "*.{js,ts,tsx,json,md}": "prettier --write"
60
- },
61
- "browserify": {
62
- "transform": [
63
- "loose-envify"
64
- ]
46
+ "test": "npx cross-env JEST_PUPPETEER_CONFIG='jest-puppeteer.config.cjs' jest -c integration/jest.config.cjs",
47
+ "typecheck": "tsc"
65
48
  },
49
+ "browserslist": [
50
+ "> 0.2% and supports es6-module and supports es6-module-dynamic-import and not dead and not IE 11",
51
+ "maintained node versions"
52
+ ],
66
53
  "prettier": {
67
54
  "semi": false,
68
- "singleQuote": true,
69
- "trailingComma": "es5"
55
+ "singleQuote": true
70
56
  },
71
57
  "devDependencies": {
58
+ "@sanity/pkg-utils": "^1.20.1",
72
59
  "@sanity/semantic-release-preset": "^2.0.2",
73
- "husky": "^8.0.1",
74
- "jest": "^26.6.3",
75
- "jest-junit": "^12.2.0",
76
- "jest-puppeteer": "^4.4.0",
77
- "lint-staged": "^13.0.3",
78
- "microbundle": "^0.15.1",
60
+ "@types/expect-puppeteer": "^5.0.2",
61
+ "@types/jest": "^29.2.3",
62
+ "@types/jest-environment-puppeteer": "^5.0.3",
63
+ "@types/puppeteer": "^7.0.4",
64
+ "cross-env": "^7.0.3",
65
+ "jest": "^29.3.1",
66
+ "jest-junit": "^15.0.0",
67
+ "jest-puppeteer": "^6.1.1",
79
68
  "prettier": "^2.8.0",
80
69
  "prettier-plugin-packagejson": "^2.3.0",
81
- "puppeteer": "^5.5.0",
70
+ "puppeteer": "^19.3.0",
82
71
  "rimraf": "^3.0.2",
83
- "serve": "^11.3.2",
72
+ "serve": "^14.1.2",
84
73
  "typescript": "^4.9.3"
85
74
  },
86
75
  "bundlesize": [
87
76
  {
88
- "path": "./umd/compute-scroll-into-view.min.js",
77
+ "path": "./dist/index.js",
89
78
  "maxSize": "3 kB",
90
79
  "compression": "none"
91
80
  }
package/src/index.ts ADDED
@@ -0,0 +1,507 @@
1
+ // Compute what scrolling needs to be done on required scrolling boxes for target to be in view
2
+
3
+ // The type names here are named after the spec to make it easier to find more information around what they mean:
4
+ // To reduce churn and reduce things that need be maintained things from the official TS DOM library is used here
5
+ // https://drafts.csswg.org/cssom-view/
6
+
7
+ // For a definition on what is "block flow direction" exactly, check this: https://drafts.csswg.org/css-writing-modes-4/#block-flow-direction
8
+
9
+ /**
10
+ * 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
11
+ * @public
12
+ */
13
+ export type ScrollMode = 'always' | 'if-needed'
14
+
15
+ /** @public */
16
+ export interface Options {
17
+ block?: ScrollLogicalPosition
18
+ inline?: ScrollLogicalPosition
19
+ scrollMode?: ScrollMode
20
+ // Custom behavior, not in any spec
21
+ boundary?: Element | ((parent: Element) => boolean) | null
22
+ /**
23
+ * New option that skips auto-scrolling all nodes with overflow: hidden set
24
+ * See FF implementation: https://hg.mozilla.org/integration/fx-team/rev/c48c3ec05012#l7.18
25
+ * @public
26
+ */
27
+ skipOverflowHiddenElements?: boolean
28
+ }
29
+
30
+ /** @public */
31
+ export interface ScrollAction {
32
+ el: Element
33
+ top: number
34
+ left: number
35
+ }
36
+
37
+ // @TODO better shadowdom test, 11 = document fragment
38
+ function isElement(el: any): el is Element {
39
+ return typeof el === 'object' && el != null && el.nodeType === 1
40
+ }
41
+
42
+ function canOverflow(
43
+ overflow: string | null,
44
+ skipOverflowHiddenElements?: boolean
45
+ ) {
46
+ if (skipOverflowHiddenElements && overflow === 'hidden') {
47
+ return false
48
+ }
49
+
50
+ return overflow !== 'visible' && overflow !== 'clip'
51
+ }
52
+
53
+ function getFrameElement(el: Element) {
54
+ if (!el.ownerDocument || !el.ownerDocument.defaultView) {
55
+ return null
56
+ }
57
+
58
+ try {
59
+ return el.ownerDocument.defaultView.frameElement
60
+ } catch (e) {
61
+ return null
62
+ }
63
+ }
64
+
65
+ function isHiddenByFrame(el: Element): boolean {
66
+ const frame = getFrameElement(el)
67
+ if (!frame) {
68
+ return false
69
+ }
70
+
71
+ return (
72
+ frame.clientHeight < el.scrollHeight || frame.clientWidth < el.scrollWidth
73
+ )
74
+ }
75
+
76
+ function isScrollable(el: Element, skipOverflowHiddenElements?: boolean) {
77
+ if (el.clientHeight < el.scrollHeight || el.clientWidth < el.scrollWidth) {
78
+ const style = getComputedStyle(el, null)
79
+ return (
80
+ canOverflow(style.overflowY, skipOverflowHiddenElements) ||
81
+ canOverflow(style.overflowX, skipOverflowHiddenElements) ||
82
+ isHiddenByFrame(el)
83
+ )
84
+ }
85
+
86
+ return false
87
+ }
88
+ /**
89
+ * Find out which edge to align against when logical scroll position is "nearest"
90
+ * Interesting fact: "nearest" works similarily to "if-needed", if the element is fully visible it will not scroll it
91
+ *
92
+ * Legends:
93
+ * ┌────────┐ ┏ ━ ━ ━ ┓
94
+ * │ target │ frame
95
+ * └────────┘ ┗ ━ ━ ━ ┛
96
+ */
97
+ function alignNearest(
98
+ scrollingEdgeStart: number,
99
+ scrollingEdgeEnd: number,
100
+ scrollingSize: number,
101
+ scrollingBorderStart: number,
102
+ scrollingBorderEnd: number,
103
+ elementEdgeStart: number,
104
+ elementEdgeEnd: number,
105
+ elementSize: number
106
+ ) {
107
+ /**
108
+ * If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B
109
+ *
110
+ * ┌──┐
111
+ * ┏━│━━│━┓
112
+ * │ │
113
+ * ┃ │ │ ┃ do nothing
114
+ * │ │
115
+ * ┗━│━━│━┛
116
+ * └──┘
117
+ *
118
+ * If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D
119
+ *
120
+ * ┏ ━ ━ ━ ━ ┓
121
+ * ┌───────────┐
122
+ * │┃ ┃│ do nothing
123
+ * └───────────┘
124
+ * ┗ ━ ━ ━ ━ ┛
125
+ */
126
+ if (
127
+ (elementEdgeStart < scrollingEdgeStart &&
128
+ elementEdgeEnd > scrollingEdgeEnd) ||
129
+ (elementEdgeStart > scrollingEdgeStart && elementEdgeEnd < scrollingEdgeEnd)
130
+ ) {
131
+ return 0
132
+ }
133
+
134
+ /**
135
+ * If element edge A is outside scrolling box edge A and element height is less than scrolling box height
136
+ *
137
+ * ┌──┐
138
+ * ┏━│━━│━┓ ┏━┌━━┐━┓
139
+ * └──┘ │ │
140
+ * from ┃ ┃ to ┃ └──┘ ┃
141
+ *
142
+ * ┗━ ━━ ━┛ ┗━ ━━ ━┛
143
+ *
144
+ * If element edge B is outside scrolling box edge B and element height is greater than scrolling box height
145
+ *
146
+ * ┏━ ━━ ━┓ ┏━┌━━┐━┓
147
+ * │ │
148
+ * from ┃ ┌──┐ ┃ to ┃ │ │ ┃
149
+ * │ │ │ │
150
+ * ┗━│━━│━┛ ┗━│━━│━┛
151
+ * │ │ └──┘
152
+ * │ │
153
+ * └──┘
154
+ *
155
+ * If element edge C is outside scrolling box edge C and element width is less than scrolling box width
156
+ *
157
+ * from to
158
+ * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓
159
+ * ┌───┐ ┌───┐
160
+ * │ ┃ │ ┃ ┃ │ ┃
161
+ * └───┘ └───┘
162
+ * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛
163
+ *
164
+ * If element edge D is outside scrolling box edge D and element width is greater than scrolling box width
165
+ *
166
+ * from to
167
+ * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓
168
+ * ┌───────────┐ ┌───────────┐
169
+ * ┃ │ ┃ │ ┃ ┃ │
170
+ * └───────────┘ └───────────┘
171
+ * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛
172
+ */
173
+ if (
174
+ (elementEdgeStart <= scrollingEdgeStart && elementSize <= scrollingSize) ||
175
+ (elementEdgeEnd >= scrollingEdgeEnd && elementSize >= scrollingSize)
176
+ ) {
177
+ return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart
178
+ }
179
+
180
+ /**
181
+ * If element edge B is outside scrolling box edge B and element height is less than scrolling box height
182
+ *
183
+ * ┏━ ━━ ━┓ ┏━ ━━ ━┓
184
+ *
185
+ * from ┃ ┃ to ┃ ┌──┐ ┃
186
+ * ┌──┐ │ │
187
+ * ┗━│━━│━┛ ┗━└━━┘━┛
188
+ * └──┘
189
+ *
190
+ * If element edge A is outside scrolling box edge A and element height is greater than scrolling box height
191
+ *
192
+ * ┌──┐
193
+ * │ │
194
+ * │ │ ┌──┐
195
+ * ┏━│━━│━┓ ┏━│━━│━┓
196
+ * │ │ │ │
197
+ * from ┃ └──┘ ┃ to ┃ │ │ ┃
198
+ * │ │
199
+ * ┗━ ━━ ━┛ ┗━└━━┘━┛
200
+ *
201
+ * If element edge C is outside scrolling box edge C and element width is greater than scrolling box width
202
+ *
203
+ * from to
204
+ * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓
205
+ * ┌───────────┐ ┌───────────┐
206
+ * │ ┃ │ ┃ │ ┃ ┃
207
+ * └───────────┘ └───────────┘
208
+ * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛
209
+ *
210
+ * If element edge D is outside scrolling box edge D and element width is less than scrolling box width
211
+ *
212
+ * from to
213
+ * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓
214
+ * ┌───┐ ┌───┐
215
+ * ┃ │ ┃ │ ┃ │ ┃
216
+ * └───┘ └───┘
217
+ * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛
218
+ *
219
+ */
220
+ if (
221
+ (elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||
222
+ (elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)
223
+ ) {
224
+ return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd
225
+ }
226
+
227
+ return 0
228
+ }
229
+
230
+ function getParentElement(element: Node): Element | null {
231
+ const parent = element.parentElement
232
+ if (parent == null) {
233
+ return (element.getRootNode() as ShadowRoot).host || null
234
+ }
235
+ return parent
236
+ }
237
+
238
+ /** @public */
239
+ export default (target: Element, options: Options): ScrollAction[] => {
240
+ if (typeof document === 'undefined') {
241
+ // If there's no DOM we assume it's not in a browser environment
242
+ return []
243
+ }
244
+
245
+ const { scrollMode, block, inline, boundary, skipOverflowHiddenElements } =
246
+ options
247
+ // Allow using a callback to check the boundary
248
+ // The default behavior is to check if the current target matches the boundary element or not
249
+ // If undefined it'll check that target is never undefined (can happen as we recurse up the tree)
250
+ const checkBoundary =
251
+ typeof boundary === 'function' ? boundary : (node: any) => node !== boundary
252
+
253
+ if (!isElement(target)) {
254
+ throw new TypeError('Invalid target')
255
+ }
256
+
257
+ // Used to handle the top most element that can be scrolled
258
+ const scrollingElement = document.scrollingElement || document.documentElement
259
+
260
+ // Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box
261
+ const frames: Element[] = []
262
+ let cursor: Element | null = target
263
+ while (isElement(cursor) && checkBoundary(cursor)) {
264
+ // Move cursor to parent
265
+ cursor = getParentElement(cursor)
266
+
267
+ // Stop when we reach the viewport
268
+ if (cursor === scrollingElement) {
269
+ frames.push(cursor)
270
+ break
271
+ }
272
+
273
+ // Skip document.body if it's not the scrollingElement and documentElement isn't independently scrollable
274
+ if (
275
+ cursor != null &&
276
+ cursor === document.body &&
277
+ isScrollable(cursor) &&
278
+ !isScrollable(document.documentElement)
279
+ ) {
280
+ continue
281
+ }
282
+
283
+ // 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
284
+ if (cursor != null && isScrollable(cursor, skipOverflowHiddenElements)) {
285
+ frames.push(cursor)
286
+ }
287
+ }
288
+
289
+ // Support pinch-zooming properly, making sure elements scroll into the visual viewport
290
+ // Browsers that don't support visualViewport will report the layout viewport dimensions on document.documentElement.clientWidth/Height
291
+ // and viewport dimensions on window.innerWidth/Height
292
+ // https://www.quirksmode.org/mobile/viewports2.html
293
+ // https://bokand.github.io/viewport/index.html
294
+ const viewportWidth = window.visualViewport?.width ?? innerWidth
295
+ const viewportHeight = window.visualViewport?.height ?? innerHeight
296
+
297
+ // Newer browsers supports scroll[X|Y], page[X|Y]Offset is
298
+ const viewportX = window.scrollX ?? pageXOffset
299
+ const viewportY = window.scrollY ?? pageYOffset
300
+
301
+ const {
302
+ height: targetHeight,
303
+ width: targetWidth,
304
+ top: targetTop,
305
+ right: targetRight,
306
+ bottom: targetBottom,
307
+ left: targetLeft,
308
+ } = target.getBoundingClientRect()
309
+
310
+ // These values mutate as we loop through and generate scroll coordinates
311
+ let targetBlock: number =
312
+ block === 'start' || block === 'nearest'
313
+ ? targetTop
314
+ : block === 'end'
315
+ ? targetBottom
316
+ : targetTop + targetHeight / 2 // block === 'center
317
+ let targetInline: number =
318
+ inline === 'center'
319
+ ? targetLeft + targetWidth / 2
320
+ : inline === 'end'
321
+ ? targetRight
322
+ : targetLeft // inline === 'start || inline === 'nearest
323
+
324
+ // Collect new scroll positions
325
+ const computations: ScrollAction[] = []
326
+ // In chrome there's no longer a difference between caching the `frames.length` to a var or not, so we don't in this case (size > speed anyways)
327
+ for (let index = 0; index < frames.length; index++) {
328
+ const frame = frames[index]
329
+
330
+ // @TODO add a shouldScroll hook here that allows userland code to take control
331
+
332
+ const { height, width, top, right, bottom, left } =
333
+ frame.getBoundingClientRect()
334
+
335
+ // If the element is already visible we can end it here
336
+ // @TODO targetBlock and targetInline should be taken into account to be compliant with https://github.com/w3c/csswg-drafts/pull/1805/files#diff-3c17f0e43c20f8ecf89419d49e7ef5e0R1333
337
+ if (
338
+ scrollMode === 'if-needed' &&
339
+ targetTop >= 0 &&
340
+ targetLeft >= 0 &&
341
+ targetBottom <= viewportHeight &&
342
+ targetRight <= viewportWidth &&
343
+ targetTop >= top &&
344
+ targetBottom <= bottom &&
345
+ targetLeft >= left &&
346
+ targetRight <= right
347
+ ) {
348
+ // Break the loop and return the computations for things that are not fully visible
349
+ return computations
350
+ }
351
+
352
+ const frameStyle = getComputedStyle(frame)
353
+ const borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)
354
+ const borderTop = parseInt(frameStyle.borderTopWidth as string, 10)
355
+ const borderRight = parseInt(frameStyle.borderRightWidth as string, 10)
356
+ const borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)
357
+
358
+ let blockScroll: number = 0
359
+ let inlineScroll: number = 0
360
+
361
+ // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here
362
+ // @TODO find out if the "as HTMLElement" overrides can be dropped
363
+ const scrollbarWidth =
364
+ 'offsetWidth' in frame
365
+ ? (frame as HTMLElement).offsetWidth -
366
+ (frame as HTMLElement).clientWidth -
367
+ borderLeft -
368
+ borderRight
369
+ : 0
370
+ const scrollbarHeight =
371
+ 'offsetHeight' in frame
372
+ ? (frame as HTMLElement).offsetHeight -
373
+ (frame as HTMLElement).clientHeight -
374
+ borderTop -
375
+ borderBottom
376
+ : 0
377
+
378
+ const scaleX =
379
+ 'offsetWidth' in frame
380
+ ? (frame as HTMLElement).offsetWidth === 0
381
+ ? 0
382
+ : width / (frame as HTMLElement).offsetWidth
383
+ : 0
384
+ const scaleY =
385
+ 'offsetHeight' in frame
386
+ ? (frame as HTMLElement).offsetHeight === 0
387
+ ? 0
388
+ : height / (frame as HTMLElement).offsetHeight
389
+ : 0
390
+
391
+ if (scrollingElement === frame) {
392
+ // Handle viewport logic (document.documentElement or document.body)
393
+
394
+ if (block === 'start') {
395
+ blockScroll = targetBlock
396
+ } else if (block === 'end') {
397
+ blockScroll = targetBlock - viewportHeight
398
+ } else if (block === 'nearest') {
399
+ blockScroll = alignNearest(
400
+ viewportY,
401
+ viewportY + viewportHeight,
402
+ viewportHeight,
403
+ borderTop,
404
+ borderBottom,
405
+ viewportY + targetBlock,
406
+ viewportY + targetBlock + targetHeight,
407
+ targetHeight
408
+ )
409
+ } else {
410
+ // block === 'center' is the default
411
+ blockScroll = targetBlock - viewportHeight / 2
412
+ }
413
+
414
+ if (inline === 'start') {
415
+ inlineScroll = targetInline
416
+ } else if (inline === 'center') {
417
+ inlineScroll = targetInline - viewportWidth / 2
418
+ } else if (inline === 'end') {
419
+ inlineScroll = targetInline - viewportWidth
420
+ } else {
421
+ // inline === 'nearest' is the default
422
+ inlineScroll = alignNearest(
423
+ viewportX,
424
+ viewportX + viewportWidth,
425
+ viewportWidth,
426
+ borderLeft,
427
+ borderRight,
428
+ viewportX + targetInline,
429
+ viewportX + targetInline + targetWidth,
430
+ targetWidth
431
+ )
432
+ }
433
+
434
+ // Apply scroll position offsets and ensure they are within bounds
435
+ // @TODO add more test cases to cover this 100%
436
+ blockScroll = Math.max(0, blockScroll + viewportY)
437
+ inlineScroll = Math.max(0, inlineScroll + viewportX)
438
+ } else {
439
+ // Handle each scrolling frame that might exist between the target and the viewport
440
+
441
+ if (block === 'start') {
442
+ blockScroll = targetBlock - top - borderTop
443
+ } else if (block === 'end') {
444
+ blockScroll = targetBlock - bottom + borderBottom + scrollbarHeight
445
+ } else if (block === 'nearest') {
446
+ blockScroll = alignNearest(
447
+ top,
448
+ bottom,
449
+ height,
450
+ borderTop,
451
+ borderBottom + scrollbarHeight,
452
+ targetBlock,
453
+ targetBlock + targetHeight,
454
+ targetHeight
455
+ )
456
+ } else {
457
+ // block === 'center' is the default
458
+ blockScroll = targetBlock - (top + height / 2) + scrollbarHeight / 2
459
+ }
460
+
461
+ if (inline === 'start') {
462
+ inlineScroll = targetInline - left - borderLeft
463
+ } else if (inline === 'center') {
464
+ inlineScroll = targetInline - (left + width / 2) + scrollbarWidth / 2
465
+ } else if (inline === 'end') {
466
+ inlineScroll = targetInline - right + borderRight + scrollbarWidth
467
+ } else {
468
+ // inline === 'nearest' is the default
469
+ inlineScroll = alignNearest(
470
+ left,
471
+ right,
472
+ width,
473
+ borderLeft,
474
+ borderRight + scrollbarWidth,
475
+ targetInline,
476
+ targetInline + targetWidth,
477
+ targetWidth
478
+ )
479
+ }
480
+
481
+ const { scrollLeft, scrollTop } = frame
482
+ // Ensure scroll coordinates are not out of bounds while applying scroll offsets
483
+ blockScroll = Math.max(
484
+ 0,
485
+ Math.min(
486
+ scrollTop + blockScroll / scaleY,
487
+ frame.scrollHeight - height / scaleY + scrollbarHeight
488
+ )
489
+ )
490
+ inlineScroll = Math.max(
491
+ 0,
492
+ Math.min(
493
+ scrollLeft + inlineScroll / scaleX,
494
+ frame.scrollWidth - width / scaleX + scrollbarWidth
495
+ )
496
+ )
497
+
498
+ // Cache the offset so that parent frames can scroll this into view correctly
499
+ targetBlock += scrollTop - blockScroll
500
+ targetInline += scrollLeft - inlineScroll
501
+ }
502
+
503
+ computations.push({ el: frame, top: blockScroll, left: inlineScroll })
504
+ }
505
+
506
+ return computations
507
+ }
package/dist/index.mjs DELETED
@@ -1,2 +0,0 @@
1
- function t(t){return"object"==typeof t&&null!=t&&1===t.nodeType}function e(t,e){return(!e||"hidden"!==t)&&"visible"!==t&&"clip"!==t}function n(t,n){if(t.clientHeight<t.scrollHeight||t.clientWidth<t.scrollWidth){var r=getComputedStyle(t,null);return e(r.overflowY,n)||e(r.overflowX,n)||function(t){var e=function(t){if(!t.ownerDocument||!t.ownerDocument.defaultView)return null;try{return t.ownerDocument.defaultView.frameElement}catch(t){return null}}(t);return!!e&&(e.clientHeight<t.scrollHeight||e.clientWidth<t.scrollWidth)}(t)}return!1}function r(t,e,n,r,i,o,l,d){return o<t&&l>e||o>t&&l<e?0:o<=t&&d<=n||l>=e&&d>=n?o-t-r:l>e&&d<n||o<t&&d>n?l-e+i:0}var i=function(e,i){var o=window,l=i.scrollMode,d=i.block,f=i.inline,h=i.boundary,u=i.skipOverflowHiddenElements,a="function"==typeof h?h:function(t){return t!==h};if(!t(e))throw new TypeError("Invalid target");for(var s=document.scrollingElement||document.documentElement,c=[],g=e;t(g)&&a(g);){if((g=g.parentElement)===s){c.push(g);break}null!=g&&g===document.body&&n(g)&&!n(document.documentElement)||null!=g&&n(g,u)&&c.push(g)}for(var p=o.visualViewport?o.visualViewport.width:innerWidth,m=o.visualViewport?o.visualViewport.height:innerHeight,w=window.scrollX||pageXOffset,v=window.scrollY||pageYOffset,W=e.getBoundingClientRect(),H=W.height,b=W.width,y=W.top,E=W.right,M=W.bottom,V=W.left,x="start"===d||"nearest"===d?y:"end"===d?M:y+H/2,I="center"===f?V+b/2:"end"===f?E:V,C=[],T=0;T<c.length;T++){var k=c[T],B=k.getBoundingClientRect(),D=B.height,O=B.width,R=B.top,X=B.right,Y=B.bottom,L=B.left;if("if-needed"===l&&y>=0&&V>=0&&M<=m&&E<=p&&y>=R&&M<=Y&&V>=L&&E<=X)return C;var S=getComputedStyle(k),j=parseInt(S.borderLeftWidth,10),q=parseInt(S.borderTopWidth,10),z=parseInt(S.borderRightWidth,10),A=parseInt(S.borderBottomWidth,10),F=0,G=0,J="offsetWidth"in k?k.offsetWidth-k.clientWidth-j-z:0,K="offsetHeight"in k?k.offsetHeight-k.clientHeight-q-A:0,N="offsetWidth"in k?0===k.offsetWidth?0:O/k.offsetWidth:0,P="offsetHeight"in k?0===k.offsetHeight?0:D/k.offsetHeight:0;if(s===k)F="start"===d?x:"end"===d?x-m:"nearest"===d?r(v,v+m,m,q,A,v+x,v+x+H,H):x-m/2,G="start"===f?I:"center"===f?I-p/2:"end"===f?I-p:r(w,w+p,p,j,z,w+I,w+I+b,b),F=Math.max(0,F+v),G=Math.max(0,G+w);else{F="start"===d?x-R-q:"end"===d?x-Y+A+K:"nearest"===d?r(R,Y,D,q,A+K,x,x+H,H):x-(R+D/2)+K/2,G="start"===f?I-L-j:"center"===f?I-(L+O/2)+J/2:"end"===f?I-X+z+J:r(L,X,O,j,z+J,I,I+b,b);var Q=k.scrollLeft,U=k.scrollTop;x+=U-(F=Math.max(0,Math.min(U+F/P,k.scrollHeight-D/P+K))),I+=Q-(G=Math.max(0,Math.min(Q+G/N,k.scrollWidth-O/N+J)))}C.push({el:k,top:F,left:G})}return C};export{i as default};
2
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","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// add support for visualViewport object currently implemented in chrome\ninterface visualViewport {\n height: number\n width: number\n}\n\ntype ScrollLogicalPosition = 'start' | 'center' | 'end' | 'nearest'\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\ntype ScrollMode = 'always' | 'if-needed'\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\ntype SkipOverflowHiddenElements = boolean\n\ninterface Options {\n block?: ScrollLogicalPosition\n inline?: ScrollLogicalPosition\n scrollMode?: ScrollMode\n boundary?: CustomScrollBoundary\n skipOverflowHiddenElements?: SkipOverflowHiddenElements\n}\n\n// Custom behavior, not in any spec\ntype CustomScrollBoundaryCallback = (parent: Element) => boolean\ntype CustomScrollBoundary = Element | CustomScrollBoundaryCallback | null\ninterface CustomScrollAction {\n el: Element\n top: number\n left: number\n}\n\n// @TODO better shadowdom test, 11 = document fragment\nfunction isElement(el: any): el is Element {\n return typeof el === 'object' && el != null && el.nodeType === 1\n}\n\nfunction canOverflow(\n overflow: string | null,\n skipOverflowHiddenElements?: boolean\n) {\n if (skipOverflowHiddenElements && overflow === 'hidden') {\n return false\n }\n\n return overflow !== 'visible' && overflow !== 'clip'\n}\n\nfunction getFrameElement(el: Element) {\n if (!el.ownerDocument || !el.ownerDocument.defaultView) {\n return null\n }\n\n try {\n return el.ownerDocument.defaultView.frameElement\n } catch (e) {\n return null\n }\n}\n\nfunction isHiddenByFrame(el: Element): boolean {\n const frame = getFrameElement(el)\n if (!frame) {\n return false\n }\n\n return (\n frame.clientHeight < el.scrollHeight || frame.clientWidth < el.scrollWidth\n )\n}\n\nfunction isScrollable(el: Element, skipOverflowHiddenElements?: boolean) {\n if (el.clientHeight < el.scrollHeight || el.clientWidth < el.scrollWidth) {\n const style = getComputedStyle(el, null)\n return (\n canOverflow(style.overflowY, skipOverflowHiddenElements) ||\n canOverflow(style.overflowX, skipOverflowHiddenElements) ||\n isHiddenByFrame(el)\n )\n }\n\n return false\n}\n/**\n * Find out which edge to align against when logical scroll position is \"nearest\"\n * Interesting fact: \"nearest\" works similarily to \"if-needed\", if the element is fully visible it will not scroll it\n *\n * Legends:\n * ┌────────┐ ┏ ━ ━ ━ ┓\n * │ target │ frame\n * └────────┘ ┗ ━ ━ ━ ┛\n */\nfunction alignNearest(\n scrollingEdgeStart: number,\n scrollingEdgeEnd: number,\n scrollingSize: number,\n scrollingBorderStart: number,\n scrollingBorderEnd: number,\n elementEdgeStart: number,\n elementEdgeEnd: number,\n elementSize: number\n) {\n /**\n * If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B\n *\n * ┌──┐\n * ┏━│━━│━┓\n * │ │\n * ┃ │ │ ┃ do nothing\n * │ │\n * ┗━│━━│━┛\n * └──┘\n *\n * If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D\n *\n * ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐\n * │┃ ┃│ do nothing\n * └───────────┘\n * ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart < scrollingEdgeStart &&\n elementEdgeEnd > scrollingEdgeEnd) ||\n (elementEdgeStart > scrollingEdgeStart && elementEdgeEnd < scrollingEdgeEnd)\n ) {\n return 0\n }\n\n /**\n * If element edge A is outside scrolling box edge A and element height is less than scrolling box height\n *\n * ┌──┐\n * ┏━│━━│━┓ ┏━┌━━┐━┓\n * └──┘ │ │\n * from ┃ ┃ to ┃ └──┘ ┃\n *\n * ┗━ ━━ ━┛ ┗━ ━━ ━┛\n *\n * If element edge B is outside scrolling box edge B and element height is greater than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━┌━━┐━┓\n * │ │\n * from ┃ ┌──┐ ┃ to ┃ │ │ ┃\n * │ │ │ │\n * ┗━│━━│━┛ ┗━│━━│━┛\n * │ │ └──┘\n * │ │\n * └──┘\n *\n * If element edge C is outside scrolling box edge C and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * │ ┃ │ ┃ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * ┃ │ ┃ │ ┃ ┃ │\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart <= scrollingEdgeStart && elementSize <= scrollingSize) ||\n (elementEdgeEnd >= scrollingEdgeEnd && elementSize >= scrollingSize)\n ) {\n return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart\n }\n\n /**\n * If element edge B is outside scrolling box edge B and element height is less than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━ ━━ ━┓\n *\n * from ┃ ┃ to ┃ ┌──┐ ┃\n * ┌──┐ │ │\n * ┗━│━━│━┛ ┗━└━━┘━┛\n * └──┘\n *\n * If element edge A is outside scrolling box edge A and element height is greater than scrolling box height\n *\n * ┌──┐\n * │ │\n * │ │ ┌──┐\n * ┏━│━━│━┓ ┏━│━━│━┓\n * │ │ │ │\n * from ┃ └──┘ ┃ to ┃ │ │ ┃\n * │ │\n * ┗━ ━━ ━┛ ┗━└━━┘━┛\n *\n * If element edge C is outside scrolling box edge C and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * │ ┃ │ ┃ │ ┃ ┃\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * ┃ │ ┃ │ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n */\n if (\n (elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||\n (elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)\n ) {\n return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd\n }\n\n return 0\n}\n\nexport default (target: Element, options: Options): CustomScrollAction[] => {\n //TODO: remove this hack when microbundle will support typescript >= 4.0\n const windowWithViewport = window as unknown as Window & {\n visualViewport: visualViewport\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 = cursor.parentElement\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 = windowWithViewport.visualViewport\n ? windowWithViewport.visualViewport.width\n : innerWidth\n const viewportHeight = windowWithViewport.visualViewport\n ? windowWithViewport.visualViewport.height\n : innerHeight\n\n // Newer browsers supports scroll[X|Y], page[X|Y]Offset is\n const viewportX = window.scrollX || pageXOffset\n const viewportY = window.scrollY || pageYOffset\n\n const {\n height: targetHeight,\n width: targetWidth,\n top: targetTop,\n right: targetRight,\n bottom: targetBottom,\n left: targetLeft,\n } = target.getBoundingClientRect()\n\n // These values mutate as we loop through and generate scroll coordinates\n let targetBlock: number =\n block === 'start' || block === 'nearest'\n ? targetTop\n : block === 'end'\n ? targetBottom\n : targetTop + targetHeight / 2 // block === 'center\n let targetInline: number =\n inline === 'center'\n ? targetLeft + targetWidth / 2\n : inline === 'end'\n ? targetRight\n : targetLeft // inline === 'start || inline === 'nearest\n\n // Collect new scroll positions\n const computations: CustomScrollAction[] = []\n // In chrome there's no longer a difference between caching the `frames.length` to a var or not, so we don't in this case (size > speed anyways)\n for (let index = 0; index < frames.length; index++) {\n const frame = frames[index]\n\n // @TODO add a shouldScroll hook here that allows userland code to take control\n\n const { height, width, top, right, bottom, left } =\n frame.getBoundingClientRect()\n\n // If the element is already visible we can end it here\n // @TODO targetBlock and targetInline should be taken into account to be compliant with https://github.com/w3c/csswg-drafts/pull/1805/files#diff-3c17f0e43c20f8ecf89419d49e7ef5e0R1333\n if (\n scrollMode === 'if-needed' &&\n targetTop >= 0 &&\n targetLeft >= 0 &&\n targetBottom <= viewportHeight &&\n targetRight <= viewportWidth &&\n targetTop >= top &&\n targetBottom <= bottom &&\n targetLeft >= left &&\n targetRight <= right\n ) {\n // Break the loop and return the computations for things that are not fully visible\n return computations\n }\n\n const frameStyle = getComputedStyle(frame)\n const borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)\n const borderTop = parseInt(frameStyle.borderTopWidth as string, 10)\n const borderRight = parseInt(frameStyle.borderRightWidth as string, 10)\n const borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)\n\n let blockScroll: number = 0\n let inlineScroll: number = 0\n\n // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here\n // @TODO find out if the \"as HTMLElement\" overrides can be dropped\n const scrollbarWidth =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth -\n (frame as HTMLElement).clientWidth -\n borderLeft -\n borderRight\n : 0\n const scrollbarHeight =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight -\n (frame as HTMLElement).clientHeight -\n borderTop -\n borderBottom\n : 0\n\n const scaleX =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth === 0\n ? 0\n : width / (frame as HTMLElement).offsetWidth\n : 0\n const scaleY =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight === 0\n ? 0\n : height / (frame as HTMLElement).offsetHeight\n : 0\n\n if (scrollingElement === frame) {\n // Handle viewport logic (document.documentElement or document.body)\n\n if (block === 'start') {\n blockScroll = targetBlock\n } else if (block === 'end') {\n blockScroll = targetBlock - viewportHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n viewportY,\n viewportY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n viewportY + targetBlock,\n viewportY + targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - viewportHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline\n } else if (inline === 'center') {\n inlineScroll = targetInline - viewportWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - viewportWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n viewportX,\n viewportX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n viewportX + targetInline,\n viewportX + targetInline + targetWidth,\n targetWidth\n )\n }\n\n // Apply scroll position offsets and ensure they are within bounds\n // @TODO add more test cases to cover this 100%\n blockScroll = Math.max(0, blockScroll + viewportY)\n inlineScroll = Math.max(0, inlineScroll + viewportX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\n\n if (block === 'start') {\n blockScroll = targetBlock - top - borderTop\n } else if (block === 'end') {\n blockScroll = targetBlock - bottom + borderBottom + scrollbarHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n top,\n bottom,\n height,\n borderTop,\n borderBottom + scrollbarHeight,\n targetBlock,\n targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - (top + height / 2) + scrollbarHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline - left - borderLeft\n } else if (inline === 'center') {\n inlineScroll = targetInline - (left + width / 2) + scrollbarWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - right + borderRight + scrollbarWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n left,\n right,\n width,\n borderLeft,\n borderRight + scrollbarWidth,\n targetInline,\n targetInline + targetWidth,\n targetWidth\n )\n }\n\n const { scrollLeft, scrollTop } = frame\n // Ensure scroll coordinates are not out of bounds while applying scroll offsets\n blockScroll = Math.max(\n 0,\n Math.min(\n scrollTop + blockScroll / scaleY,\n frame.scrollHeight - height / scaleY + scrollbarHeight\n )\n )\n inlineScroll = Math.max(\n 0,\n Math.min(\n scrollLeft + inlineScroll / scaleX,\n frame.scrollWidth - width / scaleX + scrollbarWidth\n )\n )\n\n // Cache the offset so that parent frames can scroll this into view correctly\n targetBlock += scrollTop - blockScroll\n targetInline += scrollLeft - inlineScroll\n }\n\n computations.push({ el: frame, top: blockScroll, left: inlineScroll })\n }\n\n return computations\n}\n"],"names":["isElement","el","nodeType","canOverflow","overflow","skipOverflowHiddenElements","isScrollable","clientHeight","scrollHeight","clientWidth","scrollWidth","style","getComputedStyle","overflowY","overflowX","frame","ownerDocument","defaultView","frameElement","e","getFrameElement","isHiddenByFrame","alignNearest","scrollingEdgeStart","scrollingEdgeEnd","scrollingSize","scrollingBorderStart","scrollingBorderEnd","elementEdgeStart","elementEdgeEnd","elementSize","target","options","windowWithViewport","window","scrollMode","block","inline","boundary","checkBoundary","node","TypeError","scrollingElement","document","documentElement","frames","cursor","parentElement","push","body","viewportWidth","visualViewport","width","innerWidth","viewportHeight","height","innerHeight","viewportX","scrollX","pageXOffset","viewportY","scrollY","pageYOffset","getBoundingClientRect","targetHeight","targetWidth","targetTop","top","targetRight","right","targetBottom","bottom","targetLeft","left","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,19 +0,0 @@
1
- type ScrollLogicalPosition = 'start' | 'center' | 'end' | 'nearest';
2
- type ScrollMode = 'always' | 'if-needed';
3
- type SkipOverflowHiddenElements = boolean;
4
- interface Options {
5
- block?: ScrollLogicalPosition;
6
- inline?: ScrollLogicalPosition;
7
- scrollMode?: ScrollMode;
8
- boundary?: CustomScrollBoundary;
9
- skipOverflowHiddenElements?: SkipOverflowHiddenElements;
10
- }
11
- type CustomScrollBoundaryCallback = (parent: Element) => boolean;
12
- type CustomScrollBoundary = Element | CustomScrollBoundaryCallback | null;
13
- interface CustomScrollAction {
14
- el: Element;
15
- top: number;
16
- left: number;
17
- }
18
- declare const _default: (target: Element, options: Options) => CustomScrollAction[];
19
- export default _default;
@@ -1,2 +0,0 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e||self).computeScrollIntoView=t()}(this,function(){function e(e){return"object"==typeof e&&null!=e&&1===e.nodeType}function t(e,t){return(!t||"hidden"!==e)&&"visible"!==e&&"clip"!==e}function n(e,n){if(e.clientHeight<e.scrollHeight||e.clientWidth<e.scrollWidth){var i=getComputedStyle(e,null);return t(i.overflowY,n)||t(i.overflowX,n)||function(e){var t=function(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}function i(e,t,n,i,o,r,l,f){return r<e&&l>t||r>e&&l<t?0:r<=e&&f<=n||l>=t&&f>=n?r-e-i:l>t&&f<n||r<e&&f>n?l-t+o:0}return function(t,o){var r=window,l=o.scrollMode,f=o.block,d=o.inline,u=o.boundary,h=o.skipOverflowHiddenElements,s="function"==typeof u?u:function(e){return e!==u};if(!e(t))throw new TypeError("Invalid target");for(var c=document.scrollingElement||document.documentElement,a=[],p=t;e(p)&&s(p);){if((p=p.parentElement)===c){a.push(p);break}null!=p&&p===document.body&&n(p)&&!n(document.documentElement)||null!=p&&n(p,h)&&a.push(p)}for(var g=r.visualViewport?r.visualViewport.width:innerWidth,m=r.visualViewport?r.visualViewport.height:innerHeight,w=window.scrollX||pageXOffset,v=window.scrollY||pageYOffset,W=t.getBoundingClientRect(),b=W.height,y=W.width,H=W.top,E=W.right,M=W.bottom,V=W.left,x="start"===f||"nearest"===f?H:"end"===f?M:H+b/2,I="center"===d?V+y/2:"end"===d?E:V,T=[],C=0;C<a.length;C++){var k=a[C],B=k.getBoundingClientRect(),D=B.height,O=B.width,R=B.top,S=B.right,X=B.bottom,Y=B.left;if("if-needed"===l&&H>=0&&V>=0&&M<=m&&E<=g&&H>=R&&M<=X&&V>=Y&&E<=S)return T;var j=getComputedStyle(k),L=parseInt(j.borderLeftWidth,10),q=parseInt(j.borderTopWidth,10),z=parseInt(j.borderRightWidth,10),A=parseInt(j.borderBottomWidth,10),F=0,G=0,J="offsetWidth"in k?k.offsetWidth-k.clientWidth-L-z:0,K="offsetHeight"in k?k.offsetHeight-k.clientHeight-q-A:0,N="offsetWidth"in k?0===k.offsetWidth?0:O/k.offsetWidth:0,P="offsetHeight"in k?0===k.offsetHeight?0:D/k.offsetHeight:0;if(c===k)F="start"===f?x:"end"===f?x-m:"nearest"===f?i(v,v+m,m,q,A,v+x,v+x+b,b):x-m/2,G="start"===d?I:"center"===d?I-g/2:"end"===d?I-g:i(w,w+g,g,L,z,w+I,w+I+y,y),F=Math.max(0,F+v),G=Math.max(0,G+w);else{F="start"===f?x-R-q:"end"===f?x-X+A+K:"nearest"===f?i(R,X,D,q,A+K,x,x+b,b):x-(R+D/2)+K/2,G="start"===d?I-Y-L:"center"===d?I-(Y+O/2)+J/2:"end"===d?I-S+z+J:i(Y,S,O,L,z+J,I,I+y,y);var Q=k.scrollLeft,U=k.scrollTop;x+=U-(F=Math.max(0,Math.min(U+F/P,k.scrollHeight-D/P+K))),I+=Q-(G=Math.max(0,Math.min(Q+G/N,k.scrollWidth-O/N+J)))}T.push({el:k,top:F,left:G})}return T}});
2
- //# sourceMappingURL=compute-scroll-into-view.min.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"compute-scroll-into-view.min.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// add support for visualViewport object currently implemented in chrome\ninterface visualViewport {\n height: number\n width: number\n}\n\ntype ScrollLogicalPosition = 'start' | 'center' | 'end' | 'nearest'\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\ntype ScrollMode = 'always' | 'if-needed'\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\ntype SkipOverflowHiddenElements = boolean\n\ninterface Options {\n block?: ScrollLogicalPosition\n inline?: ScrollLogicalPosition\n scrollMode?: ScrollMode\n boundary?: CustomScrollBoundary\n skipOverflowHiddenElements?: SkipOverflowHiddenElements\n}\n\n// Custom behavior, not in any spec\ntype CustomScrollBoundaryCallback = (parent: Element) => boolean\ntype CustomScrollBoundary = Element | CustomScrollBoundaryCallback | null\ninterface CustomScrollAction {\n el: Element\n top: number\n left: number\n}\n\n// @TODO better shadowdom test, 11 = document fragment\nfunction isElement(el: any): el is Element {\n return typeof el === 'object' && el != null && el.nodeType === 1\n}\n\nfunction canOverflow(\n overflow: string | null,\n skipOverflowHiddenElements?: boolean\n) {\n if (skipOverflowHiddenElements && overflow === 'hidden') {\n return false\n }\n\n return overflow !== 'visible' && overflow !== 'clip'\n}\n\nfunction getFrameElement(el: Element) {\n if (!el.ownerDocument || !el.ownerDocument.defaultView) {\n return null\n }\n\n try {\n return el.ownerDocument.defaultView.frameElement\n } catch (e) {\n return null\n }\n}\n\nfunction isHiddenByFrame(el: Element): boolean {\n const frame = getFrameElement(el)\n if (!frame) {\n return false\n }\n\n return (\n frame.clientHeight < el.scrollHeight || frame.clientWidth < el.scrollWidth\n )\n}\n\nfunction isScrollable(el: Element, skipOverflowHiddenElements?: boolean) {\n if (el.clientHeight < el.scrollHeight || el.clientWidth < el.scrollWidth) {\n const style = getComputedStyle(el, null)\n return (\n canOverflow(style.overflowY, skipOverflowHiddenElements) ||\n canOverflow(style.overflowX, skipOverflowHiddenElements) ||\n isHiddenByFrame(el)\n )\n }\n\n return false\n}\n/**\n * Find out which edge to align against when logical scroll position is \"nearest\"\n * Interesting fact: \"nearest\" works similarily to \"if-needed\", if the element is fully visible it will not scroll it\n *\n * Legends:\n * ┌────────┐ ┏ ━ ━ ━ ┓\n * │ target │ frame\n * └────────┘ ┗ ━ ━ ━ ┛\n */\nfunction alignNearest(\n scrollingEdgeStart: number,\n scrollingEdgeEnd: number,\n scrollingSize: number,\n scrollingBorderStart: number,\n scrollingBorderEnd: number,\n elementEdgeStart: number,\n elementEdgeEnd: number,\n elementSize: number\n) {\n /**\n * If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B\n *\n * ┌──┐\n * ┏━│━━│━┓\n * │ │\n * ┃ │ │ ┃ do nothing\n * │ │\n * ┗━│━━│━┛\n * └──┘\n *\n * If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D\n *\n * ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐\n * │┃ ┃│ do nothing\n * └───────────┘\n * ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart < scrollingEdgeStart &&\n elementEdgeEnd > scrollingEdgeEnd) ||\n (elementEdgeStart > scrollingEdgeStart && elementEdgeEnd < scrollingEdgeEnd)\n ) {\n return 0\n }\n\n /**\n * If element edge A is outside scrolling box edge A and element height is less than scrolling box height\n *\n * ┌──┐\n * ┏━│━━│━┓ ┏━┌━━┐━┓\n * └──┘ │ │\n * from ┃ ┃ to ┃ └──┘ ┃\n *\n * ┗━ ━━ ━┛ ┗━ ━━ ━┛\n *\n * If element edge B is outside scrolling box edge B and element height is greater than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━┌━━┐━┓\n * │ │\n * from ┃ ┌──┐ ┃ to ┃ │ │ ┃\n * │ │ │ │\n * ┗━│━━│━┛ ┗━│━━│━┛\n * │ │ └──┘\n * │ │\n * └──┘\n *\n * If element edge C is outside scrolling box edge C and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * │ ┃ │ ┃ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * ┃ │ ┃ │ ┃ ┃ │\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n */\n if (\n (elementEdgeStart <= scrollingEdgeStart && elementSize <= scrollingSize) ||\n (elementEdgeEnd >= scrollingEdgeEnd && elementSize >= scrollingSize)\n ) {\n return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart\n }\n\n /**\n * If element edge B is outside scrolling box edge B and element height is less than scrolling box height\n *\n * ┏━ ━━ ━┓ ┏━ ━━ ━┓\n *\n * from ┃ ┃ to ┃ ┌──┐ ┃\n * ┌──┐ │ │\n * ┗━│━━│━┛ ┗━└━━┘━┛\n * └──┘\n *\n * If element edge A is outside scrolling box edge A and element height is greater than scrolling box height\n *\n * ┌──┐\n * │ │\n * │ │ ┌──┐\n * ┏━│━━│━┓ ┏━│━━│━┓\n * │ │ │ │\n * from ┃ └──┘ ┃ to ┃ │ │ ┃\n * │ │\n * ┗━ ━━ ━┛ ┗━└━━┘━┛\n *\n * If element edge C is outside scrolling box edge C and element width is greater than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───────────┐ ┌───────────┐\n * │ ┃ │ ┃ │ ┃ ┃\n * └───────────┘ └───────────┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n * If element edge D is outside scrolling box edge D and element width is less than scrolling box width\n *\n * from to\n * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓\n * ┌───┐ ┌───┐\n * ┃ │ ┃ │ ┃ │ ┃\n * └───┘ └───┘\n * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛\n *\n */\n if (\n (elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||\n (elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)\n ) {\n return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd\n }\n\n return 0\n}\n\nexport default (target: Element, options: Options): CustomScrollAction[] => {\n //TODO: remove this hack when microbundle will support typescript >= 4.0\n const windowWithViewport = window as unknown as Window & {\n visualViewport: visualViewport\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 = cursor.parentElement\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 = windowWithViewport.visualViewport\n ? windowWithViewport.visualViewport.width\n : innerWidth\n const viewportHeight = windowWithViewport.visualViewport\n ? windowWithViewport.visualViewport.height\n : innerHeight\n\n // Newer browsers supports scroll[X|Y], page[X|Y]Offset is\n const viewportX = window.scrollX || pageXOffset\n const viewportY = window.scrollY || pageYOffset\n\n const {\n height: targetHeight,\n width: targetWidth,\n top: targetTop,\n right: targetRight,\n bottom: targetBottom,\n left: targetLeft,\n } = target.getBoundingClientRect()\n\n // These values mutate as we loop through and generate scroll coordinates\n let targetBlock: number =\n block === 'start' || block === 'nearest'\n ? targetTop\n : block === 'end'\n ? targetBottom\n : targetTop + targetHeight / 2 // block === 'center\n let targetInline: number =\n inline === 'center'\n ? targetLeft + targetWidth / 2\n : inline === 'end'\n ? targetRight\n : targetLeft // inline === 'start || inline === 'nearest\n\n // Collect new scroll positions\n const computations: CustomScrollAction[] = []\n // In chrome there's no longer a difference between caching the `frames.length` to a var or not, so we don't in this case (size > speed anyways)\n for (let index = 0; index < frames.length; index++) {\n const frame = frames[index]\n\n // @TODO add a shouldScroll hook here that allows userland code to take control\n\n const { height, width, top, right, bottom, left } =\n frame.getBoundingClientRect()\n\n // If the element is already visible we can end it here\n // @TODO targetBlock and targetInline should be taken into account to be compliant with https://github.com/w3c/csswg-drafts/pull/1805/files#diff-3c17f0e43c20f8ecf89419d49e7ef5e0R1333\n if (\n scrollMode === 'if-needed' &&\n targetTop >= 0 &&\n targetLeft >= 0 &&\n targetBottom <= viewportHeight &&\n targetRight <= viewportWidth &&\n targetTop >= top &&\n targetBottom <= bottom &&\n targetLeft >= left &&\n targetRight <= right\n ) {\n // Break the loop and return the computations for things that are not fully visible\n return computations\n }\n\n const frameStyle = getComputedStyle(frame)\n const borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)\n const borderTop = parseInt(frameStyle.borderTopWidth as string, 10)\n const borderRight = parseInt(frameStyle.borderRightWidth as string, 10)\n const borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)\n\n let blockScroll: number = 0\n let inlineScroll: number = 0\n\n // The property existance checks for offfset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here\n // @TODO find out if the \"as HTMLElement\" overrides can be dropped\n const scrollbarWidth =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth -\n (frame as HTMLElement).clientWidth -\n borderLeft -\n borderRight\n : 0\n const scrollbarHeight =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight -\n (frame as HTMLElement).clientHeight -\n borderTop -\n borderBottom\n : 0\n\n const scaleX =\n 'offsetWidth' in frame\n ? (frame as HTMLElement).offsetWidth === 0\n ? 0\n : width / (frame as HTMLElement).offsetWidth\n : 0\n const scaleY =\n 'offsetHeight' in frame\n ? (frame as HTMLElement).offsetHeight === 0\n ? 0\n : height / (frame as HTMLElement).offsetHeight\n : 0\n\n if (scrollingElement === frame) {\n // Handle viewport logic (document.documentElement or document.body)\n\n if (block === 'start') {\n blockScroll = targetBlock\n } else if (block === 'end') {\n blockScroll = targetBlock - viewportHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n viewportY,\n viewportY + viewportHeight,\n viewportHeight,\n borderTop,\n borderBottom,\n viewportY + targetBlock,\n viewportY + targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - viewportHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline\n } else if (inline === 'center') {\n inlineScroll = targetInline - viewportWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - viewportWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n viewportX,\n viewportX + viewportWidth,\n viewportWidth,\n borderLeft,\n borderRight,\n viewportX + targetInline,\n viewportX + targetInline + targetWidth,\n targetWidth\n )\n }\n\n // Apply scroll position offsets and ensure they are within bounds\n // @TODO add more test cases to cover this 100%\n blockScroll = Math.max(0, blockScroll + viewportY)\n inlineScroll = Math.max(0, inlineScroll + viewportX)\n } else {\n // Handle each scrolling frame that might exist between the target and the viewport\n\n if (block === 'start') {\n blockScroll = targetBlock - top - borderTop\n } else if (block === 'end') {\n blockScroll = targetBlock - bottom + borderBottom + scrollbarHeight\n } else if (block === 'nearest') {\n blockScroll = alignNearest(\n top,\n bottom,\n height,\n borderTop,\n borderBottom + scrollbarHeight,\n targetBlock,\n targetBlock + targetHeight,\n targetHeight\n )\n } else {\n // block === 'center' is the default\n blockScroll = targetBlock - (top + height / 2) + scrollbarHeight / 2\n }\n\n if (inline === 'start') {\n inlineScroll = targetInline - left - borderLeft\n } else if (inline === 'center') {\n inlineScroll = targetInline - (left + width / 2) + scrollbarWidth / 2\n } else if (inline === 'end') {\n inlineScroll = targetInline - right + borderRight + scrollbarWidth\n } else {\n // inline === 'nearest' is the default\n inlineScroll = alignNearest(\n left,\n right,\n width,\n borderLeft,\n borderRight + scrollbarWidth,\n targetInline,\n targetInline + targetWidth,\n targetWidth\n )\n }\n\n const { scrollLeft, scrollTop } = frame\n // Ensure scroll coordinates are not out of bounds while applying scroll offsets\n blockScroll = Math.max(\n 0,\n Math.min(\n scrollTop + blockScroll / scaleY,\n frame.scrollHeight - height / scaleY + scrollbarHeight\n )\n )\n inlineScroll = Math.max(\n 0,\n Math.min(\n scrollLeft + inlineScroll / scaleX,\n frame.scrollWidth - width / scaleX + scrollbarWidth\n )\n )\n\n // Cache the offset so that parent frames can scroll this into view correctly\n targetBlock += scrollTop - blockScroll\n targetInline += scrollLeft - inlineScroll\n }\n\n computations.push({ el: frame, top: blockScroll, left: inlineScroll })\n }\n\n return computations\n}\n"],"names":["isElement","el","nodeType","canOverflow","overflow","skipOverflowHiddenElements","isScrollable","clientHeight","scrollHeight","clientWidth","scrollWidth","style","getComputedStyle","overflowY","overflowX","frame","ownerDocument","defaultView","frameElement","e","getFrameElement","isHiddenByFrame","alignNearest","scrollingEdgeStart","scrollingEdgeEnd","scrollingSize","scrollingBorderStart","scrollingBorderEnd","elementEdgeStart","elementEdgeEnd","elementSize","target","options","windowWithViewport","window","scrollMode","block","inline","boundary","checkBoundary","node","TypeError","scrollingElement","document","documentElement","frames","cursor","parentElement","push","body","viewportWidth","visualViewport","width","innerWidth","viewportHeight","height","innerHeight","viewportX","scrollX","pageXOffset","viewportY","scrollY","pageYOffset","getBoundingClientRect","targetHeight","targetWidth","targetTop","top","targetRight","right","targetBottom","bottom","targetLeft","left","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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"}