UNPKG

@uiloos/core

Version:

The core of the uiloos headless UI

3,061 lines 106 kB
class _LicenseChecker {
    constructor() {
        this._licenseKey = '';
        this._logOnSuccess = false;
        this._success = false;
    }
    activateLicense(licenseKey, options = { logLicenseActivated: true }) {
        this._licenseKey = licenseKey;
        this._logOnSuccess = options.logLicenseActivated;
    }
    _checkLicense() {
        if (this._success) {
            return;
        }
        if (this._licenseKey) {
            const parts = this._licenseKey.split('-');
            if (parts.length !== 2) {
                console.warn(`uiloos > license > invalid license key detected: ${this._licenseKey}, ${buy}`);
            }
            else {
                const [_, type] = parts;
                if (this._logOnSuccess) {
                    console.log(`uiloos > license > license activated, this license is for use with ${type} developers. We thank you for your support, you can disable this message if you want to. ${owner}`);
                }
                this._success = true;
            }
        }
        else {
            console.warn(`uiloos > license > you are using commercial software, ${buy}`);
        }
    }
}
let licenseChecker = new _LicenseChecker();
const owner = 'If you are not the owner of this website please ignore this message.';
const buy = `please purchase a license at https://www.uiloos.dev. ${owner}`;

const common$3 = `uiloos > ActiveList >`;

class ActiveListAutoPlayDurationError extends Error {
    constructor() {
        super(`${common$3} autoPlay > duration cannot be negative or zero`);
        this.name = 'ActiveListAutoPlayDurationError';
    }
}

class _AutoPlay {
    constructor(activeList, config) {
        this._autoPlayTimeoutId = null;
        this._autoPlayStarted = new Date();
        this._pauseStarted = null;
        this._autoPlayCurrentDuration = 0;
        this._config = null;
        this._activeList = activeList;
        this._config = config;
    }
    _setConfig(config) {
        this._config = config;
    }
    _play(inform) {
        this._cancelTimer();
        if (!this._config || this._activeList.lastActivatedContent === null) {
            return;
        }
        const duration = this._getDuration(this._config, this._activeList.lastActivatedContent);
        if (duration <= 0) {
            throw new ActiveListAutoPlayDurationError();
        }
        this._autoPlayCurrentDuration = duration;
        if (this._pauseStarted === null) {
            this._activeList.autoPlay.duration = duration;
        }
        else {
            this._pauseStarted = null;
        }
        this._autoPlayStarted = new Date();
        this._autoPlayTimeoutId = window.setTimeout(() => {
            this._autoPlayTimeoutId = null;
            this._activeList.activateNext({ isUserInteraction: false });
        }, duration);
        this._activeList.autoPlay.isPlaying = true;
        if (inform) {
            const event = {
                type: 'AUTO_PLAY_PLAYING',
                time: new Date(),
            };
            this._activeList._inform(event);
        }
    }
    _pause() {
        if (!this._activeList.autoPlay.isPlaying) {
            return;
        }
        this._pauseStarted = new Date();
        this._cancelTimer();
        this._activeList.autoPlay.isPlaying = false;
        const event = {
            type: 'AUTO_PLAY_PAUSED',
            time: new Date(),
        };
        this._activeList._inform(event);
    }
    _stop() {
        if (!this._activeList.autoPlay.isPlaying && !this._pauseStarted) {
            return;
        }
        this._cancelTimer();
        this._pauseStarted = null;
        this._activeList.autoPlay.isPlaying = false;
        this._activeList.autoPlay.duration = 0;
        this._activeList.autoPlay.hasBeenStoppedBefore = true;
        const event = {
            type: 'AUTO_PLAY_STOPPED',
            time: new Date(),
        };
        this._activeList._inform(event);
    }
    _cancelTimer() {
        if (this._autoPlayTimeoutId !== null) {
            window.clearTimeout(this._autoPlayTimeoutId);
            this._autoPlayTimeoutId = null;
        }
    }
    _onDeactivation(activationOptions) {
        if (this._activeList.lastActivatedContent === null) {
            this._stop();
            return;
        }
        if (!this._config) {
            return;
        }
        if (this._shouldStopOnUserInteraction(activationOptions, this._config)) {
            this._stop();
            return;
        }
    }
    onActiveIndexChanged(index, activationOptions) {
        if (!this._config) {
            return;
        }
        if (this._shouldStopOnUserInteraction(activationOptions, this._config)) {
            this._stop();
        }
        else if (this._activeList.isCircular === false &&
            index === this._activeList.getLastIndex()) {
            this._stop();
        }
        else {
            this._play(false);
        }
    }
    _shouldStopOnUserInteraction(activationOptions, config) {
        return !!(activationOptions &&
            activationOptions.isUserInteraction !== false &&
            config.stopsOnUserInteraction);
    }
    _getDuration(config, lastActivatedContent) {
        if (this._pauseStarted) {
            return (this._autoPlayCurrentDuration -
                (this._pauseStarted.getTime() - this._autoPlayStarted.getTime()));
        }
        if (typeof config.duration === 'number') {
            return config.duration;
        }
        else {
            return config.duration({
                index: lastActivatedContent.index,
                content: lastActivatedContent,
                value: lastActivatedContent.value,
                activeList: this._activeList,
            });
        }
    }
}

class ActiveListContent {
    constructor(activeList, index, value) {
        this.isActive = false;
        this.hasBeenActiveBefore = false;
        this.isFirst = false;
        this.isLast = false;
        this.hasNext = false;
        this.hasPrevious = false;
        this.isNext = false;
        this.isPrevious = false;
        this.activeList = activeList;
        this.index = index;
        this.value = value;
    }
    activate(activationOptions) {
        this.activeList.activateByIndex(this.index, activationOptions);
    }
    deactivate(activationOptions) {
        this.activeList.deactivateByIndex(this.index, activationOptions);
    }
    toggle(activationOptions) {
        this.activeList.toggleByIndex(this.index, activationOptions);
    }
    remove() {
        return this.activeList.removeByIndex(this.index);
    }
    swapWith(item) {
        const itemIndex = this.activeList.getIndex(item);
        this.swapWithByIndex(itemIndex);
    }
    swapWithByIndex(index) {
        this.activeList.swapByIndex(this.index, index);
    }
    swapWithNext() {
        const nextIndex = this.activeList._getBoundedNextIndex(this.index);
        this.swapWithByIndex(nextIndex);
    }
    swapWithPrevious() {
        const previousIndex = this.activeList._getBoundedPreviousIndex(this.index);
        this.swapWithByIndex(previousIndex);
    }
    moveToIndex(to) {
        this.activeList.moveByIndex(this.index, to);
    }
    moveToPredicate(predicate, options) {
        this.activeList.moveByIndexByPredicate(this.index, predicate, options);
    }
    moveToFirst() {
        this.activeList.moveByIndex(this.index, 0);
    }
    moveToLast() {
        this.activeList.moveByIndex(this.index, this.activeList.getLastIndex());
    }
}

class ActiveListCooldownDurationError extends Error {
    constructor() {
        super(`${common$3} cooldown > duration cannot be negative or zero`);
        this.name = "ActiveListCooldownDurationError";
    }
}

class _CooldownTimer {
    constructor(activeList, cooldown) {
        this._cooldownTimeoutId = null;
        this._cooldown = undefined;
        if (typeof cooldown === 'number') {
            this._assertDuration(cooldown);
        }
        this._activeList = activeList;
        this._cooldown = cooldown;
    }
    _isActive(activationOptions) {
        if (activationOptions.isUserInteraction === false) {
            return false;
        }
        return this._activeList.cooldown.isActive;
    }
    _setCooldown(activationOptions, content) {
        if (activationOptions.isUserInteraction === false) {
            return;
        }
        const duration = this._getDuration(activationOptions, content);
        if (duration === -1) {
            this._stopCooldown();
            return;
        }
        this._activeList.cooldown.isActive = true;
        this._activeList.cooldown.duration = duration;
        this._cooldownTimeoutId = window.setTimeout(() => {
            this._stopCooldown();
        }, duration);
        const event = {
            type: 'COOLDOWN_STARTED',
            time: new Date(),
        };
        this._activeList._inform(event);
    }
    _getDuration(activationOptions, content) {
        let duration = -1;
        if (activationOptions.cooldown !== undefined) {
            duration = this._getDurationFromConfig(activationOptions.cooldown, content);
        }
        else if (this._cooldown !== undefined) {
            duration = this._getDurationFromConfig(this._cooldown, content);
        }
        else {
            return -1;
        }
        this._assertDuration(duration);
        return duration;
    }
    _getDurationFromConfig(cooldownConfig, content) {
        if (typeof cooldownConfig === 'number') {
            return cooldownConfig;
        }
        else {
            return cooldownConfig({
                index: content.index,
                content: content,
                value: content.value,
                activeList: this._activeList,
            });
        }
    }
    _assertDuration(cooldownValue) {
        if (cooldownValue <= 0) {
            throw new ActiveListCooldownDurationError();
        }
    }
    _stopCooldown() {
        if (this._cooldownTimeoutId) {
            window.clearTimeout(this._cooldownTimeoutId);
        }
        if (!this._activeList.cooldown.isActive) {
            return;
        }
        this._activeList.cooldown.isActive = false;
        this._activeList.cooldown.duration = 0;
        const event = {
            type: 'COOLDOWN_ENDED',
            time: new Date(),
        };
        this._activeList._inform(event);
    }
}

class ActiveListActivationLimitReachedError extends Error {
    constructor() {
        super(`${common$3} activateByIndex > activation limit reached`);
        this.name = 'ActiveListActivationLimitReachedError';
    }
}

class ActiveListIndexOutOfBoundsError extends Error {
    constructor(message) {
        super(message);
        this.name = "ActiveListIndexOutOfBoundsError";
    }
}
function throwIndexOutOfBoundsError(method, indexName) {
    throw new ActiveListIndexOutOfBoundsError(`${common$3} ${method} > "${indexName}" is out of bounds`);
}

class ActiveListItemNotFoundError extends Error {
    constructor() {
        super(`${common$3} getIndex > index cannot be found, item is not in contents array`);
        this.name = 'ActiveListItemNotFoundError';
    }
}

class _History {
    constructor() {
        this._events = [];
        this._keepHistoryFor = 0;
    }
    _push(event) {
        if (this._keepHistoryFor > 0) {
            this._events.push(event);
            if (this._events.length - 1 === this._keepHistoryFor) {
                this._events.shift();
            }
        }
    }
    _setKeepHistoryFor(_keepHistoryFor = 0) {
        this._keepHistoryFor = _keepHistoryFor;
    }
}

class _Observer {
    constructor() {
        this._subscribers = [];
    }
    _subscribe(subscriber) {
        this._subscribers.push(subscriber);
        return () => {
            this._unsubscribe(subscriber);
        };
    }
    _unsubscribe(subscriber) {
        this._subscribers = this._subscribers.filter((s) => subscriber !== s);
    }
    _clear() {
        this._subscribers.length = 0;
    }
    _inform(observable, event) {
        this._subscribers.forEach((subscriber) => subscriber(observable, event));
    }
}

