UNPKG

lisn.js

Version:

Simply handle user gestures and actions. Includes widgets.

199 lines (194 loc) 7.35 kB
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /** * @module Actions * * @categoryDescription Scrolling * {@link ScrollTo} scrolls to the given element or to the previous scroll * position. */ import * as MH from "../globals/minification-helpers.js"; import * as MC from "../globals/minification-constants.js"; import { waitForReferenceElement } from "../utils/dom-search.js"; import { omitKeys } from "../utils/misc.js"; import { validateNumber } from "../utils/validation.js"; import { ScrollWatcher } from "../watchers/scroll-watcher.js"; import { registerAction } from "./action.js"; /** * Scrolls to the given element or to the previous scroll position. * * ------- * * To use with auto-widgets (HTML API) as part of a trigger specification: * - Action name: "scroll-to". * - Accepted string arguments: none * - Accepted options: * - `offsetX`: A number. * - `offsetY`: A number. * - `duration`: A number. * - `scrollable`: A string element specification for an element (see * {@link Utils.getReferenceElement | getReferenceElement}). Note that, * unless it's a DOM ID, the specification is parsed relative to the * element being acted on and not the element the trigger is defined on (in * case you've used the `act-on` trigger option). * * **NOTE:** Do not place a + sign in front of the offset values (just omit it * if you want a positive offset). Otherwise it will be interpreted as a * trigger option. * * @example * When the user clicks the button, scroll the main scrolling element to * element's position: * * ```html * <button id="btn">Scroll to/back</button> * <div data-lisn-on-click="@scroll-to +target=#btn"></div> * ``` * * @example * When the user clicks the button, scroll the main scrolling element to * element's position +10px down: * * ```html * <button id="btn">Scroll to/back</button> * <div data-lisn-on-click="@scroll-to: offsetY=10 +target=#btn"></div> * ``` * * @example * When the user clicks the button, scroll the main scrolling element to * element's position 10px _down_ and 50px _left_, with a duration of 200ms: * * ```html * <button id="btn">Scroll to/back</button> * <div data-lisn-on-click="@scroll-to: offsetY=10, offsetX=-50, duration=200 +target=#btn"></div> * ``` * * @example * When the user clicks the button, scroll the closest parent element with * class `scrollable` to the element's position: * * ```html * <button id="btn">Scroll to/back</button> * <div class="scrollable"> * <div data-lisn-on-click="@scroll-to: scrollable=this.scrollable +target=#btn"></div> * </div> * ``` * * @example * As above, but using `data-lisn-ref` attribute instead of class selector. * * ```html * <button id="btn">Scroll to/back</button> * <div data-lisn-ref="scrollable"> * <div data-lisn-on-click="@scroll-to: scrollable=this-scrollable +target=#btn"></div> * </div> * ``` * * @category Scrolling */ export class ScrollTo { static register() { registerAction("scroll-to", (element, args, config) => { const offset = config ? { left: config.offsetX, top: config.offsetY } : undefined; return new ScrollTo(element, { offset, duration: config === null || config === void 0 ? void 0 : config.duration, scrollable: config === null || config === void 0 ? void 0 : config.scrollable }); }, newConfigValidator); } constructor(element, config) { /** * Scrolls the main scrolling element to the element's position. */ _defineProperty(this, "do", void 0); /** * Scrolls the main scrolling element to the last scroll position before the * action was {@link do}ne. If the action had never been done, does nothing. */ _defineProperty(this, "undo", void 0); /** * Scrolls the main scrolling element to the element's position, if it's not * already there, or otherwise scrolls the main scrolling element to the * previous saved scroll position. */ _defineProperty(this, "toggle", void 0); const watcher = ScrollWatcher.reuse(); const { scrollable } = config !== null && config !== void 0 ? config : {}; let prevScrollTop = -1, prevScrollLeft = -1; this.do = async () => { const current = await watcher.fetchCurrentScroll(scrollable); prevScrollTop = current[MC.S_SCROLL_TOP]; prevScrollLeft = current[MC.S_SCROLL_LEFT]; const action = await watcher.scrollTo(element, config); await (action === null || action === void 0 ? void 0 : action.waitFor()); }; this.undo = async () => { if (prevScrollTop !== -1) { const action = await watcher.scrollTo({ top: prevScrollTop, left: prevScrollLeft }, omitKeys(config !== null && config !== void 0 ? config : {}, { offset: true }) // no offset when undoing ); await (action === null || action === void 0 ? void 0 : action.waitFor()); } }; this[MC.S_TOGGLE] = async () => { const start = await watcher.fetchCurrentScroll(scrollable); const canReverse = prevScrollTop !== -1; let hasReversed = false; // Try to scroll to the element, but if we're already close to it, then // reverse to previous position if any. const altTarget = { top: () => { hasReversed = true; // detect if we have reversed return prevScrollTop; }, left: prevScrollLeft }; const action = await watcher.scrollTo(element, MH.merge(config, canReverse ? { altTarget } // no altOffset when reversing : {})); await (action === null || action === void 0 ? void 0 : action.waitFor()); if (!hasReversed) { // We've scrolled to the element, so save the starting position as the // previous one. prevScrollTop = start[MC.S_SCROLL_TOP]; prevScrollLeft = start[MC.S_SCROLL_LEFT]; } }; } } /** * @interface * @category Scrolling */ // -------------------- const newConfigValidator = element => { return { offsetX: (key, value) => { var _validateNumber; return (_validateNumber = validateNumber(key, value)) !== null && _validateNumber !== void 0 ? _validateNumber : 0; }, offsetY: (key, value) => { var _validateNumber2; return (_validateNumber2 = validateNumber(key, value)) !== null && _validateNumber2 !== void 0 ? _validateNumber2 : 0; }, duration: (key, value) => validateNumber(key, value), scrollable: (key, value) => { var _ref; return (_ref = MH.isLiteralString(value) ? waitForReferenceElement(value, element) : null) !== null && _ref !== void 0 ? _ref : undefined; } }; }; //# sourceMappingURL=scroll-to.js.map