/** * Copyright (c) Tiny Technologies, Inc. All rights reserved. * Licensed under the LGPL or a commercial license. * For LGPL see License.txt in the project root for license information. * For commercial licenses see https://www.tiny.cloud/ */ import { Fun, Future } from '@ephox/katamari'; import { Attr, Css, Traverse } from '@ephox/sugar'; import Styles from '../../style/Styles'; import DataAttributes from '../../util/DataAttributes'; import SmoothAnimation from '../smooth/SmoothAnimation'; import IosViewport from '../view/IosViewport'; const animator = SmoothAnimation.create(); const ANIMATION_STEP = 15; const NUM_TOP_ANIMATION_FRAMES = 10; const ANIMATION_RATE = 10; const lastScroll = 'data-' + Styles.resolve('last-scroll-top'); const getTop = function (element) { const raw = Css.getRaw(element, 'top').getOr('0'); return parseInt(raw, 10); }; const getScrollTop = function (element) { return parseInt(element.dom().scrollTop, 10); }; const moveScrollAndTop = function (element, destination, finalTop) { return Future.nu(function (callback) { const getCurrent = Fun.curry(getScrollTop, element); const update = function (newScroll) { element.dom().scrollTop = newScroll; Css.set(element, 'top', (getTop(element) + ANIMATION_STEP) + 'px'); }; const finish = function (/* dest */) { element.dom().scrollTop = destination; Css.set(element, 'top', finalTop + 'px'); callback(destination); }; animator.animate(getCurrent, destination, ANIMATION_STEP, update, finish, ANIMATION_RATE); }); }; const moveOnlyScroll = function (element, destination) { return Future.nu(function (callback) { const getCurrent = Fun.curry(getScrollTop, element); Attr.set(element, lastScroll, getCurrent()); const update = function (newScroll, abort) { const previous = DataAttributes.safeParse(element, lastScroll); // As soon as we detect a scroll value that we didn't set, assume the user // is scrolling, and abort the scrolling. if (previous !== element.dom().scrollTop) { abort(element.dom().scrollTop); } else { element.dom().scrollTop = newScroll; Attr.set(element, lastScroll, newScroll); } }; const finish = function (/* dest */) { element.dom().scrollTop = destination; Attr.set(element, lastScroll, destination); callback(destination); }; // Identify the number of steps based on distance (consistent time) const distance = Math.abs(destination - getCurrent()); const step = Math.ceil(distance / NUM_TOP_ANIMATION_FRAMES); animator.animate(getCurrent, destination, step, update, finish, ANIMATION_RATE); }); }; const moveOnlyTop = function (element, destination) { return Future.nu(function (callback) { const getCurrent = Fun.curry(getTop, element); const update = function (newTop) { Css.set(element, 'top', newTop + 'px'); }; const finish = function (/* dest */) { update(destination); callback(destination); }; const distance = Math.abs(destination - getCurrent()); const step = Math.ceil(distance / NUM_TOP_ANIMATION_FRAMES); animator.animate(getCurrent, destination, step, update, finish, ANIMATION_RATE); }); }; const updateTop = function (element, amount) { const newTop = (amount + IosViewport.getYFixedData(element)) + 'px'; Css.set(element, 'top', newTop); }; // Previously, we moved the window scroll back smoothly with the SmoothAnimation concept. // However, on tinyMCE, we seemed to get a lot more cursor flickering as the window scroll // was changing. Therefore, until tests prove otherwise, we are just going to jump to the // destination in one go. const moveWindowScroll = function (toolbar, viewport, destY) { const outerWindow = Traverse.owner(toolbar).dom().defaultView; return Future.nu(function (callback) { updateTop(toolbar, destY); updateTop(viewport, destY); outerWindow.scrollTo(0, destY); callback(destY); }); }; export default { moveScrollAndTop, moveOnlyScroll, moveOnlyTop, moveWindowScroll };