class ActiveList {
    constructor(config = {}, subscriber) {
        this._isInitializing = false;
        this.contents = [];
        this.maxActivationLimit = 1;
        this.maxActivationLimitBehavior = 'circular';
        this.active = [];
        this.activeContents = [];
        this.activeIndexes = [];
        this.lastActivated = null;
        this.lastActivatedContent = null;
        this.lastActivatedIndex = -1;
        this.lastDeactivated = null;
        this.lastDeactivatedContent = null;
        this.lastDeactivatedIndex = -1;
        this.isCircular = false;
        this.direction = 'right';
        this.oppositeDirection = 'left';
        this._history = new _History();
        this.history = this._history._events;
        this._observer = new _Observer();
        this.hasActiveChangedAtLeastOnce = false;
        this.cooldown = {
            isActive: false,
            duration: 0,
        };
        this.autoPlay = {
            isPlaying: false,
            duration: 0,
            hasBeenStoppedBefore: false,
        };
        licenseChecker._checkLicense();
        if (subscriber) {
            this.subscribe(subscriber);
        }
        this.initialize(config);
    }
    subscribe(subscriber) {
        return this._observer._subscribe(subscriber);
    }
    unsubscribe(subscriber) {
        this._observer._unsubscribe(subscriber);
    }
    unsubscribeAll() {
        this._observer._clear();
    }
    initialize(config) {
        this._isInitializing = true;
        this.maxActivationLimit =
            config.maxActivationLimit !== undefined ? config.maxActivationLimit : 1;
        this.maxActivationLimitBehavior = config.maxActivationLimitBehavior
            ? config.maxActivationLimitBehavior
            : 'circular';
        this.isCircular = !!config.isCircular;
        const contents = config.contents ? config.contents : [];
        this.contents.length = 0;
        contents.forEach((c, index) => {
            this.contents[index] = this._initializeABrokenContent(c, index, contents);
        });
        this._directions = config.directions
            ? config.directions
            : { next: 'right', previous: 'left' };
        this._history._events.length = 0;
        this._history._setKeepHistoryFor(config.keepHistoryFor);
        this._becameEmpty();
        this._activationCooldownTimer = new _CooldownTimer(this, config.cooldown);
        this.cooldown.isActive = false;
        this.cooldown.duration = 0;
        if (config.active !== undefined) {
            if (Array.isArray(config.active)) {
                config.active.forEach((active) => this.activate(active, { isUserInteraction: false }));
            }
            else {
                this.activate(config.active, { isUserInteraction: false });
            }
        }
        else if (config.activeIndexes !== undefined) {
            if (Array.isArray(config.activeIndexes)) {
                config.activeIndexes.forEach((index) => this.activateByIndex(index, {
                    isUserInteraction: false,
                }));
            }
            else {
                this.activateByIndex(config.activeIndexes, {
                    isUserInteraction: false,
                });
            }
        }
        this._emptyLastDeactivated();
        this.hasActiveChangedAtLeastOnce = false;
        this.direction = this._directions.next;
        this.oppositeDirection = this._directions.previous;
        this.autoPlay.isPlaying = false;
        this.autoPlay.duration = 0;
        this.autoPlay.hasBeenStoppedBefore = false;
        this._autoPlay = new _AutoPlay(this, config.autoPlay ? config.autoPlay : null);
        this._autoPlay._play(false);
        this._isInitializing = false;
        const event = {
            type: 'INITIALIZED',
            values: [...this.active],
            indexes: [...this.activeIndexes],
            time: new Date(),
        };
        this._inform(event);
    }
    _initializeABrokenContent(value, index, contents) {
        const content = new ActiveListContent(this, index, value);
        this._repairContent(content, index, contents);
        return content;
    }
    activateByIndex(index, activationOptions = {
        isUserInteraction: true,
        cooldown: undefined,
    }) {
        const previousDeactivatedIndex = this.lastDeactivatedIndex;
        const activatedContent = this._doActivateByIndex(index, activationOptions);
        if (!activatedContent) {
            return;
        }
        let deactivatedIndex = -1;
        let deactivatedValue = null;
        if (previousDeactivatedIndex !== this.lastDeactivatedIndex) {
            deactivatedIndex = this.lastDeactivatedIndex;
            deactivatedValue = this.lastDeactivated;
        }
        const event = {
            type: 'ACTIVATED',
            value: activatedContent.value,
            index,
            deactivatedIndex,
            deactivatedValue,
            time: new Date(),
        };
        this._inform(event);
        this._activationCooldownTimer._setCooldown(activationOptions, this.lastActivatedContent);
    }
    _doActivateByIndex(index, activationOptions) {
        if (this._checkIndex(index)) {
            throwIndexOutOfBoundsError('activateByIndex', 'index');
        }
        if (this.activeIndexes.includes(index)) {
            return null;
        }
        if (this._activationCooldownTimer._isActive(activationOptions)) {
            return null;
        }
        const limitReached = this.maxActivationLimit === false
            ? false
            : this.maxActivationLimit === this.activeIndexes.length;
        if (limitReached) {
            if (this.maxActivationLimitBehavior === 'error') {
                throw new ActiveListActivationLimitReachedError();
            }
            else if (this.maxActivationLimitBehavior === 'ignore') {
                return null;
            }
        }
        const nextIndex = this._getUnboundedNextIndex(index);
        const previousIndex = this._getUnboundedPreviousIndex(index);
        this.contents.forEach((content, i) => {
            content.isActive = content.isActive ? content.isActive : index === i;
            content.isNext = nextIndex === i;
            content.isPrevious = previousIndex === i;
            if (index === i) {
                this.activeIndexes.push(i);
                this.activeContents.push(content);
                this.active.push(content.value);
                if (limitReached) {
                    this.activeIndexes.shift();
                    this.active.shift();
                    const content = this.activeContents.shift();
                    if (content) {
                        this._deactivateContent(content);
                    }
                }
                this.direction = this._getDirectionWhenMovingToIndex(i);
                this.oppositeDirection =
                    this.direction === this._directions.next
                        ? this._directions.previous
                        : this._directions.next;
                this.lastActivated = content.value;
                this.lastActivatedContent = content;
                this.lastActivatedIndex = i;
                content.hasBeenActiveBefore = true;
            }
        });
        if (this._autoPlay) {
            this._autoPlay.onActiveIndexChanged(index, activationOptions);
        }
        this.hasActiveChangedAtLeastOnce = true;
        return this.lastActivatedContent;
    }
    activate(item, activationOptions) {
        const index = this.getIndex(item);
        this.activateByIndex(index, activationOptions);
    }
    activateByPredicate(predicate, activationOptions = {
        isUserInteraction: true,
        cooldown: undefined,
    }) {
        if (this._activationCooldownTimer._isActive(activationOptions)) {
            return undefined;
        }
        const previousActiveIndexes = [...this.activeIndexes];
        let lastActivated = null;
        let error = null;
        this._execPred(predicate, (index) => {
            try {
                const content = this._doActivateByIndex(index, activationOptions);
                if (content) {
                    lastActivated = content;
                }
            }
            catch (e) {
                if (e instanceof ActiveListActivationLimitReachedError) {
                    error = e;
                    return true;
                }
            }
        });
        if (lastActivated) {
            const values = [];
            const indexes = [];
            this.activeIndexes.forEach((current) => {
                const isNewlyActivated = previousActiveIndexes.every((prev) => prev !== current);
                if (isNewlyActivated) {
                    indexes.push(current);
                    values.push(this.contents[current].value);
                }
            });
            const deactivatedValues = [];
            const deactivatedIndexes = [];
            if (this.maxActivationLimit !== false &&
                this.maxActivationLimitBehavior === 'circular') {
                previousActiveIndexes.forEach((prev) => {
                    const isNewlyDeactivated = this.activeIndexes.every((current) => prev !== current);
                    if (isNewlyDeactivated) {
                        deactivatedIndexes.push(prev);
                        deactivatedValues.push(this.contents[prev].value);
                    }
                });
            }
            const event = {
                type: 'ACTIVATED_MULTIPLE',
                values,
                indexes,
                deactivatedIndexes,
                deactivatedValues,
                time: new Date(),
            };
            this._inform(event);
            if (lastActivated) {
                this._activationCooldownTimer._setCooldown(activationOptions, lastActivated);
            }
        }
        if (error) {
            throw error;
        }
    }
    activateNext(activationOptions) {
        if (this.isEmpty()) {
            return;
        }
        const index = this._getBoundedNextIndex(this.lastActivatedIndex);
        this.activateByIndex(index, activationOptions);
    }
    activatePrevious(activationOptions) {
        if (this.isEmpty()) {
            return;
        }
        const index = this._getBoundedPreviousIndex(this.lastActivatedIndex);
        this.activateByIndex(index, activationOptions);
    }
    activateFirst(activationOptions) {
        if (this.isEmpty()) {
            return;
        }
        this.activateByIndex(0, activationOptions);
    }
    activateLast(activationOptions) {
        if (this.isEmpty()) {
            return;
        }
        this.activateByIndex(this.getLastIndex(), activationOptions);
    }
    deactivateByIndex(index, activationOptions = {
        isUserInteraction: true,
        cooldown: undefined,
    }) {
        const deactivatedContent = this._doDeactivateByIndex(index, activationOptions);
        if (!deactivatedContent) {
            return;
        }
        const event = {
            type: 'DEACTIVATED',
            value: this.contents[index].value,
            index,
            time: new Date(),
        };
        this._inform(event);
        this._activationCooldownTimer._setCooldown(activationOptions, deactivatedContent);
    }
    _doDeactivateByIndex(index, activationOptions) {
        if (this._checkIndex(index)) {
            throwIndexOutOfBoundsError('deactivateByIndex', 'index');
        }
        const indexOfIndex = this.activeIndexes.indexOf(index);
        if (indexOfIndex === -1) {
            return null;
        }
        if (this._activationCooldownTimer._isActive(activationOptions)) {
            return null;
        }
        const deactivatedContent = this.activeContents[indexOfIndex];
        this._deactivateContent(deactivatedContent);
        this.activeIndexes.splice(indexOfIndex, 1);
        this.active.splice(indexOfIndex, 1);
        this.activeContents.splice(indexOfIndex, 1);
        if (this.activeIndexes.length === 0) {
            this._emptyLastActives();
            this.direction = this._directions.next;
        }
        else {
            this._setLastActives();
            this.direction = this._getDirectionWhenMovingToIndex(deactivatedContent.index);
            this.oppositeDirection = this.direction;
            this.direction =
                this.direction === this._directions.next
                    ? this._directions.previous
                    : this._directions.next;
        }
        this._repairContents();
        this.hasActiveChangedAtLeastOnce = true;
        if (this._autoPlay) {
            this._autoPlay._onDeactivation(activationOptions);
        }
        return deactivatedContent;
    }
    deactivate(item, activationOptions) {
        const index = this.getIndex(item);
        this.deactivateByIndex(index, activationOptions);
    }
    deactivateByPredicate(predicate, activationOptions = {
        isUserInteraction: true,
        cooldown: undefined,
    }) {
        if (this._activationCooldownTimer._isActive(activationOptions)) {
            return undefined;
        }
        const deactivatedIndexes = [];
        const deactivatedValues = [];
        let lastRemoved = null;
        this._execPred(predicate, (index) => {
            const content = this._doDeactivateByIndex(index, activationOptions);
            if (content) {
                deactivatedIndexes.push(content.index);
                deactivatedValues.push(content.value);
                lastRemoved = content;
            }
        });
        if (deactivatedIndexes.length === 0) {
            return;
        }
        const event = {
            type: 'DEACTIVATED_MULTIPLE',
            values: deactivatedValues,
            indexes: deactivatedIndexes,
            time: new Date(),
        };
        this._inform(event);
        if (lastRemoved) {
            this._activationCooldownTimer._setCooldown(activationOptions, lastRemoved);
        }
    }
    toggleByIndex(index, activationOptions) {
        if (this._checkIndex(index)) {
            throwIndexOutOfBoundsError('toggleByIndex', 'index');
        }
        if (this.contents[index].isActive) {
            this.deactivateByIndex(index, activationOptions);
        }
        else {
            this.activateByIndex(index, activationOptions);
        }
    }
    toggle(item, activationOptions) {
        const index = this.getIndex(item);
        this.toggleByIndex(index, activationOptions);
    }
    play() {
        this._autoPlay._play(true);
    }
    pause() {
        this._autoPlay._pause();
    }
    stop() {
        this._autoPlay._stop();
    }
    configureAutoPlay(autoPlayConfig) {
        this._autoPlay._setConfig(autoPlayConfig);
        if (autoPlayConfig) {
            this._autoPlay._play(true);
        }
        else {
            this._autoPlay._stop();
        }
    }
    insertAtIndex(item, index) {
        if (index < 0 || index > this.contents.length) {
            throwIndexOutOfBoundsError('insertAtIndex', 'index');
        }
        const content = this._initializeABrokenContent(item, index, this.contents);
        this.activeIndexes.forEach((i, aiIndex) => {
            this.activeIndexes[aiIndex] = i >= index ? i + 1 : i;
        });
        this.contents.splice(index, 0, content);
        if (index <= this.lastActivatedIndex) {
            this.lastActivatedIndex += 1;
        }
        this._repairContents();
        const event = {
            type: 'INSERTED',
            value: item,
            index,
            time: new Date(),
        };
        this._inform(event);
        return content;
    }
    push(item) {
        return this.insertAtIndex(item, this.contents.length);
    }
    unshift(item) {
        return this.insertAtIndex(item, 0);
    }
    insertByPredicate(item, predicate, options = { mode: 'at' }) {
        const mod = this._modeToMod(options.mode);
        return this._execPred(predicate, (index) => {
            const atIndex = Math.max(0, index + mod);
            return this.insertAtIndex(item, atIndex);
        });
    }
    removeByIndex(index) {
        const value = this._doRemoveAtIndex(index);
        const indexOfIndex = this.activeIndexes.indexOf(index);
        if (indexOfIndex !== -1) {
            this.activeIndexes.splice(indexOfIndex, 1);
            this.active.splice(indexOfIndex, 1);
            this.activeContents.splice(indexOfIndex, 1);
            this.hasActiveChangedAtLeastOnce = true;
        }
        this.activeIndexes.map((i, aiIndex) => {
            this.activeIndexes[aiIndex] = i >= index ? i - 1 : i;
        });
        if (this.isEmpty()) {
            this._becameEmpty();
            this._autoPlay._stop();
        }
        else {
            this._setLastActives();
        }
        this._repairContents();
        const event = {
            type: 'REMOVED',
            value,
            index,
            time: new Date(),
        };
        this._inform(event);
        return value;
    }
    _doRemoveAtIndex(index) {
        if (this._checkIndex(index)) {
            throwIndexOutOfBoundsError('removeByIndex', 'index');
        }
        if (this.lastDeactivated && this.lastDeactivatedIndex === index) {
            this._emptyLastDeactivated();
        }
        const value = this.contents[index].value;
        this.contents.splice(index, 1);
        return value;
    }
    remove(item) {
        const index = this.getIndex(item);
        return this.removeByIndex(index);
    }
    pop() {
        if (this.isEmpty()) {
            return undefined;
        }
        return this.removeByIndex(this.getLastIndex());
    }
    shift() {
        if (this.isEmpty()) {
            return undefined;
        }
        return this.removeByIndex(0);
    }
    removeByPredicate(predicate) {
        if (this.isEmpty()) {
            return [];
        }
        const removed = [];
        this._execPred(predicate, (index) => {
            const content = this.contents[index];
            removed.push(content);
        });
        const removedIndexes = [];
        removed.forEach((content, index) => {
            const actualIndex = content.index - index;
            this._doRemoveAtIndex(actualIndex);
            removedIndexes.push(content.index);
        });
        if (this.isEmpty()) {
            this._becameEmpty();
            this._autoPlay._stop();
        }
        else {
            removedIndexes.forEach((index) => {
                const indexOfIndex = this.activeIndexes.indexOf(index);
                if (indexOfIndex !== -1) {
                    this.activeIndexes.splice(indexOfIndex, 1);
                    this.active.splice(indexOfIndex, 1);
                    this.activeContents.splice(indexOfIndex, 1);
                    this.hasActiveChangedAtLeastOnce = true;
                }
            });
            removedIndexes.forEach((removed) => {
                this.activeIndexes.forEach((index, aiIndex) => {
                    this.activeIndexes[aiIndex] = index >= removed ? index - 1 : index;
                });
            });
            this._setLastActives();
        }
        const removedValues = removed.map((r) => r.value);
        if (removedIndexes.length > 0) {
            this._repairContents();
            const event = {
                type: 'REMOVED_MULTIPLE',
                indexes: [...removedIndexes],
                values: [...removedValues],
                time: new Date(),
            };
            this._inform(event);
        }
        return removedValues;
    }
    swapByIndex(a, b) {
        if (this._checkIndex(a)) {
            throwIndexOutOfBoundsError('swapByIndex', 'a');
        }
        if (this._checkIndex(b)) {
            throwIndexOutOfBoundsError('swapByIndex', 'b');
        }
        if (a === b) {
            return;
        }
        const itemA = this.contents[a];
        const itemB = this.contents[b];
        if (this.lastActivatedIndex === itemA.index) {
            this.lastActivatedIndex = itemB.index;
        }
        else if (this.lastActivatedIndex === itemB.index) {
            this.lastActivatedIndex = itemA.index;
        }
        const indexOfA = this.activeIndexes.indexOf(itemA.index);
        const indexOfB = this.activeIndexes.indexOf(itemB.index);
        if (indexOfA !== -1) {
            this.activeIndexes[indexOfA] = itemB.index;
        }
        if (indexOfB !== -1) {
            this.activeIndexes[indexOfB] = itemA.index;
        }
        itemA.index = b;
        itemB.index = a;
        this.contents[a] = itemB;
        this.contents[b] = itemA;
        this._repairContents();
        const event = {
            type: 'SWAPPED',
            value: {
                a: itemA.value,
                b: itemB.value,
            },
            index: {
                a,
                b,
            },
            time: new Date(),
        };
        this._inform(event);
    }
    swap(a, b) {
        const indexA = this.getIndex(a);
        const indexB = this.getIndex(b);
        this.swapByIndex(indexA, indexB);
    }
    moveByIndex(from, to) {
        if (this._checkIndex(from)) {
            throwIndexOutOfBoundsError('moveByIndex', 'from');
        }
        if (this._checkIndex(to)) {
            throwIndexOutOfBoundsError('moveByIndex', 'to');
        }
        if (from === to) {
            return;
        }
        const lastActivatedIndex = this.lastActivatedIndex;
        if (lastActivatedIndex === from) {
            this.lastActivatedIndex = to;
        }
        else if (to === lastActivatedIndex && from > lastActivatedIndex) {
            this.lastActivatedIndex += 1;
        }
        else if (to === lastActivatedIndex && from < lastActivatedIndex) {
            this.lastActivatedIndex -= 1;
        }
        else if (to > lastActivatedIndex && from < lastActivatedIndex) {
            this.lastActivatedIndex -= 1;
        }
        else if (to < lastActivatedIndex && from > lastActivatedIndex) {
            this.lastActivatedIndex += 1;
        }
        this.activeIndexes.forEach((index, aiIndex) => {
            if (index === from) {
                this.activeIndexes[aiIndex] = to;
                return;
            }
            if (index > from && index > to) {
                this.activeIndexes[aiIndex] = index;
                return;
            }
            if (index < from && index < to) {
                this.activeIndexes[aiIndex] = index;
                return;
            }
            this.activeIndexes[aiIndex] = from > to ? index + 1 : index - 1;
        });
        const fromItem = this.contents[from];
        this.contents.splice(from, 1);
        this.contents.splice(to, 0, fromItem);
        this._repairContents();
        const event = {
            type: 'MOVED',
            value: fromItem.value,
            index: {
                from,
                to,
            },
            time: new Date(),
        };
        this._inform(event);
    }
    move(item, to) {
        const from = this.getIndex(item);
        this.moveByIndex(from, to);
    }
    moveByIndexByPredicate(index, predicate, options = { mode: 'at' }) {
        const mod = this._modeToMod(options.mode);
        this._execPred(predicate, (i, length) => {
            const atIndex = Math.min(Math.max(0, i + mod), length - 1);
            this.moveByIndex(index, atIndex);
            return true;
        });
    }
    moveByPredicate(item, predicate, options) {
        const index = this.getIndex(item);
        this.moveByIndexByPredicate(index, predicate, options);
    }
    getIndex(item) {
        const contents = this.contents;
        const length = contents.length;
        for (let i = 0; i < length; i++) {
            if (contents[i].value === item) {
                return i;
            }
        }
        throw new ActiveListItemNotFoundError();
    }
    getLastIndex() {
        return this.contents.length - 1;
    }
    _getBoundedNextIndex(index) {
        let nextIndex = index + 1;
        if (nextIndex >= this.contents.length) {
            nextIndex = this.isCircular ? 0 : this.getLastIndex();
        }
        return nextIndex;
    }
    _getBoundedPreviousIndex(index) {
        let previousIndex = index - 1;
        if (previousIndex < 0) {
            previousIndex = this.isCircular ? this.getLastIndex() : 0;
        }
        return previousIndex;
    }
    _getUnboundedNextIndex(index) {
        const nextIndex = index + 1;
        if (this.isCircular && nextIndex === this.contents.length) {
            return 0;
        }
        return nextIndex;
    }
    _getUnboundedPreviousIndex(index) {
        const previousIndex = index - 1;
        if (this.isCircular && previousIndex < 0) {
            return this.getLastIndex();
        }
        return previousIndex;
    }
    isEmpty() {
        return this.contents.length === 0;
    }
    _getDirectionWhenMovingToIndex(next) {
        const lastActivatedIndex = this.lastActivatedIndex;
        if (this.isCircular) {
            if (this.lastActivatedIndex === -1) {
                return this._directions.next;
            }
            const lastActivatedLargerThanNext = this.lastActivatedIndex > next;
            const lastIndex = this.getLastIndex();
            const leftDistance = lastActivatedLargerThanNext
                ? this.lastActivatedIndex - next
                : lastIndex - next + this.lastActivatedIndex + 1;
            const rightDistance = lastActivatedLargerThanNext
                ? 1 + next + (lastIndex - this.lastActivatedIndex)
                : next - this.lastActivatedIndex;
            return leftDistance >= rightDistance
                ? this._directions.next
                : this._directions.previous;
        }
        else {
            return next >= lastActivatedIndex
                ? this._directions.next
                : this._directions.previous;
        }
    }
    _repairContents() {
        let nextIndex = null;
        let previousIndex = null;
        if (this.lastActivatedIndex !== -1) {
            nextIndex = this._getUnboundedNextIndex(this.lastActivatedIndex);
            previousIndex = this._getUnboundedPreviousIndex(this.lastActivatedIndex);
        }
        this.contents.forEach((content, index) => {
            content.index = index;
            content.isNext = nextIndex === index;
            content.isPrevious = previousIndex === index;
            this._repairContent(content, index, this.contents);
        });
    }
    _repairContent(content, index, contents) {
        content.isFirst = index === 0;
        content.isLast = index === contents.length - 1;
        if (this.isCircular) {
            content.hasNext = true;
            content.hasPrevious = true;
        }
        else {
            content.hasNext = index + 1 < contents.length;
            content.hasPrevious = index - 1 >= 0;
        }
    }
    _emptyLastActives() {
        this.lastActivatedIndex = -1;
        this.lastActivated = null;
        this.lastActivatedContent = null;
    }
    _emptyLastDeactivated() {
        this.lastDeactivatedIndex = -1;
        this.lastDeactivated = null;
        this.lastDeactivatedContent = null;
    }
    _becameEmpty() {
        this._emptyLastDeactivated();
        this._emptyLastActives();
        this.activeContents.length = 0;
        this.activeIndexes.length = 0;
        this.active.length = 0;
        this.hasActiveChangedAtLeastOnce = true;
    }
    _setLastActives() {
        if (this.activeIndexes.length === 0) {
            this._emptyLastActives();
            return;
        }
        const newLastActiveIndex = this.activeIndexes[this.activeIndexes.length - 1];
        const newLastActiveList = this.contents[newLastActiveIndex];
        this.lastActivated = newLastActiveList.value;
        this.lastActivatedContent = newLastActiveList;
        this.lastActivatedIndex = newLastActiveIndex;
    }
    _deactivateContent(content) {
        content.isActive = false;
        this.lastDeactivated = content.value;
        this.lastDeactivatedContent = content;
        this.lastDeactivatedIndex = content.index;
    }
    _execPred(predicate, action) {
        const contents = this.contents;
        const length = contents.length;
        for (let index = 0; index < length; index++) {
            const content = this.contents[index];
            const data = {
                index,
                content,
                value: content.value,
                activeList: this,
            };
            if (predicate(data)) {
                const result = action(index, length);
                if (result !== undefined) {
                    return result;
                }
            }
        }
        return null;
    }
    _inform(event) {
        if (this._isInitializing) {
            return;
        }
        this._history._push(event);
        this._observer._inform(this, event);
    }
    _checkIndex(index) {
        return index < 0 || index >= this.contents.length;
    }
    _modeToMod(mode) {
        return mode === 'at' ? 0 : mode === 'after' ? 1 : -1;
    }
}

