'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

const core = require('./core-b7b9d752.js');
const utils = require('./utils-2a7c8f6e.js');
const i18n_provider = require('./i18n.provider-5632e5ed.js');
const table_utils = require('./table.utils-12a2d306.js');
const column_renderer = require('./column.renderer-62044f6a.js');
const row = require('./row-5e276df9.js');

const dataSourceWorker = () => {
    /* Start Worker */
    // only add the listener if we're in the worker context
    if (typeof window === 'undefined') {
        addEventListener('message', (event) => {
            if (!event)
                return;
            const [options, data] = event.data;
            // Funky Safari issue https://stackoverflow.com/questions/46913856/value-is-not-a-sequence-safari-exception
            self.postMessage(processData(data, options));
        }, false);
    }
    // Doubles as an exposed entry point to be used outside a web worker
    const processData = (data, options) => {
        let dataKey = 'data';
        if (options.filter) {
            dataKey = 'filtered';
            data.filtered = data.data.filter(d => filter(d, options.filter));
            data.total = data.filtered.length;
        }
        if (options.sort || options.group) {
            data[dataKey] = sortBy(data[dataKey], options.group ? options.group.concat(options.sort ? options.sort : []) : options.sort);
            if (dataKey === 'data') {
                data.total = data.data.length;
            }
        }
        if (options.group) {
            // TODO: someday we might need multi-level grouping
            data.filtered = group(data[dataKey], options.group[0], options.size);
            data.total = data.filtered.length;
        }
        return data;
    };
    const filter = (data, filters) => {
        let included = true;
        for (let i = 0; i < filters.length; i++) {
            // Global filter first which is just a basic string contains across the object
            if (!filters[i].key) {
                included = filterGlobal(data, filters[i].query.trim().toLowerCase());
            }
            else if (typeof filters[i].query === 'string') {
                included = filterByString(data[filters[i].key], filters[i].query.trim().toLowerCase(), filters[i].type);
            }
            else if (isNumberQuery(filters[i].query)) {
                included = filterByNumber(data[filters[i].key], filters[i].query, filters[i].type);
            }
            else {
                included = filterByDate(data[filters[i].key], filters[i].query, filters[i].type);
            }
            if (!included) {
                break;
            }
        }
        return included;
    };
    const filterGlobal = (data, query) => {
        let included = false;
        for (const prop in data) {
            if (data.hasOwnProperty(prop)) {
                included = filterByString(`${data[prop]}`, query);
                if (included) {
                    break;
                }
            }
        }
        return included;
    };
    const filterByString = (data, query, filterType) => {
        if (!data)
            return false;
        const value = data.toLowerCase();
        switch (filterType) {
            case 'equals':
                return value === query;
            case 'ends-with':
                return value.indexOf(query) === value.length - query.length;
            case 'starts-with':
                return value.indexOf(query) === 0;
            default:
                return value.includes(query);
        }
    };
    const filterByDate = (data, query, filterType) => {
        const value = coerceToDateValue(data);
        if (!value)
            return false;
        switch (filterType) {
            case 'contains':
                return value >= query[0].valueOf() && value <= query[1].valueOf();
            case 'ends-with':
                return value <= query.valueOf();
            case 'starts-with':
                return value >= query.valueOf();
            default:
                return value === query.valueOf();
        }
    };
    const filterByNumber = (value, query, filterType) => {
        if (!value)
            return false;
        switch (filterType) {
            case 'contains':
                return value >= query[0] && value <= query[1];
            case 'ends-with':
                return value <= query;
            case 'starts-with':
                return value >= query;
            default:
                return value === query;
        }
    };
    const coerceToDateValue = (value) => {
        // If there's no value or it is already a date, just kick it back
        if (!value)
            return undefined;
        if (typeof value.getMonth === 'function')
            return value.valueOf();
        const maybeDate = new Date(value);
        return maybeDate.valueOf();
    };
    const isNumberQuery = (query) => {
        const value = Array.isArray(query) ? query[0] : query;
        return typeof value === 'number';
    };
    const group = (data, groupOpts, size) => {
        const groups = [];
        const groupsByKey = {};
        let itemGroup;
        let isExpanded;
        data.forEach((d) => {
            if (!groupsByKey[d[groupOpts.key]]) {
                groupsByKey[d[groupOpts.key]] = [0];
                itemGroup = {};
                itemGroup[groupOpts.groupKey] = groupsByKey[d[groupOpts.key]];
                itemGroup[groupOpts.key] = d[groupOpts.key];
                isExpanded = groupExpanded(groupOpts, d);
                groups.push(itemGroup);
            }
            if (isExpanded && groups.length > 0 && groups.length % size === 0) {
                groups.push(itemGroup);
            }
            if (isExpanded) {
                groups.push(d);
                groupsByKey[d[groupOpts.key]][0] = groupsByKey[d[groupOpts.key]][0] + 1;
            }
        });
        return groups;
    };
    const groupExpanded = (groupOpts, data) => {
        if (!groupOpts.groupKeys)
            return groupOpts.autoExpand;
        return groupOpts.autoExpand ? !groupOpts.groupKeys.includes(data[groupOpts.key]) : groupOpts.groupKeys.includes(data[groupOpts.key]);
    };
    const sortBy = (data, sorts) => {
        return data.sort((a, b) => compare(a, b, sorts));
    };
    const compare = (a, b, sorts) => {
        let value = 0;
        for (let i = 0; i < sorts.length; i++) {
            const aVal = a[sorts[i].key];
            const bVal = b[sorts[i].key];
            value = typeof aVal === 'string' ? aVal.localeCompare(bVal) : aVal - bVal;
            if (value !== 0) {
                if (sorts[i].order === 'desc') {
                    value = value * -1;
                }
                break;
            }
        }
        return value;
    };
    /* End Worker */
    // Return the entry function to be used outside the worker
    return { 'publicApi': processData };
};

