UNPKG

animatry

Version:
2,035 lines 82.6 kB
let globalOptions = {};
function setGlobalOptions(options) {
    globalOptions = { ...globalOptions, ...options };
}
const controllerFunctions = () => {
    return {
        onUpdate: () => { },
        onChange: () => { },
        onStart: () => { },
        onComplete: () => { },
        onReverseStart: () => { },
        onReverseComplete: () => { },
        onRepeat: () => { },
    };
};
const controllerOptions = () => {
    return {
        id: undefined,
        delay: 0,
        delayRecharge: false,
        duration: 1,
        repeat: 0,
        iterationDelay: 0,
        alternate: false,
        paused: false,
        ease: undefined,
        alternateEase: undefined,
        playhead: 0,
        iteration: 0,
        reversed: false,
        timeScale: 1,
        at: undefined,
        preRender: false,
        stagger: 0,
        keyframes: undefined,
        backwards: false,
    };
};
const controllerSettings = () => {
    return Object.assign({}, controllerFunctions(), controllerOptions(), globalOptions);
};

var _a;
class Ease {
    static steps(numSteps = 3) {
        const stepSize = 1 / numSteps;
        const halfStepSize = stepSize / 2;
        return (x) => {
            const centeredX = x + halfStepSize;
            const stepIndex = Math.floor(centeredX / stepSize);
            return stepIndex * stepSize;
        };
    }
    static cubicBezier(p1x, p1y, p2x, p2y) {
        function cubic(t, a1, a2) {
            const c = 3 * a1;
            const b = 3 * (a2 - a1) - c;
            const a = 1 - c - b;
            return ((a * t + b) * t + c) * t;
        }
        function derivativeCubic(t, a1, a2) {
            const c = 3 * a1;
            const b = 3 * (a2 - a1) - c;
            const a = 1 - c - b;
            return (3 * a * t + 2 * b) * t + c;
        }
        return function (x) {
            let t = x;
            for (let i = 0; i < 5; i++) {
                const xEstimate = cubic(t, p1x, p2x);
                const derivative = derivativeCubic(t, p1x, p2x);
                if (Math.abs(xEstimate - x) < 1e-5)
                    return cubic(t, p1y, p2y);
                t -= (xEstimate - x) / derivative;
            }
            return cubic(t, p1y, p2y);
        };
    }
    ;
    static parse(ease) {
        if (typeof ease == 'string') {
            const easeFunctionMatch = ease.match(/(\w+)\(([^)]*)\)/);
            if (easeFunctionMatch) {
                const functionName = easeFunctionMatch[1];
                const argsString = easeFunctionMatch[2].trim();
                const args = argsString ? argsString.split(',').map(arg => parseFloat(arg)) : [];
                if (this[functionName] !== undefined) {
                    return this[functionName](...args);
                }
                animatry.warn(`ease '${functionName}' is not defined.`);
                return _a.linear();
            }
            if (this[ease] == undefined) {
                animatry.warn(`ease '${ease}' is not defined.`);
                return _a.linear();
            }
            return this[ease]();
        }
        return ease;
    }
}
_a = Ease;
Ease.none = () => (x) => x;
Ease.linear = () => (x) => x;
Ease.powerIn = (power = 1) => (x) => Math.pow(x, power + 1);
Ease.powerOut = (power = 1) => (x) => 1 - Math.pow(1 - x, power + 1);
Ease.powerInOut = (power = 1) => (x) => x < 0.5 ? 0.5 * Math.pow(2 * x, power + 1) : 1 - 0.5 * Math.pow(2 * (1 - x), power + 1);
Ease.bounceIn = () => (x) => 1 - _a.bounceOut()(1 - x);
Ease.bounceOut = () => (x) => {
    if (x < 1 / 2.75) {
        return 7.5625 * x * x;
    }
    else if (x < 2 / 2.75) {
        return 7.5625 * (x -= 1.5 / 2.75) * x + 0.75;
    }
    else if (x < 2.5 / 2.75) {
        return 7.5625 * (x -= 2.25 / 2.75) * x + 0.9375;
    }
    else {
        return 7.5625 * (x -= 2.625 / 2.75) * x + 0.984375;
    }
};
Ease.bounceInOut = () => (x) => x < 0.5 ? 0.5 * _a.bounceIn()(x * 2) : 0.5 * _a.bounceOut()(x * 2 - 1) + 0.5;
Ease.elasticIn = (frequency = 6) => (x) => 1 - _a.elasticOut(frequency)(1 - x);
Ease.elasticOut = (frequency = 6) => (x) => {
    if (x < 0)
        return 0;
    if (x > 1)
        return 1;
    const decay = Math.pow(0.025, x);
    const oscillation = Math.pow(x, frequency ** 2);
    const smoothing = Math.pow(1 - x, 1);
    return 1 + Math.sin(x * frequency * Math.PI - Math.PI / 2) * decay * (1 - oscillation) * smoothing;
};
Ease.elasticInOut = (frequency = 6) => (x) => x < 0.5 ? _a.elasticIn(frequency)(x * 2) * 0.5 : _a.elasticOut(frequency)(x * 2 - 1) * 0.5 + 0.5;
Ease.backIn = (magnitude = 1.70158) => (x) => x * x * ((magnitude + 1) * x - magnitude);
Ease.backOut = (magnitude = 1.70158) => (x) => 1 + ((x - 1) ** 2 * ((magnitude + 1) * (x - 1) + magnitude));
Ease.backInOut = (magnitude = 1.70158) => (x) => x < 0.5 ? 0.5 * _a.backIn(magnitude)(x * 2) : 0.5 * _a.backOut(magnitude)(x * 2 - 1) + 0.5;