function _callSubscriber(subscriberName, event, component, config) {
    const methodName = 'on' +
        event.type
            .toLowerCase()
            .split('_')
            .reduce((acc, word) => {
            const letters = word.split('');
            letters[0] = letters[0].toUpperCase();
            return acc + letters.join('');
        }, '');
    const method = config[methodName];
    if (!method) {
        if (config.debug) {
            console.warn(`uiloos > ${subscriberName} event '${event.type}' was fired but '${methodName}' method is not implemented, this might not be correct.`);
        }
        return;
    }
    method(event, component);
}

function createActiveListSubscriber(config) {
    return (activeList, event) => {
        _callSubscriber('createActiveListSubscriber', event, activeList, config);
    };
}

const common$2 = `uiloos > ViewChannel >`;

class ViewChannelAutoDismissDurationError extends Error {
    constructor() {
        super(`${common$2} autoDismiss > duration cannot be negative or zero`);
        this.name = 'ViewChannelAutoDismissDurationError';
    }
}

class _AutoDismiss {
    constructor(view, config) {
        this._autoDismissTimeoutId = null;
        this._autoDismissStarted = new Date();
        this._pauseStarted = null;
        this._autoDismissCurrentDuration = 0;
        this._config = null;
        this._view = view;
        this._config = config;
    }
    _play(inform) {
        if (this._view.autoDismiss.isPlaying) {
            return;
        }
        this._cancelTimer();
        if (!this._config) {
            return;
        }
        const duration = this._getDuration(this._config);
        if (duration <= 0) {
            throw new ViewChannelAutoDismissDurationError();
        }
        this._autoDismissCurrentDuration = duration;
        this._autoDismissStarted = new Date();
        if (this._pauseStarted === null) {
            this._view.autoDismiss.duration = duration;
        }
        const result = this._config.result;
        this._autoDismissTimeoutId = window.setTimeout(() => {
            if (this._view.isPresented) {
                this._view.autoDismiss.isPlaying = false;
                this._view.autoDismiss.duration = 0;
                this._view.viewChannel._doRemoveByIndex(this._view.index, result, 'AUTO_DISMISS');
            }
            this._autoDismissTimeoutId = null;
        }, duration);
        this._view.result.then(() => {
            this._cancelTimer();
        });
        this._view.autoDismiss.isPlaying = true;
        if (inform) {
            const event = {
                type: 'AUTO_DISMISS_PLAYING',
                view: this._view,
                index: this._view.index,
                time: new Date(),
            };
            this._view.viewChannel._inform(event);
        }
    }
    _pause() {
        if (!this._view.autoDismiss.isPlaying) {
            return;
        }
        this._pauseStarted = new Date();
        this._cancelTimer();
        this._view.autoDismiss.isPlaying = false;
        const event = {
            type: 'AUTO_DISMISS_PAUSED',
            view: this._view,
            index: this._view.index,
            time: new Date(),
        };
        this._view.viewChannel._inform(event);
    }
    _stop() {
        if (!this._view.autoDismiss.isPlaying && !this._pauseStarted) {
            return;
        }
        this._cancelTimer();
        this._pauseStarted = null;
        this._view.autoDismiss.isPlaying = false;
        this._view.autoDismiss.duration = 0;
        const event = {
            type: 'AUTO_DISMISS_STOPPED',
            view: this._view,
            index: this._view.index,
            time: new Date(),
        };
        this._view.viewChannel._inform(event);
    }
    _cancelTimer() {
        if (this._autoDismissTimeoutId !== null) {
            window.clearTimeout(this._autoDismissTimeoutId);
            this._autoDismissTimeoutId = null;
        }
    }
    _getDuration(config) {
        if (this._pauseStarted) {
            return (this._autoDismissCurrentDuration -
                (this._pauseStarted.getTime() - this._autoDismissStarted.getTime()));
        }
        return config.duration;
    }
}