const loadInlineWorker = (workerFn) => {
    const blob = new Blob([parseWorker(workerFn.toString())]);
    const blobURL = window.URL.createObjectURL(blob);
    return new Worker(blobURL);
};
const parseWorker = (workerString) => {
    const worker = workerString.split('{').slice(1).join('{');
    const split = worker.split('return');
    return worker.split('return').slice(0, split.length - 1).join('return');
};

class BaseDataSource {
    constructor(data) {
        this.data = data;
    }
    /**
     * Editing
     */
    async patch(options) {
        for (const action in options.patch) {
            if (options.patch.hasOwnProperty(action)) {
                options.patch[action].forEach(data => this[action](data, options));
            }
        }
        return (options.patch.create || options.patch.update) ? this.sort(options) : this.paginate(options);
    }
    create(data) {
        this.data.unshift(data);
    }
    delete(data) {
        this.data = this.data.filter(d => d !== data);
    }
    update(data, options) {
        const index = this.data.findIndex(d => d[options.key] === data[options.key]);
        this.data[index] = data;
    }
}

class LocalDataSource extends BaseDataSource {
    constructor(source) {
        super(Array.isArray(source) ? source : undefined);
        this.remoteOperations = {};
        if (!Array.isArray(source)) {
            Object.assign(this, source);
            this.load = source.load;
        }
    }
    /**
     * Entry point to getting data
     */
    async fetchData(options) {
        return this.paginate(options, this.sort(options, this.load(options)));
    }
    /**
     * Loads data locally by default. Can be overridden by user configuration to load data from a remote source
     */
    async load(options) {
        if (!options) {
            return;
        }
        return this.localLoad(options);
    }
    /**
     * Paginate the complete data set
     */
    async paginate(options, data) {
        if (!data && (this.remoteOperations.paging)) {
            return this.fetchData(options);
        }
        let loaded = await data || await this.localLoad();
        if (loaded.data) {
            this.data = loaded.data;
        }
        if (loaded.filtered) {
            this.filtered = loaded.filtered;
            loaded = { data: loaded.filtered, total: loaded.total };
        }
        if (!options.range || this.remoteOperations.paging) {
            return loaded;
        }
        return {
            data: loaded.data.slice(options.range[0], options.range[0] + options.size),
            total: loaded.total
        };
    }
    /**
     * Sorting and filtering of a master set
     */
    async sort(options, data) {
        if (!data && this.remoteOperations.sorting) {
            return this.fetchData(options);
        }
        if (data) {
            return (!this.remoteOperations.sorting && (options.sort || options.filter || options.group)) ? this.asyncSort(options, data) : data;
        }
        return this.paginate(options, this.asyncSort(options, this.localLoad(options)));
    }
    async asyncSort(options, data) {
        const loaded = await data;
        return new Promise(resolve => {
            this.worker = this.connect(resolve);
            // TODO diff old vs new options so we don't do things we don't have to
            return this.delayStart([options, loaded]);
        });
    }
    // Load may get overwritten by custom data sources, so keep this here
    async localLoad(options) {
        return {
            data: options ? this.data : undefined,
            filtered: !options && this.filtered ? this.filtered : this.data,
            total: !options && this.filtered ? this.filtered.length : this.data.length
        };
    }
    async delayStart(message) {
        return new Promise(resolve => {
            window.setTimeout(() => {
                this.worker.postMessage(message);
                resolve();
            });
        });
    }
    connect(resolve) {
        const worker = loadInlineWorker(dataSourceWorker);
        worker.onmessage = (event) => {
            resolve(event.data);
            this.disconnect();
        };
        return worker;
    }
    disconnect() {
        if (this.worker) {
            this.worker.terminate();
            this.worker = undefined;
        }
    }
}

class SimpleDataSource extends BaseDataSource {
    constructor(data) {
        super(data);
        this.processor = dataSourceWorker().publicApi;
    }
    /**
     * Entry point to getting data
     */
    async fetchData(options) {
        return this.paginate(options, this.sort(options, this.load(options)));
    }
    /**
     * Fits the data source API
     */
    async load(options) {
        return {
            data: this.data,
            filtered: !options && this.filtered ? this.filtered : this.data,
            total: !options && this.filtered ? this.filtered.length : this.data.length
        };
    }
    /**
     * Paginate the complete data set
     */
    async paginate(options, data) {
        let loaded = await data || await this.load(options);
        if (loaded.data) {
            this.data = loaded.data;
        }
        if (loaded.filtered) {
            this.filtered = loaded.filtered;
            loaded = { data: loaded.filtered, total: loaded.total };
        }
        if (!options.range) {
            return loaded;
        }
        return {
            data: loaded.data.slice(options.range[0], options.range[0] + options.size),
            total: loaded.total
        };
    }
    async sort(options, data) {
        if (data) {
            return (options.sort || options.filter || options.group) ? this.processData(options, data) : data;
        }
        return this.paginate(options, this.processData(options, this.load(options)));
    }
    async processData(options, waiting) {
        const data = await waiting;
        return this.processor(data, options);
    }
}

class NativeRenderer {
    cellTemplate(cell, offset, col, row, rowIndex, template) {
        if (!cell || rowIndex === undefined) {
            return;
        }
        this.fromTemplate(template, cell, row, rowIndex);
        col.initCell(cell, offset);
    }
    // No different in vanilla/native, but there is in Angular
    rowTemplate(template, targetEl, row, rowIndex) {
        return this.fromTemplate(template, targetEl, row, rowIndex);
    }
    fromTemplate(template, targetEl, row, rowIndex) {
        if (!template || !targetEl || rowIndex === undefined) {
            return;
        }
        targetEl.innerHTML = fromTemplate(template, row);
    }
}
const fromTemplate = (template, row) => {
    const parts = template.split(/\${(.*?)}/g);
    return parts.reduce((total = '', part) => {
        part = template.includes('\${' + part + '}') ? getValue(row, part.replace('row.', '')) : part;
        return total + part;
    });
};
const getValue = (obj, keys) => {
    const parts = Array.isArray(keys) ? keys : keys.split('.');
    return keys.length === 0 ? obj : getValue(obj[parts[0]], parts.slice(1));
};

