Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

scroll-behavior-polyfill.js 38KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931
  1. // https://www.jsdelivr.com/package/npm/scroll-behavior-polyfill?path=dist
  2. /*!
  3. scroll-behavior-polyfill 2.0.4
  4. license: MIT (https://github.com/wessberg/scroll-behavior-polyfill/blob/master/LICENSE.md)
  5. Copyright © 2019 Frederik Wessberg <frederikwessberg@hotmail.com>
  6. */
  7. (function () {
  8. 'use strict';
  9. /**
  10. * Is true if the browser natively supports the 'scroll-behavior' CSS-property.
  11. * @type {boolean}
  12. */
  13. var SUPPORTS_SCROLL_BEHAVIOR = "scrollBehavior" in document.documentElement.style;
  14. /*! *****************************************************************************
  15. Copyright (c) Microsoft Corporation. All rights reserved.
  16. Licensed under the Apache License, Version 2.0 (the "License"); you may not use
  17. this file except in compliance with the License. You may obtain a copy of the
  18. License at http://www.apache.org/licenses/LICENSE-2.0
  19. THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  20. KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
  21. WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
  22. MERCHANTABLITY OR NON-INFRINGEMENT.
  23. See the Apache Version 2.0 License for specific language governing permissions
  24. and limitations under the License.
  25. ***************************************************************************** */
  26. var __assign = function() {
  27. __assign = Object.assign || function __assign(t) {
  28. for (var s, i = 1, n = arguments.length; i < n; i++) {
  29. s = arguments[i];
  30. for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
  31. }
  32. return t;
  33. };
  34. return __assign.apply(this, arguments);
  35. };
  36. function __read(o, n) {
  37. var m = typeof Symbol === "function" && o[Symbol.iterator];
  38. if (!m) return o;
  39. var i = m.call(o), r, ar = [], e;
  40. try {
  41. while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
  42. }
  43. catch (error) { e = { error: error }; }
  44. finally {
  45. try {
  46. if (r && !r.done && (m = i["return"])) m.call(i);
  47. }
  48. finally { if (e) throw e.error; }
  49. }
  50. return ar;
  51. }
  52. var styleDeclarationPropertyName = "scrollBehavior";
  53. var styleAttributePropertyName = "scroll-behavior";
  54. var styleAttributePropertyNameRegex = new RegExp(styleAttributePropertyName + ":\\s*([^;]*)");
  55. /**
  56. * Determines the scroll behavior to use, depending on the given ScrollOptions and the position of the Element
  57. * within the DOM
  58. * @param {Element|HTMLElement|Window} inputTarget
  59. * @param {ScrollOptions} [options]
  60. * @returns {ScrollBehavior}
  61. */
  62. function getScrollBehavior(inputTarget, options) {
  63. // If the given 'behavior' is 'smooth', apply smooth scrolling no matter what
  64. if (options != null && options.behavior === "smooth")
  65. return "smooth";
  66. var target = "style" in inputTarget ? inputTarget : document.scrollingElement != null ? document.scrollingElement : document.documentElement;
  67. var value;
  68. if ("style" in target) {
  69. // Check if scroll-behavior is set as a property on the CSSStyleDeclaration
  70. var scrollBehaviorPropertyValue = target.style[styleDeclarationPropertyName];
  71. // Return it if it is given and has a proper value
  72. if (scrollBehaviorPropertyValue != null && scrollBehaviorPropertyValue !== "") {
  73. value = scrollBehaviorPropertyValue;
  74. }
  75. }
  76. if (value == null) {
  77. var attributeValue = target.getAttribute("scroll-behavior");
  78. if (attributeValue != null && attributeValue !== "") {
  79. value = attributeValue;
  80. }
  81. }
  82. if (value == null) {
  83. // Otherwise, check if it is set as an inline style
  84. var styleAttributeValue = target.getAttribute("style");
  85. if (styleAttributeValue != null && styleAttributeValue.includes(styleAttributePropertyName)) {
  86. var match = styleAttributeValue.match(styleAttributePropertyNameRegex);
  87. if (match != null) {
  88. var _a = __read(match, 2), behavior = _a[1];
  89. if (behavior != null && behavior !== "") {
  90. value = behavior;
  91. }
  92. }
  93. }
  94. }
  95. if (value == null) {
  96. // Take the computed style for the element and see if it contains a specific 'scroll-behavior' value
  97. var computedStyle = getComputedStyle(target);
  98. var computedStyleValue = computedStyle.getPropertyValue("scrollBehavior");
  99. if (computedStyleValue != null && computedStyleValue !== "") {
  100. value = computedStyleValue;
  101. }
  102. }
  103. // In all other cases, use the value from the CSSOM
  104. return value;
  105. }
  106. var HALF = 0.5;
  107. /**
  108. * The easing function to use when applying the smooth scrolling
  109. * @param {number} k
  110. * @returns {number}
  111. */
  112. function ease(k) {
  113. return HALF * (1 - Math.cos(Math.PI * k));
  114. }
  115. /**
  116. * The duration of a smooth scroll
  117. * @type {number}
  118. */
  119. var SCROLL_TIME = 15000;
  120. /**
  121. * Performs a smooth repositioning of the scroll
  122. * @param {ISmoothScrollOptions} options
  123. */
  124. function smoothScroll(options) {
  125. var startTime = options.startTime, startX = options.startX, startY = options.startY, endX = options.endX, endY = options.endY, method = options.method;
  126. var timeLapsed = 0;
  127. var distanceX = endX - startX;
  128. var distanceY = endY - startY;
  129. var speed = Math.max(Math.abs(distanceX / 1000 * SCROLL_TIME), Math.abs(distanceY / 1000 * SCROLL_TIME));
  130. requestAnimationFrame(function animate(timestamp) {
  131. timeLapsed += timestamp - startTime;
  132. var percentage = Math.max(0, Math.min(1, speed === 0 ? 0 : (timeLapsed / speed)));
  133. var positionX = Math.floor(startX + (distanceX * ease(percentage)));
  134. var positionY = Math.floor(startY + (distanceY * ease(percentage)));
  135. method(positionX, positionY);
  136. if (positionX !== endX || positionY !== endY) {
  137. requestAnimationFrame(animate);
  138. }
  139. });
  140. }
  141. /**
  142. * Returns a High Resolution timestamp if possible, otherwise fallbacks to Date.now()
  143. * @returns {number}
  144. */
  145. function now() {
  146. if ("performance" in window)
  147. return performance.now();
  148. return Date.now();
  149. }
  150. var ELEMENT_ORIGINAL_SCROLL = Element.prototype.scroll;
  151. var WINDOW_ORIGINAL_SCROLL = window.scroll;
  152. var ELEMENT_ORIGINAL_SCROLL_BY = Element.prototype.scrollBy;
  153. var WINDOW_ORIGINAL_SCROLL_BY = window.scrollBy;
  154. var ELEMENT_ORIGINAL_SCROLL_TO = Element.prototype.scrollTo;
  155. var WINDOW_ORIGINAL_SCROLL_TO = window.scrollTo;
  156. /**
  157. * A fallback if Element.prototype.scroll is not defined
  158. * @param {number} x
  159. * @param {number} y
  160. */
  161. function elementPrototypeScrollFallback(x, y) {
  162. this.__adjustingScrollPosition = true;
  163. this.scrollLeft = x;
  164. this.scrollTop = y;
  165. delete this.__adjustingScrollPosition;
  166. }
  167. /**
  168. * A fallback if Element.prototype.scrollTo is not defined
  169. * @param {number} x
  170. * @param {number} y
  171. */
  172. function elementPrototypeScrollToFallback(x, y) {
  173. return elementPrototypeScrollFallback.call(this, x, y);
  174. }
  175. /**
  176. * A fallback if Element.prototype.scrollBy is not defined
  177. * @param {number} x
  178. * @param {number} y
  179. */
  180. function elementPrototypeScrollByFallback(x, y) {
  181. this.__adjustingScrollPosition = true;
  182. this.scrollLeft += x;
  183. this.scrollTop += y;
  184. delete this.__adjustingScrollPosition;
  185. }
  186. /**
  187. * Gets the original non-patched prototype method for the given kind
  188. * @param {ScrollMethodName} kind
  189. * @param {Element|Window} element
  190. * @return {Function}
  191. */
  192. function getOriginalScrollMethodForKind(kind, element) {
  193. switch (kind) {
  194. case "scroll":
  195. if (element instanceof Element) {
  196. if (ELEMENT_ORIGINAL_SCROLL != null) {
  197. return ELEMENT_ORIGINAL_SCROLL;
  198. }
  199. else {
  200. return elementPrototypeScrollFallback;
  201. }
  202. }
  203. else {
  204. return WINDOW_ORIGINAL_SCROLL;
  205. }
  206. case "scrollBy":
  207. if (element instanceof Element) {
  208. if (ELEMENT_ORIGINAL_SCROLL_BY != null) {
  209. return ELEMENT_ORIGINAL_SCROLL_BY;
  210. }
  211. else {
  212. return elementPrototypeScrollByFallback;
  213. }
  214. }
  215. else {
  216. return WINDOW_ORIGINAL_SCROLL_BY;
  217. }
  218. case "scrollTo":
  219. if (element instanceof Element) {
  220. if (ELEMENT_ORIGINAL_SCROLL_TO != null) {
  221. return ELEMENT_ORIGINAL_SCROLL_TO;
  222. }
  223. else {
  224. return elementPrototypeScrollToFallback;
  225. }
  226. }
  227. else {
  228. return WINDOW_ORIGINAL_SCROLL_TO;
  229. }
  230. }
  231. }
  232. /**
  233. * Gets the Smooth Scroll Options to use for the step function
  234. * @param {Element|Window} element
  235. * @param {number} x
  236. * @param {number} y
  237. * @param {ScrollMethodName} kind
  238. * @returns {ISmoothScrollOptions}
  239. */
  240. function getSmoothScrollOptions(element, x, y, kind) {
  241. var startTime = now();
  242. if (!(element instanceof Element)) {
  243. // Use window as the scroll container
  244. var scrollX_1 = window.scrollX, pageXOffset_1 = window.pageXOffset, scrollY_1 = window.scrollY, pageYOffset_1 = window.pageYOffset;
  245. var startX = scrollX_1 == null || scrollX_1 === 0 ? pageXOffset_1 : scrollX_1;
  246. var startY = scrollY_1 == null || scrollY_1 === 0 ? pageYOffset_1 : scrollY_1;
  247. return {
  248. startTime: startTime,
  249. startX: startX,
  250. startY: startY,
  251. endX: Math.floor(kind === "scrollBy"
  252. ? startX + x
  253. : x),
  254. endY: Math.floor(kind === "scrollBy"
  255. ? startY + y
  256. : y),
  257. method: getOriginalScrollMethodForKind("scrollTo", window).bind(window)
  258. };
  259. }
  260. else {
  261. var scrollLeft = element.scrollLeft, scrollTop = element.scrollTop;
  262. var startX = scrollLeft;
  263. var startY = scrollTop;
  264. return {
  265. startTime: startTime,
  266. startX: startX,
  267. startY: startY,
  268. endX: Math.floor(kind === "scrollBy"
  269. ? startX + x
  270. : x),
  271. endY: Math.floor(kind === "scrollBy"
  272. ? startY + y
  273. : y),
  274. method: getOriginalScrollMethodForKind("scrollTo", element).bind(element)
  275. };
  276. }
  277. }
  278. /**
  279. * Ensures that the given value is numeric
  280. * @param {number} value
  281. * @return {number}
  282. */
  283. function ensureNumeric(value) {
  284. if (value == null)
  285. return 0;
  286. else if (typeof value === "number") {
  287. return value;
  288. }
  289. else if (typeof value === "string") {
  290. return parseFloat(value);
  291. }
  292. else {
  293. return 0;
  294. }
  295. }
  296. /**
  297. * Returns true if the given value is some ScrollToOptions
  298. * @param {number | ScrollToOptions} value
  299. * @return {value is ScrollToOptions}
  300. */
  301. function isScrollToOptions(value) {
  302. return value != null && typeof value === "object";
  303. }
  304. /**
  305. * Handles a scroll method
  306. * @param {Element|Window} element
  307. * @param {ScrollMethodName} kind
  308. * @param {number | ScrollToOptions} optionsOrX
  309. * @param {number} y
  310. */
  311. function handleScrollMethod(element, kind, optionsOrX, y) {
  312. onScrollWithOptions(getScrollToOptionsWithValidation(optionsOrX, y), element, kind);
  313. }
  314. /**
  315. * Invoked when a 'ScrollToOptions' dict is provided to 'scroll()' as the first argument
  316. * @param {ScrollToOptions} options
  317. * @param {Element|Window} element
  318. * @param {ScrollMethodName} kind
  319. */
  320. function onScrollWithOptions(options, element, kind) {
  321. var behavior = getScrollBehavior(element, options);
  322. // If the behavior is 'auto' apply instantaneous scrolling
  323. if (behavior == null || behavior === "auto") {
  324. getOriginalScrollMethodForKind(kind, element).call(element, options.left, options.top);
  325. }
  326. else {
  327. smoothScroll(getSmoothScrollOptions(element, options.left, options.top, kind));
  328. }
  329. }
  330. /**
  331. * Normalizes the given scroll coordinates
  332. * @param {number?} x
  333. * @param {number?} y
  334. * @return {Required<Pick<ScrollToOptions, "top" | "left">>}
  335. */
  336. function normalizeScrollCoordinates(x, y) {
  337. return {
  338. left: ensureNumeric(x),
  339. top: ensureNumeric(y)
  340. };
  341. }
  342. /**
  343. * Gets ScrollToOptions based on the given arguments. Will throw if validation fails
  344. * @param {number | ScrollToOptions} optionsOrX
  345. * @param {number} y
  346. * @return {Required<ScrollToOptions>}
  347. */
  348. function getScrollToOptionsWithValidation(optionsOrX, y) {
  349. // If only one argument is given, and it isn't an options object, throw a TypeError
  350. if (y === undefined && !isScrollToOptions(optionsOrX)) {
  351. throw new TypeError("Failed to execute 'scroll' on 'Element': parameter 1 ('options') is not an object.");
  352. }
  353. // Scroll based on the primitive values given as arguments
  354. if (!isScrollToOptions(optionsOrX)) {
  355. return __assign({}, normalizeScrollCoordinates(optionsOrX, y), { behavior: "auto" });
  356. }
  357. // Scroll based on the received options object
  358. else {
  359. return __assign({}, normalizeScrollCoordinates(optionsOrX.left, optionsOrX.top), { behavior: optionsOrX.behavior == null ? "auto" : optionsOrX.behavior });
  360. }
  361. }
  362. /**
  363. * Patches the 'scroll' method on the Element prototype
  364. */
  365. function patchElementScroll() {
  366. Element.prototype.scroll = function (optionsOrX, y) {
  367. handleScrollMethod(this, "scroll", optionsOrX, y);
  368. };
  369. }
  370. /**
  371. * Patches the 'scrollBy' method on the Element prototype
  372. */
  373. function patchElementScrollBy() {
  374. Element.prototype.scrollBy = function (optionsOrX, y) {
  375. handleScrollMethod(this, "scrollBy", optionsOrX, y);
  376. };
  377. }
  378. /**
  379. * Patches the 'scrollTo' method on the Element prototype
  380. */
  381. function patchElementScrollTo() {
  382. Element.prototype.scrollTo = function (optionsOrX, y) {
  383. handleScrollMethod(this, "scrollTo", optionsOrX, y);
  384. };
  385. }
  386. /**
  387. * Patches the 'scroll' method on the Window prototype
  388. */
  389. function patchWindowScroll() {
  390. window.scroll = function (optionsOrX, y) {
  391. handleScrollMethod(this, "scroll", optionsOrX, y);
  392. };
  393. }
  394. /**
  395. * Patches the 'scrollBy' method on the Window prototype
  396. */
  397. function patchWindowScrollBy() {
  398. window.scrollBy = function (optionsOrX, y) {
  399. handleScrollMethod(this, "scrollBy", optionsOrX, y);
  400. };
  401. }
  402. /**
  403. * Patches the 'scrollTo' method on the Window prototype
  404. */
  405. function patchWindowScrollTo() {
  406. window.scrollTo = function (optionsOrX, y) {
  407. handleScrollMethod(this, "scrollTo", optionsOrX, y);
  408. };
  409. }
  410. // tslint:disable:no-any
  411. /**
  412. * Gets the parent of an element, taking into account DocumentFragments, ShadowRoots, as well as the root context (window)
  413. * @param {EventTarget} currentElement
  414. * @returns {EventTarget | null}
  415. */
  416. function getParent(currentElement) {
  417. if ("nodeType" in currentElement && currentElement.nodeType === 1) {
  418. return currentElement.parentNode;
  419. }
  420. if ("ShadowRoot" in window && (currentElement instanceof window.ShadowRoot)) {
  421. return currentElement.host;
  422. }
  423. else if (currentElement === document) {
  424. return window;
  425. }
  426. else if (currentElement instanceof Node)
  427. return currentElement.parentNode;
  428. return null;
  429. }
  430. var scrollingElement = document.scrollingElement != null ? document.scrollingElement : document.documentElement;
  431. /**
  432. * Returns true if the given overflow property represents a scrollable overflow value
  433. * @param {string | null} overflow
  434. * @return {boolean}
  435. */
  436. function canOverflow(overflow) {
  437. return overflow !== "visible" && overflow !== "clip";
  438. }
  439. /**
  440. * Returns true if the given element is scrollable
  441. * @param {Element} element
  442. * @return {boolean}
  443. */
  444. function isScrollable(element) {
  445. if (element.clientHeight < element.scrollHeight || element.clientWidth < element.scrollWidth) {
  446. var style = getComputedStyle(element, null);
  447. return (canOverflow(style.overflowY) ||
  448. canOverflow(style.overflowX));
  449. }
  450. return false;
  451. }
  452. /**
  453. * Finds the nearest ancestor of an element that can scroll
  454. * @param {Element} target
  455. * @returns {Element|Window?}
  456. */
  457. function findNearestAncestorsWithScrollBehavior(target) {
  458. var currentElement = target;
  459. while (currentElement != null) {
  460. var behavior = getScrollBehavior(currentElement);
  461. if (behavior != null && (currentElement === scrollingElement || isScrollable(currentElement))) {
  462. return [currentElement, behavior];
  463. }
  464. var parent_1 = getParent(currentElement);
  465. currentElement = parent_1;
  466. }
  467. // No such element could be found. Start over, but this time find the nearest ancestor that can simply scroll
  468. currentElement = target;
  469. while (currentElement != null) {
  470. if (currentElement === scrollingElement || isScrollable(currentElement)) {
  471. return [currentElement, "auto"];
  472. }
  473. var parent_2 = getParent(currentElement);
  474. currentElement = parent_2;
  475. }
  476. // Default to the scrolling element
  477. return [scrollingElement, "auto"];
  478. }
  479. // tslint:disable:no-any
  480. /**
  481. * Finds the nearest root from an element
  482. * @param {Element} target
  483. * @returns {Document|ShadowRoot}
  484. */
  485. function findNearestRoot(target) {
  486. var currentElement = target;
  487. while (currentElement != null) {
  488. if ("ShadowRoot" in window && (currentElement instanceof window.ShadowRoot)) {
  489. // Assume this is a ShadowRoot
  490. return currentElement;
  491. }
  492. var parent_1 = getParent(currentElement);
  493. if (parent_1 === currentElement) {
  494. return document;
  495. }
  496. currentElement = parent_1;
  497. }
  498. return document;
  499. }
  500. /**
  501. * A Regular expression that matches id's of the form "#[digit]"
  502. * @type {RegExp}
  503. */
  504. var ID_WITH_LEADING_DIGIT_REGEXP = /^#\d/;
  505. /**
  506. * Catches anchor navigation to IDs within the same root and ensures that they can be smooth-scrolled
  507. * if the scroll behavior is smooth in the first rooter within that context
  508. */
  509. function catchNavigation() {
  510. // Listen for 'click' events globally
  511. window.addEventListener("click", function (e) {
  512. // Only work with trusted events on HTMLAnchorElements
  513. if (!e.isTrusted || !(e.target instanceof HTMLAnchorElement))
  514. return;
  515. var hrefAttributeValue = e.target.getAttribute("href");
  516. // Only work with HTMLAnchorElements that navigates to a specific ID
  517. if (hrefAttributeValue == null || !hrefAttributeValue.startsWith("#"))
  518. return;
  519. // Find the nearest root, whether it be a ShadowRoot or the document itself
  520. var root = findNearestRoot(e.target);
  521. // Attempt to match the selector from that root. querySelector' doesn't support IDs that start with a digit, so work around that limitation
  522. var elementMatch = hrefAttributeValue.match(ID_WITH_LEADING_DIGIT_REGEXP) != null
  523. ? root.getElementById(hrefAttributeValue.slice(1))
  524. : root.querySelector(hrefAttributeValue);
  525. // If no selector could be found, don't proceed
  526. if (elementMatch == null)
  527. return;
  528. // Find the nearest ancestor that can be scrolled
  529. var _a = __read(findNearestAncestorsWithScrollBehavior(elementMatch), 2), ancestorWithScrollBehavior = _a[0], behavior = _a[1];
  530. // If the behavior isn't smooth, don't proceed
  531. if (behavior !== "smooth")
  532. return;
  533. // Otherwise, first prevent the default action.
  534. e.preventDefault();
  535. // Now, scroll to the element with that ID
  536. ancestorWithScrollBehavior.scrollTo({
  537. behavior: behavior,
  538. top: elementMatch.offsetTop,
  539. left: elementMatch.offsetLeft
  540. });
  541. });
  542. }
  543. var ELEMENT_ORIGINAL_SCROLL_INTO_VIEW = Element.prototype.scrollIntoView;
  544. /**
  545. * The majority of this file is based on https://github.com/stipsan/compute-scroll-into-view (MIT license),
  546. * but has been rewritten to accept a scroller as an argument.
  547. */
  548. /**
  549. * Find out which edge to align against when logical scroll position is "nearest"
  550. * Interesting fact: "nearest" works similarly to "if-needed", if the element is fully visible it will not scroll it
  551. *
  552. * Legends:
  553. * ┌────────┐ ┏ ━ ━ ━ ┓
  554. * │ target │ frame
  555. * └────────┘ ┗ ━ ━ ━ ┛
  556. */
  557. function alignNearest(scrollingEdgeStart, scrollingEdgeEnd, scrollingSize, scrollingBorderStart, scrollingBorderEnd, elementEdgeStart, elementEdgeEnd, elementSize) {
  558. /**
  559. * If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B
  560. *
  561. * ┌──┐
  562. * ┏━│━━│━┓
  563. * │ │
  564. * ┃ │ │ ┃ do nothing
  565. * │ │
  566. * ┗━│━━│━┛
  567. * └──┘
  568. *
  569. * If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D
  570. *
  571. * ┏ ━ ━ ━ ━ ┓
  572. * ┌───────────┐
  573. * │┃ ┃│ do nothing
  574. * └───────────┘
  575. * ┗ ━ ━ ━ ━ ┛
  576. */
  577. if ((elementEdgeStart < scrollingEdgeStart &&
  578. elementEdgeEnd > scrollingEdgeEnd) ||
  579. (elementEdgeStart > scrollingEdgeStart && elementEdgeEnd < scrollingEdgeEnd)) {
  580. return 0;
  581. }
  582. /**
  583. * If element edge A is outside scrolling box edge A and element height is less than scrolling box height
  584. *
  585. * ┌──┐
  586. * ┏━│━━│━┓ ┏━┌━━┐━┓
  587. * └──┘ │ │
  588. * from ┃ ┃ to ┃ └──┘ ┃
  589. *
  590. * ┗━ ━━ ━┛ ┗━ ━━ ━┛
  591. *
  592. * If element edge B is outside scrolling box edge B and element height is greater than scrolling box height
  593. *
  594. * ┏━ ━━ ━┓ ┏━┌━━┐━┓
  595. * │ │
  596. * from ┃ ┌──┐ ┃ to ┃ │ │ ┃
  597. * │ │ │ │
  598. * ┗━│━━│━┛ ┗━│━━│━┛
  599. * │ │ └──┘
  600. * │ │
  601. * └──┘
  602. *
  603. * If element edge C is outside scrolling box edge C and element width is less than scrolling box width
  604. *
  605. * from to
  606. * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓
  607. * ┌───┐ ┌───┐
  608. * │ ┃ │ ┃ ┃ │ ┃
  609. * └───┘ └───┘
  610. * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛
  611. *
  612. * If element edge D is outside scrolling box edge D and element width is greater than scrolling box width
  613. *
  614. * from to
  615. * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓
  616. * ┌───────────┐ ┌───────────┐
  617. * ┃ │ ┃ │ ┃ ┃ │
  618. * └───────────┘ └───────────┘
  619. * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛
  620. */
  621. if ((elementEdgeStart <= scrollingEdgeStart && elementSize <= scrollingSize) ||
  622. (elementEdgeEnd >= scrollingEdgeEnd && elementSize >= scrollingSize)) {
  623. return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart;
  624. }
  625. /**
  626. * If element edge B is outside scrolling box edge B and element height is less than scrolling box height
  627. *
  628. * ┏━ ━━ ━┓ ┏━ ━━ ━┓
  629. *
  630. * from ┃ ┃ to ┃ ┌──┐ ┃
  631. * ┌──┐ │ │
  632. * ┗━│━━│━┛ ┗━└━━┘━┛
  633. * └──┘
  634. *
  635. * If element edge A is outside scrolling box edge A and element height is greater than scrolling box height
  636. *
  637. * ┌──┐
  638. * │ │
  639. * │ │ ┌──┐
  640. * ┏━│━━│━┓ ┏━│━━│━┓
  641. * │ │ │ │
  642. * from ┃ └──┘ ┃ to ┃ │ │ ┃
  643. * │ │
  644. * ┗━ ━━ ━┛ ┗━└━━┘━┛
  645. *
  646. * If element edge C is outside scrolling box edge C and element width is greater than scrolling box width
  647. *
  648. * from to
  649. * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓
  650. * ┌───────────┐ ┌───────────┐
  651. * │ ┃ │ ┃ │ ┃ ┃
  652. * └───────────┘ └───────────┘
  653. * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛
  654. *
  655. * If element edge D is outside scrolling box edge D and element width is less than scrolling box width
  656. *
  657. * from to
  658. * ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓
  659. * ┌───┐ ┌───┐
  660. * ┃ │ ┃ │ ┃ │ ┃
  661. * └───┘ └───┘
  662. * ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛
  663. *
  664. */
  665. if ((elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||
  666. (elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)) {
  667. return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd;
  668. }
  669. return 0;
  670. }
  671. function computeScrollIntoView(target, scroller, options) {
  672. var block = options.block, inline = options.inline;
  673. // Used to handle the top most element that can be scrolled
  674. var scrollingElement = document.scrollingElement || document.documentElement;
  675. // Support pinch-zooming properly, making sure elements scroll into the visual viewport
  676. // Browsers that don't support visualViewport will report the layout viewport dimensions on document.documentElement.clientWidth/Height
  677. // and viewport dimensions on window.innerWidth/Height
  678. // https://www.quirksmode.org/mobile/viewports2.html
  679. // https://bokand.github.io/viewport/index.html
  680. var viewportWidth = window.visualViewport != null
  681. ? visualViewport.width
  682. : innerWidth;
  683. var viewportHeight = window.visualViewport != null
  684. ? visualViewport.height
  685. : innerHeight;
  686. var viewportX = window.scrollX != null ? window.scrollX : window.pageXOffset;
  687. var viewportY = window.scrollY != null ? window.scrollY : window.pageYOffset;
  688. var _a = target.getBoundingClientRect(), targetHeight = _a.height, targetWidth = _a.width, targetTop = _a.top, targetRight = _a.right, targetBottom = _a.bottom, targetLeft = _a.left;
  689. // These values mutate as we loop through and generate scroll coordinates
  690. var targetBlock = block === "start" || block === "nearest"
  691. ? targetTop
  692. : block === "end"
  693. ? targetBottom
  694. : targetTop + targetHeight / 2; // block === 'center
  695. var targetInline = inline === "center"
  696. ? targetLeft + targetWidth / 2
  697. : inline === "end"
  698. ? targetRight
  699. : targetLeft; // inline === 'start || inline === 'nearest
  700. var _b = scroller.getBoundingClientRect(), height = _b.height, width = _b.width, top = _b.top, right = _b.right, bottom = _b.bottom, left = _b.left;
  701. var frameStyle = getComputedStyle(scroller);
  702. var borderLeft = parseInt(frameStyle.borderLeftWidth, 10);
  703. var borderTop = parseInt(frameStyle.borderTopWidth, 10);
  704. var borderRight = parseInt(frameStyle.borderRightWidth, 10);
  705. var borderBottom = parseInt(frameStyle.borderBottomWidth, 10);
  706. var blockScroll = 0;
  707. var inlineScroll = 0;
  708. // The property existance checks for offset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here
  709. // @TODO find out if the "as HTMLElement" overrides can be dropped
  710. var scrollbarWidth = "offsetWidth" in scroller
  711. ? scroller.offsetWidth -
  712. scroller.clientWidth -
  713. borderLeft -
  714. borderRight
  715. : 0;
  716. var scrollbarHeight = "offsetHeight" in scroller
  717. ? scroller.offsetHeight -
  718. scroller.clientHeight -
  719. borderTop -
  720. borderBottom
  721. : 0;
  722. if (scrollingElement === scroller) {
  723. // Handle viewport logic (document.documentElement or document.body)
  724. if (block === "start") {
  725. blockScroll = targetBlock;
  726. }
  727. else if (block === "end") {
  728. blockScroll = targetBlock - viewportHeight;
  729. }
  730. else if (block === "nearest") {
  731. blockScroll = alignNearest(viewportY, viewportY + viewportHeight, viewportHeight, borderTop, borderBottom, viewportY + targetBlock, viewportY + targetBlock + targetHeight, targetHeight);
  732. }
  733. else {
  734. // block === 'center' is the default
  735. blockScroll = targetBlock - viewportHeight / 2;
  736. }
  737. if (inline === "start") {
  738. inlineScroll = targetInline;
  739. }
  740. else if (inline === "center") {
  741. inlineScroll = targetInline - viewportWidth / 2;
  742. }
  743. else if (inline === "end") {
  744. inlineScroll = targetInline - viewportWidth;
  745. }
  746. else {
  747. // inline === 'nearest' is the default
  748. inlineScroll = alignNearest(viewportX, viewportX + viewportWidth, viewportWidth, borderLeft, borderRight, viewportX + targetInline, viewportX + targetInline + targetWidth, targetWidth);
  749. }
  750. // Apply scroll position offsets and ensure they are within bounds
  751. // @TODO add more test cases to cover this 100%
  752. blockScroll = Math.max(0, blockScroll + viewportY);
  753. inlineScroll = Math.max(0, inlineScroll + viewportX);
  754. }
  755. else {
  756. // Handle each scrolling frame that might exist between the target and the viewport
  757. if (block === "start") {
  758. blockScroll = targetBlock - top - borderTop;
  759. }
  760. else if (block === "end") {
  761. blockScroll = targetBlock - bottom + borderBottom + scrollbarHeight;
  762. }
  763. else if (block === "nearest") {
  764. blockScroll = alignNearest(top, bottom, height, borderTop, borderBottom + scrollbarHeight, targetBlock, targetBlock + targetHeight, targetHeight);
  765. }
  766. else {
  767. // block === 'center' is the default
  768. blockScroll = targetBlock - (top + height / 2) + scrollbarHeight / 2;
  769. }
  770. if (inline === "start") {
  771. inlineScroll = targetInline - left - borderLeft;
  772. }
  773. else if (inline === "center") {
  774. inlineScroll = targetInline - (left + width / 2) + scrollbarWidth / 2;
  775. }
  776. else if (inline === "end") {
  777. inlineScroll = targetInline - right + borderRight + scrollbarWidth;
  778. }
  779. else {
  780. // inline === 'nearest' is the default
  781. inlineScroll = alignNearest(left, right, width, borderLeft, borderRight + scrollbarWidth, targetInline, targetInline + targetWidth, targetWidth);
  782. }
  783. var scrollLeft = scroller.scrollLeft, scrollTop = scroller.scrollTop;
  784. // Ensure scroll coordinates are not out of bounds while applying scroll offsets
  785. blockScroll = Math.max(0, Math.min(scrollTop + blockScroll, scroller.scrollHeight - height + scrollbarHeight));
  786. inlineScroll = Math.max(0, Math.min(scrollLeft + inlineScroll, scroller.scrollWidth - width + scrollbarWidth));
  787. }
  788. return {
  789. top: blockScroll,
  790. left: inlineScroll
  791. };
  792. }
  793. /**
  794. * Patches the 'scrollIntoView' method on the Element prototype
  795. */
  796. function patchElementScrollIntoView() {
  797. Element.prototype.scrollIntoView = function (arg) {
  798. var normalizedOptions = arg == null || arg === true
  799. ? {
  800. block: "start",
  801. inline: "nearest"
  802. }
  803. : arg === false
  804. ? {
  805. block: "end",
  806. inline: "nearest"
  807. }
  808. : arg;
  809. // Find the nearest ancestor that can be scrolled
  810. var _a = __read(findNearestAncestorsWithScrollBehavior(this), 2), ancestorWithScroll = _a[0], ancestorWithScrollBehavior = _a[1];
  811. var behavior = normalizedOptions.behavior != null
  812. ? normalizedOptions.behavior
  813. : ancestorWithScrollBehavior;
  814. // If the behavior isn't smooth, simply invoke the original implementation and do no more
  815. if (behavior !== "smooth") {
  816. // Assert that 'scrollIntoView' is actually defined
  817. if (ELEMENT_ORIGINAL_SCROLL_INTO_VIEW != null) {
  818. ELEMENT_ORIGINAL_SCROLL_INTO_VIEW.call(this, normalizedOptions);
  819. }
  820. // Otherwise, invoke 'scrollTo' instead and provide the scroll coordinates
  821. else {
  822. var _b = computeScrollIntoView(this, ancestorWithScroll, normalizedOptions), top_1 = _b.top, left = _b.left;
  823. getOriginalScrollMethodForKind("scrollTo", this).call(this, left, top_1);
  824. }
  825. return;
  826. }
  827. ancestorWithScroll.scrollTo(__assign({ behavior: behavior }, computeScrollIntoView(this, ancestorWithScroll, normalizedOptions)));
  828. };
  829. }
  830. var ELEMENT_ORIGINAL_SCROLL_TOP_SET_DESCRIPTOR = Object.getOwnPropertyDescriptor(Element.prototype, "scrollTop").set;
  831. /**
  832. * Patches the 'scrollTop' property descriptor on the Element prototype
  833. */
  834. function patchElementScrollTop() {
  835. Object.defineProperty(Element.prototype, "scrollTop", {
  836. set: function (scrollTop) {
  837. if (this.__adjustingScrollPosition) {
  838. return ELEMENT_ORIGINAL_SCROLL_TOP_SET_DESCRIPTOR.call(this, scrollTop);
  839. }
  840. handleScrollMethod(this, "scrollTo", this.scrollLeft, scrollTop);
  841. return scrollTop;
  842. }
  843. });
  844. }
  845. var ELEMENT_ORIGINAL_SCROLL_LEFT_SET_DESCRIPTOR = Object.getOwnPropertyDescriptor(Element.prototype, "scrollLeft").set;
  846. /**
  847. * Patches the 'scrollLeft' property descriptor on the Element prototype
  848. */
  849. function patchElementScrollLeft() {
  850. Object.defineProperty(Element.prototype, "scrollLeft", {
  851. set: function (scrollLeft) {
  852. if (this.__adjustingScrollPosition) {
  853. return ELEMENT_ORIGINAL_SCROLL_LEFT_SET_DESCRIPTOR.call(this, scrollLeft);
  854. }
  855. handleScrollMethod(this, "scrollTo", scrollLeft, this.scrollTop);
  856. return scrollLeft;
  857. }
  858. });
  859. }
  860. /**
  861. * Applies the polyfill
  862. */
  863. function patch() {
  864. // Element.prototype methods
  865. patchElementScroll();
  866. patchElementScrollBy();
  867. patchElementScrollTo();
  868. patchElementScrollIntoView();
  869. // Element.prototype descriptors
  870. patchElementScrollLeft();
  871. patchElementScrollTop();
  872. // window methods
  873. patchWindowScroll();
  874. patchWindowScrollBy();
  875. patchWindowScrollTo();
  876. // Navigation
  877. catchNavigation();
  878. }
  879. /**
  880. * Is true if the browser natively supports the Element.prototype.[scroll|scrollTo|scrollBy|scrollIntoView] methods
  881. * @type {boolean}
  882. */
  883. var SUPPORTS_ELEMENT_PROTOTYPE_SCROLL_METHODS = "scroll" in Element.prototype && "scrollTo" in Element.prototype && "scrollBy" in Element.prototype && "scrollIntoView" in Element.prototype;
  884. if (!SUPPORTS_SCROLL_BEHAVIOR || !SUPPORTS_ELEMENT_PROTOTYPE_SCROLL_METHODS) {
  885. patch();
  886. }
  887. }());