class ViewChannelView {
    constructor(viewChannel, index, data, priority, autoDismissConfig) {
        var _a;
        this.autoDismiss = {
            isPlaying: false,
            duration: 0,
        };
        this._resolve = null;
        this.isPresented = true;
        this.viewChannel = viewChannel;
        this.index = index;
        this.data = data;
        this.priority = priority;
        this.result = new Promise((resolve) => {
            this._resolve = resolve;
        });
        this._autoDismiss = new _AutoDismiss(this, autoDismissConfig);
        this._autoDismiss._play(false);
        this.autoDismiss.duration = (_a = autoDismissConfig === null || autoDismissConfig === void 0 ? void 0 : autoDismissConfig.duration) !== null && _a !== void 0 ? _a : 0;
    }
    dismiss(result) {
        this.viewChannel.dismiss(this, result);
    }
    play() {
        this._autoDismiss._play(true);
    }
    pause() {
        this._autoDismiss._pause();
    }
    stop() {
        this._autoDismiss._stop();
    }
    changeData(data) {
        this.viewChannel.changeData(this, data);
    }
}

class ViewChannelIndexOutOfBoundsError extends Error {
    constructor(method) {
        super(`${common$2} ${method} > "index" is out of bounds`);
        this.name = 'ViewChannelIndexOutOfBoundsError';
    }
}

class ViewChannelViewNotFoundError extends Error {
    constructor(method) {
        super(`${common$2} ${method} > "ViewChannelView" not found in views array`);
        this.name = 'ViewChannelViewNotFoundError';
    }
}

class ViewChannel {
    constructor(config = {}, subscriber) {
        this.views = [];
        this._history = new _History();
        this.history = this._history._events;
        this._observer = new _Observer();
        licenseChecker._checkLicense();
        if (subscriber) {
            this.subscribe(subscriber);
        }
        this.initialize(config);
    }
    initialize(config) {
        this._history._events.length = 0;
        this._history._setKeepHistoryFor(config.keepHistoryFor);
        this._clearViews();
        const event = {
            type: 'INITIALIZED',
            time: new Date(),
        };
        this._inform(event);
    }
    subscribe(subscriber) {
        return this._observer._subscribe(subscriber);
    }
    unsubscribe(subscriber) {
        this._observer._unsubscribe(subscriber);
    }
    unsubscribeAll() {
        this._observer._clear();
    }
    present(viewConfig) {
        const priority = viewConfig.priority ? viewConfig.priority : 0;
        const priorityArray = Array.isArray(priority) ? priority : [priority];
        const index = this._getIndexForPriority(priorityArray);
        const view = new ViewChannelView(this, index, viewConfig.data, priorityArray, viewConfig.autoDismiss);
        this.views.splice(index, 0, view);
        this._repairIndexes();
        const event = {
            type: 'PRESENTED',
            view,
            index,
            time: new Date(),
        };
        this._inform(event);
        return view;
    }
    _doRemoveByIndex(index, result, reason) {
        if (index < 0 || index >= this.views.length) {
            throw new ViewChannelIndexOutOfBoundsError('dismissByIndex');
        }
        const view = this.views[index];
        this.views.splice(index, 1);
        this._repairIndexes();
        view.isPresented = false;
        view.autoDismiss.duration = 0;
        view.autoDismiss.isPlaying = false;
        view._resolve(result);
        const event = {
            type: 'DISMISSED',
            view,
            index,
            reason,
            time: new Date(),
        };
        this._inform(event);
    }
    dismissByIndex(index, result) {
        this._doRemoveByIndex(index, result, 'USER_INTERACTION');
    }
    dismiss(view, result) {
        if (!view.isPresented) {
            return;
        }
        const index = this.views.indexOf(view);
        if (index === -1) {
            throw new ViewChannelViewNotFoundError('dismiss');
        }
        this.dismissByIndex(index, result);
    }
    dismissAll(result) {
        if (this.views.length === 0) {
            return;
        }
        const indexes = [];
        const dismissedViews = [...this.views];
        this._clearViews();
        dismissedViews.forEach((view) => {
            view.isPresented = false;
            view._resolve(result);
            indexes.push(view.index);
        });
        const event = {
            type: 'DISMISSED_ALL',
            views: dismissedViews,
            indexes,
            time: new Date(),
        };
        this._inform(event);
    }
    changeDataByIndex(index, data) {
        if (index < 0 || index >= this.views.length) {
            throw new ViewChannelIndexOutOfBoundsError('changeDataByIndex');
        }
        const view = this.views[index];
        view.data = data;
        const event = {
            type: 'DATA_CHANGED',
            view: view,
            data,
            index: view.index,
            time: new Date(),
        };
        this._inform(event);
    }
    changeData(view, data) {
        const index = this.views.indexOf(view);
        if (index === -1) {
            throw new ViewChannelViewNotFoundError('changeData');
        }
        this.changeDataByIndex(index, data);
    }
    _getIndexForPriority(priority) {
        for (let view of this.views) {
            const largestArray = priority.length > view.priority.length ? priority : view.priority;
            for (let level = 0; level < largestArray.length; level++) {
                const inserted = this._getPriorityAtLevel(priority, level);
                const existing = this._getPriorityAtLevel(view.priority, level);
                if (inserted < existing) {
                    return view.index;
                }
            }
        }
        return this.views.length;
    }
    _getPriorityAtLevel(priorityArray, level) {
        const priority = priorityArray[level];
        if (priority !== undefined) {
            return priority;
        }
        else {
            return 0;
        }
    }
    _repairIndexes() {
        this.views.forEach((view, index) => {
            view.index = index;
        });
    }
    _clearViews() {
        this.views.length = 0;
    }
    _inform(event) {
        this._history._push(event);
        this._observer._inform(this, event);
    }
}

