/* eslint-disable object-shorthand */
/**
 * An event object for passing data to event handlers and letting them control propagation.
 * <p>This is pretty much identical to how W3C and jQuery implement events.</p>
 * @class EventData
 * @constructor
 */
export class SlickEventData {
    get defaultPrevented() {
        return this._isDefaultPrevented;
    }
    constructor(event, args) {
        this.event = event;
        this.args = args;
        this._isPropagationStopped = false;
        this._isImmediatePropagationStopped = false;
        this._isDefaultPrevented = false;
        this.returnValue = undefined;
        this.nativeEvent = event;
        this._arguments = args;
        // when we already have an event, we want to keep some of the event properties
        // looping through some props is the only way to keep and sync these properties to the returned EventData
        if (event) {
            [
                'altKey', 'ctrlKey', 'metaKey', 'shiftKey', 'key', 'keyCode',
                'clientX', 'clientY', 'offsetX', 'offsetY', 'pageX', 'pageY',
                'bubbles', 'target', 'type', 'which', 'x', 'y'
            ].forEach(key => this[key] = event[key]);
        }
        this._eventTarget = this.nativeEvent ? this.nativeEvent.target : undefined;
    }
    /**
     * Stops event from propagating up the DOM tree.
     * @method stopPropagation
     */
    stopPropagation() {
        this._isPropagationStopped = true;
        this.nativeEvent?.stopPropagation();
    }
    /**
     * Returns whether stopPropagation was called on this event object.
     * @method isPropagationStopped
     * @return {Boolean}
     */
    isPropagationStopped() {
        return this._isPropagationStopped;
    }
    /**
     * Prevents the rest of the handlers from being executed.
     * @method stopImmediatePropagation
     */
    stopImmediatePropagation() {
        this._isImmediatePropagationStopped = true;
        if (this.nativeEvent) {
            this.nativeEvent.stopImmediatePropagation();
        }
    }
    /**
     * Returns whether stopImmediatePropagation was called on this event object.\
     * @method isImmediatePropagationStopped
     * @return {Boolean}
     */
    isImmediatePropagationStopped() {
        return this._isImmediatePropagationStopped;
    }
    getNativeEvent() {
        return this.nativeEvent;
    }
    preventDefault() {
        if (this.nativeEvent) {
            this.nativeEvent.preventDefault();
        }
        this._isDefaultPrevented = true;
    }
    isDefaultPrevented() {
        if (this.nativeEvent) {
            return this.nativeEvent.defaultPrevented;
        }
        return this._isDefaultPrevented;
    }
    addReturnValue(value) {
        if (this.returnValue === undefined && value !== undefined) {
            this.returnValue = value;
        }
    }
    getReturnValue() {
        return this.returnValue;
    }
    getArguments() {
        return this._arguments;
    }
    resetReturnValue() {
        this.returnValue = undefined;
    }
}
/**
 * A simple publisher-subscriber implementation.
 * @class Event
 * @constructor
 */