class MetaColumn {
    constructor(table, config = {}, translator) {
        this.table = table;
        this.translator = translator;
        this.cellRenderer = column_renderer.ColumnRenderer.renderCell;
        this.dataType = 'string';
        this.defaultValue = '';
        this.editable = true;
        this.filterable = true;
        this.filterTypes = column_renderer.FILTER_TYPES;
        this.groupKey = 'items';
        this.headerRenderer = column_renderer.ColumnRenderer.renderHeader;
        this.internal = false;
        this.pinnable = true;
        this.pinned = null;
        this.orderable = true;
        this.rules = [];
        this.selectable = true;
        this.sizable = true;
        this.sortable = true;
        this.width = 'auto';
        this.visible = true;
        this.renderer = new column_renderer.ColumnRenderer(this);
        if (typeof config === 'string') {
            this.field = config;
        }
        else {
            Object.assign(this, config);
        }
        if (!this.name) {
            this.name = column_renderer.ColumnRenderer.generateColumnName(this.field, this.caption);
        }
        if (!this.editRenderer) {
            this.editRenderer = column_renderer.ColumnRenderer.renderEdit.bind({ translator: this.translator });
        }
    }
    async contextMenu() {
        return this.renderer.contextMenu();
    }
    async setOffset(offset) {
        return this.renderer.setOffset(offset);
    }
    async setWidth(width) {
        return this.renderer.setWidth(width);
    }
    async initCell(cell, offset) {
        if (this.pinned && !offset && !cell.parentElement) {
            await this.onAttached(cell);
        }
        return this.renderer.initCell(cell, offset);
    }
    /**
     * This little bit of hell fixes https://git.zias.io/ui-foundation/anjuna/issues/738.
     * For some reason, meta columns (and only meta columns, no idea why) consistently get passed to the initCell method before they've attached to the DOM.
     * This breaks the logic for calculating the offset of pinned columns on first render.
     * So what this does is waits for it to attach, and then next time around resolves.
     * If it resolves as soon as it attaches, it doesn't break, but the alignment can be off.
     * I am not proud of this. A more stable fix would probably be to move this into the table after render,
     * but because I don't understand why this isn't a problem for declarative columns, it seems better to bastardize it here for now. -r
     */
    async onAttached(cell) {
        return new Promise(resolve => {
            let attached = false;
            const waiter = window.setInterval(() => {
                if (attached) {
                    window.clearInterval(waiter);
                    resolve();
                }
                else if (cell.parentElement) {
                    attached = true;
                }
            }, 20);
        });
    }
}

const Colgroup = ({ columns }) => (core.h("colgroup", null, columns.map((col) => core.h("col", { class: column_renderer.ColumnRenderer.classFromColumnName(col.name), span: col.span }))));

const Tbody = (props, children) => (core.h("tbody", { class: props.classList }, children));

var Translations;
(function (Translations) {
    Translations["SORT_ASC"] = "Press enter to sort column ascending";
    Translations["SORT_DESC"] = "Press enter to sort column descending";
})(Translations || (Translations = {}));
const Th = ({ classList, column, columnIndex, filtering, offset, translator }) => {
    const attrs = column.span ? { colspan: column.span } : {};
    const label = (column.sortOrder === undefined || column.sortOrder === 'asc')
        ? translator(Translations.SORT_DESC)
        : translator(Translations.SORT_ASC);
    const ariaLabel = column.sortable
        ? label
        : undefined;
    return (core.h("th", Object.assign({}, attrs, { class: classList, tabIndex: column.sortable ? 0 : -1, "aria-label": `${column.caption || column.field}: ${ariaLabel}`, "data-column-index": columnIndex, ref: r => column.initCell(r, offset) }),
        column.dataType === 'number'
            ? core.h("anj-column-control", { column: column, field: column.field, sortIndex: column.sortIndex, sortOrder: column.sortOrder, pinned: column.pinned })
            : undefined,
        column.headerFormatter
            ? core.h("span", { class: table_utils.TableClasses.caption, innerHTML: column.headerFormatter(column.field, column.caption) })
            : core.h("span", { class: table_utils.TableClasses.caption }, column.headerRenderer(column.field, column.caption)),
        column.dataType !== 'number'
            ? core.h("anj-column-control", { column: column, field: column.field, sortIndex: column.sortIndex, sortOrder: column.sortOrder, pinned: column.pinned })
            : undefined,
        filtering && column.filterable ?
            core.h("anj-column-filter-toggle", { types: column.filterTypes, filter: column.filter, field: column.field, dataType: column.dataType }) : undefined));
};

const Thead = ({ columns, filtering, offsets, pinBoundaries, translator }) => (core.h("thead", { class: table_utils.TableClasses.header },
    core.h("tr", { class: table_utils.TableClasses.row }, columns.map((col, index) => {
        const classList = utils.concatClasses(column_renderer.ColumnRenderer.columnHeaderClasses(col), column_renderer.ColumnRenderer.pinnedBorderClass(index, pinBoundaries));
        return core.h(Th, { classList: classList, column: col, columnIndex: index, filtering: filtering, offset: offsets[index], translator: translator });
    }))));

const DefaultCellRenderer = {
    when: () => true,
    cell: (props) => core.h(row.Td, Object.assign({}, props))
};

const DefaultRowRenderer = {
    when: () => true,
    row: (props) => core.h(row.Row, Object.assign({}, props))
};