class Controller {
    constructor(options) {
        this.delayProgress = 0;
        this.iterationDelayProgress = 0;
        this.isPaused = false;
        this.isReversed = false;
        this.playhead = 0;
        this.frame = -1;
        this.tick = 0;
        this.initialized = false;
        this.options = Object.assign(controllerSettings(), options);
        this.options.ease = Ease.parse(this.options.ease);
        this.options.alternateEase = Ease.parse(this.options.alternateEase);
        this.playhead = this.options.playhead;
        this.setReversed(this.options.reversed);
        Promise.resolve().then(() => {
            if (!this.getParent() && !this.options.paused && !this.isPaused)
                this.play();
        });
    }
    _render() {
        if (this.parent != undefined)
            return;
        const now = performance.now();
        const deltaTime = (this.isReversed ? this.tick - now : now - this.tick) * this.getTimeScale() / 1000;
        this.tick = now;
        if (!this.isReversed && this.delayProgress < 1 || this.options.delayRecharge && this.isReversed && this.getTotalProgress() == 0) {
            this.setDelayProgress(this.getDelay() > 0 ? (this.getDelayProgress() + deltaTime / this.getDelay()) : 1);
        }
        else {
            this.setTotalProgress(this.getTotalProgress() + deltaTime / (this.getTotalDuration() || 1e-8));
        }
        if (!this.isPaused) {
            this.frame = requestAnimationFrame(this._render.bind(this));
        }
    }
    play(seek = undefined) {
        if (this.playhead == 1)
            return;
        this.isPaused = false;
        this.tick = performance.now();
        this.isReversed = false;
        if (this.frame == -1)
            this._render();
        if (seek != undefined)
            this.seek(seek);
    }
    pause() {
        this.isPaused = true;
        cancelAnimationFrame(this.frame);
        this.frame = -1;
        if (this.getDelayProgress() != 1) {
            this.setDelayProgress(0);
        }
    }
    reverse() {
        if (this.playhead == 0)
            return;
        this.isPaused = false;
        this.tick = performance.now();
        this.isReversed = true;
        if (this.frame == -1)
            this._render();
    }
    continue() {
        if (this.isReversed) {
            this.reverse();
        }
        else {
            this.play();
        }
    }
    restart() {
        this.pause();
        this.setTotalProgress(0);
        this.play();
    }
    reset() {
        this.setDelayProgress(0);
        this.setTotalProgress(0);
    }
    seek(elapsed) {
        this.setTotalElapsed(elapsed);
    }
    complete() {
        this.setTotalProgress(1);
    }
    lastPlay() {
        this.setIteration(this.getReversed() ? 0 : this.getRepeat());
    }
    getInitialized() {
        return this.options.preRender || this.initialized;
    }
    setInitialized() {
        this.initialized = true;
    }
    getParent() {
        return this.parent;
    }
    setParent(parent) {
        this.parent = parent;
    }
    getId() {
        var _a;
        return (_a = this.options.id) !== null && _a !== void 0 ? _a : '';
    }
    setId(id) {
        this.options.id = id;
    }
    getDuration() {
        return Math.min(this.options.duration, 10 ** 8);
    }
    setDuration(duration, keepProgress = true) {
        if (!keepProgress) {
            this.setProgress((this.getProgress() / duration) * this.getDuration());
        }
        this.options.duration = duration;
        return this;
    }
    getProgress() {
        return Controller.getProgress(this, this.playhead);
    }
    setProgress(progress) {
        this.setTotalElapsed(this.getIteration() * (this.getDuration() + this.getIterationDelay()) + (this.isAlternating() ? 1 - progress : progress) * this.getDuration());
        return this;
    }
    getElapsed() {
        return this.getProgress() * this.getDuration();
    }
    setElapsed(elapsed) {
        this.setProgress(this.getDuration() == 0 ? elapsed : elapsed / this.getDuration());
        return this;
    }
    getTotalProgress() {
        return this.playhead;
    }
    setTotalProgress(totalProgress, events = true) {
        totalProgress = animatry.clamp(totalProgress, 0, 1);
        if (this.getReversed()) {
            if (totalProgress != 0)
                this.isPaused = false;
        }
        else {
            if (totalProgress != 1)
                this.isPaused = false;
        }
        if (events) {
            if (this.getTotalProgress() == 0 && totalProgress > 0)
                this.options.onStart(this);
            if (this.getTotalProgress() < 1 && totalProgress == 1)
                this.options.onComplete(this);
            if (this.getTotalProgress() == 1 && totalProgress < 1)
                this.options.onReverseStart(this);
            if (this.getTotalProgress() > 0 && totalProgress == 0)
                this.options.onReverseComplete(this);
            if (this.getIteration() != Controller.getIteration(this, totalProgress))
                this.options.onRepeat(this);
        }
        this.playhead = totalProgress;
        if (this.isPlaying()) {
            if (this.getReversed()) {
                if (totalProgress == 0) {
                    if (!this.options.delayRecharge || this.getDelayProgress() == 0) {
                        this.pause();
                    }
                }
            }
            else {
                if (totalProgress == 1) {
                    this.pause();
                }
            }
        }
    }
    getTotalElapsed() {
        return this.getTotalProgress() * (this.getTotalDuration() || 1e-8);
    }
    setTotalElapsed(totalElapsed) {
        this.setTotalProgress(totalElapsed / (this.getTotalDuration() || 1e-8));
    }
    getDelay() {
        return this.options.delay;
    }
    setDelay(delay, restartDelay = undefined) {
        if (restartDelay == undefined) {
            restartDelay = this.delayProgress !== 1;
        }
        const delayBefore = this.options.delay;
        this.options.delay = delay;
        if (restartDelay) {
            this.delayProgress = 0;
            return this;
        }
        this.delayProgress = animatry.clamp((this.delayProgress / delay) * delayBefore, 0, delay);
        return this;
    }
    getDelayProgress() {
        if (this.getDelay() == 0)
            return 1;
        return this.delayProgress;
    }
    setDelayProgress(delayProgress) {
        this.delayProgress = animatry.clamp(delayProgress, 0, 1);
        return this;
    }
    getDelayElapsed() {
        return this.getDelayProgress() * this.getDelay();
    }
    setDelayElapsed(delayElapsed) {
        this.setDelayProgress(delayElapsed / this.getDelay());
        return this;
    }
    getReversed() {
        return this.isReversed;
    }
    setReversed(reversed) {
        this.isReversed = reversed;
    }
    getRepeat() {
        const { repeat, duration } = this.options;
        if (this.getDuration() == 0)
            return repeat == -1 ? 10 ** 8 : repeat;
        return Math.floor(repeat == -1 ? 10 ** 8 / duration : Math.min(repeat, 10 ** 8 / duration));
    }
    setRepeat(repeat, keepIterations = true) {
        const iteration = this.getIteration();
        const progress = this.getProgress();
        this.options.repeat = repeat;
        this.setIteration(iteration);
        this.setProgress(progress);
        if (!keepIterations)
            this.setIteration(0);
        if (iteration > repeat)
            this.setTotalProgress(1);
        return this;
    }
    getAlternate() {
        return this.options.alternate;
    }
    setAlternate(alternate, smoothJump = true) {
        const wasAlternating = this.isAlternating();
        if (this.getAlternate() != alternate) {
            this.options.alternate = alternate;
            if (smoothJump && wasAlternating !== this.isAlternating()) {
                this.setProgress(1 - this.getProgress());
            }
        }
        return this;
    }
    getIteration() {
        if (this.getTotalDuration() == 0) {
            return Math.round(this.getTotalProgress()) * this.getRepeat();
        }
        if (this.getTotalProgress() == 1) {
            return this.getRepeat();
        }
        const duration = this.getDuration() + this.getIterationDelay();
        const totalElapsed = this.getTotalDuration() * this.playhead;
        return Math.floor(totalElapsed / duration);
    }
    setIteration(iteration, smoothJump = true) {
        if (smoothJump && this.getAlternate() && this.getIteration() % 2 != iteration % 2) {
            this.setProgress(1 - this.getProgress());
        }
        this.setTotalElapsed(iteration * (this.getDuration() + this.getIterationDelay()) + (this.isAlternating() ? this.getDuration() - this.getElapsed() : this.getElapsed()));
        return this;
    }
    getIterationDelay() {
        return this.options.iterationDelay;
    }
    setIterationDelay(iterationDelay) {
        const progress = this.getProgress();
        const iteration = this.getIteration();
        this.options.iterationDelay = iterationDelay;
        this.setIteration(iteration);
        this.setProgress(progress);
        return this;
    }
    getIterationDelayProgress() {
        return this.iterationDelayProgress;
    }
    setIterationDelayProgress(iterationDelayProgress) {
        this.iterationDelayProgress = animatry.clamp(iterationDelayProgress, 0, 1);
        return this;
    }
    getTimeScale() {
        return this.options.timeScale;
    }
    setTimeScale(timeScale) {
        this.options.timeScale = timeScale;
    }
    getTotalDuration() {
        return (this.getRepeat() + 1) * this.getDuration() + this.getRepeat() * this.getIterationDelay();
    }
    getEasedProgress() {
        return Controller.getEasedProgress(this, this.playhead);
    }
    getEasedElapsed() {
        return this.getEasedProgress() * this.getDuration();
    }
    isAlternating() {
        return Controller.isAlternating(this, this.playhead);
    }
    isPlaying() {
        return !this.isPaused;
    }
    onChange(callback) {
        this.options.onChange = callback;
    }
    onUpdate(callback) {
        this.options.onUpdate = callback;
    }
    onStart(callback) {
        this.options.onStart = callback;
    }
    onRepeat(callback) {
        this.options.onRepeat = callback;
    }
    onComplete(callback) {
        this.options.onComplete = callback;
    }
    onReverseStart(callback) {
        this.options.onReverseStart = callback;
    }
    onReverseComplete(callback) {
        this.options.onReverseComplete = callback;
    }
    static getProgress(instance, ph) {
        if (instance.getDuration() == 0) {
            if (instance.getRepeat() > 0) {
                const value = Math.ceil(animatry.clamp(ph * instance.getRepeat(), 0, 1));
                return (Controller.isAlternating(instance, ph) ? 1 - value : value);
            }
            return Math.round(ph);
        }
        if (ph == 1 && instance.getIterationDelay() == 0) {
            if (instance.getAlternate()) {
                if (Controller.isAlternating(instance, ph)) {
                    return 0;
                }
            }
            return 1;
        }
        const duration = instance.getDuration() + instance.getIterationDelay();
        const totalElapsed = (instance.getTotalDuration() || 1e-8) * ph;
        let elapsed = totalElapsed % duration;
        let progress = animatry.clamp(elapsed / instance.getDuration(), 0, 1);
        return (Controller.isAlternating(instance, ph) ? 1 - progress : progress);
    }
    static getEasedProgress(instance, ph) {
        var _a;
        const progress = Controller.getProgress(instance, ph);
        const easeFunction = (Controller.isAlternating(instance, instance.options.backwards ? 1 - ph : ph) && instance.options.alternateEase !== undefined)
            ? instance.options.alternateEase
            : ((_a = instance.options.ease) !== null && _a !== void 0 ? _a : Ease.powerInOut());
        return instance.options.backwards ? 1 - easeFunction(progress) : easeFunction(progress);
    }
    static getIteration(instance, ph) {
        if (ph == 0) {
            return Math.round(ph) * instance.getRepeat();
        }
        if (ph == 1) {
            return instance.getRepeat();
        }
        const duration = instance.getDuration() + instance.getIterationDelay();
        const totalElapsed = (instance.getTotalDuration() || 1e-8) * ph;
        return Math.floor(totalElapsed / duration);
    }
    static isAlternating(instance, ph) {
        return instance.getAlternate() && Controller.getIteration(instance, ph) % 2 == 1;
    }
}