export class SlickEvent {
    get subscriberCount() {
        return this._handlers.length;
    }
    /**
     * Constructor
     * @param {String} [eventName] - event name that could be used for dispatching CustomEvent (when enabled)
     * @param {BasePubSub} [pubSubService] - event name that could be used for dispatching CustomEvent (when enabled)
     */
    constructor(eventName, pubSub) {
        this.eventName = eventName;
        this.pubSub = pubSub;
        this._handlers = [];
        this._pubSubService = pubSub;
    }
    /**
     * Adds an event handler to be called when the event is fired.
     * <p>Event handler will receive two arguments - an <code>EventData</code> and the <code>data</code>
     * object the event was fired with.<p>
     * @method subscribe
     * @param {Function} fn - Event handler.
     */
    subscribe(fn) {
        this._handlers.push(fn);
    }
    /**
     * Removes an event handler added with <code>subscribe(fn)</code>.
     * @method unsubscribe
     * @param {Function} [fn] - Event handler to be removed.
     */
    unsubscribe(fn) {
        for (let i = this._handlers.length - 1; i >= 0; i--) {
            if (this._handlers[i] === fn) {
                this._handlers.splice(i, 1);
            }
        }
    }
    /**
     * Fires an event notifying all subscribers.
     * @method notify
     * @param {Object} args Additional data object to be passed to all handlers.
     * @param {EventData} [event] - An <code>EventData</code> object to be passed to all handlers.
     *      For DOM events, an existing W3C event object can be passed in.
     * @param {Object} [scope] - The scope ("this") within which the handler will be executed.
     *      If not specified, the scope will be set to the <code>Event</code> instance.
     */
    notify(args, evt, scope, ignorePrevEventDataValue = false) {
        const sed = evt instanceof SlickEventData ? evt : new SlickEventData(evt, args);
        if (ignorePrevEventDataValue) {
            sed.resetReturnValue();
        }
        scope = scope || this;
        for (let i = 0; i < this._handlers.length && !(sed.isPropagationStopped() || sed.isImmediatePropagationStopped()); i++) {
            const returnValue = this._handlers[i].call(scope, sed, args);
            sed.addReturnValue(returnValue);
        }
        // user can optionally add a global PubSub Service which makes it easy to publish/subscribe to events
        if (typeof this._pubSubService?.publish === 'function' && this.eventName) {
            const ret = this._pubSubService.publish(this.eventName, { args, eventData: sed }, undefined, 
            // assign the PubSub internal event to our SlickEventData.nativeEvent
            // so that we can call preventDefault() which would return a `returnValue = false`
            (evt) => sed.nativeEvent ??= evt);
            sed.addReturnValue(ret);
        }
        return sed;
    }
    setPubSubService(pubSub) {
        this._pubSubService = pubSub;
    }
}
export class SlickEventHandler {
    constructor() {
        this.handlers = [];
    }
    get subscriberCount() {
        return this.handlers.length;
    }
    subscribe(event, handler) {
        this.handlers.push({ event, handler });
        event.subscribe(handler);
        return this; // allow chaining
    }
    unsubscribe(event, handler) {
        let i = this.handlers.length;
        while (i--) {
            if (this.handlers[i].event === event &&
                this.handlers[i].handler === handler) {
                this.handlers.splice(i, 1);
                event.unsubscribe(handler);
                return;
            }
        }
        return this; // allow chaining
    }
    unsubscribeAll() {
        let i = this.handlers.length;
        while (i--) {
            this.handlers[i].event.unsubscribe(this.handlers[i].handler);
        }
        this.handlers = [];
        return this; // allow chaining
    }
}
/**
 * A structure containing a range of cells.
 * @class Range
 * @constructor
 * @param fromRow {Integer} Starting row.
 * @param fromCell {Integer} Starting cell.
 * @param toRow {Integer} Optional. Ending row. Defaults to <code>fromRow</code>.
 * @param toCell {Integer} Optional. Ending cell. Defaults to <code>fromCell</code>.
 */
export class SlickRange {
    constructor(fromRow, fromCell, toRow, toCell) {
        if (toRow === undefined && toCell === undefined) {
            toRow = fromRow;
            toCell = fromCell;
        }
        /**
         * @property fromRow
         * @type {Integer}
         */
        this.fromRow = Math.min(fromRow, toRow);
        /**
         * @property fromCell
         * @type {Integer}
         */
        this.fromCell = Math.min(fromCell, toCell);
        /**
         * @property toRow
         * @type {Integer}
         */
        this.toRow = Math.max(fromRow, toRow);
        /**
         * @property toCell
         * @type {Integer}
         */
        this.toCell = Math.max(fromCell, toCell);
    }
    /**
     * Returns whether a range represents a single cell.
     * @method isSingleCell
     * @return {Boolean}
     */
    isSingleCell() {
        return this.fromRow === this.toRow && this.fromCell === this.toCell;
    }
    /**
     * Returns whether a range represents a single row.
     * @method isSingleRow
     * @return {Boolean}
     */
    isSingleRow() {
        return this.fromRow === this.toRow;
    }
    /**
     * Returns whether a range contains a given cell.
     * @method contains
     * @param row {Integer}
     * @param cell {Integer}
     * @return {Boolean}
     */
    contains(row, cell) {
        return row >= this.fromRow && row <= this.toRow &&
            cell >= this.fromCell && cell <= this.toCell;
    }
    /**
     * Returns a readable representation of a range.
     * @method toString
     * @return {String}
     */
    toString() {
        if (this.isSingleCell()) {
            return `(${this.fromRow}:${this.fromCell})`;
        }
        else {
            return `(${this.fromRow}:${this.fromCell} - ${this.toRow}:${this.toCell})`;
        }
    }
    ;
}
/**
 * A base class that all special / non-data rows (like Group and GroupTotals) derive from.
 * @class NonDataItem
 * @constructor
 */
export class SlickNonDataItem {
    constructor() {
        this.__nonDataRow = true;
    }
}
/**
 * Information about a group of rows.
 * @class Group
 * @extends SlickNonDataItem
 * @constructor
 */