function createViewChannelSubscriber(config) {
    return (viewChannel, event) => {
        _callSubscriber('createViewChannelSubscriber', event, viewChannel, config);
    };
}

class TypewriterCursor {
    constructor(typewriter, position, data, selection) {
        this.isBlinking = true;
        this._blinkTimeoutId = null;
        this._typewriter = typewriter;
        this.position = position;
        this.selection = selection;
        this.data = data;
    }
    _startBlink() {
        if (this.isBlinking) {
            return;
        }
        this._clearBlink();
        this._blinkTimeoutId = window.setTimeout(() => {
            this.isBlinking = true;
            const event = {
                type: 'BLINKING',
                time: new Date(),
                cursor: this,
            };
            this._typewriter._inform(event);
        }, this._typewriter.blinkAfter);
    }
    _clearBlink() {
        if (this._blinkTimeoutId) {
            window.clearTimeout(this._blinkTimeoutId);
            this._blinkTimeoutId = null;
        }
    }
}

const name = 'Typewriter';
const common$1 = `uiloos > ${name} >`;

class TypewriterBlinkAfterError extends Error {
    constructor() {
        super(`${common$1} blinkAfter cannot be negative or zero`);
        this.name = `${name}BlinkAfterError`;
    }
}

class TypewriterDelayError extends Error {
    constructor() {
        super(`${common$1} delay cannot be negative or zero`);
        this.name = `${name}DelayError`;
    }
}

class TypewriterRepeatError extends Error {
    constructor() {
        super(`${common$1} repeat cannot be negative or zero`);
        this.name = `${name}RepeatError`;
    }
}

class TypewriterRepeatDelayError extends Error {
    constructor() {
        super(`${common$1} repeatDelay cannot be a negative number`);
        this.name = `${name}RepeatDelayError`;
    }
}

class TypewriterCursorOutOfBoundsError extends Error {
    constructor() {
        super(`${common$1} cursor is out of bounds`);
        this.name = `${name}CursorOutOfBoundsError`;
    }
}

class TypewriterCursorNotAtSelectionEdgeError extends Error {
    constructor() {
        super(`${common$1} cursor is not placed on edges of selection`);
        this.name = `${name}CursorNotAtSelectionEdgeError`;
    }
}

class TypewriterCursorSelectionInvalidRangeError extends Error {
    constructor() {
        super(`${common$1} cursors selection has an invalid range: start is equal or larger than the end`);
        this.name = `${name}CursorSelectionInvalidRangeError`;
    }
}

class TypewriterCursorSelectionOutOfBoundsError extends Error {
    constructor(name) {
        super(`${common$1} cursor selection ${name} is out of bounds`);
        this.name = `${name}InvalidCursorSelectionOutOfBoundsError`;
    }
}

class TypewriterActionUnknownCursorError extends Error {
    constructor() {
        super(`${common$1} action uses an unknown cursor`);
        this.name = `${name}ActionUnknownCursorError`;
    }
}