const _timestamp = (previous, time, labels, object = undefined) => {
    var _a, _b, _c, _d, _e;
    let prevStart = (_a = previous === null || previous === void 0 ? void 0 : previous.getStartTime()) !== null && _a !== void 0 ? _a : 0;
    let prevEnd = (_b = previous === null || previous === void 0 ? void 0 : previous.getEndTime()) !== null && _b !== void 0 ? _b : 0;
    if (typeof time === 'number')
        return time !== null && time !== void 0 ? time : prevEnd;
    if (!time && prevEnd >= 10 ** 8) {
        animatry.warn(`Placing a tween after someting infinite will not work.`);
    }
    if (!time)
        return prevEnd;
    const [, label, calc, number, unit] = time.match(/(^[A-Za-z][\w]*|^<|^>)?([+-]=)?([+-]?\d*\.?\d+)?(\w*|%)?$/) || [];
    if ((unit && !number && !label) || (unit && !number)) {
        animatry.warn(`invalid format '${time}'`);
        return 0;
    }
    const hasLabel = /^[A-Za-z]/.test(time);
    if (hasLabel && labels[label] == undefined)
        label ? animatry.warn(`label '${label}' not defined`) : animatry.warn(`invalid format '${time}'`);
    const start = hasLabel ? labels[label] : /^</.test(time) ? prevStart : prevEnd;
    let additive = hasLabel || /^[<>]/.test(time) || unit === '%' || /[+-]=/.test(calc);
    let nmbr = parseFloat(number) || 0;
    if (calc === null || calc === void 0 ? void 0 : calc.startsWith('-'))
        nmbr *= -1;
    let unitMultiplier;
    if (unit === '%') {
        if (!object) {
            unitMultiplier = (((_d = (_c = previous === null || previous === void 0 ? void 0 : previous.getAnimation()) === null || _c === void 0 ? void 0 : _c.getParent()) === null || _d === void 0 ? void 0 : _d.getDuration()) || 0) / 100;
        }
        else if (/[+-]=/.test(calc) || (hasLabel && !calc)) {
            unitMultiplier = ((object === null || object === void 0 ? void 0 : object.getTotalDuration()) || 1e-8) / 100;
        }
        else {
            unitMultiplier = (((_e = previous === null || previous === void 0 ? void 0 : previous.getAnimation()) === null || _e === void 0 ? void 0 : _e.getTotalDuration()) || 1e-8) / 100;
        }
    }
    else if (unit === 'ms') {
        unitMultiplier = 1 / 1000;
    }
    else {
        unitMultiplier = 1;
    }
    const res = (additive ? start : 0) + nmbr * unitMultiplier;
    return isNaN(res) ? prevEnd : res;
};
class Attached {
    constructor(animation, time) {
        this.animation = animation;
        this.time = time;
    }
    getAnimation() {
        return this.animation;
    }
    getStartTime() {
        return this.time + this.animation.getDelay();
    }
    getEndTime() {
        return this.getStartTime() + this.animation.getTotalDuration() / this.animation.getTimeScale() + (this.animation.getTotalDuration() == 0 ? 1e-8 : 0);
    }
}
class Timeline extends Controller {
    constructor(options = {}) {
        super(Object.assign({
            duration: 0,
            ease: Ease.none()
        }, options));
        this.attacheds = [];
        this.labels = {};
        this.controller = options;
    }
    getController() {
        return this.controller;
    }
    setTotalProgress(totalProgress, events = true) {
        totalProgress = animatry.clamp(totalProgress, 0, 1);
        const easedElapsed = Controller.getEasedProgress(this, totalProgress) * this.getDuration();
        let reversed = totalProgress < this.getTotalProgress() !== this.isAlternating();
        const attachedList = reversed ? this.attacheds.slice().reverse() : this.attacheds;
        const shouldInitialize = this.options.preRender;
        attachedList.forEach(attached => {
            const animation = attached.getAnimation();
            let elapsed = easedElapsed - attached.getStartTime();
            const isConditionMet = reversed ? easedElapsed < attached.getEndTime() : easedElapsed > attached.getStartTime();
            if (shouldInitialize || isConditionMet) {
                animation.setInitialized();
            }
            if (isConditionMet) {
                if (animation.getIteration() > 0) {
                    if (animation.isAlternating()) {
                        animation.setTotalProgress(1, elapsed >= attached.getEndTime());
                    }
                }
                animation.setTotalElapsed(elapsed);
            }
        });
        super.setTotalProgress(totalProgress, events);
    }
    _updateDuration(crop = false) {
        let maxTime = 0;
        if (!crop) {
            maxTime = this.getDuration();
        }
        this.attacheds.forEach(attached => {
            if (attached.getEndTime() > maxTime) {
                maxTime = attached.getEndTime();
            }
        });
        this.setDuration(maxTime, false);
    }
    label(name, time = undefined) {
        if (!/^[A-Za-z][\w-]*$/.test(name)) {
            animatry.warn(`'${name}' includes invalid characters`);
            return this;
        }
        this.labels[name] = _timestamp(this.attacheds[this.attacheds.length - 1], time, this.labels);
        return this;
    }
    add(object, time = undefined) {
        var _a, _b;
        if (!(object instanceof Controller)) {
            animatry.warn(`invalid object '${object}' did you intend to use .to() instead of .add() ?`);
            object = animatry.to(object, time);
            time = undefined;
        }
        let previous = this.attacheds[this.attacheds.length - 1];
        const start = _timestamp(previous, (_b = (_a = time !== null && time !== void 0 ? time : object.options.at) !== null && _a !== void 0 ? _a : previous === null || previous === void 0 ? void 0 : previous.getEndTime()) !== null && _b !== void 0 ? _b : 0, this.labels, object);
        if (object.options.duration >= 10 ** 8 && (object.options.repeat != 0 || this.options.repeat != 0)) {
            object.setRepeat(0);
            this.setRepeat(0);
        }
        else if (this.options.repeat != 0 && object.options.repeat == -1) {
            this.setRepeat(0);
        }
        object.setParent(this);
        const attached = new Attached(object, start);
        this.attacheds.push(attached);
        this._updateDuration();
        if (!this.options.paused)
            this.play();
        return this;
    }
    play(seek = undefined) {
        this.attacheds.forEach(attached => {
            attached.getAnimation().play();
        });
        super.play(seek);
    }
    reverse() {
        this.attacheds.forEach(attached => {
            attached.getAnimation().reverse();
        });
        super.reverse();
    }
    seek(elapsed) {
        if (typeof elapsed === 'string' && /^[A-Za-z]|^[<>]/.test(elapsed)) {
            let previous = this.attacheds[this.attacheds.length - 1];
            elapsed = _timestamp(previous, elapsed, this.labels);
        }
        super.seek(elapsed);
    }
    fromTo(el, from, to, time = undefined) {
        return this.add(animatry.fromTo(el, from, to), time);
    }
    to(el, to, time = undefined) {
        return this.add(animatry.to(el, to), time);
    }
    from(el, from, time = undefined) {
        return this.add(animatry.from(el, from), time);
    }
    set(el, options, time = undefined) {
        return this.add(animatry.set(el, options), time);
    }
}

const keyframes = (tween, element, from, to) => {
    const timeline = new Timeline();
    const options = tween.options.keyframes;
    const duration = tween.getDuration() || 1e-8;
    const preRender = tween.options.preRender;
    timeline.options.ease = options.ease ? Ease.parse(options.ease) : Ease.parse('none');
    const fromValues = animatry.filterObjects(from, controllerSettings())[1];
    if (options instanceof Array) {
        const frameCount = Object.keys(options).length;
        options.forEach((keyframe, index) => {
            timeline.add(new Tween(element, index == 0 ? fromValues : {}, {
                ...keyframe,
                at: index * duration / frameCount,
                duration: duration / frameCount,
                ...(preRender && index === 0 && { preRender: true })
            }));
        });
    }
    else {
        const keyframes = animatry.filterObjects(options, { ease: null, easeEach: null })[1];
        const toValues = animatry.filterObjects(to, controllerSettings())[1];
        if (Object.keys(keyframes)[0].includes('%')) {
            const percentageKey = Object.keys(keyframes).sort((a, b) => parseFloat(a) - parseFloat(b));
            let lastAppeared = {};
            const keyframeProperties = Array.from(new Set(Object.values(keyframes).flatMap(Object.keys)));
            const additionalKeyframes = {
                '0%': { ...Object.fromEntries(Object.entries(fromValues).filter(([key]) => keyframeProperties.includes(key))) },
                '100%': { ...Object.fromEntries(Object.entries(toValues).filter(([key]) => keyframeProperties.includes(key))) }
            };
            const combinedPercentages = [...new Set([
                    ...(Object.keys(additionalKeyframes['0%']).length > 0 ? ['0%'] : []),
                    ...percentageKey,
                    ...(Object.keys(additionalKeyframes['100%']).length > 0 ? ['100%'] : [])
                ])];
            const combinedKeyframes = combinedPercentages.reduce((acc, percentage) => ({
                ...acc,
                [percentage]: {
                    ...(additionalKeyframes[percentage] || {}),
                    ...(keyframes[percentage] || {}),
                },
            }), {});
            combinedPercentages.forEach(key => {
                const filtered = animatry.filterObjects(combinedKeyframes[key], controllerOptions());
                const controller = filtered[0];
                const frameOptions = filtered[1];
                let numberKey = 1 / 100 * Number.parseFloat(key);
                Object.keys(frameOptions).forEach(property => {
                    var _a, _b, _c, _d, _e;
                    if (numberKey != 0) {
                        timeline.add(new Tween(element, lastAppeared[property] != null ? { [property]: combinedKeyframes[(lastAppeared[property] * 100) + '%'][property] } : {}, {
                            [property]: frameOptions[property],
                            duration: (numberKey - ((_a = lastAppeared[property]) !== null && _a !== void 0 ? _a : 0)) * duration,
                            ...controller,
                            ease: (_d = (_c = (_b = controller['ease']) !== null && _b !== void 0 ? _b : options['easeEach']) !== null && _c !== void 0 ? _c : tween.options.ease) !== null && _d !== void 0 ? _d : Ease.powerInOut(),
                            ...(preRender && !lastAppeared[property]) ? { preRender: true } : {},
                        }), ((_e = lastAppeared[property]) !== null && _e !== void 0 ? _e : 0) * duration);
                    }
                    lastAppeared[property] = numberKey;
                });
                timeline.setDuration(duration);
            });
        }
        else {
            const keys = Object.keys(keyframes);
            keys.forEach(key => {
                var _a;
                const values = keyframes[key];
                const valueCount = values.length;
                const stepDuration = duration / Math.max(1, valueCount - 1);
                for (let index = 0; index < (valueCount === 1 ? 1 : valueCount - 1); index++) {
                    timeline.add(new Tween(element, {
                        [key]: values[index]
                    }, {
                        [key]: valueCount === 1 ? values[0] : values[index + 1],
                        at: valueCount === 1 ? 0 : index * stepDuration,
                        duration: valueCount === 1 ? duration / valueCount : stepDuration,
                        ease: (_a = options['easeEach']) !== null && _a !== void 0 ? _a : tween.options.ease,
                        ...(preRender && index === 0 ? { preRender: true } : {}),
                    }));
                }
            });
        }
    }
    return timeline;
};

const regex = /^\s*([+-]=)?\s*([+-]?\d*\.?\d+)\s*([a-z%Q]+)?\s*$/;
const isSignableNumber = (s) => regex.test(s);
const combineSignedNumbers = (uc, prop, a, b) => {
    [a, b] = unifySignedNumberUnits(uc, prop, a, b);
    return [false, b[0] ? a[1] + b[1] : b[1], b[2]];
};
const toSignedNumber = (s) => {
    if (s == undefined) {
        animatry.warn('empty signed number');
        return [false, 0, ''];
    }
    const match = s.toString().match(regex);
    if (!match) {
        animatry.warn('invalid signed number');
        return [false, 0, ''];
    }
    const array = match === null || match === void 0 ? void 0 : match.slice(1);
    return [!!array[0], parseFloat(array[1]) * (/^-/.test(array[0]) ? -1 : 1), array[2]];
};
const unifySignedNumberUnits = (uc, prop, from, to) => {
    if (!from || !to)
        return [[false, 0, ''], [false, 0, '']];
    const [, fromN, fromU] = from;
    const [toS, toN, toU] = to;
    if (CSS.supports(prop, '1deg') || CSS.supports('transform', `${prop}(1deg)`)) {
        return [
            [false, (uc.getAngle(fromU !== null && fromU !== void 0 ? fromU : 'deg') / uc.getAngle(toU !== null && toU !== void 0 ? toU : 'deg')) * fromN, toU !== null && toU !== void 0 ? toU : 'deg'], [toS, toN, toU !== null && toU !== void 0 ? toU : 'deg']
        ];
    }
    else if (CSS.supports(prop, '1px') || CSS.supports('transform', `${prop}(1px)`) || /^(el[wh]|)$/.test(prop)) {
        return [
            [false, (uc.getLength(prop, fromU !== null && fromU !== void 0 ? fromU : 'px') / uc.getLength(prop, toU !== null && toU !== void 0 ? toU : 'px')) * fromN, toU !== null && toU !== void 0 ? toU : 'px'], [toS, toN, toU !== null && toU !== void 0 ? toU : 'px']
        ];
    }
    else if (CSS.supports(prop, '1') || CSS.supports('transform', `${prop}(1)`)) {
        return [
            [false, fromN, ''], to
        ];
    }
    animatry.warn(`unit conversion failed with invalid property '${prop}'`);
    return [[false, 0, ''], to];
};
const convertSignedNumbers = (uc, prop, from, to, fallback) => {
    var _a;
    from = from !== null && from !== void 0 ? from : fallback;
    to = (_a = to !== null && to !== void 0 ? to : from) !== null && _a !== void 0 ? _a : fallback;
    return unifySignedNumberUnits(uc, prop, combineSignedNumbers(uc, prop, fallback, from), combineSignedNumbers(uc, prop, fallback, to));
};
const stringifySignedNumber = (sn) => {
    return `${sn[1]}${sn[2]}`;
};

