import { Optional, Type } from '@ephox/katamari'; import { PlatformDetection } from '@ephox/sand'; import * as Insert from '../dom/Insert'; import * as Remove from '../dom/Remove'; import * as SugarBody from '../node/SugarBody'; import { SugarElement } from '../node/SugarElement'; import * as SugarLocation from './SugarLocation'; import { SugarPosition } from './SugarPosition'; interface ScrollCapture { readonly save: () => void; readonly restore: () => void; } // get scroll position (x,y) relative to document _doc (or global if not supplied) const get = (_DOC?: SugarElement): SugarPosition => { const doc = _DOC !== undefined ? _DOC.dom : document; // ASSUMPTION: This is for cross-browser support, body works for Safari & EDGE, and when we have an iframe body scroller const x = doc.body.scrollLeft || doc.documentElement.scrollLeft; const y = doc.body.scrollTop || doc.documentElement.scrollTop; return SugarPosition(x, y); }; // Scroll content to (x,y) relative to document _doc (or global if not supplied) const to = (x: number, y: number, _DOC?: SugarElement): void => { const doc = _DOC !== undefined ? _DOC.dom : document; const win = doc.defaultView; if (win) { win.scrollTo(x, y); } }; // Scroll content by (x,y) relative to document _doc (or global if not supplied) const by = (x: number, y: number, _DOC?: SugarElement): void => { const doc = _DOC !== undefined ? _DOC.dom : document; const win = doc.defaultView; if (win) { win.scrollBy(x, y); } }; // Set the window scroll position to the element const setToElement = (win: Window, element: SugarElement): void => { const pos = SugarLocation.absolute(element); const doc = SugarElement.fromDom(win.document); to(pos.left, pos.top, doc); }; // call f() preserving the original scroll position relative to document doc const preserve = (doc: SugarElement, f: () => void): void => { const before = get(doc); f(); const after = get(doc); if (before.top !== after.top || before.left !== after.left) { to(before.left, before.top, doc); } }; // capture the current scroll location and provide save and restore methods const capture = (doc: SugarElement): ScrollCapture => { let previous = Optional.none(); const save = () => { previous = Optional.some(get(doc)); }; // TODO: this is quite similar to the code in nomad. const restore = () => { previous.each((p) => { to(p.left, p.top, doc); }); }; save(); return { save, /* Saves the current page scroll position */ restore /* Restores the page scroll to its former position when invoked */ }; }; // TBIO-4472 Safari 10 - Scrolling typeahead with keyboard scrolls page const intoView = (element: SugarElement, alignToTop: boolean): void => { const isSafari = PlatformDetection.detect().browser.isSafari(); // this method isn't in TypeScript if (isSafari && Type.isFunction((element.dom as any).scrollIntoViewIfNeeded)) { (element.dom as any).scrollIntoViewIfNeeded(false); // false=align to nearest edge } else { element.dom.scrollIntoView(alignToTop); // true=to top, false=to bottom } }; // If the element is above the container, or below the container, then scroll to the top or bottom const intoViewIfNeeded = (element: SugarElement, container: SugarElement): void => { const containerBox = container.dom.getBoundingClientRect(); const elementBox = element.dom.getBoundingClientRect(); if (elementBox.top < containerBox.top) { // element top is above the viewport top, scroll so it's at the top intoView(element, true); } else if (elementBox.bottom > containerBox.bottom) { // element bottom is below the viewport bottom, scroll so it's at the bottom intoView(element, false); } }; // Return the scroll bar width (calculated by temporarily inserting an element into the dom) const scrollBarWidth = (): number => { // From https://davidwalsh.name/detect-scrollbar-width const scrollDiv = SugarElement.fromHtml('
'); Insert.after(SugarBody.body(), scrollDiv); const w = scrollDiv.dom.offsetWidth - scrollDiv.dom.clientWidth; Remove.remove(scrollDiv); return w; }; export { get, to, by, preserve, capture, intoView, intoViewIfNeeded, setToElement, scrollBarWidth };