export class SlickGroup extends SlickNonDataItem {
    constructor() {
        super();
        this.__group = true;
        /**
         * Grouping level, starting with 0.
         * @property level
         * @type {Number}
         */
        this.level = 0;
        /**
         * Number of rows in the group.
         * @property count
         * @type {Integer}
         */
        this.count = 0;
        /**
         * Grouping value.
         * @property value
         * @type {Object}
         */
        this.value = null;
        /**
         * Formatted display value of the group.
         * @property title
         * @type {String}
         */
        this.title = null;
        /**
         * Whether a group is collapsed.
         * @property collapsed
         * @type {Boolean}
         */
        this.collapsed = false;
        /**
         * Whether a group selection checkbox is checked.
         * @property selectChecked
         * @type {Boolean}
         */
        this.selectChecked = false;
        /**
         * GroupTotals, if any.
         * @property totals
         * @type {GroupTotals}
         */
        this.totals = null;
        /**
         * Rows that are part of the group.
         * @property rows
         * @type {Array}
         */
        this.rows = [];
        /**
         * Sub-groups that are part of the group.
         * @property groups
         * @type {Array}
         */
        this.groups = null;
        /**
         * A unique key used to identify the group.
         * This key can be used in calls to DataView `collapseGroup()` or `expandGroup()`.
         * @property groupingKey
         * @type {Object}
         */
        this.groupingKey = '';
    }
    /**
     * Compares two Group instances.
     * @method equals
     * @return {Boolean}
     * @param group {Group} Group instance to compare to.
     */
    equals(group) {
        return this.value === group.value &&
            this.count === group.count &&
            this.collapsed === group.collapsed &&
            this.title === group.title;
    }
    ;
}
/**
 * Information about group totals.
 * An instance of GroupTotals will be created for each totals row and passed to the aggregators
 * so that they can store arbitrary data in it.  That data can later be accessed by group totals
 * formatters during the display.
 * @class GroupTotals
 * @extends SlickNonDataItem
 * @constructor
 */
export class SlickGroupTotals extends SlickNonDataItem {
    constructor() {
        super();
        this.__groupTotals = true;
        /**
         * Parent Group.
         * @param group
         * @type {Group}
         */
        this.group = null; // pre-assign to null
        /**
         * Whether the totals have been fully initialized / calculated.
         * Will be set to false for lazy-calculated group totals.
         * @param initialized
         * @type {Boolean}
         */
        this.initialized = false;
    }
}
/**
 * A locking helper to track the active edit controller and ensure that only a single controller
 * can be active at a time.  This prevents a whole class of state and validation synchronization
 * issues.  An edit controller (such as SlickGrid) can query if an active edit is in progress
 * and attempt a commit or cancel before proceeding.
 * @class EditorLock
 * @constructor
 */