class Typewriter {
    constructor(config = {}, subscriber) {
        this.cursors = [];
        this._originalCursors = [];
        this.actions = [];
        this.lastPerformedAction = null;
        this.text = '';
        this._originalText = '';
        this.blinkAfter = 250;
        this.isPlaying = false;
        this._stopped = false;
        this.isFinished = false;
        this.repeat = false;
        this.repeatDelay = 0;
        this._repeated = 0;
        this.hasBeenStoppedBefore = false;
        this._index = 0;
        this._animationTimeoutId = null;
        this._tickStarted = new Date();
        this._pauseStarted = null;
        this._history = new _History();
        this.history = this._history._events;
        this._observer = new _Observer();
        licenseChecker._checkLicense();
        if (subscriber) {
            this.subscribe(subscriber);
        }
        this.initialize(config);
    }
    initialize(config) {
        this._clearAnimation();
        this.cursors.forEach((c) => {
            c._clearBlink();
        });
        this.actions.length = 0;
        this.cursors.length = 0;
        this._history._events.length = 0;
        this._history._setKeepHistoryFor(config.keepHistoryFor);
        this.text = config.text !== undefined ? config.text : '';
        this._originalText = this.text;
        const textLength = Array.from(this.text).length;
        this._originalCursors.length = 0;
        if (config.cursors) {
            config.cursors.forEach((cursor) => {
                const position = cursor.position;
                if (position < 0 || position > textLength) {
                    throw new TypewriterCursorOutOfBoundsError();
                }
                const selection = cursor.selection;
                if (selection) {
                    const { start, end } = selection;
                    if (position !== start && position !== end) {
                        throw new TypewriterCursorNotAtSelectionEdgeError();
                    }
                    if (start < 0 || start > textLength) {
                        throw new TypewriterCursorSelectionOutOfBoundsError(_START);
                    }
                    if (end < 0 || end > textLength) {
                        throw new TypewriterCursorSelectionOutOfBoundsError(_END);
                    }
                    if (start >= end) {
                        throw new TypewriterCursorSelectionInvalidRangeError();
                    }
                }
                this._originalCursors.push({
                    position: cursor.position,
                    data: cursor.data,
                    selection: selection
                        ? {
                            start: selection.start,
                            end: selection.end,
                        }
                        : undefined,
                });
                this.cursors.push(new TypewriterCursor(this, cursor.position, cursor.data ? cursor.data : undefined, selection));
            });
        }
        else {
            this.cursors.push(new TypewriterCursor(this, textLength, undefined, undefined));
            this._originalCursors.push({ data: undefined, position: textLength });
        }
        if (config.actions) {
            for (let i = 0; i < config.actions.length; i++) {
                const action = config.actions[i];
                if (action.delay <= 0) {
                    throw new TypewriterDelayError();
                }
                if (this.cursors[action.cursor] === undefined) {
                    throw new TypewriterActionUnknownCursorError();
                }
                this.actions.push(action);
            }
        }
        this._index = 0;
        this.isPlaying =
            (config.autoPlay === true || config.autoPlay === undefined) &&
                this.actions.length > 0;
        this.isFinished = false;
        this.blinkAfter = config.blinkAfter !== undefined ? config.blinkAfter : 250;
        if (this.blinkAfter <= 0) {
            throw new TypewriterBlinkAfterError();
        }
        this._repeated = 0;
        this.repeat = config.repeat !== undefined ? config.repeat : false;
        if (typeof this.repeat === 'number' && this.repeat <= 0) {
            throw new TypewriterRepeatError();
        }
        this.repeatDelay =
            config.repeatDelay !== undefined ? config.repeatDelay : 0;
        if (this.repeatDelay < 0) {
            throw new TypewriterRepeatDelayError();
        }
        this.hasBeenStoppedBefore = false;
        this._pauseStarted = null;
        if (this.isPlaying) {
            this._tick();
        }
        const event = {
            type: 'INITIALIZED',
            time: new Date(),
        };
        this._inform(event);
    }
    subscribe(subscriber) {
        return this._observer._subscribe(subscriber);
    }
    unsubscribe(subscriber) {
        this._observer._unsubscribe(subscriber);
    }
    unsubscribeAll() {
        this._observer._clear();
    }
    play() {
        if (this.isFinished || this._stopped) {
            this._init();
            this._stopped = false;
            this.hasBeenStoppedBefore = false;
            this._resetTandC();
        }
        else if (this.isPlaying || this.actions.length === 0) {
            return;
        }
        this.isPlaying = true;
        this._tick();
        const event = {
            type: 'PLAYING',
            time: new Date(),
        };
        this._inform(event);
    }
    pause() {
        if (!this.isPlaying) {
            return;
        }
        this._clearAnimation();
        this.isPlaying = false;
        this._pauseStarted = new Date();
        this.cursors.forEach((c) => c._startBlink());
        const event = {
            type: 'PAUSED',
            time: new Date(),
        };
        this._inform(event);
    }
    stop() {
        if (this.isFinished || (!this.isPlaying && !this._pauseStarted)) {
            return;
        }
        this._clearAnimation();
        this.isPlaying = false;
        this.hasBeenStoppedBefore = true;
        this._stopped = true;
        this._init();
        const event = {
            type: 'STOPPED',
            time: new Date(),
        };
        this._inform(event);
    }
    _init() {
        this.isFinished = false;
        this._index = 0;
        this._pauseStarted = null;
        this._repeated = 0;
        this.cursors.forEach((c) => (c.isBlinking = true));
    }
    _tick() {
        const action = this.actions[this._index];
        const cursor = this.cursors[action.cursor];
        let delay = action.delay;
        if (this._pauseStarted) {
            delay -= this._pauseStarted.getTime() - this._tickStarted.getTime();
            this._pauseStarted = null;
        }
        this._tickStarted = new Date();
        this._animationTimeoutId = window.setTimeout(() => {
            var _a, _b;
            const textArray = Array.from(this.text);
            cursor.isBlinking = false;
            cursor._startBlink();
            let noOp = false;
            if (action.type !== 'mouse') {
                if (action.text === '⎚') {
                    if (this.text === '') {
                        noOp = true;
                    }
                    else {
                        this.text = '';
                        this.cursors.forEach((c) => {
                            c.position = 0;
                            c.selection = undefined;
                        });
                    }
                }
                else if (action.text === '←') {
                    noOp = this._actionLorR(cursor, -1, ((_a = cursor.selection) === null || _a === void 0 ? void 0 : _a.start) || 0, 0);
                }
                else if (action.text === '→') {
                    noOp = this._actionLorR(cursor, 1, ((_b = cursor.selection) === null || _b === void 0 ? void 0 : _b.end) || 0, textArray.length);
                }
                else if (action.text === '⇧←') {
                    noOp = this._actionSLorR(cursor, -1, _START, 0);
                }
                else if (action.text === '⇧→') {
                    noOp = this._actionSLorR(cursor, 1, _END, textArray.length);
                }
                else if (action.text === '⌫') {
                    const removed = {
                        start: -1,
                        end: -1,
                        no: -1,
                    };
                    if (cursor.selection) {
                        const start = cursor.selection.start;
                        const end = cursor.selection.end;
                        const no = end - start;
                        textArray.splice(cursor.selection.start, no);
                        this.text = textArray.join('');
                        removed.start = start;
                        removed.end = end;
                        removed.no = no;
                    }
                    else {
                        const position = cursor.position;
                        if (position !== 0) {
                            textArray.splice(position - 1, 1);
                            this.text = textArray.join('');
                            removed.start = position - 1;
                            removed.end = position;
                            removed.no = 1;
                        }
                        else {
                            noOp = true;
                        }
                    }
                    cursor.selection = undefined;
                    if (!noOp) {
                        this.cursors.forEach((c) => {
                            const hasOverlap = this._overlap(c.selection, removed) > 0;
                            if (c.position > removed.start) {
                                if (c.selection &&
                                    hasOverlap &&
                                    removed.start < c.selection.start &&
                                    c.position < removed.end) {
                                    c.position = removed.start;
                                }
                                else {
                                    c.position -= removed.no;
                                }
                            }
                            const selection = c.selection;
                            if (selection) {
                                if (hasOverlap) {
                                    const overlap = this._overlap(selection, removed);
                                    if (selection.start <= removed.start) {
                                        selection.end -= overlap;
                                    }
                                    else {
                                        selection.start -= overlap;
                                        selection.end -= removed.no;
                                    }
                                    if (selection.start === selection.end) {
                                        c.selection = undefined;
                                    }
                                }
                                else if (removed.start <= selection.start &&
                                    removed.end <= selection.start) {
                                    selection.start -= removed.no;
                                    selection.end -= removed.no;
                                }
                            }
                        });
                    }
                }
                else {
                    const text = Array.from(action.text);
                    if (cursor.selection) {
                        const start = cursor.selection.start;
                        const end = cursor.selection.end;
                        const no = end - start;
                        textArray.splice(cursor.selection.start, no);
                        cursor.position = start;
                        const removed = {
                            start,
                            end,
                            no,
                        };
                        textArray.splice(cursor.position, 0, ...text);
                        this.text = textArray.join('');
                        this.cursors.forEach((c) => {
                            if (cursor === c) {
                                return;
                            }
                            const removedNo = removed.no - text.length;
                            const selection = c.selection;
                            if (selection) {
                                if (selection.start >= removed.start &&
                                    selection.end <= removed.end) {
                                    c.selection = undefined;
                                    c.position = removed.start;
                                }
                                else {
                                    const hasOverlap = this._overlap(selection, removed) > 0;
                                    if (hasOverlap) {
                                        const isPosStart = selection.start === c.position;
                                        const overlap = this._overlap(selection, removed);
                                        if (selection.start > removed.start) {
                                            selection.start -= removed.no - overlap;
                                            selection.start = Math.max(0, selection.start);
                                        }
                                        if (selection.end === removed.end) {
                                            selection.end -= removed.no;
                                        }
                                        else if (selection.end === removed.end - 1) {
                                            selection.end -= removed.no - 1;
                                        }
                                        else if (selection.end !== removed.start) {
                                            selection.end -= removed.no - text.length;
                                        }
                                        c.position = isPosStart ? selection.start : selection.end;
                                    }
                                    else if (removed.start <= selection.start &&
                                        removed.end <= selection.start) {
                                        const isPosStart = selection.start === c.position;
                                        const leftMost = Math.min(selection.start, selection.end);
                                        if (removed.end === leftMost) {
                                            selection.start = removed.start;
                                        }
                                        else {
                                            selection.start -= removedNo;
                                        }
                                        selection.end -= removedNo;
                                        selection.start = Math.max(0, selection.start);
                                        c.position = isPosStart ? selection.start : selection.end;
                                    }
                                }
                            }
                            else {
                                if (c.position >= removed.start && c.position <= removed.end) {
                                    c.position = removed.start;
                                }
                                else if (c.position > removed.end) {
                                    c.position -= removedNo;
                                }
                            }
                        });
                        cursor.position += text.length;
                    }
                    else {
                        textArray.splice(cursor.position, 0, ...text);
                        this.text = textArray.join('');
                        this.cursors.forEach((c) => {
                            if (cursor === c) {
                                return;
                            }
                            const selection = c.selection;
                            if (selection) {
                                if (cursor.position < selection.start) {
                                    selection.start += text.length;
                                    selection.end += text.length;
                                }
                                else if (cursor.position < selection.end) {
                                    selection.end += text.length;
                                }
                            }
                            if (cursor.position < c.position) {
                                c.position += text.length;
                            }
                        });
                        cursor.position += text.length;
                    }
                    cursor.selection = undefined;
                }
            }
            else {
                const position = Math.min(textArray.length, Math.max(0, action.position));
                if (cursor.position === position &&
                    this._same(cursor.selection, action.selection)) {
                    noOp = true;
                }
                else {
                    cursor.position = position;
                    if (action.selection) {
                        cursor.selection = {
                            start: action.selection.start,
                            end: action.selection.end,
                        };
                    }
                    else {
                        cursor.selection = undefined;
                    }
                }
            }
            this._index += 1;
            if (this._index >= this.actions.length) {
                if (this.repeat === false || this.repeat === this._repeated + 1) {
                    this.isFinished = true;
                    this.isPlaying = false;
                    const event = {
                        type: 'FINISHED',
                        action,
                        time: new Date(),
                        cursor,
                    };
                    this.lastPerformedAction = action;
                    this._inform(event);
                }
                else {
                    this._repeated += 1;
                    this._change(noOp, action, cursor);
                    this._animationTimeoutId = window.setTimeout(() => {
                        this._index = 0;
                        this._resetTandC();
                        this.cursors.forEach((c) => {
                            c._clearBlink();
                            c.isBlinking = true;
                        });
                        const event = {
                            type: 'REPEATING',
                            time: new Date(),
                            cursor,
                        };
                        this._inform(event);
                        this._tick();
                    }, this.repeatDelay);
                }
            }
            else {
                this._change(noOp, action, cursor);
                this._tick();
            }
        }, delay);
    }
    _clearAnimation() {
        if (this._animationTimeoutId) {
            window.clearTimeout(this._animationTimeoutId);
            this._animationTimeoutId = null;
            return;
        }
    }
    _resetTandC() {
        this.text = this._originalText;
        this._originalCursors.forEach((copy, index) => {
            const cursor = this.cursors[index];
            cursor.data = copy.data ? copy.data : undefined;
            cursor.position = copy.position;
            const selection = copy.selection;
            if (selection) {
                cursor.selection = {
                    start: selection.start,
                    end: selection.end,
                };
            }
            else {
                cursor.selection = undefined;
            }
        });
    }
    _overlap(s, r) {
        if (!s) {
            return 0;
        }
        return Math.min(s.end, r.end) - Math.max(s.start, r.start);
    }
    _same(a, b) {
        if (a === b) {
            return true;
        }
        if (a && b) {
            return a.start === b.start && a.end === b.end;
        }
        else {
            return false;
        }
    }
    _actionLorR(cursor, mod, select, stop) {
        if (cursor.position === stop) {
            if (cursor.selection === undefined) {
                return true;
            }
        }
        else {
            if (cursor.selection) {
                cursor.position = select;
            }
            else {
                cursor.position += mod;
            }
        }
        cursor.selection = undefined;
        return false;
    }
    _actionSLorR(cursor, mod, which, stop) {
        if (cursor.position === stop) {
            return true;
        }
        else {
            cursor.position += mod;
            const selection = cursor.selection;
            if (selection) {
                selection[which] += mod;
            }
            else {
                const selection = { start: -1, end: -1 };
                selection[which === _START ? _END : _START] = cursor.position - mod;
                selection[which] = cursor.position;
                cursor.selection = selection;
            }
        }
        return false;
    }
    _change(noOp, action, cursor) {
        if (noOp) {
            return;
        }
        const event = {
            type: 'CHANGED',
            action,
            time: new Date(),
            cursor,
        };
        this.lastPerformedAction = action;
        this._inform(event);
    }
    _inform(event) {
        this._history._push(event);
        this._observer._inform(this, event);
    }
    *[Symbol.iterator]() {
        const text = Array.from(this.text);
        const cursorMap = {};
        this.cursors.forEach((c) => {
            const pos = c.position;
            if (cursorMap[pos]) {
                cursorMap[pos].push(c);
            }
            else {
                cursorMap[pos] = [c];
            }
        });
        for (let i = 0; i < text.length; i++) {
            const cursors = cursorMap[i];
            yield {
                position: i,
                cursors: cursors ? cursors : [],
                character: text[i],
                selected: this.cursors.filter((c) => c.selection && i >= c.selection.start && i < c.selection.end),
            };
        }
        const finalCursors = cursorMap[text.length];
        if (finalCursors) {
            yield {
                position: text.length,
                character: '',
                cursors: finalCursors,
                selected: [],
            };
        }
    }
}
const _START = 'start';
const _END = 'end';

function createTypewriterSubscriber(config) {
    return (typewriter, event) => {
        _callSubscriber('createTypewriterSubscriber', event, typewriter, config);
    };
}

const typewriterActionTypeBackspace = '⌫';

function typewriterFromSentences(config, subscriber) {
    const delay = config.delay === undefined ? 50 : config.delay;
    const sentenceDelay = config.sentenceDelay === undefined ? 2000 : config.sentenceDelay;
    const actions = [];
    let text = config.text ? Array.from(config.text) : [];
    let firstSentence = true;
    for (let sentence of config.sentences) {
        const sentenceArray = Array.from(sentence);
        let charsInCommonFromStart = 0;
        for (let i = 0; i < text.length; i++) {
            const fromChar = text[i];
            const toChar = sentenceArray[i];
            if (toChar === fromChar) {
                charsInCommonFromStart += 1;
            }
            else {
                break;
            }
        }
        const backspaces = text.length - charsInCommonFromStart;
        text = text.slice(0, text.length - backspaces);
        for (let i = 0; i < backspaces; i++) {
            const actualDelay = !firstSentence && i === 0 ? sentenceDelay : delay;
            actions.push({
                type: _KEYBOARD,
                text: typewriterActionTypeBackspace,
                delay: actualDelay,
                cursor: 0,
            });
        }
        const missingChars = text.length > 0
            ? sentenceArray.slice(charsInCommonFromStart)
            : sentenceArray;
        for (const missingChar of missingChars) {
            actions.push({
                type: _KEYBOARD,
                text: missingChar,
                delay,
                cursor: 0,
            });
        }
        text = text.concat(missingChars);
        firstSentence = false;
    }
    return new Typewriter(Object.assign(Object.assign({ repeatDelay: sentenceDelay }, config), { actions }), subscriber);
}
const _KEYBOARD = 'keyboard';

const DATE_GALLERY_MODES = [
    'day',
    'week',
    'month',
    'month-six-weeks',
    'month-pad-to-week',
    'year',
];

class DateGalleryDate {
    constructor(dateGallery, date, events, isPadding, isSelected) {
        this.canBeSelected = true;
        this.dateGallery = dateGallery;
        this.date = date;
        this.events = events;
        this.isPadding = isPadding;
        this.isSelected = isSelected;
        this.isToday = dateGallery._sameDay(new Date(), date);
        this.hasEvents = events.length > 0;
        this.hasEventsWithOverlap = this.events.some(e => e.isOverlapping);
        if (dateGallery._canSelect) {
            this.canBeSelected = dateGallery._canSelect(this);
        }
    }
    select() {
        this.dateGallery.selectDate(this.date);
    }
    deselect() {
        this.dateGallery.deselectDate(this.date);
    }
    toggle() {
        this.dateGallery.toggleDateSelection(this.date);
    }
}

function _hasOverlap(a, b) {
    const earlier = a.startDate < b.startDate ? a : b;
    const later = earlier === a ? b : a;
    const laterStart = later.startDate.getTime();
    const earlierStart = earlier.startDate.getTime();
    const earlierEnd = earlier.endDate.getTime();
    return laterStart >= earlierStart && laterStart < earlierEnd;
}