let tableId = 0;
const Table = class {
    constructor(hostRef) {
        core.registerInstance(this, hostRef);
        this.cellMouseOverListener = (e) => this.highlightColumn(e);
        this.cellMouseOutListener = (e) => this.clearColumnHighlight(e);
        this.cellRenderers = [DefaultCellRenderer];
        this.id = `anj-table-${tableId++}`;
        this.inited = false;
        this.rowRenderers = [DefaultRowRenderer];
        /**
         * Sets the width of all columns.
         * Options are `"auto"`, `"auto-fix"`, or a number representing the number of pixels. Default is auto.
         */
        this.columnWidth = 'auto';
        /**
         * Height of the table
         */
        this.height = 'auto';
        /**
         * Highlight on hover
         * Possible values are: `"rows"`, `"columns"`, and `"none"`. Default is "rows"
         */
        this.highlight = 'rows';
        /**
         * Implicitly set the columns based on the records
         */
        this.implicitColumns = false;
        /**
         * A unique identifier for the table
         */
        this.name = this.id;
        /**
         * Is the table re-orderable by users
         */
        this.orderable = false;
        /** @internal */
        this.renderer = new NativeRenderer();
        /**
         * Columns that have been selected by field name
         */
        this.selectedColumnFields = [];
        /**
         * Row keys that have been selected
         */
        this.selectedRowKeys = [];
        /**
         * Is the table re-sizable by users
         */
        this.sizable = false;
        this.anjColumnsDidLoad = core.createEvent(this, "anjColumnsDidLoad", 7);
        this.anjColumnsDidUpdate = core.createEvent(this, "anjColumnsDidUpdate", 7);
        this.anjRowsWillLoad = core.createEvent(this, "anjRowsWillLoad", 7);
        this.anjRowsDidLoad = core.createEvent(this, "anjRowsDidLoad", 7);
        this.anjTableWillUpdate = core.createEvent(this, "anjTableWillUpdate", 7);
        this.anjTableDidUpdate = core.createEvent(this, "anjTableDidUpdate", 7);
    }
    get pagerOptions() {
        return this.pager ? {
            page: this.pager.page,
            range: this.pager.range,
            size: this.pager.size
        } : {};
    }
    set pagerOptions(options) {
        if (this.pager) {
            this.pager.page = options.page;
            this.pager.size = options.size;
            this.pager.range = options.range;
        }
    }
    async onColumnsChange() {
        if (!this.inited)
            return;
        return this.loadColumns();
    }
    async onDataSourceChange() {
        if (this.colDefs && Array.isArray(this.dataSource)) {
            if (this.source) {
                return this.updateData(this.dataSource);
            }
            else {
                return this.connectDataSource();
            }
        }
    }
    async componentDidLoad() {
        await this.loadColumns();
        await this.connectDataSource();
        this.inited = true;
    }
    componentWillUpdate() {
        if (this.highlight === 'columns') {
            Array.from(this.element.querySelectorAll('td, th')).forEach((el) => {
                el.removeEventListener('mouseenter', this.cellMouseOverListener);
                el.removeEventListener('mouseleave', this.cellMouseOutListener);
            });
        }
        this.anjTableWillUpdate.emit();
    }
    componentDidUpdate() {
        if (this.highlight === 'columns') {
            Array.from(this.element.querySelectorAll('td, th')).forEach((el) => {
                el.addEventListener('mouseenter', this.cellMouseOverListener);
                el.addEventListener('mouseleave', this.cellMouseOutListener);
            });
        }
        if (this.colDefs && this.columnWidth === 'auto-fix') {
            this.colDefs.filter(c => c.visible && c.width === 'auto').forEach(c => {
                const colClass = column_renderer.ColumnRenderer.classFromColumnName(c.name);
                const firstCell = this.element.querySelector(`td.${colClass}`);
                c.width = firstCell ? `${firstCell.offsetWidth}px` : 'auto';
            });
        }
        this.anjTableDidUpdate.emit();
    }
    componentDidUnload() {
        this.componentWillUpdate();
    }
    /* Loading of various controls */
    onControlLoaded(event) {
        event.stopPropagation();
        const target = event.target;
        switch (target.tagName) {
            case 'ANJ-FILTER':
                this.filter = event.detail;
                break;
            case 'ANJ-COLUMN-FILTERING':
                this.filtering = event.target;
                break;
            case 'ANJ-STATE-STORE':
                this.store = event.target;
                break;
            default:
                this.pager = target;
                break;
        }
    }
    /* Row Render Controls */
    onRowControlLoaded(event) {
        event.stopPropagation();
        const target = event.target;
        this.rowRenderers.unshift(target);
        if (target.tagName === 'ANJ-ROW-GROUPING') {
            this.rowGroupControl = target;
        }
    }
    /* Cell Render Controls */
    onCellControlLoaded(event) {
        event.stopPropagation();
        const target = event.target;
        this.cellRenderers.unshift(target);
    }
    /* Column Events */
    async onColumnToggle({ detail }) {
        const column = this.colDefs.find(c => c.name === detail);
        column.visible = !column.visible;
        this.colDefs = this.groupColumns(this.colDefs);
        return this.scroller.doResize();
    }
    onOrderChange({ detail }) {
        const columns = this.colDefs.slice(0);
        const column = columns.splice(detail.currentIndex, 1)[0];
        column.pinned = columns[detail.targetIndex].pinned;
        columns.splice(detail.targetIndex, 0, column);
        this.colDefs = this.groupColumns(columns);
    }
    onPinChange({ detail }) {
        this.colDefs[detail.index].pinned = detail.pin;
        this.colDefs = this.groupColumns(this.colDefs);
    }
    async persistState() {
        if (!this.store)
            return;
        const options = await this.dataOptions(false);
        if (options && this.colDefs) {
            options.columns = this.colDefs.map(table_utils.persistColumn);
            return this.store.set(options);
        }
    }
    /* End Column Events */
    /* Data Events */
    /**
     * Eat any load events from child tables
     */
    async onRowChange(event) {
        if (event.target !== this.element) {
            event.stopPropagation();
        }
        if (event.type === 'anjColumnsDidUpdate' || event.type === 'anjRowsDidLoad') {
            return this.persistState();
        }
    }
    async onPage() {
        return this.setRows(this.spinner.spinWhile(this.source.paginate(await this.dataOptions())));
    }
    async onColumnGroupChange() {
        this.anjRowsWillLoad.emit();
        return this.setRows(this.spinner.spinWhile(this.source.sort(await this.dataOptions())));
    }
    async onSort(event) {
        // ignore events from child tables
        if (!this.isChild(event.target)) {
            return;
        }
        this.anjRowsWillLoad.emit();
        // TODO: handle multisort
        if (event.detail.field) {
            this.colDefs = this.colDefs.map((col) => {
                if (event.detail.type) {
                    if (col.field === event.detail.field) {
                        col.filter = utils.hasValue(event.detail.query) ? event.detail : undefined;
                    }
                }
                else {
                    col.sortIndex = col.field === event.detail.field ? 0 : undefined;
                    col.sortOrder = col.field === event.detail.field ? event.detail.order : 'asc';
                }
                return col;
            });
        }
        if (this.pager) {
            this.pager.page = 1;
        }
        return this.setRows(this.spinner.spinWhile(this.source.sort(await this.dataOptions())));
    }
    addRow({ detail, target }) {
        if (target.mode === 'noop') {
            return;
        }
        if (detail.index) {
            const rows = this.rows.slice(0);
            rows.splice(detail.index, 0, detail.data);
            this.rows = rows;
        }
        else {
            this.rows = [detail.data].concat(this.rows);
        }
    }
    onDelete({ detail, target }) {
        if (target.mode === 'noop') {
            return;
        }
        const rows = this.rows.slice(0);
        rows.splice(detail.index, 1);
        this.rows = rows;
    }
    async updateRow({ detail }) {
        const rows = this.rows.slice(0);
        rows[detail.index] = detail.data;
        this.rows = rows;
    }
    /**
     * Send changes made to the rows to the data source to become persistent.
     */
    async patchData({ detail }) {
        const opts = await this.dataOptions();
        return this.setRows(this.spinner.spinWhile(this.source.patch(Object.assign(opts, { patch: detail }))));
    }
    /* End Data Events */
    /**
     * Finds all anj-context-menu items within the table and returns their nodes
     */
    async columnMenu() {
        return Array.from(this.element.querySelectorAll('anj-context-menu-item'))
            .map(i => i.cloneNode(true));
    }
    /**
     * Returns the current instantiated column definitions
     */
    async getColumns() {
        return this.colDefs;
    }
    /**
     * Returns the current rows displayed (not the whole data set)
     */
    async getRows() {
        return this.rows;
    }
    /**
     * Refresh the underlying data source with a new array of data
     */
    async updateData(data) {
        this.source.data = data;
        return this.loadRowData();
    }
    /**
     * Scroll the table to a given set of coordinates by column field and/or row key.
     */
    async scrollByKey(columnField, rowKey) {
        const rowIndex = rowKey ? this.rows.findIndex(r => r[this.rowKey] === rowKey) : 0;
        const columnIndex = columnField ? this.colDefs.findIndex(c => c.field === columnField) : 0;
        return this.scrollByIndex(columnIndex, rowIndex);
    }
    /**
     * Scroll the table to a given set of coordinates by column and/or row index.
     */
    async scrollByIndex(columnIndex, rowIndex) {
        // In the event we need scroll by index, everything below can be moved there, then call that here -r
        const rowEl = this.element.querySelectorAll('tr')[rowIndex];
        const colEl = this.element.querySelectorAll('th')[columnIndex];
        const rowOffset = rowEl.offsetTop + rowEl.clientHeight;
        const colOffex = colEl.offsetWidth + colEl.offsetLeft;
        let rowCoords;
        let colCoords;
        if (rowOffset > this.scroller.clientHeight + this.scroller.scrollTop) {
            rowCoords = rowOffset - this.scroller.clientHeight;
        }
        else if (rowOffset < this.element.scrollTop + this.element.clientHeight) {
            rowCoords = this.scroller.scrollTop - rowEl.clientHeight > -1 ? this.scroller.scrollTop - rowEl.clientHeight : 0;
        }
        if (colOffex > this.scroller.scrollLeft + this.scroller.offsetWidth) {
            colCoords = colOffex - this.scroller.offsetWidth;
        }
        else if (colOffex < this.scroller.scrollLeft + this.scroller.offsetWidth) {
            colCoords = this.scroller.scrollLeft - colEl.offsetWidth > -1 ? this.scroller.scrollLeft - colEl.offsetWidth : 0;
        }
        return this.scroller.debouncedScrollTo(colCoords, rowCoords);
    }
    connectDataSource() {
        if (!this.dataSource)
            return;
        this.source = Array.isArray(this.dataSource) && this.dataSource.length < 1000
            ? new SimpleDataSource(this.dataSource) : new LocalDataSource(this.dataSource);
        return this.loadRowData();
    }
    async loadRowData() {
        return this.setRows(this.spinner.spinWhile(this.source.fetchData(await this.dataOptions(true))));
    }
    async loadColumns() {
        const columns = await Promise.all(Array.from(this.element.querySelectorAll('anj-column'))
            .filter(col => this.isChild(col))
            .map((col) => col.componentOnReady()));
        const storedCols = this.store && !this.colDefs ? await this.loadColumnsFromStore() : undefined;
        this.colDefs = this.groupColumns(columns.concat(this.loadMetaColumns()), storedCols);
    }
    loadMetaColumns() {
        if (!this.columns)
            return [];
        return this.columns.map(col => new MetaColumn(this.element, col, this.translator));
    }
    groupColumns(loaded, stored) {
        const columns = stored ? this.initColumnsFromStore(loaded, stored) : loaded;
        // this is kinda dumb, but it gets around Safari not having a stable sort -r
        const visible = columns.filter(visibleColumnFilter);
        const left = visible.filter(col => col.pinned === 'left');
        const right = visible.filter(col => col.pinned === 'right');
        const scrolling = visible.filter(col => !col.pinned);
        const allVisible = left.concat(scrolling, right);
        const allColumns = allVisible.concat(columns.filter(col => !col.visible || col.groupIndex !== undefined)).map((c, i) => {
            c.index = i;
            return c;
        });
        this.pinBoundaries = table_utils.calculatePinBoundaries(left.length, right.length, scrolling.length);
        this.offsets = table_utils.calculateOffsets(allVisible);
        if (this.colDefs) {
            this.anjColumnsDidUpdate.emit(allColumns);
        }
        else {
            this.anjColumnsDidLoad.emit(allColumns);
        }
        return allColumns;
    }
    async loadColumnsFromStore() {
        const store = await this.store.get();
        return store && store.columns ? store.columns : undefined;
    }
    initColumnsFromStore(columns, stored) {
        return columns.map(col => {
            const s = stored.find(c => c.name === col.name);
            return s ? Object.assign(col, s) : col;
        }).sort((a, b) => a.index - b.index);
    }
    isChild(el) {
        const table = el.closest(this.element.tagName);
        return table.name === this.name;
    }
    async setRows(pending) {
        try {
            const data = await pending;
            if (this.implicitColumns && this.colDefs.filter(c => !c.internal).length === 0 && data.data && data.data.length > 0) {
                this.columns = table_utils.implicitColumnFactory(Object.keys(data.data[0]), data.data);
                await this.loadColumns();
            }
            this.rows = data.data || [];
            if (this.pager) {
                this.pager.total = data.total || 0;
            }
            this.anjRowsDidLoad.emit();
        }
        catch (e) {
            throw e;
        }
    }
    // TODO these should probably move to the data source itself
    async dataOptions(fromStore = false) {
        if (fromStore && this.store) {
            return this.initOptionsFromStore();
        }
        const baseOpts = { key: this.rowKey };
        const sorts = this.colDefs.filter(c => c.sortIndex > -1).sort((a, b) => a.sortIndex - b.sortIndex).map(c => {
            return {
                key: c.field,
                order: c.sortOrder,
                index: c.sortIndex
            };
        });
        const groups = this.colDefs.filter(c => c.groupIndex > -1).sort((a, b) => a.groupIndex - b.groupIndex).map(c => {
            return {
                groupKeys: c.groupKeys,
                key: c.field,
                groupKey: c.groupKey,
                index: c.groupIndex,
                autoExpand: this.rowGroupControl.autoExpand
            };
        });
        const filters = this.colDefs.filter(c => c.filter).map(c => {
            return {
                key: c.field,
                query: c.filter.query,
                type: c.filter.type
            };
        });
        if (this.filter && this.filter.value) {
            filters.unshift({ type: 'contains', query: this.filter.value || '', key: undefined });
        }
        return Object.assign({
            filter: filters.length > 0 ? filters : undefined,
            group: groups.length > 0 ? groups : undefined,
            sort: sorts.length > 0 ? sorts : undefined
        }, this.pagerOptions, baseOpts);
    }
    async initOptionsFromStore() {
        const options = await this.store.get();
        if (!options) {
            return this.dataOptions();
        }
        if (this.filter && options.filter && !options.filter[0].key) {
            this.filter.value = options.filter[0].query;
        }
        if (this.pager) {
            this.pagerOptions = options;
        }
        if (options.sort) {
            options.sort.forEach((sort) => {
                const col = this.colDefs.find((c) => c.field === sort.key);
                col.sortIndex = sort.index;
                col.sortOrder = sort.order;
            });
        }
        return options;
    }
    highlightColumn({ target }) {
        table_utils.colCellsFromCell(target).forEach(el => el.classList.add('anj-highlight'));
    }
    clearColumnHighlight({ target }) {
        table_utils.colCellsFromCell(target).forEach(el => el.classList.remove('anj-highlight'));
    }
    getRowRenderer(row, rowIndex, columns) {
        const props = {
            cellRenderers: this.cellRenderers,
            columns,
            offsets: this.offsets,
            pinBoundaries: this.pinBoundaries,
            renderer: this.renderer,
            row,
            rowIndex
        };
        return this.rowRenderers.find(r => r.when(row)).row(props);
    }
    render() {
        const columns = this.colDefs ? this.colDefs.filter(visibleColumnFilter) : undefined;
        return [
            core.h("div", { class: "anj-table-header" }, core.h("div", { class: "anj-table-filter" }, core.h("slot", { name: "filter" })), core.h("div", { class: "anj-table-controls" }, core.h("slot", null))),
            columns && this.rows ?
                core.h("anj-scroller", { style: { maxHeight: this.height }, ref: (r) => this.scroller = r }, core.h("table", { class: table_utils.TableClasses.table }, core.h(Colgroup, { columns: columns }), core.h(Thead, { columns: columns, filtering: !!this.filtering, offsets: this.offsets, pinBoundaries: this.pinBoundaries, translator: this.translator }), core.h(Tbody, { classList: table_utils.TableClasses.body }, this.rows.map((row, index) => this.getRowRenderer(row, index, columns)))))
                : undefined,
            core.h("div", { style: { display: this.rows && this.rows.length === 0 ? 'block' : 'none' } }, core.h("slot", { name: "footer-message" })),
            core.h("div", { style: { display: this.rows && this.rows.length > 0 ? 'block' : 'none' } }, core.h("slot", { name: "footer" })),
            core.h("anj-spinner-controller", { ref: (r) => this.spinner = r })
        ];
    }
    get element() { return core.getElement(this); }
    static get watchers() { return {
        "columns": ["onColumnsChange"],
        "dataSource": ["onDataSourceChange"]
    }; }
    static get style() { return ".anj-base-table, anj-table .anj-table {\n  border-collapse: separate;\n  border-spacing: 0;\n  min-width: 100%;\n  max-width: none;\n  position: relative;\n  table-layout: fixed;\n}\n.anj-base-table, anj-table .anj-table, .anj-base-table thead, anj-table .anj-table thead, .anj-base-table tbody, anj-table .anj-table tbody, .anj-base-table tr, anj-table .anj-table tr, .anj-base-table th, anj-table .anj-table th, .anj-base-table td, anj-table .anj-table td {\n  background-color: inherit;\n}\n.anj-base-table > thead th, anj-table .anj-table > thead th {\n  color: var(--anj-neutral-dark);\n  cursor: default;\n  font-size: 0.75rem;\n  -webkit-transition: border-color 0.3s ease, color 0.3s ease, -webkit-transform 0.3s ease;\n  transition: border-color 0.3s ease, color 0.3s ease, -webkit-transform 0.3s ease;\n  transition: border-color 0.3s ease, color 0.3s ease, transform 0.3s ease;\n  transition: border-color 0.3s ease, color 0.3s ease, transform 0.3s ease, -webkit-transform 0.3s ease;\n}\n.anj-base-table > thead th.anj-overflow-header .anj-column-caption, anj-table .anj-table > thead th.anj-overflow-header .anj-column-caption {\n  overflow: visible;\n  white-space: normal;\n}\n.anj-base-table > thead th > span, anj-table .anj-table > thead th > span {\n  display: inline-block;\n  line-height: 1;\n  max-width: 100%;\n  padding-right: 0.25rem;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  text-transform: uppercase;\n}\n.anj-base-table > tbody th, anj-table .anj-table > tbody th,\n.anj-base-table > tbody td,\nanj-table .anj-table > tbody td {\n  cursor: default;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  -webkit-transition: border-color 0.3s ease;\n  transition: border-color 0.3s ease;\n}\n.anj-base-table > tbody th.anj-pinned-left, anj-table .anj-table > tbody th.anj-pinned-left,\n.anj-base-table > tbody td.anj-pinned-left,\nanj-table .anj-table > tbody td.anj-pinned-left {\n  z-index: 1;\n}\n.anj-base-table > tbody th.anj-overflow-cell, anj-table .anj-table > tbody th.anj-overflow-cell,\n.anj-base-table > tbody td.anj-overflow-cell,\nanj-table .anj-table > tbody td.anj-overflow-cell {\n  overflow: visible;\n  white-space: normal;\n}\n.anj-base-table > thead th, anj-table .anj-table > thead th,\n.anj-base-table > tbody th,\nanj-table .anj-table > tbody th,\n.anj-base-table > tbody td,\nanj-table .anj-table > tbody td {\n  border-bottom: 1px solid var(--anj-border);\n  border-left: 0;\n  border-right: 0;\n  border-top: 0;\n  padding: 0.657rem 0.75rem;\n  vertical-align: top;\n  white-space: nowrap;\n}\n.anj-base-table > thead th:focus, anj-table .anj-table > thead th:focus,\n.anj-base-table > tbody th:focus,\nanj-table .anj-table > tbody th:focus,\n.anj-base-table > tbody td:focus,\nanj-table .anj-table > tbody td:focus {\n  border-bottom-color: #b8c1ca;\n  outline: none;\n}\n.anj-base-table > thead th.anj-pinned-left, anj-table .anj-table > thead th.anj-pinned-left, .anj-base-table > thead th.anj-pinned-right, anj-table .anj-table > thead th.anj-pinned-right,\n.anj-base-table > tbody th.anj-pinned-left,\nanj-table .anj-table > tbody th.anj-pinned-left,\n.anj-base-table > tbody th.anj-pinned-right,\nanj-table .anj-table > tbody th.anj-pinned-right,\n.anj-base-table > tbody td.anj-pinned-left,\nanj-table .anj-table > tbody td.anj-pinned-left,\n.anj-base-table > tbody td.anj-pinned-right,\nanj-table .anj-table > tbody td.anj-pinned-right {\n  position: -webkit-sticky;\n  position: sticky;\n}\n.anj-base-table > thead th.anj-pin-border.anj-pinned-left, anj-table .anj-table > thead th.anj-pin-border.anj-pinned-left,\n.anj-base-table > tbody th.anj-pin-border.anj-pinned-left,\nanj-table .anj-table > tbody th.anj-pin-border.anj-pinned-left,\n.anj-base-table > tbody td.anj-pin-border.anj-pinned-left,\nanj-table .anj-table > tbody td.anj-pin-border.anj-pinned-left {\n  border-right: 1px solid transparent;\n}\n.anj-base-table > thead th.anj-pin-border.anj-pinned-right, anj-table .anj-table > thead th.anj-pin-border.anj-pinned-right,\n.anj-base-table > tbody th.anj-pin-border.anj-pinned-right,\nanj-table .anj-table > tbody th.anj-pin-border.anj-pinned-right,\n.anj-base-table > tbody td.anj-pin-border.anj-pinned-right,\nanj-table .anj-table > tbody td.anj-pin-border.anj-pinned-right {\n  border-left: 1px solid transparent;\n}\n.anj-base-table > thead th anj-button, anj-table .anj-table > thead th anj-button,\n.anj-base-table > tbody th anj-button,\nanj-table .anj-table > tbody th anj-button,\n.anj-base-table > tbody td anj-button,\nanj-table .anj-table > tbody td anj-button {\n  margin: -0.75rem 0 !important;\n}\n.anj-base-table > thead th, anj-table .anj-table > thead th {\n  position: -webkit-sticky;\n  position: sticky;\n  top: 0;\n  z-index: 1;\n}\n.anj-base-table > thead th:focus .anj-column-caption, anj-table .anj-table > thead th:focus .anj-column-caption {\n  color: var(--anj-text);\n}\n.anj-base-table > thead th.anj-pinned-left, anj-table .anj-table > thead th.anj-pinned-left, .anj-base-table > thead th.anj-pinned-right, anj-table .anj-table > thead th.anj-pinned-right {\n  z-index: 2;\n}\n\@supports not ((position: -webkit-sticky) or (position: sticky)) {\n  .anj-base-table .anj-header-cell, anj-table .anj-table .anj-header-cell,\n.anj-base-table .anj-pinned-left,\nanj-table .anj-table .anj-pinned-left,\n.anj-base-table .anj-pinned-right,\nanj-table .anj-table .anj-pinned-right {\n    position: relative;\n    bottom: auto !important;\n    left: auto !important;\n    right: auto !important;\n    top: auto !important;\n  }\n}\n\nanj-table {\n  display: block;\n  font-size: 0.875rem;\n  font-family: var(--anj-font-family);\n  font-weight: normal;\n  line-height: 1.5;\n  outline: none;\n  background-color: var(--anj-light);\n  position: relative;\n}\nanj-table, anj-table::before, anj-table::after {\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n  outline: none;\n}\nanj-table:focus, anj-table:active, anj-table *:focus, anj-table *:active {\n  outline: none;\n}\nanj-table[highlight=cells] tbody td {\n  -webkit-transition: border-color 0.3s ease, background-color 0.3s ease, color 0.3s ease;\n  transition: border-color 0.3s ease, background-color 0.3s ease, color 0.3s ease;\n}\nanj-table[highlight=cells] tbody td:hover {\n  background-color: #f7f8f9;\n}\nanj-table[highlight=columns] .anj-table .anj-header-cell.anj-selectable {\n  -webkit-transition: border-color 0.3s ease, background-color 0.3s ease, color 0.3s ease, -webkit-transform 0.3s ease;\n  transition: border-color 0.3s ease, background-color 0.3s ease, color 0.3s ease, -webkit-transform 0.3s ease;\n  transition: border-color 0.3s ease, background-color 0.3s ease, color 0.3s ease, transform 0.3s ease;\n  transition: border-color 0.3s ease, background-color 0.3s ease, color 0.3s ease, transform 0.3s ease, -webkit-transform 0.3s ease;\n}\nanj-table[highlight=columns] .anj-table .anj-header-cell.anj-selectable.anj-highlight {\n  background-color: #f7f8f9;\n}\nanj-table[highlight=columns] .anj-table .anj-cell.anj-selectable {\n  -webkit-transition: border-color 0.3s ease, background-color 0.3s ease, color 0.3s ease;\n  transition: border-color 0.3s ease, background-color 0.3s ease, color 0.3s ease;\n}\nanj-table[highlight=columns] .anj-table .anj-cell.anj-selectable.anj-highlight {\n  background-color: #f7f8f9;\n}\nanj-table[highlight=rows] tbody > tr {\n  -webkit-transition: background-color 0.3s ease;\n  transition: background-color 0.3s ease;\n}\nanj-table[highlight=rows] tbody > tr:hover, anj-table[highlight=rows] tbody > tr:focus {\n  background-color: #f7f8f9;\n}\nanj-table[orderable] .anj-header-cell {\n  cursor: pointer;\n}\nanj-table anj-button-group {\n  display: block;\n}\nanj-table .anj-table-header {\n  display: -ms-flexbox;\n  display: flex;\n  -ms-flex: 1;\n  flex: 1;\n  -ms-flex-flow: column-reverse;\n  flex-flow: column-reverse;\n  position: relative;\n  z-index: 3;\n}\nanj-table .anj-table-header .anj-table-controls {\n  display: -ms-flexbox;\n  display: flex;\n  -ms-flex: 0 0 100%;\n  flex: 0 0 100%;\n  -ms-flex-pack: end;\n  justify-content: flex-end;\n  max-width: 100%;\n}\nanj-table .anj-table-header .anj-table-controls anj-button {\n  margin-bottom: 1rem;\n}\n\@media (min-width: 768px) {\n  anj-table .anj-table-header {\n    -ms-flex-flow: row wrap;\n    flex-flow: row wrap;\n  }\n  anj-table .anj-table-header .anj-table-filter,\nanj-table .anj-table-header .anj-table-controls {\n    -ms-flex: 0 0 50%;\n    flex: 0 0 50%;\n    max-width: 50%;\n  }\n}\nanj-table anj-scroller.anj-overflow-x {\n  padding-bottom: 0.5rem;\n}\nanj-table anj-scroller.anj-overflow-x .anj-header-cell.anj-pin-border.anj-pinned-left,\nanj-table anj-scroller.anj-overflow-x .anj-cell.anj-pin-border.anj-pinned-left {\n  border-right-color: var(--anj-border);\n}\nanj-table anj-scroller.anj-overflow-x .anj-header-cell.anj-pin-border.anj-pinned-right,\nanj-table anj-scroller.anj-overflow-x .anj-cell.anj-pin-border.anj-pinned-right {\n  border-left-color: var(--anj-border);\n}\nanj-table anj-scroller.anj-overflow-x ~ anj-pager {\n  margin-top: 0.5rem !important;\n}\nanj-table .anj-table .anj-datatype-boolean {\n  text-align: center;\n}\nanj-table .anj-table .anj-datatype-number {\n  text-align: right;\n}\nanj-table .anj-table .anj-datatype-number .anj-column-caption {\n  padding-left: 0.25rem;\n  padding-right: 0;\n}"; }
};
i18n_provider.Tunnel.injectProps(Table, ['dateFormatter', 'datetimeFormatter', 'numberFormatter', 'translator']);
const visibleColumnFilter = (c) => {
    return (c.visible && c.groupIndex === undefined);
};

exports.anj_table = Table;