export class SlickEditorLock {
    constructor() {
        this.activeEditController = null;
    }
    /**
     * Returns true if a specified edit controller is active (has the edit lock).
     * If the parameter is not specified, returns true if any edit controller is active.
     * @method isActive
     * @param editController {EditController}
     * @return {Boolean}
     */
    isActive(editController) {
        return (editController ? this.activeEditController === editController : this.activeEditController !== null);
    }
    ;
    /**
     * Sets the specified edit controller as the active edit controller (acquire edit lock).
     * If another edit controller is already active, and exception will be throw new Error(.
     * @method activate
     * @param editController {EditController} edit controller acquiring the lock
     */
    activate(editController) {
        if (editController === this.activeEditController) { // already activated?
            return;
        }
        if (this.activeEditController !== null) {
            throw new Error(`SlickEditorLock.activate: an editController is still active, can't activate another editController`);
        }
        if (!editController.commitCurrentEdit) {
            throw new Error('SlickEditorLock.activate: editController must implement .commitCurrentEdit()');
        }
        if (!editController.cancelCurrentEdit) {
            throw new Error('SlickEditorLock.activate: editController must implement .cancelCurrentEdit()');
        }
        this.activeEditController = editController;
    }
    ;
    /**
     * Unsets the specified edit controller as the active edit controller (release edit lock).
     * If the specified edit controller is not the active one, an exception will be throw new Error(.
     * @method deactivate
     * @param editController {EditController} edit controller releasing the lock
     */
    deactivate(editController) {
        if (!this.activeEditController) {
            return;
        }
        if (this.activeEditController !== editController) {
            throw new Error('SlickEditorLock.deactivate: specified editController is not the currently active one');
        }
        this.activeEditController = null;
    }
    ;
    /**
     * Attempts to commit the current edit by calling "commitCurrentEdit" method on the active edit
     * controller and returns whether the commit attempt was successful (commit may fail due to validation
     * errors, etc.).  Edit controller's "commitCurrentEdit" must return true if the commit has succeeded
     * and false otherwise.  If no edit controller is active, returns true.
     * @method commitCurrentEdit
     * @return {Boolean}
     */
    commitCurrentEdit() {
        return (this.activeEditController ? this.activeEditController.commitCurrentEdit() : true);
    }
    ;
    /**
     * Attempts to cancel the current edit by calling "cancelCurrentEdit" method on the active edit
     * controller and returns whether the edit was successfully cancelled.  If no edit controller is
     * active, returns true.
     * @method cancelCurrentEdit
     * @return {Boolean}
     */
    cancelCurrentEdit() {
        return (this.activeEditController ? this.activeEditController.cancelCurrentEdit() : true);
    }
    ;
}
export class Utils {
    static height(el, value) {
        if (!el) {
            return;
        }
        if (value === undefined) {
            return el.getBoundingClientRect().height;
        }
        Utils.setStyleSize(el, 'height', value);
    }
    static width(el, value) {
        if (!el || !el.getBoundingClientRect) {
            return;
        }
        if (value === undefined) {
            return el.getBoundingClientRect().width;
        }
        Utils.setStyleSize(el, 'width', value);
    }
    static setStyleSize(el, style, val) {
        if (typeof val === 'function') {
            val = val();
        }
        el.style[style] = (typeof val === 'string') ? val : `${val}px`;
    }
    static isHidden(el) {
        return el.offsetWidth === 0 && el.offsetHeight === 0;
    }
    static parents(el, selector) {
        const parents = [];
        const visible = selector === ':visible';
        const hidden = selector === ':hidden';
        while ((el = el.parentNode) && el !== document) {
            if (!el || !el.parentNode) {
                break;
            }
            if (hidden) {
                if (Utils.isHidden(el)) {
                    parents.push(el);
                }
            }
            else if (visible) {
                if (!Utils.isHidden(el)) {
                    parents.push(el);
                }
            }
            else if (!selector || el.matches(selector)) {
                parents.push(el);
            }
        }
        return parents;
    }
    static toFloat(value) {
        const x = parseFloat(value);
        if (isNaN(x)) {
            return 0;
        }
        return x;
    }
    static show(el, type = '') {
        if (Array.isArray(el)) {
            el.forEach((e) => e.style.display = type);
        }
        else {
            el.style.display = type;
        }
    }
    static hide(el) {
        if (Array.isArray(el)) {
            el.forEach((e) => e.style.display = 'none');
        }
        else {
            el.style.display = 'none';
        }
    }
    static applyDefaults(targetObj, srcObj) {
        if (typeof srcObj === 'object') {
            Object.keys(srcObj).forEach(key => {
                if (srcObj.hasOwnProperty(key) && !targetObj.hasOwnProperty(key)) {
                    targetObj[key] = srcObj[key];
                }
            });
        }
    }
    /**
     * User could optionally add PubSub Service to SlickEvent
     * When it is defined then a SlickEvent `notify()` call will also dispatch it by using the PubSub publish() method
     * @param {BasePubSub} [pubSubService]
     * @param {*} scope
     */
    static addSlickEventPubSubWhenDefined(pubSub, scope) {
        if (pubSub) {
            for (const prop in scope) {
                if (scope[prop] instanceof SlickEvent && typeof scope[prop].setPubSubService === 'function') {
                    scope[prop].setPubSubService(pubSub);
                }
            }
        }
    }
}
Utils.storage = {
    // https://stackoverflow.com/questions/29222027/vanilla-alternative-to-jquery-data-function-any-native-javascript-alternati
    _storage: new WeakMap(),
    put: function (element, key, obj) {
        if (!this._storage.has(element)) {
            this._storage.set(element, new Map());
        }
        this._storage.get(element).set(key, obj);
    },
    get: function (element, key) {
        const el = this._storage.get(element);
        if (el) {
            return el.get(key);
        }
        return null;
    },
    remove: function (element, key) {
        const ret = this._storage.get(element).delete(key);
        if (!(this._storage.get(element).size === 0)) {
            this._storage.delete(element);
        }
        return ret;
    }
};
export const SlickGlobalEditorLock = new SlickEditorLock();
export const preClickClassName = 'slick-edit-preclick';
//# sourceMappingURL=slickCore.js.map