class DateGalleryEvent {
    constructor(dateGallery, data, startDate, endDate) {
        this.overlappingEvents = [];
        this.isOverlapping = false;
        this.spansMultipleDays = false;
        this.dateGallery = dateGallery;
        this.data = data;
        this.startDate = startDate;
        this.endDate = endDate;
    }
    _recalculate() {
        this.overlappingEvents.length = 0;
        this.dateGallery.events.forEach((other) => {
            if (other === this) {
                return;
            }
            if (_hasOverlap(this, other)) {
                this.overlappingEvents.push(other);
            }
        });
        this.overlappingEvents.sort((a, b) => {
            return a.startDate.getTime() - b.startDate.getTime();
        });
        this.spansMultipleDays = !this.dateGallery._sameDay(this.startDate, this.endDate);
        this.isOverlapping = this.overlappingEvents.length > 0;
    }
    remove() {
        this.dateGallery.removeEvent(this);
    }
    move(range) {
        this.dateGallery.moveEvent(this, range);
    }
    changeData(data) {
        this.dateGallery.changeEventData(this, data);
    }
}

const common = `uiloos > DateGallery >`;

class DateGalleryEventInvalidRangeError extends Error {
    constructor() {
        super(`${common} invalid range, an events startDate lies after its endDate`);
        this.name = 'DateGalleryEventInvalidRangeError';
    }
}

class DateGalleryEventNotFoundError extends Error {
    constructor(method) {
        super(`${common} ${method} > "DateGalleryEvent" not found in events array`);
        this.name = 'DateGalleryEventNotFoundError';
    }
}

class DateGalleryFirstDayOfWeekError extends Error {
    constructor() {
        super(`${common} invalid firstDayOfWeek`);
        this.name = 'DateGalleryFirstDayOfWeekError';
    }
}

class DateGalleryInvalidDateError extends Error {
    constructor(method, dateName) {
        super(`${common} ${method} > "${dateName}" is an or contains an invalid date`);
        this.name = 'DateGalleryInvalidDateError';
    }
}

class DateGalleryModeError extends Error {
    constructor(mode) {
        super(`${common} unknown mode: "${mode}" provided`);
        this.name = 'DateGalleryModeError';
    }
}

class DateGalleryNumberOfFramesError extends Error {
    constructor() {
        super(`${common} numberOfFrames cannot be negative or zero`);
        this.name = 'DateGalleryNumberOfFramesError';
    }
}

class DateGallerySelectionLimitReachedError extends Error {
    constructor(method) {
        super(`${common} ${method} > selection limit reached`);
        this.name = 'DateGallerySelectionLimitReachedError';
    }
}

