UNPKG

lisn.js

Version:

Simply handle user gestures and actions. Includes widgets.

749 lines (717 loc) 29.8 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 Widgets */ // [TODO v2]: Instead of wrapping children and changing which element is the // actual scrollable (and having to mapScrollable, etc), use the provided // element as the scrolling one but wrap IT (not its children) and insert the // scrollbars before it. Then remove, "id" and "className" config options. import * as MC from "../globals/minification-constants.js"; import * as MH from "../globals/minification-helpers.js"; import { settings } from "../globals/settings.js"; import { supportsSticky, isMobile, isInQuirksMode } from "../utils/browser.js"; import { showElement, hideElement, displayElement, undisplayElement, hasClass, addClasses, addClassesNow, removeClasses, removeClassesNow, getData, setData, setBooleanData, setBooleanDataNow, setDataNow, delData, delDataNow, getComputedStyleProp, getComputedStylePropNow, setStyleProp, setNumericStyleJsVars } from "../utils/css-alter.js"; import { moveElementNow, moveElement, isAllowedToWrap, getContentWrapper, wrapChildren, unwrapContentNow, getOrAssignID } from "../utils/dom-alter.js"; import { waitForMeasureTime, waitForMutateTime } from "../utils/dom-optimize.js"; import { addEventListenerTo, removeEventListenerFrom, preventSelect } from "../utils/event.js"; import { logError, logWarn } from "../utils/log.js"; import { toArrayIfSingle } from "../utils/misc.js"; import { isScrollable, getDefaultScrollingElement, getClientWidthNow, getClientHeightNow, mapScrollable, unmapScrollable, tryGetMainScrollableElement } from "../utils/scroll.js"; import { formatAsString } from "../utils/text.js"; import { validateStrList, validateNumber, validateBoolean, validateString } from "../utils/validation.js"; import { ScrollWatcher } from "../watchers/scroll-watcher.js"; import { SizeWatcher } from "../watchers/size-watcher.js"; import { Widget, registerWidget } from "./widget.js"; import debug from "../debug/debug.js"; /** * Configures the given element, which must be scrollable, to use a * {@link Scrollbar}. * * The Scrollbar widget is a customizable alternative to the native * scrollbars (vertical and horizontal). You can position each of the two * scrollbars on any of the four sides of the element, make them automatically * hide after certain time of inactivity, style them as a traditional handle * scrollbar or a percentage fill progress bar and so on. * * It is also itself draggable/clickable so it _can_ be used to scroll the * element similar to the native scrollbar. The drag/click functionality can be * disabled too. * * **NOTE:** If you have disabled the {@link Widgets.PageLoader | PageLoader} * and have left {@link ScrollbarConfig.hideNative} ON, but are seeing the * native scrollbars just for a fraction of a second at the beginning of the * page load, you may want to manually add `lisn-hide-scroll` class on the * scrollable element to make sure the scrollbars are hidden as soon as * possible (before the scrollbar widget has time to initialize. * * **IMPORTANT:** If you are using the Scrollbar on an element other than the * main scrollable element, it's highly recommended to * {@link settings.contentWrappingAllowed | enable content wrapping} (it is * enabled by default). Otherwise, Scrollbar will rely on position: sticky. If * you want to instead manually create the wrappers yourself, ensure your * structure is as follows: * ```html * <div class="scrollable"><!-- Element you instantiate as Scrollbar --> * <div class="lisn-scrollbar__content"><!-- Optional wrapper to avoid relying on sticky --> * <div class="lisn-wrapper"><!-- Optional wrapper to enable efficient scroll tracking --> * <!-- YOUR CONTENT --> * </div> * </div> * </div> * ``` * * **IMPORTANT:** You should not instantiate more than one {@link Scrollbar} * widget on a given element. Use {@link Scrollbar.get} to get an existing * instance if any. If there is already a widget instance, it will be destroyed! * * ----- * * You can use the following dynamic attributes or CSS properties in your * stylesheet: * * The following dynamic attributes are set on the element: * - `data-lisn-has-scrollbar-top`: `"true"` or `"false"` * - `data-lisn-has-scrollbar-bottom`: `"true"` or `"false"` * - `data-lisn-has-scrollbar-left`: `"true"` or `"false"` * - `data-lisn-has-scrollbar-right`: `"true"` or `"false"` * * The following dynamic attributes are set on each progressbar element: * - `data-lisn-orientation`: `"horizontal"` or `"vertical"` * - `data-lisn-place`: `"top"`, `"bottom"`, `"left"` or `"right"` * - `data-lisn-draggable`: `"true"` or `"false"` * - `data-lisn-clickable`: `"true"` or `"false"` * * ----- * * To use with auto-widgets (HTML API) (see {@link settings.autoWidgets}), the * following CSS classes or data attributes are recognized: * - `lisn-scrollbar` class or `data-lisn-scrollbar` attribute set on the * scrollable element that you want to enable custom scrollbars for * * See below examples for what values you can use set for the data attribute * in order to modify the configuration of the automatically created widget. * * @example * This will create custom scrollbars for * {@link settings.mainScrollableElementSelector | the main scrolling element}. * * This will work even if {@link settings.autoWidgets}) is false * * ```html * <!-- LISN should be loaded beforehand --> * <script> * // You can also just customise global default settings: * // LISN.settings.scrollbarPositionV = "top"; * // LISN.settings.scrollbarAutoHide = 3000; * // LISN.settings.scrollbarUseHandle = true; * * LISN.widgets.Scrollbar.enableMain({ * position: "top", * autoHide: 3000, * useHandle: true * }); * </script> * ``` * * @example * This will create custom scrollbars for a custom scrolling element (i.e. one * with overflow "auto" or "scroll"). * * ```html * <div class="scrolling lisn-scrollbar"> * <!-- content here... --> * </div> * ``` * * @example * As above but with custom settings. * * ```html * <div * class="scrolling" * data-lisn-scrollbar="hide-native=false * | positionH=top * | positionV=left * | auto-hide=2000 * | click-scroll=false * | drag-scroll=false * | use-handle=false * "> * <!-- content here... --> * </div> * ``` */ export class Scrollbar extends Widget { /** * If element is omitted, returns the instance created by {@link enableMain} * if any. */ static get(scrollable) { if (!scrollable) { return mainWidget; } if (scrollable === MH.getDocElement()) { scrollable = MH.getBody(); } const instance = super.get(scrollable, DUMMY_ID); if (MH.isInstanceOf(instance, Scrollbar)) { return instance; } return null; } /** * Enables scrollbars on the * {@link settings.mainScrollableElementSelector | the main scrolling element}. * * **NOTE:** It returns a Promise to a widget because it will wait for the * main scrollable element to be present in the DOM if not already. */ static async enableMain(config) { // [TODO v2]: enableMain should be synchronous and the constructor should // wait for the scrollable, allowing users who want to use the main // scrollable to just pass null/undefined/window. Then getScrollable should // return null or the actual scrollable if available + add fetchScrollable // to return a Promise. const scrollable = await ScrollWatcher.fetchMainScrollableElement(); const widget = new Scrollbar(scrollable, config); widget.onDestroy(() => { if (mainWidget === widget) { mainWidget = null; } }); mainWidget = widget; return widget; } static register() { registerWidget(WIDGET_NAME, (element, config) => { if (MH.isHTMLElement(element)) { if (!Scrollbar.get(element)) { return new Scrollbar(element, config); } } else { logError(MH.usageError("Only HTMLElement is supported for Scrollbar widget")); } return null; }, configValidator); } /** * Note that passing `document.body` is considered equivalent to * `document.documentElement`. */ constructor(scrollable, config) { var _Scrollbar$get; if (scrollable === MH.getDocElement()) { scrollable = MH.getBody(); } const destroyPromise = (_Scrollbar$get = Scrollbar.get(scrollable)) === null || _Scrollbar$get === void 0 ? void 0 : _Scrollbar$get.destroy(); super(scrollable, { id: DUMMY_ID }); /** * Returns the actual scrollable element us which, unless the scrollable you * passed to the constructor is the * {@link settings.mainScrollableElementSelector | the main scrolling element} * or unless * {@link settings.contentWrappingAllowed | you've disabled content wrapping}, * this will be a new element created by us that is a descendant of the * original element you passed. */ _defineProperty(this, "getScrollable", void 0); const props = getScrollableProps(scrollable); const ourScrollable = props.scrollable; (destroyPromise || MH.promiseResolve()).then(() => { if (this.isDestroyed()) { return; } init(this, scrollable, props, config); }); this.getScrollable = () => ourScrollable; } } /** * @interface */ // -------------------- const WIDGET_NAME = "scrollbar"; const PREFIXED_NAME = MH.prefixName(WIDGET_NAME); // Only one Scrollbar widget per element is allowed, but Widget // requires a non-blank ID. const DUMMY_ID = PREFIXED_NAME; const PREFIX_ROOT = `${PREFIXED_NAME}__root`; const PREFIX_CONTAINER = `${PREFIXED_NAME}__container`; const PREFIX_CONTENT = `${PREFIXED_NAME}__content`; const PREFIX_BAR = `${PREFIXED_NAME}__bar`; const PREFIX_WRAPPER = `${PREFIXED_NAME}__wrapper`; const PREFIX_FILL = `${PREFIXED_NAME}__fill`; const PREFIX_SPACER = `${PREFIXED_NAME}__spacer`; const PREFIX_HANDLE = `${PREFIXED_NAME}__handle`; const PREFIX_DRAGGABLE = MH.prefixName("draggable"); const PREFIX_CLICKABLE = MH.prefixName("clickable"); const PREFIX_HAS_WRAPPER = MH.prefixName("has-wrapper"); const PREFIX_ALLOW_COLLAPSE = MH.prefixName("allow-collapse"); const PREFIX_HAS_V_SCROLL = MH.prefixName("has-v-scroll"); const PREFIX_HAS_SCROLLBAR = MH.prefixName("has-scrollbar"); const PREFIX_HIDE_SCROLL = MH.prefixName("hide-scroll"); const S_SET_POINTER_CAPTURE = "setPointerCapture"; const S_RELEASE_POINTER_CAPTURE = "releasePointerCapture"; const S_ARIA_VALUENOW = MC.ARIA_PREFIX + "valuenow"; const S_SCROLLBAR = "scrollbar"; let mainWidget = null; const configValidator = { id: validateString, className: validateStrList, hideNative: validateBoolean, onMobile: validateBoolean, positionH: validateString, positionV: validateString, autoHide: validateNumber, clickScroll: validateBoolean, dragScroll: validateBoolean, useHandle: validateBoolean }; const getScrollableProps = containerElement => { // If main scrollable element doesn't exist yet, then the containerElement // passed can't be it anyway, so no need to use fetchMainScrollableElement. const mainScrollableElement = tryGetMainScrollableElement(); const body = MH.getBody(); const defaultScrollable = getDefaultScrollingElement(); const isBody = containerElement === body; const isMainScrollable = (isBody ? defaultScrollable : containerElement) === mainScrollableElement; const root = isMainScrollable ? mainScrollableElement : isBody ? defaultScrollable : containerElement; // check if we're using body in quirks mode const isBodyInQuirks = isBody && isInQuirksMode(); const allowedToWrap = isAllowedToWrap(containerElement); const barParent = isMainScrollable ? body : containerElement; const hasVScroll = isScrollable(root, { axis: "y" }); let contentWrapper = null; let supported = true; let hasExistingWrapper = true; if (!isMainScrollable && !isBody) { // we need to wrap if possible contentWrapper = getContentWrapper(containerElement, { _classNames: [PREFIX_CONTENT] }); hasExistingWrapper = !MH.isNullish(contentWrapper); if (!contentWrapper) { const warnMsgPrefix = "Scrollbar on elements other than " + "the main scrollable when content wrapping is " + "disabled relies on position: sticky"; if (allowedToWrap) { // we'll wrap later, but create the wrapper now as it will be the actual // scrollable contentWrapper = MH.createElement("div"); } else if (supportsSticky()) { logWarn(`${warnMsgPrefix}, is experimental and may not work properly.`); } else { logError(`${warnMsgPrefix}, but this browser does not support sticky.`); supported = false; } } } const needsSticky = !isMainScrollable && !allowedToWrap && !hasExistingWrapper; return { supported, isMainScrollable, isBody, isBodyInQuirks, root, scrollable: contentWrapper !== null && contentWrapper !== void 0 ? contentWrapper : root, barParent, contentWrapper, hasExistingWrapper, needsSticky, hasVScroll }; }; const init = (widget, containerElement, props, config) => { var _ref, _config$onMobile, _ref2, _config$hideNative, _config$autoHide, _config$clickScroll, _ref3, _config$dragScroll, _ref4, _config$useHandle; const { supported, isMainScrollable, isBody, isBodyInQuirks, root, scrollable, barParent, contentWrapper, hasExistingWrapper, needsSticky, hasVScroll } = props; const logger = debug ? new debug.Logger({ name: `Scrollbar-${formatAsString(root)}`, logAtCreation: { props, config } }) : null; // config const onMobile = (_ref = (_config$onMobile = config === null || config === void 0 ? void 0 : config.onMobile) !== null && _config$onMobile !== void 0 ? _config$onMobile : settings.scrollbarOnMobile) !== null && _ref !== void 0 ? _ref : false; const hideNative = (_ref2 = (_config$hideNative = config === null || config === void 0 ? void 0 : config.hideNative) !== null && _config$hideNative !== void 0 ? _config$hideNative : settings.scrollbarHideNative) !== null && _ref2 !== void 0 ? _ref2 : false; const positionH = (config === null || config === void 0 ? void 0 : config.positionH) || settings.scrollbarPositionH; const positionV = (config === null || config === void 0 ? void 0 : config.positionV) || settings.scrollbarPositionV; const autoHideDelay = (_config$autoHide = config === null || config === void 0 ? void 0 : config.autoHide) !== null && _config$autoHide !== void 0 ? _config$autoHide : settings.scrollbarAutoHide; const clickScroll = (_config$clickScroll = config === null || config === void 0 ? void 0 : config.clickScroll) !== null && _config$clickScroll !== void 0 ? _config$clickScroll : settings.scrollbarClickScroll; const dragScroll = (_ref3 = (_config$dragScroll = config === null || config === void 0 ? void 0 : config.dragScroll) !== null && _config$dragScroll !== void 0 ? _config$dragScroll : settings.scrollbarDragScroll) !== null && _ref3 !== void 0 ? _ref3 : false; const useHandle = (_ref4 = (_config$useHandle = config === null || config === void 0 ? void 0 : config.useHandle) !== null && _config$useHandle !== void 0 ? _config$useHandle : settings.scrollbarUseHandle) !== null && _ref4 !== void 0 ? _ref4 : false; if (isMobile() && !onMobile) { return; } // Ensure scroll tracking that will be setup on the original element uses the // new scrollable we create. // XXX TODO But this still breaks any existing scroll tracking mapScrollable(root, scrollable); // ---------- const newScrollbar = (wrapper, position) => { const barIsHorizontal = position === MC.S_TOP || position === MC.S_BOTTOM; const scrollbar = MH.createElement("div"); addClassesNow(scrollbar, PREFIX_BAR); setDataNow(scrollbar, MC.PREFIX_ORIENTATION, barIsHorizontal ? MC.S_HORIZONTAL : MC.S_VERTICAL); setDataNow(scrollbar, MC.PREFIX_PLACE, position); if (clickScroll || dragScroll) { MH.setAttr(scrollbar, MC.S_ROLE, S_SCROLLBAR); MH.setAttr(scrollbar, MC.S_ARIA_CONTROLS, scrollDomID); } const fill = MH.createElement("div"); addClassesNow(fill, useHandle ? PREFIX_SPACER : PREFIX_FILL); let handle = null; if (useHandle) { handle = MH.createElement("div"); addClassesNow(handle, PREFIX_HANDLE); setBooleanDataNow(handle, PREFIX_DRAGGABLE, dragScroll); } setBooleanDataNow(scrollbar, PREFIX_DRAGGABLE, dragScroll && !useHandle); setBooleanDataNow(scrollbar, PREFIX_CLICKABLE, clickScroll); moveElementNow(fill, { to: scrollbar }); if (handle) { moveElementNow(handle, { to: scrollbar }); } moveElementNow(scrollbar, { to: wrapper }); return { _bar: scrollbar, _handle: handle, _fill: fill }; }; // ---------- const setProgress = (scrollData, tracksH) => { const scrollbar = tracksH ? scrollbarH : scrollbarV; const hasBarPrefix = `${PREFIX_HAS_SCROLLBAR}-${tracksH ? positionH : positionV}`; const completeFraction = tracksH ? scrollData[MC.S_SCROLL_LEFT_FRACTION] : scrollData[MC.S_SCROLL_TOP_FRACTION]; const viewFraction = tracksH ? scrollData[MC.S_CLIENT_WIDTH] / scrollData[MC.S_SCROLL_WIDTH] : scrollData[MC.S_CLIENT_HEIGHT] / scrollData[MC.S_SCROLL_HEIGHT]; debug: logger === null || logger === void 0 || logger.debug9("Updating progress", { tracksH, completeFraction, viewFraction }); MH.setAttr(scrollbar, S_ARIA_VALUENOW, MH.round(completeFraction * 100) + ""); setNumericStyleJsVars(scrollbar, { viewFr: viewFraction, completeFr: completeFraction }, { _numDecimal: 4 }); const scrollAxis = tracksH ? "x" : "y"; // TODO When using content-box, reading scrollWidth/Height even on the // subsequent measure time still shows the "old" value that includes the // border width before it seems to adjust. So sometimes it gives false // positives for it being scrollable. const canScroll = viewFraction < 0.99 && (completeFraction > 0 || isScrollable(scrollable, { axis: scrollAxis, noCache: true })); if (canScroll) { setBooleanData(containerElement, hasBarPrefix); displayElement(scrollbar); } else { delData(containerElement, hasBarPrefix); undisplayElement(scrollbar); } }; // ---------- const updateProgress = (target, scrollData) => { if (!isMainScrollable && !isBody) { setBoxMeasureProps(containerElement); } setProgress(scrollData, true); setProgress(scrollData, false); if (autoHideDelay > 0) { showElement(wrapper).then(() => hideElement(wrapper, autoHideDelay)); } }; const updatePropsOnResize = (target, sizeData) => { setBoxMeasureProps(containerElement); setNumericStyleJsVars(containerElement, { barHeight: sizeData.border[MC.S_HEIGHT] }, { _units: "px", _numDecimal: 2 }); }; // ---------- let isDragging = false; let lastOffset = 0; let lastTargetFraction = 0; let scrollAction; const onClickOrDrag = async (event, tracksH) => { MH.preventDefault(event); const scrollbar = tracksH ? scrollbarH : scrollbarV; const handle = tracksH ? handleH : handleV; const target = MH.targetOf(event); if (!MH.isMouseEvent(event) || !MH.isHTMLElement(target)) { return; } const eventType = event.type; const isClick = eventType === MC.S_POINTERDOWN || eventType === MC.S_MOUSEDOWN; const isHandleClick = isClick && useHandle && hasClass(target, PREFIX_HANDLE); const startsDrag = isClick && dragScroll && (isHandleClick || !useHandle); if (startsDrag) { isDragging = true; setOrReleasePointerCapture(event, scrollbar, S_SET_POINTER_CAPTURE); } debug: logger === null || logger === void 0 || logger.debug10("Click or drag", { eventType, isClick, isHandleClick, isDragging, startsDrag }); if (!isClick && !isDragging || isClick && !startsDrag && !clickScroll) { // - Either moving pointer when no drag scroll has been started OR // - Clicking when no drag is allowed in the context of the click and no // click scroll is allowed either return; } await waitForMeasureTime(); const barIsHorizontal = isHorizontal(scrollbar); const barLength = barIsHorizontal ? scrollbar[MC.S_CLIENT_WIDTH] : scrollbar[MC.S_CLIENT_HEIGHT]; const currScrollOffset = tracksH ? scrollable[MC.S_SCROLL_LEFT] : scrollable[MC.S_SCROLL_TOP]; const maxScrollOffset = tracksH ? scrollable[MC.S_SCROLL_WIDTH] - getClientWidthNow(scrollable) : scrollable[MC.S_SCROLL_HEIGHT] - getClientHeightNow(scrollable); // Get click offset relative to the scrollbar regardless of what the // event target is and what transforms is has applied. const rect = MH.getBoundingClientRect(scrollbar); const offset = barIsHorizontal ? event.clientX - rect.left : event.clientY - rect.top; debug: logger === null || logger === void 0 || logger.debug10("Pointer offset", offset); if (offset === lastOffset) { return; } const deltaOffset = isClick ? offset : offset - lastOffset; lastOffset = offset; if (!isClick && useHandle) { // Dragging the handle const handleLength = handle ? MH.parseFloat(getComputedStylePropNow(handle, barIsHorizontal ? MC.S_WIDTH : MC.S_HEIGHT)) : 0; lastTargetFraction = lastTargetFraction + deltaOffset / (barLength - (handleLength || 0)); } else if (isHandleClick) { // Starting a handle drag lastTargetFraction = currScrollOffset / maxScrollOffset; } else { // Clicking or dragging on the bar -> scroll to the offset under the pointer lastTargetFraction = offset / barLength; } if (isHandleClick || isClick && !clickScroll) { return; } const targetScrollOffset = lastTargetFraction * maxScrollOffset; const targetCoordinates = tracksH ? { left: targetScrollOffset } : { top: targetScrollOffset }; debug: logger === null || logger === void 0 || logger.debug10("Scroll target offset", { lastTargetFraction, targetCoordinates }); if (isClick) { // smooth scroll scrollAction = await scrollWatcher.scrollTo(targetCoordinates, { scrollable, weCanInterrupt: true }); } else { var _scrollAction; (_scrollAction = scrollAction) === null || _scrollAction === void 0 || _scrollAction.cancel(); scrollAction = null; MH.elScrollTo(scrollable, targetCoordinates); } }; // ---------- const onRelease = (event, tracksH) => { const scrollbar = tracksH ? scrollbarH : scrollbarV; setOrReleasePointerCapture(event, scrollbar, S_RELEASE_POINTER_CAPTURE); isDragging = false; scrollAction = null; }; const onClickOrDragH = event => onClickOrDrag(event, true); const onClickOrDragV = event => onClickOrDrag(event, false); const onReleaseH = event => onRelease(event, true); const onReleaseV = event => onRelease(event, false); // ---------- const maybeSetNativeHidden = () => { if (hideNative) { addClasses(scrollable, PREFIX_HIDE_SCROLL); if (isBodyInQuirks) { addClasses(MH.getDocElement(), PREFIX_HIDE_SCROLL); } } }; const setNativeShown = () => { removeClasses(scrollable, PREFIX_HIDE_SCROLL); if (isBodyInQuirks) { removeClasses(MH.getDocElement(), PREFIX_HIDE_SCROLL); } }; // ---------- const addWatchers = () => { // Track scroll in any direction as well as changes in border or content size // of the element and its contents. scrollWatcher.trackScroll(updateProgress, { threshold: 0, scrollable }); // Track changes in content or border size of the container element which // would also detect changes in its padding. sizeWatcher.onResize(updatePropsOnResize, { target: containerElement, threshold: 0 }); }; const removeWatchers = () => { scrollWatcher.noTrackScroll(updateProgress, scrollable); sizeWatcher.offResize(updatePropsOnResize, containerElement); }; // SETUP ------------------------------ if (!supported) { setNativeShown(); return; } const scrollWatcher = ScrollWatcher.reuse({ [MC.S_DEBOUNCE_WINDOW]: 0 }); const sizeWatcher = SizeWatcher.reuse({ [MC.S_DEBOUNCE_WINDOW]: 0 }); if (!isMainScrollable && !isBody) { addClasses(containerElement, PREFIX_CONTAINER); } setBooleanData(containerElement, PREFIX_ALLOW_COLLAPSE, !isMobile()); setBooleanData(containerElement, PREFIX_HAS_WRAPPER, !!contentWrapper); setBooleanData(containerElement, PREFIX_HAS_V_SCROLL, !!contentWrapper && hasVScroll); // Wrap children if needed if (contentWrapper && !hasExistingWrapper) { wrapChildren(containerElement, { wrapper: contentWrapper, ignoreMove: true }); // no need to await here addClasses(contentWrapper, PREFIX_CONTENT); } maybeSetNativeHidden(); const origDomID = scrollable.id; if (config !== null && config !== void 0 && config.id) { scrollable.id = config.id; } if (config !== null && config !== void 0 && config.className) { addClasses(scrollable, ...toArrayIfSingle(config.className)); } const scrollDomID = // for ARIA clickScroll || dragScroll ? getOrAssignID(scrollable, S_SCROLLBAR) : ""; addClasses(barParent, PREFIX_ROOT); const wrapper = MH.createElement("div"); preventSelect(wrapper); addClasses(wrapper, MC.PREFIX_NO_TOUCH_ACTION); addClasses(wrapper, PREFIX_WRAPPER); if (isBody || isMainScrollable) { setData(wrapper, MC.PREFIX_POSITION, MC.S_FIXED); } else if (needsSticky) { setData(wrapper, MC.PREFIX_POSITION, MC.S_STICKY); } const { _bar: scrollbarH, _handle: handleH } = newScrollbar(wrapper, positionH); const { _bar: scrollbarV, _handle: handleV } = newScrollbar(wrapper, positionV); moveElement(wrapper, { to: barParent, position: "prepend" }); addWatchers(); // Track clicking and dragging on the two scrollbars if (dragScroll) { addEventListenerTo(scrollbarH, MC.S_POINTERMOVE, onClickOrDragH); addEventListenerTo(scrollbarV, MC.S_POINTERMOVE, onClickOrDragV); addEventListenerTo(scrollbarH, MC.S_POINTERUP, onReleaseH); addEventListenerTo(scrollbarV, MC.S_POINTERUP, onReleaseV); } if (dragScroll || clickScroll) { addEventListenerTo(scrollbarH, MC.S_POINTERDOWN, onClickOrDragH); addEventListenerTo(scrollbarV, MC.S_POINTERDOWN, onClickOrDragV); } widget.onDisable(() => { removeWatchers(); undisplayElement(scrollbarH); undisplayElement(scrollbarV); setNativeShown(); }); widget.onEnable(() => { addWatchers(); displayElement(scrollbarH); displayElement(scrollbarV); maybeSetNativeHidden(); }); widget.onDestroy(async () => { unmapScrollable(root); scrollable.id = origDomID; if (config !== null && config !== void 0 && config.className) { removeClasses(scrollable, ...toArrayIfSingle(config.className)); } await waitForMutateTime(); if (contentWrapper && !hasExistingWrapper) { unwrapContentNow(contentWrapper, [PREFIX_CONTENT]); } moveElementNow(wrapper); // remove if (dragScroll) { removeEventListenerFrom(scrollbarH, MC.S_POINTERMOVE, onClickOrDragH); removeEventListenerFrom(scrollbarV, MC.S_POINTERMOVE, onClickOrDragV); removeEventListenerFrom(scrollbarH, MC.S_POINTERUP, onReleaseH); removeEventListenerFrom(scrollbarV, MC.S_POINTERUP, onReleaseV); } if (dragScroll || clickScroll) { removeEventListenerFrom(scrollbarH, MC.S_POINTERDOWN, onClickOrDragH); removeEventListenerFrom(scrollbarV, MC.S_POINTERDOWN, onClickOrDragV); } removeClassesNow(barParent, PREFIX_ROOT); removeClassesNow(containerElement, PREFIX_CONTAINER); for (const position of [MC.S_TOP, MC.S_BOTTOM, MC.S_LEFT, MC.S_RIGHT]) { delDataNow(containerElement, `${PREFIX_HAS_SCROLLBAR}-${position}`); } delDataNow(containerElement, PREFIX_ALLOW_COLLAPSE); delDataNow(containerElement, PREFIX_HAS_WRAPPER); delDataNow(containerElement, PREFIX_HAS_V_SCROLL); }); }; const isHorizontal = scrollbar => getData(scrollbar, MC.PREFIX_ORIENTATION) === MC.S_HORIZONTAL; const setBoxMeasureProps = async element => { for (const side of [MC.S_TOP, MC.S_RIGHT, MC.S_BOTTOM, MC.S_LEFT]) { for (const key of [`padding-${side}`, `border-${side}-width`]) { const value = await getComputedStyleProp(element, key); setStyleProp(element, MH.prefixCssJsVar(key), value); } } }; const setOrReleasePointerCapture = (event, scrollbar, method) => { if (MH.isPointerEvent(event) && method in scrollbar) { scrollbar[method](event.pointerId); } }; //# sourceMappingURL=scrollbar.js.map