const validateColor = (c) => CSS.supports('color', c);
const colorToRgba = (color) => {
    var _a, _b;
    let res = [];
    if (/^rgb(a)?\(/.test(color)) {
        const match = color.match(/^(?:rgb(?:a)?\()(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)(?:\s*,\s*(\d*\.?\d+))?\)/);
        if (!match) {
            console.warn('invalid rgba format');
        }
        const [r, g, b, a = 1] = (match !== null && match !== void 0 ? match : [0, 0, 0, 0]).slice(1);
        res = [r, g, b, a];
    }
    else if (/^hsl(a)?\(/.test(color)) {
        const match = color.match(/^(?:hsl(?:a)?\()(\d*\.?\d+)(?:%)?\s*,\s*(\d*\.?\d+)(?:%)?\s*,\s*(\d*\.?\d+)(?:%)?(?:\s*,\s*(\d*\.?\d+)(?:%)?)?\)/);
        let [h, s, l, a] = ((_a = match === null || match === void 0 ? void 0 : match.slice(1)) !== null && _a !== void 0 ? _a : [0, 0, 0, 0]).map(x => (x ? parseFloat(x) : 1));
        s /= 100;
        l /= 100;
        const c = (1 - Math.abs(2 * l - 1)) * s;
        const x = c * (1 - Math.abs((h / 60) % 2 - 1));
        const m1 = l - c / 2;
        const [r1, g1, b1] = h < 60 ? [c, x, 0] :
            h < 120 ? [x, c, 0] :
                h < 180 ? [0, c, x] :
                    h < 240 ? [0, x, c] :
                        h < 300 ? [x, 0, c] : [c, 0, x];
        const [r, g, b] = [r1 + m1, g1 + m1, b1 + m1].map(val => Math.round(val * 255));
        res = [r, g, b, a];
    }
    else if (/^#/.test(color)) {
        const match = new RegExp(`^#(\\w(?:\\w)?)(\\w(?:\\w)?)(\\w(?:\\w)?)${color.length === 5 || color.length === 9 ? '(\\w(?:\\w)?)' : ''}$`).exec(color);
        if (!match)
            console.warn('invalid hex format');
        const [r, g, b, a = 255] = ((_b = match === null || match === void 0 ? void 0 : match.slice(1)) !== null && _b !== void 0 ? _b : ['#fff'])
            .map(x => (x.length === 1 ? x + x : x))
            .map(x => parseInt(x, 16));
        res = [r, g, b, a / 255];
    }
    else if (/^transparent/.test(color)) {
        res = [0, 0, 0, 0];
    }
    else if (CSS.supports('color', color)) {
        const canvas = document.createElement('canvas');
        canvas.width = 1;
        canvas.height = 1;
        const ctx = canvas.getContext('2d', { willReadFrequently: true });
        ctx.fillStyle = color;
        ctx.fillRect(0, 0, 1, 1);
        const d = ctx.getImageData(0, 0, 1, 1).data;
        res = [...d].slice(0, 3).concat(d[3] / 255);
    }
    return ['rgba(', res[0], ',', res[1], ',', res[2], ',', res[3], ')'];
};
function unifyAllValues(uc, from, to) {
    let res1 = [];
    let res2 = [];
    from.forEach((fromItem, index) => {
        const toItem = to[index];
        const comparisonResult = unifyValue(uc, '', fromItem, toItem, fromItem !== null && fromItem !== void 0 ? fromItem : toItem);
        res1.push(...stringifySignedNumber(comparisonResult[0]));
        res2.push(...stringifySignedNumber(comparisonResult[1]));
        res1.push(' ');
        res2.push(' ');
    });
    return [res1, res2];
}
const parseBorderOrOutline = (prop, b) => {
    var _a;
    const res = {};
    b = b.replace('none', '#fff solid 0px');
    ((_a = b.match(/(?:[^\s()]+|\([^\)]*\))+/g)) !== null && _a !== void 0 ? _a : []).forEach(d => {
        if (validateColor(d)) {
            res[`${prop}Color`] = d;
        }
        else if (/^(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)$/.test(d)) {
            res[`${prop}Style`] = d;
        }
        else if (isSignableNumber(d)) {
            res[`${prop}Width`] = d;
        }
    });
    return res;
};
const parseMarginOrPadding = (key, input) => {
    const [top, right = top, bottom = top, left = right] = input.toString().trim().split(/\s+/);
    return { [`${key}Top`]: top, [`${key}Right`]: right, [`${key}Bottom`]: bottom, [`${key}Left`]: left };
};
function parseBorderRadius(key, input) {
    const [h, v = ""] = input.split("/").map(part => part.trim().split(/\s+/));
    const expand = (vals) => vals.length === 1 ? Array(4).fill(vals[0]) :
        vals.length === 2 ? [vals[0], vals[1], vals[0], vals[1]] :
            vals.length === 3 ? [vals[0], vals[1], vals[2], vals[1]] : vals.slice(0, 4);
    const horizontal = expand(h);
    const vertical = expand(Array.isArray(v) && v.length ? v : horizontal);
    return {
        borderTopLeftRadius: `${horizontal[0]} ${vertical[0]}`,
        borderTopRightRadius: `${horizontal[1]} ${vertical[1]}`,
        borderBottomRightRadius: `${horizontal[2]} ${vertical[2]}`,
        borderBottomLeftRadius: `${horizontal[3]} ${vertical[3]}`,
    };
}
function disassambleBorderCornerRadius(value) {
    let res = value.split(' ');
    if (res.length == 1) {
        res = [...res, ...res];
    }
    return res;
}
function parsePositionProperty(key, value) {
    let d = value.split(' ');
    let res = [];
    let maxValues = key === 'backgroundPosition' ? 2 : 3;
    d.forEach(o => {
        if (/^(left|right)$/.test(o)) {
            res[0] = o;
        }
        else if (/^(top|bottom)$/.test(o)) {
            res[1] = o;
        }
        else if (/^(\d+(\.\d+)?(px|%)?)$/.test(o) || o === 'center') {
            if (!res[0]) {
                res[0] = o;
            }
            else if (!res[1]) {
                res[1] = o;
            }
            else if (maxValues === 3 && !res[2]) {
                res[2] = o;
            }
        }
    });
    [res[0], res[1], res[2]].forEach((o, i) => {
        var _a;
        if (i < maxValues && !o) {
            res[i] = i === 2 ? '0px' : d[0] !== res[i === 0 ? 1 : 0] ? d[0] : (_a = d[1]) !== null && _a !== void 0 ? _a : '50%';
        }
    });
    return res
        .slice(0, maxValues)
        .map(v => v
        .replace(/left|top/, '0%')
        .replace(/center/, '50%')
        .replace(/right|bottom/, '100%'));
}
function parseAndUnifyShadows(property, shadowString1, shadowString2) {
    function parseShadows(shadowString) {
        shadowString = shadowString.replace('none', 'black');
        const flatShadows = [];
        shadowString.split(',').forEach(shadow => {
            const parts = shadow.trim().split(/\s+/);
            const color = parts.find(part => validateColor(part));
            const [x = '0px', y = '0px', blur = '0px', spread = '0px'] = parts.filter(part => part !== color);
            if (property === "box-shadow") {
                flatShadows.push(x, y, blur, spread, color, ',');
            }
            else {
                flatShadows.push(x, y, blur, color, ',');
            }
        });
        if (flatShadows[flatShadows.length - 1] === ',') {
            flatShadows.pop();
        }
        return flatShadows;
    }
    const shadowArray1 = parseShadows(shadowString1);
    const shadowArray2 = parseShadows(shadowString2);
    const maxLength = Math.max(shadowArray1.length, shadowArray2.length);
    const defaultShadowBox = [',', "0px", "0px", "0px", "0px", "rgba(0,0,0,0)"];
    const defaultShadowText = [',', "0px", "0px", "0px", "rgba(0,0,0,0)"];
    while (shadowArray1.length < maxLength) {
        shadowArray1.push(...(property === "box-shadow" ? defaultShadowBox : defaultShadowText));
    }
    while (shadowArray2.length < maxLength) {
        shadowArray2.push(...(property === "box-shadow" ? defaultShadowBox : defaultShadowText));
    }
    return [[...shadowArray1], [...shadowArray2]];
}
function parseAndUnifyFilters(from, to) {
    function filterToArray(filter) {
        const regex = /(\w+-?\w*)\(([^()]*\([^()]*\)[^()]*)\)|(\w+-?\w*)\(([^()]*)\)/g;
        const filters = [];
        let match;
        while ((match = regex.exec(filter)) !== null) {
            filters.push([match[1] || match[3], match[2] || match[4]]);
        }
        return filters;
    }
    const defaultFilters = {
        'blur': '0px',
        'brightness': '100%',
        'contrast': '100%',
        'grayscale': '0',
        'hue-rotate': '0deg',
        'invert': '0',
        'opacity': '100%',
        'saturate': '100%',
        'sepia': '0',
        'drop-shadow': '0px 0px 0px black',
    };
    function unifyFilters(from, to) {
        const resFrom = [];
        const resTo = [];
        const parsedFrom = filterToArray(from);
        const parsedTo = filterToArray(to);
        const filterTypesFrom = parsedFrom.map((filter) => filter[0]);
        const filterTypesTo = parsedTo.map((filter) => filter[0]);
        let i = 0;
        while (i < parsedFrom.length && i < parsedTo.length && filterTypesFrom[i] === filterTypesTo[i]) {
            resFrom.push(parsedFrom[i]);
            resTo.push(parsedTo[i]);
            i++;
        }
        if (i === parsedFrom.length) {
            parsedTo.slice(i).forEach((object) => {
                resFrom.push([object[0], defaultFilters[object[0]]]);
                resTo.push(object);
            });
        }
        else if (i === parsedTo.length) {
            parsedFrom.slice(i).forEach((object) => {
                resFrom.push(object);
                resTo.push([object[0], defaultFilters[object[0]]]);
            });
        }
        else {
            parsedFrom.forEach((object) => {
                resFrom.push(object);
                resTo.push([object[0], defaultFilters[object[0]]]);
            });
            parsedTo.forEach((object) => {
                resFrom.push([object[0], defaultFilters[object[0]]]);
                resTo.push(object);
            });
        }
        return [resFrom, resTo];
    }
    return unifyFilters(from, to);
}
function unifyValue(uc, propertyKey, from, to, fb) {
    var _a, _b;
    from = from !== null && from !== void 0 ? from : fb;
    to = (_a = to !== null && to !== void 0 ? to : from) !== null && _a !== void 0 ? _a : fb;
    propertyKey = animatry.camelToKebab(propertyKey);
    if (/^border-\w+-\w+-radius$/.test(propertyKey)) {
        const arrayFrom = disassambleBorderCornerRadius(from);
        const arrayTo = disassambleBorderCornerRadius(to);
        const arrayFallback = disassambleBorderCornerRadius(fb);
        const a = unifyValue(uc, 'elw', arrayFrom[0], arrayTo[0], arrayFallback[0]);
        const b = unifyValue(uc, 'elh', arrayFrom[1], arrayTo[1], arrayFallback[1]);
        return [[...a[0], ' ', ...b[0]], [...a[1], ' ', ...b[1]]];
    }
    if (/^transform-origin$/.test(propertyKey)) {
        const arrayFrom = parsePositionProperty(propertyKey, from);
        const arrayTo = parsePositionProperty(propertyKey, to);
        const arrayFallback = parsePositionProperty(propertyKey, fb);
        const x = unifyValue(uc, 'elw', arrayFrom[0], arrayTo[0], arrayFallback[0]);
        const y = unifyValue(uc, 'elh', arrayFrom[1], arrayTo[1], arrayFallback[1]);
        const z = unifyValue(uc, '', arrayFrom[2], arrayTo[2], arrayFallback[2]);
        return [[...x[0], ' ', ...y[0], ' ', ...z[0]], [...x[1], ' ', ...y[1], ' ', ...z[1]]];
    }
    if (/^background-position$/.test(propertyKey)) {
        const arrayFrom = parsePositionProperty(propertyKey, from);
        const arrayTo = parsePositionProperty(propertyKey, to);
        const arrayFallback = parsePositionProperty(propertyKey, fb);
        const x = unifyValue(uc, 'elw', arrayFrom[0], arrayTo[0], arrayFallback[0]);
        const y = unifyValue(uc, 'elh', arrayFrom[1], arrayTo[1], arrayFallback[1]);
        return [[...x[0], ' ', ...y[0]], [...x[1], ' ', ...y[1]]];
    }
    if (/^(box|text)-shadow$/.test(propertyKey)) {
        const parsedShadows = parseAndUnifyShadows(propertyKey, from, to);
        return unifyAllValues(uc, parsedShadows[0], parsedShadows[1]);
    }
    if (/^filter$/.test(propertyKey)) {
        const res = parseAndUnifyFilters(from, to);
        let parsedFilters = [[], []];
        res[0].forEach((obj, index) => {
            const start = obj[1];
            const end = res[1][index][1];
            const result = /^drop-shadow$/.test(obj[0]) ?
                unifyAllValues(uc, parseAndUnifyShadows('text-shadow', start, end)[0], parseAndUnifyShadows('text-shadow', start, end)[1]) :
                unifyValue(uc, /^invert$/.test(obj[0]) ? 'opacity' : /^hue-rotate$/.test(obj[0]) ? 'rotate' : '', start, end, start !== null && start !== void 0 ? start : end);
            parsedFilters[0].push(`${obj[0]}(`, ...stringifySignedNumber(result[0]), ') ');
            parsedFilters[1].push(`${obj[0]}(`, ...stringifySignedNumber(result[1]), ') ');
        });
        return parsedFilters;
    }
    if (/^opacity$/.test(propertyKey)) {
        if (typeof from === 'string')
            from = from.replace(/(\d+)%/g, (_, p1) => (p1 / 100).toString());
        if (typeof to === 'string')
            to = to.replace(/(\d+)%/g, (_, p1) => (p1 / 100).toString());
    }
    if (/^(letter|word)-spacing$/.test(propertyKey)) {
        if (typeof from === 'string')
            from = from.replace('normal', '0px');
        if (typeof to === 'string')
            to = to.replace('normal', '0px');
    }
    if (validateColor(from) || validateColor(to)) {
        return [colorToRgba(from), colorToRgba(to)];
    }
    if (isSignableNumber((_b = from) !== null && _b !== void 0 ? _b : to)) {
        return convertSignedNumbers(uc, propertyKey, toSignedNumber(from), toSignedNumber(to), toSignedNumber(fb));
    }
    return [[from], [to]];
}
function unifyProperties(uc, propertyKeys, from, to) {
    const res = [{}, {}, {}];
    propertyKeys.forEach(propertyKey => {
        var _a, _b, _c;
        if (/^autoHide$/.test(propertyKey)) {
            if (propertyKeys.includes('visibility')) {
                animatry.warn(`remove 'visibility' in order to use 'autoHide'`);
            }
        }
        else if (!(propertyKey in document.documentElement.style)) {
            animatry.warn(`Unknown property '${propertyKey}'. Missing plugin?`);
            propertyKeys = propertyKeys.filter(k => k !== propertyKey);
            return;
        }
        else if (!CSS.supports(animatry.camelToKebab(propertyKey), (_a = from[propertyKey]) !== null && _a !== void 0 ? _a : to[propertyKey]) && !CSS.supports(animatry.camelToKebab(propertyKey), ((_b = from[propertyKey]) !== null && _b !== void 0 ? _b : to[propertyKey]) + 'px')) {
            animatry.warn(`Property ${propertyKey} has invalid value ${(_c = from[propertyKey]) !== null && _c !== void 0 ? _c : to[propertyKey]}`);
            return;
        }
        [from, to].forEach(object => {
            if (!object[propertyKey])
                return;
            const splitableFunctions = [
                [/^(border(Top|Right|Bottom|Left)?|outline)$/, parseBorderOrOutline],
                [/^(margin|padding)$/, parseMarginOrPadding],
                [/^borderRadius$/, parseBorderRadius]
            ];
            for (const [regex, parseFunction] of splitableFunctions) {
                if (regex.test(propertyKey)) {
                    const newProperties = parseFunction(propertyKey, object[propertyKey]);
                    Object.assign(object, newProperties);
                    propertyKeys = propertyKeys
                        .filter(k => k !== propertyKey)
                        .concat(Object.keys(newProperties).filter(k => !propertyKeys.includes(k)));
                    delete object[propertyKey];
                    break;
                }
            }
        });
    });
    propertyKeys.forEach(propertyKey => {
        [res[0][propertyKey],] = unifyValue(uc, propertyKey, undefined, undefined, uc.css[propertyKey]);
        [res[1][propertyKey], res[2][propertyKey]] = unifyValue(uc, propertyKey, from[propertyKey], to[propertyKey], uc.css[propertyKey]);
    });
    return res;
}

const RAD_TO_DEGREE = 180 / Math.PI;
const vectorLength = ([x, y, z]) => Math.sqrt(x * x + y * y + z * z);
const normalizeVector = ([x, y, z], length) => [x, y, z].map(s => s * 1 / length);
const crossProduct = ([x1, y1, z1], [x2, y2, z2]) => [y1 * z2 - z1 * y2, z1 * x2 - x1 * z2, x1 * y2 - y1 * x2];
const dotProduct = ([x1, y1, z1], [x2, y2, z2]) => x1 * x2 + y1 * y2 + z1 * z2;
const linearCombination = ([x1, y1, z1], [x2, y2, z2], s1, s2) => [x1 * s1 + x2 * s2, y1 * s1 + y2 * s2, z1 * s1 + z2 * s2];
function decomposeMatrix(matrix) {
    const dM = new DOMMatrix(matrix);
    const ma = [
        [dM.m11, dM.m12, dM.m13],
        [dM.m21, dM.m22, dM.m23],
        [dM.m31, dM.m32, dM.m33],
    ];
    const scale = new Array(3);
    const skew = new Array(3);
    scale[0] = vectorLength(ma[0]);
    ma[0] = normalizeVector(ma[0], scale[0]);
    skew[0] = dotProduct(ma[0], ma[1]);
    ma[1] = linearCombination(ma[1], ma[0], 1.0, -skew[0]);
    scale[1] = vectorLength(ma[1]);
    ma[1] = normalizeVector(ma[1], scale[1]);
    skew[0] /= scale[1];
    skew[1] = dotProduct(ma[0], ma[2]);
    ma[2] = linearCombination(ma[2], ma[0], 1.0, -skew[1]);
    skew[2] = dotProduct(ma[1], ma[2]);
    ma[2] = linearCombination(ma[2], ma[1], 1.0, -skew[2]);
    skew[0] = Math.atan(skew[0]);
    skew[1] = Math.atan(skew[1]);
    skew[2] = Math.atan(skew[2]);
    scale[2] = vectorLength(ma[2]);
    ma[2] = normalizeVector(ma[2], scale[2]);
    skew[1] /= scale[2];
    skew[2] /= scale[2];
    if (dotProduct(ma[0], crossProduct(ma[1], ma[2])) < 0) {
        for (let i = 0; i < 3; i++) {
            scale[i] *= -1;
            ma[i] = ma[i].map(val => -val);
        }
    }
    const quaternion = new Array(4);
    quaternion[0] = 0.5 * Math.sqrt(Math.max(1 + ma[0][0] - ma[1][1] - ma[2][2], 0));
    quaternion[1] = 0.5 * Math.sqrt(Math.max(1 - ma[0][0] + ma[1][1] - ma[2][2], 0));
    quaternion[2] = 0.5 * Math.sqrt(Math.max(1 - ma[0][0] - ma[1][1] + ma[2][2], 0));
    quaternion[3] = 0.5 * Math.sqrt(Math.max(1 + ma[0][0] + ma[1][1] + ma[2][2], 0));
    if (ma[2][1] > ma[1][2])
        quaternion[0] = -quaternion[0];
    if (ma[0][2] > ma[2][0])
        quaternion[1] = -quaternion[1];
    if (ma[1][0] > ma[0][1])
        quaternion[2] = -quaternion[2];
    const [qx, qy, qz, qw] = quaternion;
    const qw2 = qw * qw;
    const qx2 = qx * qx;
    const qy2 = qy * qy;
    const qz2 = qz * qz;
    const test = qx * qy + qz * qw;
    const unit = qw2 + qx2 + qy2 + qz2;
    const rotation = test > 0.49999 * unit
        ? [0, 2 * Math.atan2(qx, qw) * RAD_TO_DEGREE, 90]
        : test < -0.49999 * unit
            ? [0, -2 * Math.atan2(qx, qw) * RAD_TO_DEGREE, -90]
            : [
                Math.round(Math.atan2(2 * qx * qw - 2 * qy * qz, 1 - 2 * qx2 - 2 * qz2) * RAD_TO_DEGREE),
                Math.round(Math.atan2(2 * qy * qw - 2 * qx * qz, 1 - 2 * qy2 - 2 * qz2) * RAD_TO_DEGREE),
                Math.round(Math.asin(2 * (qx * qy + qz * qw)) * RAD_TO_DEGREE)
            ];
    return {
        translateX: dM.m41,
        translateY: dM.m42,
        translateZ: dM.m43,
        scaleX: Math.round(scale[0] * 1000) / 1000,
        scaleY: Math.round(scale[1] * 1000) / 1000,
        scaleZ: Math.round(scale[2] * 1000) / 1000,
        rotateX: rotation[0],
        rotateY: rotation[1],
        rotateZ: rotation[2],
        skewX: Math.round(skew[0] * RAD_TO_DEGREE * 1000) / 1000,
        skewY: Math.round(skew[1] * RAD_TO_DEGREE * 1000) / 1000,
    };
}
function matrixToAbsolute(uc, matrix) {
    const propToPx = (prop, value) => {
        const res = unifySignedNumberUnits(uc, prop, unifyMatrixEntry(prop, value), toSignedNumber('0px'))[0];
        return `${res[1]}${res[2]}`;
    };
    const splitMatrix = (val) => { var _a; return (_a = (val !== null && val !== void 0 ? val : '').match(/(\w+\()([^()]*)(\))/g)) !== null && _a !== void 0 ? _a : []; };
    const getParams = (val) => { var _a; return (_a = (val !== null && val !== void 0 ? val : '').match(/(\w+\()([^,]+)(?:,\s*([^,]+))?(?:,\s*([^,]+))?\)/)) !== null && _a !== void 0 ? _a : []; };
    const handleMatrix = (obj, axes) => `${obj[0]}${axes.map((axis, index) => propToPx(axis, obj[index + 1])).join(', ')})`;
    const step1 = splitMatrix(matrix);
    const step2 = step1.map((each) => /^translate3d\(|^translate\(|^translate[XYZ]\(/.test(each)
        ? getParams(each).slice(1).filter((e) => e !== undefined)
        : each);
    return step2.flatMap((obj) => {
        if (Array.isArray(obj)) {
            if (/^translate3d/.test(obj[0])) {
                return handleMatrix(obj, ['translateX', 'translateY', 'translateZ']);
            }
            else if (/^translate\(/.test(obj[0])) {
                return handleMatrix(obj, ['translateX', 'translateY']);
            }
            else if (/(^translate[XYZ])\(/.test(obj[0])) {
                return `${obj[0]}${propToPx(obj[0].slice(0, -1), obj[1])})`;
            }
            else {
                return obj;
            }
        }
        return [obj];
    }).join(' ');
}
function unifyMatrixEntry(prop, s) {
    let res = toSignedNumber(s);
    if (res[2] == undefined)
        res[2] = /^(skew|rotate)[XYZ]/.test(prop.toString()) ? 'deg' : /^scale/.test(prop.toString()) ? '' : 'px';
    return res;
}
function unifyMatrix(matrix) {
    const res = {};
    Object.keys(matrix).forEach(t => {
        res[t] = unifyMatrixEntry(t, matrix[t]);
    });
    return res;
}
function buildTransformString({ translateX, translateY, translateZ, scaleX, scaleY, scaleZ, rotateX, rotateY, rotateZ, skewX, skewY }) {
    return [
        (translateX || translateY || translateZ) && `translate3d(${translateX !== null && translateX !== void 0 ? translateX : 0}, ${translateY !== null && translateY !== void 0 ? translateY : 0}, ${translateZ !== null && translateZ !== void 0 ? translateZ : 0})`,
        rotateX && rotateX != '0deg' && `rotateX(${rotateX})`,
        rotateY && rotateY != '0deg' && `rotateY(${rotateY})`,
        rotateZ && rotateZ != '0deg' && `rotate(${rotateZ})`,
        ((scaleX || scaleY) && (scaleX != '1' || scaleY != '1')) && `scale(${scaleX !== null && scaleX !== void 0 ? scaleX : 1}, ${scaleY !== null && scaleY !== void 0 ? scaleY : 1})`,
        scaleZ && scaleZ != '1' && `scaleZ(${scaleZ})`,
        skewX && skewX != '0deg' && `skewX(${skewX})`,
        skewY && skewY != '0deg' && `skewY(${skewY})`
    ].filter(Boolean).join(' ');
}

const viewportUnits = { wiw: 0, wih: 0, vmin: 0, vmax: 0 };
function updateViewportUnits() {
    viewportUnits.wiw = window.innerWidth / 100;
    viewportUnits.wih = window.innerHeight / 100;
    viewportUnits.vmin = Math.min(viewportUnits.wiw, viewportUnits.wih);
    viewportUnits.vmax = Math.max(viewportUnits.wiw, viewportUnits.wih);
}
const angles = {
    'deg': 1,
    'rad': 180 / Math.PI,
    'grad': 9 / 10,
    'turn': 360
};
class UnitConverter {
    constructor(el) {
        this.el = el;
        this.css = getComputedStyle(el);
        this.dcss = getComputedStyle(document.documentElement);
    }
    getLength(prop, unit) {
        var _a, _b;
        if (viewportUnits.wiw === 0) {
            updateViewportUnits();
            window.addEventListener('resize', updateViewportUnits);
        }
        let prc = 1;
        switch (true) {
            case /^(elw|\w+[xX])$/.test(prop):
                prc = this.el.offsetWidth;
                break;
            case /^(elh|\w+[yY])$/.test(prop):
                prc = this.el.offsetHeight;
                break;
            case /^((max|min|column)-)?width|(margin-|padding-)?(top|right|bottom|left)$/.test(prop):
                this.par = (_a = this.par) !== null && _a !== void 0 ? _a : animatry.getParent(this.el);
                prc = this.par.offsetWidth;
                break;
            case /^((max|min|column)-height|top|bottom)$/.test(prop):
                this.par = (_b = this.par) !== null && _b !== void 0 ? _b : animatry.getParent(this.el);
                prc = this.par.offsetHeight;
                break;
            case /^font-size$/.test(prop):
                prc = parseFloat(this.dcss.fontSize);
                break;
            case /^line-height$/.test(prop):
                prc = parseFloat(this.css.fontSize);
                break;
        }
        prc /= 100;
        switch (unit) {
            case 'px': return 1;
            case '%': return prc;
            case 'rem': return parseFloat(this.dcss.fontSize);
            case 'em': return parseFloat(this.css.fontSize);
            case 'cm': return 37.8;
            case 'mm': return 3.78;
            case 'Q': return 0.945;
            case 'in': return 96;
            case 'pc': return 16;
            case 'pt': return 1.333;
            case 'ex': return parseFloat(this.css.fontSize) * 0.5;
            case 'ch': return parseFloat(this.css.fontSize) * 0.6;
            case 'vmin': return viewportUnits.vmin;
            case 'vmax': return viewportUnits.vmax;
            case 'vb': return /^(ltr|rtl)$/.test(this.css.direction) ? viewportUnits.wih : viewportUnits.wiw;
            case 'vw':
            case 'svw':
            case 'lvw':
            case 'dvw': return viewportUnits.wiw;
            case 'vh':
            case 'svh':
            case 'lvh':
            case 'dvh': return viewportUnits.wih;
            default: return 0;
        }
    }
    getAngle(unit) {
        return angles[unit] || 0;
    }
}

function lerpSingle(s, e, p) {
    return /^[+-]?\d*\.?\d+$/.test(s) ? animatry.round((1 - p) * s + p * e, 4) : p > 0 ? e : s;
}
function lerpMulti(s, e, p) {
    return s.map((v, i) => lerpSingle(v, e[i], p));
}
function unifyTransformations(uc, keys, from, to, css) {
    const trans = [{}, {}, {}];
    if (keys.length === 0)
        return trans;
    let matrixBefore = unifyMatrix(decomposeMatrix(css.transform));
    const keyMap = {
        x: 'translateX',
        y: 'translateY',
        z: 'translateZ',
        rotate: 'rotateZ',
    };
    const handleScaleOrTranslate = (mappedKey, values, i) => {
        const props = /^scale$/.test(mappedKey) ? ['scaleX', 'scaleY'] : ['translateX', 'translateY', 'translateZ'];
        const parsedValues = mappedKey === 'scale' && values.length === 1 ? [values[0], values[0]] : values;
        parsedValues.forEach((value, index) => {
            var _a;
            trans[i][props[index]] = combineSignedNumbers(uc, props[index], i === 0 ? matrixBefore[props[index]] : (_a = trans[1][props[index]]) !== null && _a !== void 0 ? _a : matrixBefore[props[index]], toSignedNumber(value));
        });
    };
    [from, to].forEach((obj, i) => {
        const transformBase = obj['transform'] ? unifyMatrix(decomposeMatrix(matrixToAbsolute(uc, obj['transform']))) : undefined;
        if (transformBase || i === 1) {
            trans[i + 1] = transformBase !== null && transformBase !== void 0 ? transformBase : structuredClone(trans[1]);
        }
        delete obj['transform'];
        keys.forEach(key => {
            var _a, _b;
            if (obj[key] == null)
                return;
            let mappedKey = (_a = keyMap[key]) !== null && _a !== void 0 ? _a : key;
            if (/^scale$|^translate$|^translate3d$/.test(mappedKey)) {
                handleScaleOrTranslate(mappedKey, Array.isArray(obj[key]) ? obj[key] : obj[key].toString().split(' '), i + 1);
                return;
            }
            if (!Array.isArray(obj[key]) && typeof obj[key] === 'number') {
                const unit = /^(skew|rotate)[XYZ]/.test(mappedKey) ? 'deg' : /^scale/.test(mappedKey) ? '' : 'px';
                obj[key] = `${obj[key]}${unit}`;
            }
            trans[i + 1][mappedKey] = combineSignedNumbers(uc, mappedKey, i == 0 ? matrixBefore[mappedKey] : (_b = trans[1][mappedKey]) !== null && _b !== void 0 ? _b : matrixBefore[mappedKey], toSignedNumber(obj[key]));
        });
    });
    [...new Set([...Object.keys(trans[1]), ...Object.keys(trans[2])])].forEach((key) => {
        [trans[0][key]] = convertSignedNumbers(uc, key, undefined, trans[1][key], matrixBefore[key]);
        [trans[1][key], trans[2][key]] = convertSignedNumbers(uc, key, trans[1][key], trans[2][key], matrixBefore[key]);
    });
    return trans;
}
function readProperties(el, from, to) {
    const css = getComputedStyle(el);
    const uc = new UnitConverter(el);
    const [tkeys, ckeys] = Array.from(new Set([...Object.keys(from), ...Object.keys(to)])).reduce((acc, key) => {
        acc[/^([xyz]|transform|(translate([XYZ]|3d)?)|(rotat(e|ion))?[XYZ]?|scale[XYZ]?|skew[XYZ])$/i.test(key) ? 0 : 1].push(key);
        return acc;
    }, [[], []]);
    const resTrans = unifyTransformations(uc, tkeys, from, to, css);
    const resCss = unifyProperties(uc, ckeys, from, to);
    [resTrans[0], resCss[0], resTrans[1], resCss[1], resTrans[2], resCss[2]].forEach(o => {
        Object.keys(o).forEach(key => {
            o[key] = o[key].filter((item) => typeof item !== 'boolean');
        });
    });
    return [
        [resTrans[0], resCss[0]],
        [resTrans[1], resCss[1]],
        [resTrans[2], resCss[2]]
    ];
}
function lerpProperties([state, from, to], p, options) {
    const tfm = {};
    const css = {};
    if (animatry.round(p, 4) === (options.backwards ? 1 : 0) && !options.preRender) {
        Object.keys(from[0]).forEach(key => {
            tfm[key] = state[0][key].join('');
        });
    }
    else {
        Object.keys(from[0]).forEach(key => {
            tfm[key] = lerpMulti(from[0][key], to[0][key], p).join('');
        });
    }
    Object.keys(from[1]).forEach(key => {
        const res = lerpMulti(from[1][key], to[1][key], p).join('');
        if (!CSS.supports(animatry.camelToKebab(key), res) && p < 0)
            return;
        css[key] = res;
    });
    return [tfm, css];
}
function applyProperties(el, props) {
    Object.keys(props[1]).forEach(k => {
        if (k === 'autoHide') {
            el.style.visibility = props[1]['opacity'] == 0 ? 'hidden' : 'visible';
        }
        else {
            el.style[k] = props[1][k];
        }
    });
    el.style.transform = buildTransformString(props[0]);
}

class Fragment {
    constructor(element) {
        this.element = element;
        this.initialTransform = unifyMatrix(decomposeMatrix(getComputedStyle(element).transform));
        Object.keys(this.initialTransform).forEach(key => {
            this.initialTransform[key] = stringifySignedNumber(this.initialTransform[key]);
        });
        this.properties = [];
    }
    getProperties() {
        return this.properties;
    }
    setProperties(properties) {
        this.properties = [
            { ...this.initialTransform, ...this.properties[0], ...properties[0] },
            { ...this.properties[1], ...properties[1] }
        ];
    }
    getElement() {
        return this.element;
    }
}
class Renderer {
    static addTween(element, tween) {
        let fragment = Renderer.fragments.find((fragment) => fragment.getElement() === element);
        if (!fragment) {
            fragment = new Fragment(element);
            Renderer.fragments.push(fragment);
        }
        if (!this.tweens.find((found) => found === tween)) {
            this.tweens.push(tween);
        }
        return fragment;
    }
    static getTweens(elements) {
        let found = [];
        elements = animatry.select(elements);
        elements.forEach(element => {
            this.tweens.forEach(tween => {
                if (tween.elements[0] == element) {
                    found.push(tween);
                }
            });
        });
        return found;
    }
}
Renderer.fragments = [];
Renderer.tweens = [];

const _elementCenter = (element) => {
    const rect = element.getBoundingClientRect();
    return [rect.left + rect.width / 2, rect.top + rect.height / 2];
};
const _measureGrid = (elements) => {
    return [new Set(elements.map(element => _elementCenter(element)[0])).size, new Set(elements.map(element => _elementCenter(element)[1])).size];
};
const _gridIndex = ([rows, columns], index) => {
    const row = Math.floor(index / columns);
    const column = index % columns;
    const x = columns > 1 ? column / (columns - 1) : 0.5;
    const y = rows > 1 ? row / (rows - 1) : 0.5;
    return [x, y];
};
const _normalizeArray = (array) => {
    const min = Math.min(...array);
    const max = Math.max(...array);
    return array.map(value => (value - min) / (max - min));
};
const _allPositionsEqual = (arr) => {
    return arr.every(([first, second]) => first === arr[0][0] && second === arr[0][1]);
};
const _distributeArray = (elements, options) => {
    if (options.each == 0)
        return Array.from({ length: elements.length }, () => 0);
    if ((Array.isArray(options.from) || options.axis || options.from == 'center' || options.from == 'right top' || options.from == 'left bottom') && options.layout == undefined) {
        options.layout = 'distance';
    }
    switch (options.from) {
        case 'start':
        case 'left':
        case 'top left':
        case 'left top':
        case undefined:
            options.from = options.layout ? [0, 0] : 0;
            break;
        case 'center':
            options.from = options.layout ? [0.5, 0.5] : (elements.length - 1) / 2;
            break;
        case 'end':
        case 'right':
        case 'bottom right':
        case 'right bottom':
        case -1:
            options.from = options.layout ? [1, 1] : elements.length - 1;
            break;
        case 'top right':
        case 'right top':
            options.from = [1, 0];
            break;
        case 'bottom left':
        case 'left bottom':
            options.from = [0, 1];
            break;
        case 'random':
            if (options.invert)
                animatry.warn(`stagger invert has no effect when using random`);
            if (options.axis)
                animatry.warn(`stagger axis has no effect when using random`);
            return _normalizeArray(Array.from({ length: elements.length }, () => Math.random())).map(value => (value * options.each * (elements.length - 1)));
    }
    let result = [];
    if (options.layout) {
        let elementCenters = [];
        let { layout, axis = 'xy', from = [0.5, 0.5] } = options;
        let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
        if (Array.isArray(layout) || layout === 'grid') {
            layout = layout === 'grid' ? _measureGrid(elements) : layout;
            const fromCoords = Array.isArray(from) ? from : _gridIndex(layout, from);
            from = fromCoords;
            [minX, maxX, minY, maxY] = [0, 1, 0, 1];
            elementCenters = elements.map((_, i) => _gridIndex(layout, i));
        }
        else {
            elementCenters = elements.map(_elementCenter);
            elementCenters.forEach(([x, y]) => {
                minX = Math.min(minX, x);
                minY = Math.min(minY, y);
                maxX = Math.max(maxX, x);
                maxY = Math.max(maxY, y);
            });
            if (!Array.isArray(from)) {
                const [x, y] = elementCenters[from];
                options.from = [(x - minX) / (maxX - minX), (y - minY) / (maxY - minY)];
            }
        }
        if (_allPositionsEqual(elementCenters)) {
            animatry.warn(`Elements on the same location. Can't apply any stagger layouts.`);
            result = _normalizeArray(elements.map((_, i) => i)).map(value => (value * options.each * (elements.length - 1)));
            return result;
        }
        const [rangeX, rangeY] = [maxX - minX, maxY - minY];
        const [startX, startY] = [minX + rangeX * options.from[0], minY + rangeY * options.from[1]];
        const [dirX, dirY] = [axis.includes('x') ? 1 : 0, axis.includes('y') ? 1 : 0];
        const computeTiming = (x, y) => {
            const [distanceX, distanceY] = [x - startX, y - startY];
            if (dirX && dirY)
                return Math.sqrt(distanceX ** 2 + distanceY ** 2) / Math.sqrt(rangeX ** 2 + rangeY ** 2);
            if (dirX)
                return rangeX === 0 ? 0 : distanceX / rangeX;
            if (dirY)
                return rangeY === 0 ? 0 : distanceY / rangeY;
            return 0;
        };
        const maxDistance = Math.max(...elementCenters.map(([x, y]) => computeTiming(x, y)));
        result = elementCenters.map(([x, y]) => Math.abs(computeTiming(x, y) / (maxDistance || 1)));
    }
    else {
        for (let i = 0; i < elements.length; i++) {
            result.push(Math.abs(i - options.from));
        }
    }
    result = _normalizeArray(result);
    if (options.invert)
        result = result.map(element => Math.abs(element - 1));
    result = result.map(value => value * options.each * (elements.length - 1));
    return result;
};
const stagger = (tween, elements, from, to) => {
    var _a, _b;
    elements = animatry.select(elements);
    let options = tween.options.stagger;
    if (typeof options === 'number')
        options = { each: options };
    options.each = (_a = options.each) !== null && _a !== void 0 ? _a : (options.duration ? (options.duration / (elements.length - 1)) : 0);
    Object.keys(controllerFunctions()).forEach(func => {
        to[func] = options[func] ? options[func] : () => { };
    });
    const timeline = new Timeline({ ease: (_b = options.ease) !== null && _b !== void 0 ? _b : Ease.none() });
    const staggerArray = _distributeArray(elements, options);
    elements.forEach((element, index) => {
        var _a, _b, _c;
        timeline.add(new Tween(element, from, Object.assign({}, to, {
            at: Number.parseFloat(staggerArray[index].toFixed(4)),
            duration: tween.options.duration,
            ease: tween.options.ease,
            alternateEase: tween.options.alternateEase,
            repeat: (_a = tween.options.stagger.repeat) !== null && _a !== void 0 ? _a : 0,
            iterationDelay: (_b = tween.options.stagger.iterationDelay) !== null && _b !== void 0 ? _b : 0,
            alternate: (_c = tween.options.stagger.alternate) !== null && _c !== void 0 ? _c : false,
            preRender: tween.options.preRender,
            delay: 0,
            timeScale: 1,
            stagger: 0,
        })));
    });
    tween.setDuration(timeline.getDuration());
    tween.to = {};
    return timeline;
};

class Tween extends Controller {
    constructor(elements, from, to) {
        let filterFrom = animatry.filterObjects(from, controllerSettings());
        let filterTo = animatry.filterObjects(to, controllerSettings());
        let options = { ...filterFrom[0], ...filterTo[0] };
        super(options);
        this.elements = elements ? animatry.select(elements) : [];
        this.from = filterFrom[1];
        this.to = filterTo[1];
        if (this.elements.length == 0) {
            animatry.warn(`Element '${elements}' not found`);
            return;
        }
        if (this.elements.length > 1) {
            this.timeline = stagger(this, this.elements, from, to);
            this.elements = [];
        }
        else if (this.options.keyframes) {
            this.timeline = keyframes(this, this.elements[0], from, to);
        }
        if (this.elements[0])
            this.fragment = Renderer.addTween(this.elements[0], this);
        if (this.options.preRender) {
            if (this.elements[0])
                this.render();
            this.setTotalProgress(0);
        }
        if (this.timeline)
            this.timeline.setParent(this);
    }
    render() {
        this.properties = readProperties(this.elements[0], this.from, this.to);
    }
    setTotalProgress(totalProgress, events = true) {
        totalProgress = animatry.round(animatry.clamp(totalProgress, 0, 1), 10);
        super.setTotalProgress(totalProgress, events);
        if (this.fragment) {
            if (!this.properties && totalProgress >= 0 && totalProgress <= 1) {
                this.render();
            }
            if (this.properties) {
                this.fragment.setProperties(lerpProperties(this.properties, Controller.getEasedProgress(this, totalProgress), this.options));
                applyProperties(this.elements[0], this.fragment.getProperties());
            }
        }
        if (this.timeline) {
            this.timeline.setTotalProgress(Controller.getProgress(this, totalProgress));
        }
    }
    play() {
        if (this.timeline)
            this.timeline.play();
        super.play();
    }
    reverse() {
        if (this.timeline)
            this.timeline.reverse();
        super.reverse();
    }
}

class Animatry {
    constructor() {
        this.pipeline = (...funcs) => {
            return function (...args) {
                let result = args;
                for (const func of funcs) {
                    result = func(...result);
                }
                return result;
            };
        };
        this.filterObjects = (object, filter) => {
            const matched = {};
            const unmatched = {};
            Object.keys(object).forEach((key) => {
                if (key in filter) {
                    matched[key] = object[key];
                }
                else {
                    unmatched[key] = object[key];
                }
            });
            return [matched, unmatched];
        };
        this.random = (...args) => {
            const isFunction = args[args.length - 1] === true;
            const [min, max, step = 1] = args;
            if (Array.isArray(min)) {
                return () => min[Math.floor(Math.random() * min.length)];
            }
            return () => {
                const range = max - min;
                const randomValue = step === 0
                    ? Math.random() * range + min
                    : Math.floor(Math.random() * (range / step)) * step + min;
                return isFunction ? parseFloat(randomValue).toFixed(4) : randomValue;
            };
        };
        this.shuffle = (array) => {
            array = [...array];
            for (let i = array.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [array[i], array[j]] = [array[j], array[i]];
            }
            return array;
        };
        this.clamp = (number, min = 0, max = 1) => {
            return Math.min(max, Math.max(min, number));
        };
        this.round = (value, decimalPlaces = 0) => {
            return Number(value.toFixed(decimalPlaces));
        };
        this.lerp = (start, end, progress) => {
            return this.round((1 - progress) * start + progress * end, 4);
        };
        this.isEven = (n) => {
            return n % 2;
        };
        this.isOdd = (n) => {
            return !this.isEven(n);
        };
        this.pageReady = (callback) => {
            if (document.readyState === 'complete') {
                callback();
                return;
            }
            window.addEventListener('load', () => {
                document.fonts.ready.then(() => {
                    callback();
                });
            });
        };
        this.select = (input) => {
            if (typeof input === 'string') {
                const match = input.match(/\w+\[(\d+)\]/);
                if (match) {
                    const index = parseInt(match[1], 10);
                    const elements = document.querySelectorAll(input.replace(`[${match[1]}]`, ''));
                    return [elements[index]];
                }
                return Array.from(document.querySelectorAll(input));
            }
            else if (input instanceof Element) {
                return [input];
            }
            else if (input instanceof NodeList || input instanceof Array) {
                return Array.from(input).flatMap(entry => this.select(entry));
            }
            else if (input instanceof Document) {
                return [input.documentElement];
            }
            animatry.warn('Could not collect these elements:' + input);
            return [];
        };
        this.selectCss = (property, value, parent, negate = false) => {
            const elements = this.select(parent)[0].querySelectorAll('*');
            const targets = Array.from(elements).filter((element) => {
                const computed = window.getComputedStyle(element).getPropertyValue(property).trim();
                if (negate) {
                    return computed !== value.trim();
                }
                else {
                    return computed === value.trim();
                }
            });
            return targets;
        };
        this.getParent = (el) => {
            const position = getComputedStyle(el).position;
            if (position === 'absolute' || position === 'fixed') {
                while (el && getComputedStyle(el).position !== 'relative' && el !== document.body) {
                    el = el.parentElement || document.body;
                }
                return el;
            }
            return el.parentElement || document.body;
        };
        this.camelToKebab = (s) => {
            return s.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
        };
        this._p = 'background-color:rgba(0,0,0,0.5);padding:3px 7.5px;';
    }
    version() {
        animatry.log(`v${'0.0.1'}`);
    }
    options(options) {
        setGlobalOptions(options);
    }
    fromTo(elements, from = {}, to = {}) {
        return new Tween(elements, Object.assign({ preRender: true }, from), to);
    }
    to(elements, to = {}) {
        return new Tween(elements, {}, to);
    }
    from(elements, from = {}) {
        return new Tween(elements, {}, Object.assign({ preRender: true }, from, { backwards: !from.backwards }));
    }
    timeline(options) {
        return new Timeline(options);
    }
    set(elements, to = {}) {
        return new Tween(elements, {}, Object.assign(to, { duration: 0 }));
    }
    use(plugin) {
        if (typeof plugin !== 'function') {
            animatry.warn(`A plugin has to be a function.`);
        }
        else {
            plugin(this);
        }
    }
    log(m) { console.log(`%cAnimatry%c ${m}`, this._p, ''); }
    warn(m) { console.warn(`%cAnimatry%c ${m}`, this._p, ''); }
}
const animatry = new Animatry();

const select = (input) => {
    if (typeof input === 'string') {
        const match = input.match(/\w+\[(\d+)\]/);
        if (match) {
            const index = parseInt(match[1], 10);
            const elements = document.querySelectorAll(input.replace(`[${match[1]}]`, ''));
            return [elements[index]];
        }
        return Array.from(document.querySelectorAll(input));
    }
    else if (input instanceof Element) {
        return [input];
    }
    else if (input instanceof NodeList || input instanceof Array) {
        return Array.from(input).flatMap(entry => select(entry));
    }
    else if (input instanceof Document) {
        return [input.documentElement];
    }
    warn('Could not collect these elements:' + input);
    return [];
};
const _p = 'background-color:rgba(0,0,0,0.5);padding:3px 7.5px;';
const warn = (m) => { console.warn(`%cAnimatry%c ${m}`, _p, ''); };

function splitText(elements, options) {
    elements = select(elements);
    const splitChars = /char/.test(options);
    const splitWords = /word/.test(options);
    const splitLines = /line/.test(options);
    const lines = [];
    const words = [];
    const chars = [];
    elements.forEach((element) => {
        element.style.minWidth = element.clientWidth + 'px';
        const clone = element.cloneNode(true);
        const processNode = (node) => {
            var _a;
            if (node.nodeType === Node.TEXT_NODE) {
                const textContent = (_a = node.textContent) !== null && _a !== void 0 ? _a : '';
                if (textContent.trim() === '')
                    return;
                const wordsArray = textContent.split(/(\s+)/);
                const container = document.createDocumentFragment();
                wordsArray.forEach((wordString) => {
                    if (wordString.trim() !== '') {
                        const word = document.createElement('span');
                        word.classList.add('word');
                        word.style.display = 'inline-block';
                        if (splitChars) {
                            wordString.split('').forEach((charString) => {
                                const char = document.createElement('span');
                                char.classList.add('char');
                                char.style.display = 'inline-block';
                                char.textContent = charString;
                                word.appendChild(char);
                                chars.push(char);
                            });
                        }
                        else {
                            word.textContent = wordString;
                        }
                        container.appendChild(word);
                        words.push(word);
                    }
                    else {
                        container.appendChild(document.createTextNode(' '));
                    }
                });
                node.replaceWith(container);
            }
            else if (node.nodeType === Node.ELEMENT_NODE) {
                Array.from(node.childNodes).forEach(processNode);
            }
        };
        if (splitWords || splitChars || splitLines) {
            processNode(clone);
        }
        const processLines = () => {
            const nodes = Array.from(clone.childNodes);
            element.innerHTML = '';
            const lineFragments = [];
            let currentHeight = 0;
            function createLine(node) {
                const lineFragment = document.createElement('div');
                lineFragment.classList.add('line');
                lineFragment.appendChild(node);
                element.appendChild(lineFragment);
                currentHeight = lineFragment.clientHeight;
                lineFragments.push(lineFragment);
            }
            if (nodes.length > 0) {
                createLine(nodes.shift());
            }
            while (nodes.length > 0) {
                const lastLine = lineFragments[lineFragments.length - 1];
                lastLine.appendChild(nodes.shift());
                if (lastLine.clientHeight > currentHeight) {
                    createLine(lastLine.lastChild);
                }
            }
            return lineFragments;
        };
        if (splitLines) {
            lines.push(...processLines());
        }
        else {
            element.replaceWith(clone);
        }
    });
    return {
        lines,
        words,
        chars,
    };
}

export { animatry, splitText };