class DateGallery {
    constructor(config = {}, subscriber) {
        this._isInitializing = false;
        this.isUTC = false;
        this.frames = [];
        this.firstFrame = {
            dates: [],
            events: [],
            anchorDate: new Date(),
        };
        this.numberOfFrames = 1;
        this.maxSelectionLimit = false;
        this.maxSelectionLimitBehavior = 'circular';
        this.selectedDates = [];
        this._canSelect = undefined;
        this.events = [];
        this.mode = 'month-six-weeks';
        this.firstDayOfWeek = 0;
        this._anchorDate = new Date();
        this._history = new _History();
        this.history = this._history._events;
        this._observer = new _Observer();
        licenseChecker._checkLicense();
        if (subscriber) {
            this.subscribe(subscriber);
        }
        this._doInitialize(config, 'constructor');
    }
    subscribe(subscriber) {
        return this._observer._subscribe(subscriber);
    }
    unsubscribe(subscriber) {
        this._observer._unsubscribe(subscriber);
    }
    unsubscribeAll() {
        this._observer._clear();
    }
    initialize(config) {
        this._doInitialize(config, 'initialize');
    }
    _doInitialize(config, method) {
        this._isInitializing = true;
        this.isUTC = config.isUTC !== undefined ? config.isUTC : false;
        this.mode = config.mode ? config.mode : 'month-six-weeks';
        this._checkMode(this.mode);
        this.firstDayOfWeek = config.firstDayOfWeek ? config.firstDayOfWeek : 0;
        if (this.firstDayOfWeek < 0 || this.firstDayOfWeek > 6) {
            throw new DateGalleryFirstDayOfWeekError();
        }
        this.numberOfFrames =
            config.numberOfFrames !== undefined ? config.numberOfFrames : 1;
        if (this.numberOfFrames <= 0) {
            throw new DateGalleryNumberOfFramesError();
        }
        this.maxSelectionLimit =
            config.maxSelectionLimit !== undefined ? config.maxSelectionLimit : false;
        this.maxSelectionLimitBehavior =
            config.maxSelectionLimitBehavior !== undefined
                ? config.maxSelectionLimitBehavior
                : 'circular';
        this._anchorDate = config.initialDate
            ? this._toDate(config.initialDate, method, 'initialDate')
            : new Date();
        this._toMidnight(this._anchorDate);
        this._dragAnchor();
        this.events.length = 0;
        this.selectedDates.length = 0;
        this._canSelect = config.canSelect;
        if (config.selectedDates) {
            config.selectedDates.forEach((s) => {
                this.selectDate(this._toDate(s, method, 'selectedDates'));
            });
        }
        if (config.events) {
            config.events.forEach((config) => {
                this._doAddEvent(config, method);
            });
            this.events.forEach((e) => {
                e._recalculate();
            });
        }
        this._buildFrames();
        this._history._events.length = 0;
        this._history._setKeepHistoryFor(config.keepHistoryFor);
        this._isInitializing = false;
        const event = {
            type: 'INITIALIZED',
            time: new Date(),
        };
        this._inform(event);
    }
    changeConfig(config) {
        if (config.initialDate === undefined &&
            config.mode === undefined &&
            config.numberOfFrames === undefined) {
            return;
        }
        let changed = false;
        let drag = false;
        const method = 'changeConfig';
        if (config.mode !== undefined) {
            this._checkMode(config.mode);
            if (this.mode !== config.mode) {
                this.mode = config.mode;
                changed = true;
                drag = true;
            }
        }
        if (config.initialDate !== undefined) {
            const date = this._toDate(config.initialDate, method, 'initialDate');
            this._toMidnight(date);
            if (!this._sameDay(date, this._anchorDate)) {
                this._anchorDate = date;
                changed = true;
                drag = true;
            }
        }
        if (config.numberOfFrames !== undefined) {
            if (config.numberOfFrames <= 0) {
                throw new DateGalleryNumberOfFramesError();
            }
            if (this.numberOfFrames !== config.numberOfFrames) {
                this.numberOfFrames = config.numberOfFrames;
                changed = true;
            }
        }
        if (changed) {
            if (drag) {
                this._dragAnchor();
            }
            this._buildFrames();
            const event = {
                type: 'CONFIG_CHANGED',
                mode: this.mode,
                anchorDate: new Date(this._anchorDate),
                numberOfFrames: this.numberOfFrames,
                frames: this.frames,
                time: new Date(),
            };
            this._inform(event);
        }
    }
    today() {
        this.changeConfig({
            initialDate: new Date(),
        });
    }
    _buildFrames(inform = false) {
        this.frames.length = 0;
        const anchorAtStart = this._anchorDate;
        for (let i = 0; i < this.numberOfFrames; i++) {
            if (i !== 0) {
                this._moveFrame(1);
            }
            const frame = {
                dates: [],
                events: [],
                anchorDate: new Date(this._anchorDate),
            };
            const anchor = new Date(this._anchorDate);
            if (this.mode === 'day') {
                frame.dates.push(this._makeDate(anchor));
            }
            else if (this.mode === 'week') {
                this._addNoDates(anchor, 7, frame);
            }
            else if (this.mode === 'year') {
                const year = this._getFullYear(anchor);
                const isLeapYear = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
                this._addNoDates(anchor, isLeapYear ? 366 : 365, frame);
            }
            else if (this.mode === 'month') {
                const anchorMonth = this._getMonth(anchor);
                this._addMonth(anchor, anchorMonth, frame);
            }
            else if (this.mode === 'month-pad-to-week') {
                const date = this._firstDayOfWeek(anchor);
                const anchorMonth = this._getMonth(anchor);
                while (this._getMonth(date) !== anchorMonth) {
                    this._pushDay(date, frame);
                }
                this._addMonth(date, anchorMonth, frame);
                while (this._getDay(date) !== this.firstDayOfWeek) {
                    this._pushDay(date, frame);
                }
            }
            else if (this.mode === 'month-six-weeks') {
                const date = this._firstDayOfWeek(anchor);
                this._addNoDates(date, 42, frame);
            }
            this.frames.push(frame);
            const startDate = new Date(frame.dates[0].date);
            const endDate = new Date(frame.dates[frame.dates.length - 1].date);
            this._toMidnight(startDate);
            this._moveDateBy(endDate, 1);
            this._toMidnight(endDate);
            this.events.forEach((event) => {
                if (_hasOverlap({ startDate, endDate }, event)) {
                    frame.events.push(event);
                }
            });
        }
        this._anchorDate = anchorAtStart;
        this.firstFrame = this.frames[0];
        if (inform) {
            const event = {
                type: 'FRAME_CHANGED',
                frames: this.frames,
                time: new Date(),
            };
            this._inform(event);
        }
    }
    next() {
        this._moveFrame(this.numberOfFrames);
        this._buildFrames(true);
    }
    previous() {
        this._moveFrame(-this.numberOfFrames);
        this._buildFrames(true);
    }
    _moveFrame(mod) {
        const date = new Date(this._anchorDate);
        if (this.mode === 'day') {
            this._moveDateBy(date, 1 * mod);
        }
        else if (this.mode === 'week') {
            this._moveDateBy(date, 7 * mod);
        }
        else if (this.mode === 'year') {
            if (this.isUTC) {
                date.setUTCFullYear(date.getUTCFullYear() + 1 * mod);
            }
            else {
                date.setFullYear(date.getFullYear() + 1 * mod);
            }
        }
        else {
            if (this.isUTC) {
                date.setUTCMonth(date.getUTCMonth() + 1 * mod);
            }
            else {
                date.setMonth(date.getMonth() + 1 * mod);
            }
        }
        this._anchorDate = date;
    }
    selectDate(date) {
        const method = 'selectDate';
        const _date = this._toDate(date, method, 'date');
        const [index] = this._indexOfDate(_date, method);
        if (index === -1) {
            this._doSelectDate(_date, method);
        }
    }
    _doSelectDate(date, method) {
        const deselectedDates = [];
        const midnight = this._pushSelectDate(date, method, deselectedDates);
        if (!midnight) {
            return;
        }
        this._buildFrames();
        let deselectedDate = deselectedDates[0];
        if (!deselectedDate) {
            deselectedDate = null;
        }
        const event = {
            type: 'DATE_SELECTED',
            date: new Date(midnight),
            deselectedDate,
            time: new Date(),
        };
        this._inform(event);
    }
    _pushSelectDate(date, method, deselectedDates) {
        const midnight = this._toDate(date, method, 'date');
        this._toMidnight(midnight);
        if (this._canSelect && !this._canSelect(this._makeDate(midnight))) {
            return null;
        }
        const limitReached = this.maxSelectionLimit === false
            ? false
            : this.maxSelectionLimit === this.selectedDates.length;
        if (limitReached) {
            if (this.maxSelectionLimitBehavior === 'error') {
                throw new DateGallerySelectionLimitReachedError(method);
            }
            else if (this.maxSelectionLimitBehavior === 'ignore') {
                return null;
            }
            else {
                const deselected = this.selectedDates.shift();
                if (deselected) {
                    deselectedDates.push(deselected);
                }
            }
        }
        this.selectedDates.push(midnight);
        return midnight;
    }
    deselectDate(date) {
        const [index, _date] = this._indexOfDate(date, 'deselectDate');
        if (index === -1) {
            return;
        }
        this._doDeselectDate(index, _date);
    }
    _doDeselectDate(index, date) {
        this.selectedDates.splice(index, 1);
        this._buildFrames();
        const event = {
            type: 'DATE_DESELECTED',
            date,
            time: new Date(),
        };
        this._inform(event);
    }
    toggleDateSelection(date) {
        const [index, _date] = this._indexOfDate(date, 'toggleDateSelection');
        if (index === -1) {
            this._doSelectDate(_date, 'toggleDateSelection');
        }
        else {
            this._doDeselectDate(index, _date);
        }
    }
    _indexOfDate(date, method) {
        const _date = this._toDate(date, method, 'date');
        const index = this.selectedDates.findIndex((s) => {
            return this._sameDay(s, _date);
        });
        return [index, _date];
    }
    deselectAll() {
        if (this.selectedDates.length === 0) {
            return;
        }
        const dates = [...this.selectedDates];
        this.selectedDates.length = 0;
        this._buildFrames();
        const e = {
            type: 'DATE_DESELECTED_MULTIPLE',
            dates,
            time: new Date(),
        };
        this._inform(e);
    }
    selectRange(a, b) {
        const method = 'selectRange';
        const aDate = this._toDate(a, method, 'a');
        const bDate = this._toDate(b, method, 'b');
        const startDate = bDate.getTime() > aDate.getTime() ? aDate : bDate;
        const endDate = aDate === startDate ? bDate : aDate;
        this._toMidnight(startDate);
        this._moveDateBy(endDate, 1);
        this._toMidnight(endDate);
        const date = new Date(startDate);
        const oldSelected = [...this.selectedDates];
        const selectedCollector = [];
        const deselectedCollector = [];
        let error = null;
        try {
            while (!this._sameDay(date, endDate)) {
                const index = this.selectedDates.findIndex((s) => {
                    return this._sameDay(s, date);
                });
                if (index === -1) {
                    const midnight = this._pushSelectDate(date, method, deselectedCollector);
                    if (midnight) {
                        selectedCollector.push(midnight);
                    }
                }
                this._moveDateBy(date, 1);
            }
        }
        catch (e) {
            if (e instanceof DateGallerySelectionLimitReachedError) {
                error = e;
            }
        }
        const reportedSelectedDates = selectedCollector.filter((selectedDate) => !deselectedCollector.includes(selectedDate));
        if (reportedSelectedDates.length === 0) {
            if (error) {
                throw error;
            }
            return;
        }
        const deselectedDates = oldSelected.filter((oldSelect) => !this.selectedDates.some((selectedDate) => this._sameDay(oldSelect, selectedDate)));
        this._buildFrames();
        const event = {
            type: 'DATE_SELECTED_MULTIPLE',
            dates: reportedSelectedDates.map((d) => new Date(d)),
            deselectedDates: deselectedDates.map((d) => new Date(d)),
            time: new Date(),
        };
        this._inform(event);
        if (error) {
            throw error;
        }
    }
    addEvent(event) {
        const addedEvent = this._doAddEvent(event, 'addEvent');
        this.events.forEach((e) => {
            e._recalculate();
        });
        this._buildFrames();
        const e = {
            type: 'EVENT_ADDED',
            event: addedEvent,
            time: new Date(),
        };
        this._inform(e);
        return addedEvent;
    }
    _doAddEvent(config, method) {
        const startDate = this._toDate(config.startDate, method, 'event.startDate');
        const endDate = this._toDate(config.endDate, method, 'event.endDate');
        if (startDate.getTime() > endDate.getTime()) {
            throw new DateGalleryEventInvalidRangeError();
        }
        const event = new DateGalleryEvent(this, config.data, startDate, endDate);
        this.events.push(event);
        this.events.sort((a, b) => {
            return a.startDate.getTime() - b.startDate.getTime();
        });
        return event;
    }
    removeEvent(event) {
        const index = this.events.indexOf(event);
        if (index === -1) {
            return event;
        }
        this.events.splice(index, 1);
        this.events.forEach((e) => {
            e._recalculate();
        });
        this._buildFrames();
        const e = {
            type: 'EVENT_REMOVED',
            event,
            time: new Date(),
        };
        this._inform(e);
        return event;
    }
    moveEvent(event, range) {
        const startDate = this._toDate(range.startDate, 'moveEvent', 'range.startDate');
        const endDate = this._toDate(range.endDate, 'moveEvent', 'range.endDate');
        if (startDate.getTime() > endDate.getTime()) {
            throw new DateGalleryEventInvalidRangeError();
        }
        if (startDate.getTime() === event.startDate.getTime() &&
            endDate.getTime() === event.endDate.getTime()) {
            return;
        }
        const index = this.events.indexOf(event);
        if (index === -1) {
            throw new DateGalleryEventNotFoundError('moveEvent');
        }
        event.startDate = startDate;
        event.endDate = endDate;
        this.events.sort((a, b) => {
            return a.startDate.getTime() - b.startDate.getTime();
        });
        this.events.forEach((e) => {
            e._recalculate();
        });
        this._buildFrames();
        const e = {
            type: 'EVENT_MOVED',
            event,
            time: new Date(),
        };
        this._inform(e);
    }
    changeEventData(event, data) {
        const index = this.events.indexOf(event);
        if (index === -1) {
            throw new DateGalleryEventNotFoundError('changeEventData');
        }
        event.data = data;
        const e = {
            type: 'EVENT_DATA_CHANGED',
            event,
            data,
            time: new Date(),
        };
        this._inform(e);
    }
    _dragAnchor() {
        if (this.mode === 'year') {
            if (this.isUTC) {
                this._anchorDate.setUTCDate(1);
                this._anchorDate.setUTCMonth(0);
            }
            else {
                this._anchorDate.setDate(1);
                this._anchorDate.setMonth(0);
            }
        }
        else if (this.mode === 'week') {
            this._anchorDate = this._firstDayOfWeek(this._anchorDate);
        }
        else if (this.mode.startsWith('month')) {
            if (this.isUTC) {
                this._anchorDate.setUTCDate(1);
            }
            else {
                this._anchorDate.setDate(1);
            }
        }
    }
    _inform(event) {
        if (this._isInitializing) {
            return;
        }
        this._history._push(event);
        this._observer._inform(this, event);
    }
    _toDate(date, method, dateName) {
        if (date instanceof Date) {
            this._checkDate(date, method, dateName);
        }
        const result = new Date(date);
        this._checkDate(result, method, dateName);
        return result;
    }
    _checkDate(date, method, dateName) {
        const isValid = Object.prototype.toString.call(date) === '[object Date]' &&
            !isNaN(date.valueOf());
        if (!isValid) {
            throw new DateGalleryInvalidDateError(method, dateName);
        }
    }
    _firstDayOfWeek(date) {
        const copy = new Date(date);
        while (this._getDay(copy) !== this.firstDayOfWeek) {
            this._moveDateBy(copy, -1);
        }
        return copy;
    }
    _addNoDates(date, amount, frame) {
        for (let i = 0; i < amount; i++) {
            this._pushDay(date, frame);
        }
    }
    _addMonth(date, month, frame) {
        while (this._getMonth(date) === month) {
            this._pushDay(date, frame);
        }
    }
    _pushDay(date, frame) {
        frame.dates.push(this._makeDate(date));
        this._moveDateBy(date, 1);
    }
    _makeDate(date) {
        const _date = new Date(date);
        this._toMidnight(_date);
        let isPadding = false;
        if (this.mode === 'month-pad-to-week' || this.mode === 'month-six-weeks') {
            const anchorMonth = this._getMonth(this._anchorDate);
            isPadding = this._getMonth(_date) !== anchorMonth;
        }
        return new DateGalleryDate(this, _date, this.events.filter((event) => {
            const time = _date.getTime();
            const startDate = new Date(event.startDate);
            const start = this._toMidnight(startDate);
            const endDate = new Date(event.endDate);
            this._moveDateBy(endDate, 1);
            const end = this._toMidnight(endDate);
            return time >= start && time < end;
        }), isPadding, this.selectedDates.some((selected) => {
            return this._sameDay(selected, _date);
        }));
    }
    isSameDay(a, b) {
        const method = 'isSameDay';
        return this._sameDay(this._toDate(a, method, 'a'), this._toDate(b, method, 'b'));
    }
    _sameDay(a, b) {
        return (this._getFullYear(a) === this._getFullYear(b) &&
            this._getMonth(a) === this._getMonth(b) &&
            this._getDate(a) === this._getDate(b));
    }
    _getFullYear(date) {
        return this.isUTC ? date.getUTCFullYear() : date.getFullYear();
    }
    _getMonth(date) {
        return this.isUTC ? date.getUTCMonth() : date.getMonth();
    }
    _getDate(date) {
        return this.isUTC ? date.getUTCDate() : date.getDate();
    }
    _getDay(date) {
        return this.isUTC ? date.getUTCDay() : date.getDay();
    }
    _checkMode(mode) {
        if (!DATE_GALLERY_MODES.includes(mode)) {
            throw new DateGalleryModeError(mode);
        }
    }
    _toMidnight(date) {
        if (this.isUTC) {
            return date.setUTCHours(0, 0, 0, 0);
        }
        else {
            return date.setHours(0, 0, 0, 0);
        }
    }
    _moveDateBy(date, mod) {
        if (this.isUTC) {
            date.setUTCDate(date.getUTCDate() + mod);
        }
        else {
            date.setDate(date.getDate() + mod);
        }
    }
}

function createDateGallerySubscriber(config) {
    return (activeList, event) => {
        _callSubscriber('createDateGallerySubscriber', event, activeList, config);
    };
}

export { ActiveList, ActiveListActivationLimitReachedError, ActiveListAutoPlayDurationError, ActiveListContent, ActiveListCooldownDurationError, ActiveListIndexOutOfBoundsError, ActiveListItemNotFoundError, DATE_GALLERY_MODES, DateGallery, DateGalleryDate, DateGalleryEvent, DateGalleryEventInvalidRangeError, DateGalleryEventNotFoundError, DateGalleryFirstDayOfWeekError, DateGalleryInvalidDateError, DateGalleryModeError, DateGalleryNumberOfFramesError, DateGallerySelectionLimitReachedError, Typewriter, TypewriterActionUnknownCursorError, TypewriterBlinkAfterError, TypewriterCursor, TypewriterCursorNotAtSelectionEdgeError, TypewriterCursorOutOfBoundsError, TypewriterCursorSelectionInvalidRangeError, TypewriterCursorSelectionOutOfBoundsError, TypewriterDelayError, TypewriterRepeatDelayError, TypewriterRepeatError, ViewChannel, ViewChannelAutoDismissDurationError, ViewChannelIndexOutOfBoundsError, ViewChannelView, ViewChannelViewNotFoundError, _LicenseChecker, createActiveListSubscriber, createDateGallerySubscriber, createTypewriterSubscriber, createViewChannelSubscriber, licenseChecker, typewriterFromSentences };
//# sourceMappingURL=index.js.map