import prefix from "../global/prefix";
import optScroll from "../options/optScroll";
import createEl from "../utils/createEl";
import debounce from "../utils/debounce";
import extend from "../utils/extend";
import getDataType from "../utils/getDataType";
import getEl from "../utils/getEl";
import isMobi from "../utils/isMobi";
import pipe from "../utils/pipe";
import transformTools from "../utils/transformTools";
import style from "../utils/style";
import throttle from "../utils/throttle";
import Gesture from "./Gesture";
import PubBase from "./PubBase";
import PubCommon from "./PubCommon";
import PubListen from "./PubListen";
import PubUpdate from "./PubUpdate";
import delay from "../utils/delay";
import clampVal from "../utils/clampVal";
import toNumber from "../utils/toNumber";
import isNull from "../utils/isNull";
import isEmpty from "../utils/isEmpty";
import toPixel from "../utils/toPixel";
import config from "../global/config";
class Scroll extends pipe(PubListen, PubUpdate, PubCommon)(PubBase) {
    constructor(elem, options = {}, initial = config.initial) {
        super();
        this.options = {};
        this.ready({
            options,
            host: elem,
            maps: optScroll,
            component: false,
            spread: ['snap', 'drift', 'swipe', 'bar', 'keyboard']
        });
        if (this.options.axis === 'x') {
            this.propsMap = { position: 'left', axis: 'x', overflow: 'overflowX', client: 'clientX', size: 'width', dim: 'offsetWidth', index: 4, offset: 'offsetLeft', gap: 'marginLeft', reverse: { size: 'height', gap: 'marginTop' } };
        }
        else if (this.options.axis === 'y') {
            this.propsMap = { position: 'top', axis: 'y', overflow: 'overflowY', client: 'clientY', size: 'height', dim: 'offsetHeight', index: 5, offset: 'offsetTop', gap: 'marginTop', reverse: { size: 'width', gap: 'marginLeft' } };
        }
        this.wrapEl = this.options.wrapper ? getEl(this.options.wrapper) : this.targetEl.firstElementChild;
        if (!this.wrapEl)
            return this;
        this.snappeds = [];
        this.unsnappeds = [];
        this.snapped = null;
        this.started = false;
        this.scrolled = true;
        this.cross = '';
        this.isMobi = isMobi;
        this.childAvgSize = null;
        this.resizeCount = 0;
        this.mutateCount = 0;
        this.canResize = true;
        this.canMutate = true;
        this.initialResize = false;
        this.key2Trans = throttle((e) => {
            (e.code === 'ArrowLeft') ? this.toPrev() : (e.code === 'ArrowRight') ? this.toNext() : null;
        }, { intvl: this.options.keyboard.intvl, prevent: true });
        this.resizeListener = debounce((data) => {
            this.resizeCount++;
            for (let k of data) {
                if (!k.contentRect.width && !k.contentRect.height)
                    return;
                this.response();
            }
            super.listen({ name: 'resized', params: [data] });
            if (this.resizeCount === this.options.resizeThr || (!this.wrapEl.firstElementChild && this.resizeCount === this.options.resizeThr - 1)) {
                this.setTransInit();
                this.initialResize = true;
                super.listen({ name: 'prepared', params: [data] });
            }
        }, this.options.delay);
        this.mutationListener = debounce((data) => {
            this.mutateCount++;
            for (let k of data) {
                if (k.type === "attributes") {
                    this.updateProg();
                    this.options.bar.enable && this.updateBarProg();
                }
                if (k.type === "childList") {
                    this.options.snap.enable && this.getSnappeds();
                    this.response();
                }
            }
            super.listen({ name: 'refactored', params: [data] });
        }, this.options.delay);
        super.listen({ name: 'ready' });
        initial && this.init();
    }
    /**
    * @description Initialization instance.
    * @param {fn_cb_none} [cb] - Callback execution after initialization.
    * @returns {Promise<any>} - Returns a proimise whose argument is the instance itself.
    */
    async init(cb) {
        super.listen({ name: 'launch' });
        this.options.b4Init && await this.options.b4Init.call(this);
        this.setEmpty();
        this.setAttrs();
        this.transNow = this.transLast = this.options.paddingStart;
        this.gap = toPixel(this.options.child.gap);
        this.createBar();
        this.updateDiff();
        this.initSnapArgs();
        this.getSnappeds();
        this.getGestureIns();
        this.setObserver();
        super.listen({ name: 'init', cb });
        return this;
    }
    initSnapArgs() {
        if (!this.options.snap.enable)
            return this;
        if (this.options.snap.mode === 'auto') {
            !this.options.snap.selector && (this.options.snap.selector = '[unalignable]');
        }
        else {
            !this.options.snap.selector && (this.options.snap.selector = '[alignable]');
            !this.options.snap.offset && (this.options.snap.offset = 50);
        }
    }
    setEmpty() {
    }
    setAttrs() {
        this.targetEl.classList.add(`${prefix}scroll`);
        this.wrapEl.classList.add(`${prefix}scroll-wrap`);
        this.targetEl.setAttribute('axis', this.options.axis);
        this.options.gridded && this.targetEl.setAttribute('gridded', '');
    }
    getGestureIns() {
        this.gestureIns = new Gesture(this.wrapEl, extend({
            target: {
                wheel: true,
                keyboard: {
                    enable: this.options.keyboard,
                    prev: this.options.axis === 'x' ? 'ArrowLeft' : 'ArrowUp',
                    next: this.options.axis === 'x' ? 'ArrowRight' : 'ArrowDown',
                    target: this.targetEl,
                },
                spy: this.options.keyboard,
                step: {
                    mode: 'translate',
                    axis: this.options.axis,
                    value: this.options.wheel.value,
                    linkage: false,
                    duration: this.options.duration,
                },
                drift: {
                    enable: this.options.drift.enable,
                    coef: this.options.drift.coef,
                    auto: false,
                },
                swipe: {
                    enable: this.options.swipe.enable,
                    coef: 1,
                    auto: false,
                },
                initialVal: { [this.options.axis]: this.transNow },
                onStep: (data) => {
                    this.getStepVal(data.step.direction[this.options.axis]);
                },
                onStepped: (data) => {
                    this.scrollTo({
                        target: data.translate.value[this.options.axis],
                        duration: this.options.duration,
                        snap: true,
                    });
                },
                onTranslate: (data) => {
                    super.listen({ name: 'start', params: [data.translate] });
                },
                onTranslating: (data) => {
                    if (data.translate.diff.h > 2) {
                        this.transNow = data.translate.value[this.propsMap.axis];
                        this.cross = this.crossLinkage();
                        if (this.cross.includes('baby')) {
                            if (data.pointer === 'wheel')
                                return this;
                            if (this.cross === 'baby-start') {
                                this.transNow = this.getDmpVals('start+');
                            }
                            else if (this.cross === 'baby-start-reverse') {
                                this.transNow = this.getDmpVals('start-');
                            }
                        }
                        else if (this.cross === 'teen-start') {
                            if (data.pointer === 'wheel') {
                                this.toStart();
                                return this;
                            }
                            this.transNow = this.getDmpVals('start+');
                        }
                        else if (this.cross === 'teen-end') {
                            if (data.pointer === 'wheel') {
                                this.toEnd();
                                return this;
                            }
                            this.transNow = this.getDmpVals('end+');
                        }
                        else if (this.cross === 'adult-start') {
                            if (data.pointer === 'wheel') {
                                this.toStart();
                                return this;
                            }
                            this.transNow = this.getDmpVals('start+');
                        }
                        else if (this.cross === 'adult-end') {
                            if (data.pointer === 'wheel') {
                                this.toEnd();
                                return this;
                            }
                            this.transNow = this.getDmpVals('end+');
                        }
                        transformTools.set({
                            el: this.wrapEl,
                            data: { translate: { [this.options.axis]: this.transNow } }
                        });
                        this.exceedHandle({ linkage: this.cross });
                        super.listen({ name: 'move', params: [data.translate] });
                    }
                },
                onTranslated: (data) => {
                    this.transLast = this.transNow;
                    this.exceedHandle({
                        start: async (ratio) => {
                            this.options.b4ToStart && await this.options.b4ToStart.call(this, ratio);
                            this.toStart();
                            this.gestureIns.preventEase = true;
                        },
                        end: async (ratio) => {
                            this.options.b4ToEnd && await this.options.b4ToEnd.call(this, ratio);
                            this.toEnd(undefined, false);
                            this.gestureIns.preventEase = true;
                        },
                        normal: () => {
                            this.options.snap.enable && !data.translate.canDrfit && this.setSnapped();
                        }
                    });
                    super.listen({ name: 'end', params: [data.translate] });
                },
                onSwipe: (data) => {
                    this.setEaseTo(data, 'swipe');
                },
                onDrift: (data) => {
                    this.setEaseTo(data);
                },
            },
            source: this.options.gesture,
        }));
    }
    setTransInit() {
        let target = this.options.initial.value || this.rangeEnd, params = { target, snap: true };
        this.options.initial.mode === 'localte' ? this.locateTo(params) : this.scrollTo(params);
    }
    createBar() {
        if (this.options.bar.enable) {
            if (!this.trackEl) {
                this.thumbEl = createEl('span');
                this.trackEl = createEl('div', { class: `${prefix}scroll-bar` }, this.thumbEl);
                this.options.bar.sticky && this.trackEl.setAttribute('shown', '');
                this.targetEl.appendChild(this.trackEl);
            }
            if (this.options.bar.triggerable && !this.isMobi) {
                let prog = 0;
                this.barIns = new Gesture(this.trackEl, {
                    onTranslate: () => {
                        prog = this.progress;
                        this.trackEl.setAttribute('scrolling', '');
                    },
                    onTranslating: (data) => {
                        this.locateTo({
                            target: prog + data.translate.value[this.options.axis] / this.barSize.diff,
                            isRatio: true,
                        });
                    },
                    onTranslated: () => {
                        this.trackEl.removeAttribute('scrolling');
                    }
                });
            }
        }
    }
    setObserver() {
        this.wrapStyleObs = new MutationObserver(mutations => {
            this.canMutate && this.mutationListener(mutations);
        });
        this.wrapStyleObs.observe(this.wrapEl, {
            childList: true,
            attributes: true,
            attributeFilter: ['style']
        });
        this.sizeObs = new ResizeObserver((entries) => {
            this.canResize && this.resizeListener(entries);
        });
        this.sizeObs.observe(this.targetEl);
        this.sizeObs.observe(this.wrapEl);
    }
    updatePadding(start, end) {
        if (isNull(start) && isNull(end))
            return;
        !isNull(start) && typeof start === 'number' && (this.options.paddingStart = start);
        !isNull(end) && typeof end === 'number' && (this.options.paddingEnd = end);
        this.updateDiff();
        this.transNow < this.rangeStart && this.toEnd(undefined, false);
        this.transNow > this.rangeEnd && this.toStart();
    }
    response() {
        this.updateDiff();
        if (this.isBaby) {
            this.toStart();
        }
        else if (this.wrapEl[this.propsMap.offset] > this.paddingStart) {
            this.toStart();
        }
        else if (this.transNow < this.rangeStart) {
            this.toEnd(undefined, false);
        }
        else {
            this.options.snap.enable && this.setSnapped();
            /*
              this.scrollTo({
                  target:this.nowTrans,
                  snap:true,
              }) */
        }
    }
    getDmpVals(placement) {
        let val = 0, dmp = this.options.dmpRatio ** 2;
        if (placement === 'start+') {
            val = this.options.bounce ? this.paddingStart + (this.transNow - this.paddingStart) * dmp : this.paddingStart;
        }
        else if (placement === 'end+') {
            val = this.options.bounce ? this.rangeStart + (this.transNow - this.rangeStart) * dmp : this.rangeStart;
        }
        else if (placement === 'start-') {
            val = this.options.bounce ? (this.transNow - this.paddingStart) * dmp : this.paddingStart;
        }
        return val;
    }
    getTruePadding(val) {
        let result;
        if (val === 'center') {
            let firstSize = ~~(style(this.wrapEl.firstElementChild)[this.propsMap.size]).replace('px', ''), value = !isNull(this.childAvgSize) ? this.childAvgSize : firstSize;
            result = (this.targetSize - value) / 2;
        }
        else if (typeof val === 'number') {
            if (val <= 0) {
                result = 0;
            }
            else if (val < 1) {
                result = this.targetSize * val;
            }
            else {
                result = val;
            }
        }
        return result;
    }
    setChildSize() {
        let getVal = (val) => {
            let type = typeof val;
            if (type === 'string') {
                if (val.includes('/')) {
                    let avg = ~~(val.split('/')[1].trim());
                    return avg <= 1 ? this.targetSize : (this.targetSize - (avg - 1) * this.gap) / avg;
                }
                else {
                    return toPixel(val);
                }
            }
            else if (type === 'number') {
                return val < 0 ? 0 : (val <= 1 && val >= 0) ? this.targetSize * val : val;
            }
            else {
                return null;
            }
        };
        if (!isEmpty(this.options.child.size)) {
            let children = [...this.wrapEl.children];
            if (Array.isArray(this.options.child.size)) {
                if (this.options.child.settable) {
                    let vals = this.options.child.size.map((k) => getVal(k));
                    children.forEach((k, i) => {
                        !isNull(vals[i]) && (k.style[this.propsMap.size] = vals[i] + 'px');
                    });
                }
            }
            else {
                let val = getVal(this.options.child.size);
                this.childAvgSize = val;
                if (!isNull(val) && this.options.child.settable) {
                    for (let k of children) {
                        k.style[this.propsMap.size] = val + 'px';
                    }
                }
            }
        }
    }
    updateDiff() {
        let targetStyle = style(this.targetEl), wrapStyle = style(this.wrapEl), targetHeight = parseInt(targetStyle.height), wrapHeight = parseInt(wrapStyle.height), targetWidth = parseInt(targetStyle.width), wrapWidth = parseInt(wrapStyle.width), outerSize = 0, reverseSize = 0, innerSize = 0;
        if (this.options.axis === 'x') {
            outerSize = targetWidth;
            reverseSize = targetHeight;
            innerSize = wrapWidth;
        }
        else if (this.options.axis === 'y') {
            outerSize = targetHeight;
            reverseSize = targetWidth;
            innerSize = wrapHeight;
        }
        this.targetSize = outerSize;
        this.reverseSize = reverseSize;
        this.wrapSize = innerSize;
        this.setChildSize();
        this.paddingStart = this.getTruePadding(this.options.paddingStart);
        this.paddingEnd = this.getTruePadding(this.options.paddingEnd);
        if (this.paddingStart + this.paddingEnd >= outerSize) {
            this.paddingStart = this.paddingEnd = 0;
            console.warn('The combined padding values on both sides exceed the container size; padding values have been removed!');
        }
        this.rangeEnd = this.paddingStart;
        this.rangeStart = outerSize - innerSize - this.paddingEnd;
        this.activeSize = this.rangeEnd - this.rangeStart;
        this.baseSize = this.targetSize - this.paddingStart - this.paddingEnd;
        this.updateProg();
        this.getBarSize();
        this.updateBarProg();
        this.isBaby = this.rangeStart - this.paddingStart > 0;
    }
    getNowTargetDist(target) {
        if (!target)
            return 0;
        let targetType = getDataType(target);
        return targetType === 'Number' ? Math.abs(target - this.transNow) : targetType.includes('HTML') ? Math.abs(this.getChildParentOffset(target) - this.rangeEnd + this.transNow) : 0;
    }
    setEaseTo(data, type = 'drift') {
        data[type].value[this.options.axis] = this.getEaseDist(data.translate.direction[this.options.axis], this.getEdgeValue(data[type].value[this.options.axis]), type);
        let target = (this.options.snap.enable && this.options.snap.mode === 'auto') ? this.getNearestSnap(data[type].value[this.options.axis]).nearest : data[type].value[this.options.axis];
        this.scrollTo({
            target,
            snap: true,
        });
    }
    getEaseDist(dir, value, type = 'drift') {
        let result = value;
        if (this.options[type].mode === 'ratio') {
            let dft = this.options[type].value || 1;
            result = this.transNow + this.baseSize * dft * dir;
        }
        else if (this.options[type].mode === 'const') {
            this.options[type].value && (result = this.transNow + this.options[type].value * dir);
        }
        else if (this.options[type].mode === 'snap' && this.options.snap.enable && this.options.snap.mode === 'auto') {
            let tmp = this.getEaseNextTrans(dir, type);
            !isNull(tmp) && (result = tmp);
        }
        else if (this.options[type].mode === 'slide') {
            let tmp = this.getEaseNextTrans(dir, type, 'slide');
            !isNull(tmp) && (result = tmp);
        }
        return result;
    }
    getEaseNextTrans(dir, type = 'drift', mode = 'snap') {
        let result = null, chidlren = mode === 'slide' ? [...this.wrapEl.children] : this.snappeds, nearest = mode === 'slide' ? this.getNearestSlide() : this.snapped, idx = chidlren.findIndex((k) => k === nearest);
        if (idx > -1) {
            let num = [0, 1].includes(this.options[type].value) ? 1 : this.options[type].value, dist = dir === 1 ? this.getNowTargetDist(chidlren[idx - num]) : dir === -1 ? this.getNowTargetDist(chidlren[idx + num]) * -1 : 0;
            result = this.transNow + dist;
        }
        return result;
    }
    getStepVal(dir) {
        if (this.options.wheel.mode === 'ratio') {
            let dft = this.options.wheel.value || 0.25, val = this.baseSize * dft;
            this.gestureIns.stepVal = val;
        }
        else if (this.options.wheel.mode === 'const') {
            this.options.wheel.value && (this.gestureIns.stepVal = this.options.wheel.value);
        }
        else if (this.options.wheel.mode === 'snap' && this.options.snap.enable && this.options.snap.mode === 'auto') {
            this.getWheelNextTrans(dir);
        }
        else if (this.options.wheel.mode === 'slide') {
            this.getWheelNextTrans(dir, 'slide');
        }
    }
    getWheelNextTrans(dir, mode = 'snap') {
        let chidlren = mode === 'slide' ? [...this.wrapEl.children] : this.snappeds, nearest = mode === 'slide' ? this.getNearestSlide() : this.snapped, idx = chidlren.findIndex((k) => k === nearest);
        if (idx > -1) {
            let num = [0, 1].includes(this.options.wheel.value) ? 1 : this.options.wheel.value;
            this.gestureIns.stepVal = dir === 1 ? this.getNowTargetDist(chidlren[idx + num]) : dir === -1 ? this.getNowTargetDist(chidlren[idx - num]) : 0;
        }
    }
    getEdgeValue(data, isRatio = false) {
        let value = this.rangeEnd, dataType = getDataType(data);
        if (dataType.includes('HTML') || dataType === 'String') {
            let el = getEl(data);
            el && (value = -this.getChildParentOffset(el) + this.rangeEnd);
        }
        else {
            if (isRatio) {
                let ratio = clampVal({ min: 0, max: 1, val: data });
                value = this.rangeEnd - this.activeSize * ratio;
            }
            else {
                value = data;
            }
        }
        if (value || value === 0) {
            if (value >= this.rangeEnd || this.isBaby) {
                value = this.rangeEnd;
            }
            else if (value <= this.rangeStart) {
                value = this.rangeStart;
            }
        }
        return value;
    }
    updateProg(value = this.transNow) {
        if (this.activeSize <= 0) {
            this.progress = 1;
        }
        else {
            this.progress = (this.rangeEnd - value) / this.activeSize;
        }
    }
    getBarSize() {
        if (this.options.bar.enable) {
            let track = parseInt(style(this.trackEl)[this.propsMap.size]), ratio = Math.min(this.baseSize / this.wrapSize, 1), thumb = track * ratio;
            this.barSize = { track, thumb, diff: track - thumb };
        }
    }
    updateBarProg() {
        if (this.options.bar.enable) {
            let exceedRatio = this.progress < 0 ? Math.abs(this.progress) : this.progress > 1 ? this.progress - 1 : 0, progress = (this.progress >= 0 || this.progress <= 1) ? this.progress : 1, exceedSize = this.barSize.thumb * (1 - exceedRatio ** 2), thumbPos = (this.barSize.track - exceedSize) * progress;
            this.thumbEl.style[this.propsMap.size] = exceedSize + 'px';
            this.thumbEl.style[this.propsMap.position] = thumbPos + 'px';
        }
    }
    exceedListen(ratio, placement) {
        this.listen({ name: 'exceed', params: [ratio, placement] });
        ratio >= 1 && this.listen({ name: 'completed', params: [ratio, placement] });
    }
    exceedHandle({ start, end, normal, linkage }) {
        let ratio = 0, cross = linkage || this.crossLinkage();
        if (cross.includes('start')) {
            ratio = Math.abs(this.transNow / this.options.completedThr);
            start && start(ratio.toFixed(2));
            linkage && this.exceedListen(toNumber(ratio, { places: 2 }), 'start');
        }
        else if (cross.includes('end')) {
            ratio = Math.abs(this.transNow - this.rangeStart) / this.options.completedThr;
            end && end(ratio.toFixed(2));
            linkage && this.exceedListen(toNumber(ratio, { places: 2 }), 'end');
        }
        else {
            normal && normal();
        }
    }
    crossLinkage(value = this.transNow) {
        let result = 'regular';
        if (this.rangeStart > 0) {
            if (this.isBaby) {
                if (value > this.paddingStart) {
                    result = 'baby-start';
                }
                else {
                    result = 'baby-start-reverse';
                }
            }
            else {
                if (value > this.paddingStart) {
                    result = 'teen-start';
                }
                else if (value < this.rangeStart) {
                    result = 'teen-end';
                }
            }
        }
        else {
            if (value > this.paddingStart) {
                result = 'adult-start';
            }
            else if (value < this.rangeStart) {
                result = 'adult-end';
            }
        }
        return result;
    }
    rollback(dist, duration = this.options.duration) {
        setTimeout(() => {
            duration && (this.wrapEl.style.transitionDuration = duration + 'ms');
            transformTools.set({
                el: this.wrapEl,
                data: { translate: { [this.options.axis]: dist } }
            });
        }, 0);
    }
    toStart(duration, snap = true) {
        if (this.destroyed || this.transNow === this.rangeEnd)
            return this;
        this.transNow = this.transLast = this.paddingStart;
        this.scrollTo({
            snap,
            duration: duration || this.options.duration,
            after: () => {
                super.listen({ name: 'toStart' });
            }
        });
    }
    toEnd(duration, snap = true) {
        if (this.destroyed || this.transNow === this.rangeStart)
            return this;
        this.transNow = this.transLast = this.rangeStart;
        this.scrollTo({
            snap,
            duration: duration || this.options.duration,
            after: () => {
                super.listen({ name: 'toEnd' });
            }
        });
    }
    toPrev() {
        if (this.destroyed || this.transNow === this.rangeEnd)
            return this;
    }
    toNext() {
        if (this.destroyed || this.transNow === this.rangeStart)
            return this;
    }
    getSnappeds() {
        if (this.options.snap.enable) {
            if (this.options.snap.mode === 'auto') {
                this.unsnappeds = super.single2Els(this.options.snap.selector, this.wrapEl);
                this.snappeds = [...this.wrapEl.children].filter(k => !this.unsnappeds.includes(k));
            }
            else {
                this.snappeds = super.single2Els(this.options.snap.selector, this.wrapEl);
            }
            super.listen({ name: 'getSnappeds', params: [this.snappeds] });
        }
    }
    getChildParentOffset(child, parent = this.wrapEl) {
        return child.getBoundingClientRect()[this.propsMap.position] - parent.getBoundingClientRect()[this.propsMap.position];
    }
    sortEls(arr, val = this.transNow) {
        let tmp = arr.map((k) => {
            return { el: k, offset: Math.abs(val - this.paddingStart + this.getChildParentOffset(k)) };
        });
        tmp.sort((a, b) => a.offset - b.offset);
        return tmp;
    }
    getNearestSnap(val = this.transNow) {
        let snappeds = this.sortEls(this.snappeds, val), nearest = null;
        if (this.options.snap.mode === 'auto') {
            if (this.options.snap.offset) {
                let temp = snappeds.find((k) => k.offset <= this.options.snap.offset);
                nearest = temp ? temp.el : null;
            }
            else {
                nearest = snappeds[0].el;
            }
        }
        else {
            nearest = snappeds[0].offset <= this.options.snap.offset ? snappeds[0].el : null;
        }
        return { nearest, snappeds };
    }
    getNearestSlide(val = this.transNow) {
        let tmp = this.sortEls([...this.wrapEl.children], val);
        return tmp[0]?.el;
    }
    setSnapped(options = {}) {
        if (this.destroyed || this.wrapEl.children.length === 0 || !this.options.snap.enable)
            return this;
        this.scrolled = false;
        let opt = Object.assign({ force: this.options.snap.force }, options), current = this.getNearestSnap(), snappeds = current.snappeds, nearest = current.nearest, toEl = opt.target ? getEl(opt.target, this.wrapEl) : null, target = toEl || nearest;
        if (!nearest || !target)
            return this;
        if (target !== this.snapped || opt.force) {
            this.toggleSnappedAttr(target, snappeds);
            super.listen({ name: 'snap', cb: opt.before, params: [target] });
        }
        this.scrollTo({
            target,
            duration: opt.duration || this.options.duration,
            after: () => {
                if (target === this.snapped) {
                    super.listen({ name: 'snapped', cb: opt.after, params: [target] });
                }
            }
        });
    }
    toggleSnappedAttr(target, snappeds = this.getNearestSnap().snappeds) {
        let el = getEl(target, this.wrapEl), last;
        if (el) {
            el.setAttribute('snapped', '');
            last = snappeds.find(k => k.el !== el && k.el.hasAttribute('snapped'));
            last && last.el.removeAttribute('snapped');
            this.snapped = el;
        }
    }
    scrollTo({ target = this.transNow, duration, before, after, snap = false, isRatio = false }) {
        if (this.destroyed)
            return this;
        this.scrolled = false;
        let now = this.transNow, trans = this.getEdgeValue(target, isRatio), compare = Math.abs(this.transNow - trans) / this.targetSize, dur = duration || duration == 0 ? duration : Math.trunc(this.options.duration * compare);
        this.wrapEl.style.transitionDuration = clampVal({ val: dur, min: 200, max: 2000 }) + 'ms';
        transformTools.set({ el: this.wrapEl, data: { translate: { [this.options.axis]: trans } } });
        super.listen({ name: 'scroll', cb: before, params: [trans, now] });
        this.transLast = this.transNow = trans;
        delay({
            duration: dur,
            done: () => {
                if (this.options.snap.enable) {
                    snap ? this.setSnapped() : this.getNearestSnap();
                }
                super.listen({ name: 'scrolled', cb: after, params: [trans, now] });
                this.scrolled = true;
            }
        });
    }
    locateTo({ target = this.transNow, before, after, snap = false, isRatio = false }) {
        if (this.destroyed)
            return this;
        let now = this.transNow, trans = this.getEdgeValue(target, isRatio);
        this.wrapEl.style.transitionDuration = '0ms';
        transformTools.set({ el: this.wrapEl, data: { translate: { [this.options.axis]: trans } } });
        super.listen({ name: 'locate', cb: before, params: [trans, now] });
        this.transLast = this.transNow = trans;
        if (this.options.snap.enable) {
            snap ? this.setSnapped() : this.getNearestSnap();
        }
        super.listen({ name: 'located', cb: after, params: [trans, now] });
    }
    /**
     * @description Destroy instance and prohibit use.
     * @param {fn_cb_none} [cb] - Callback execution after destruction.
     * @returns {any} - Return the instance itself.
     */
    destroy(cb) {
        if (this.destroyed)
            return this;
        this.sizeObs.disconnect();
        this.wrapStyleObs.disconnect();
        this.options.bar.enable && this.options.bar.triggerable && !this.isMobi && this.barIns.destroy();
        this.gestureIns.destroy();
        this.destroyed = true;
        super.listen({ name: 'destroy', cb });
        return this;
    }
}
Scroll.hostType = 'node';
export default Scroll;
