'use strict';

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

const core = require('./core-7e2b0104.js');
require('./utils-2a7c8f6e.js');
const table_utils = require('./table.utils-12a2d306.js');
const column_renderer = require('./column.renderer-2366bd3b.js');
const i18n_provider = require('./i18n.provider-b7733c9d.js');

const EditCell = ({ classList, column, columnIndex, offset, row, rowIndex, renderer }) => {
    const attrs = column_renderer.ColumnRenderer.columnAttributes(column, columnIndex, row);
    const refFn = r => column.initCell(r, offset);
    const allClasses = `${classList} ${table_utils.TableClasses.editCell}`;
    // TODO: abstract this out better
    if (column.editTemplate) {
        return core.h("td", Object.assign({}, attrs, { class: allClasses, ref: r => renderer.cellTemplate(r, offset, column, row, rowIndex, column.editTemplate) }));
    }
    else if (column.editFormatter) {
        return core.h("td", Object.assign({}, attrs, { class: allClasses, innerHTML: column.editFormatter(row, column, rowIndex), ref: refFn }));
    }
    else {
        return core.h("td", Object.assign({}, attrs, { class: allClasses, ref: refFn }), column.editRenderer(row, column));
    }
};

const CONTROL_SELECTOR = 'anj-input, anj-checkbox, anj-select';
class BaseEditingController {
    constructor(control, table) {
        this.control = control;
        this.table = table;
    }
    static isDirty(data, row) {
        let dirty = false;
        for (const prop in data) {
            if (data[prop] !== row[prop]) {
                dirty = true;
                break;
            }
        }
        return dirty;
    }
    async onCreateStart() {
        const data = {};
        const columns = await this.table.getColumns();
        columns.forEach(c => {
            data[c.field] = typeof c.defaultValue === 'function' ? c.defaultValue(c) : c.defaultValue;
        });
        return data;
    }
    async onCreateEnd({ target }) {
        return this.getData(target);
    }
    async onDelete({ target }) {
        const rows = await this.table.getRows();
        const index = this.indexByElement(target);
        return [rows[index], index];
    }
    async onUpdateStart(event) {
        const rows = await this.table.getRows();
        const index = this.indexByElement(event.target);
        return [index, rows[index]];
    }
    async onUpdateEnd({ target }) {
        const rows = await this.table.getRows();
        const index = this.indexByElement(target);
        const row = rows[index];
        const data = this.getData(target);
        return BaseEditingController.isDirty(data, row) ? [data, row, index] : undefined;
    }
    onUpdateCancel() { }
    async validate(el) {
        if (!el)
            return;
        const results = await Promise.all(Array.from(el.querySelectorAll(CONTROL_SELECTOR)).map(async (e) => e.checkValidity()));
        // Find any validity check where the value is false, if there are none, then it is valid
        return (results.find(i => !i) !== false);
    }
    getData(target) {
        const controls = target.closest('tr').querySelectorAll(CONTROL_SELECTOR);
        const data = {};
        controls.forEach(c => {
            if (c.tagName === 'ANJ-CHECKBOX') {
                const control = c;
                data[control.name] = control.checked;
            }
            else {
                const control = c;
                data[control.name] = control.value;
            }
        });
        return data;
    }
    indexByElement(target) {
        return Array.from(this.table.querySelectorAll('anj-row-edit-control')).findIndex(el => el === target);
    }
}

class BatchEditingController extends BaseEditingController {
    constructor(control, table) {
        super(control, table);
        this.deleteKeys = [];
        this.updateKeys = [];
    }
    // @ts-ignore
    getEditType(data) {
        return undefined;
    }
    async onUpdateEnd(event) {
        const change = await super.onUpdateEnd(event);
        if (change) {
            this.updateKeys.push(event.detail.key);
        }
        return change;
    }
    async onDelete(event) {
        const change = await super.onDelete(event);
        if (change[0][this.table.rowKey]) {
            this.deleteKeys.push(change[0][this.table.rowKey]);
        }
        return change;
    }
    async undo(change) {
        const rows = await this.table.getRows();
        let data;
        let key;
        let index;
        if (change.create) {
            data = change.create[0];
            key = data[this.table.rowKey];
            this.deleteKeys = this.deleteKeys.filter(k => k !== key);
            this.table.dispatchEvent(new CustomEvent('anjCreateStart', { detail: { key, data } }));
        }
        else if (change.update) {
            data = change.update[0];
            key = data[this.table.rowKey];
            index = rows.findIndex(r => r[this.table.rowKey] === key);
            const keyIndex = this.updateKeys.findIndex(k => k === change.update[0][this.table.rowKey]);
            this.updateKeys.splice(keyIndex, 1);
            this.table.dispatchEvent(new CustomEvent('anjUpdateEnd', { detail: { key, data, index } }));
        }
        else {
            data = change.delete[0];
            key = data[this.table.rowKey];
            index = rows.findIndex(r => r[this.table.rowKey] === key);
            this.table.dispatchEvent(new CustomEvent('anjDelete', { detail: { key, data, index } }));
        }
    }
    async save() {
        const valid = await this.validate(this.table.querySelector('table'));
        if (!valid) {
            return undefined;
        }
        const rows = await this.table.getRows();
        const creates = rows.filter(r => !r[this.table.rowKey]);
        const change = {};
        if (creates.length > 0) {
            change.create = creates;
        }
        if (this.deleteKeys.length > 0) {
            change.delete = this.deleteKeys;
        }
        if (this.updateKeys.length > 0) {
            change.update = rows.filter(r => (r[this.table.rowKey] && this.updateKeys.includes(r[this.table.rowKey])));
        }
        return change;
    }
}

class NoopEditingController extends BaseEditingController {
    constructor(control, table) {
        super(control, table);
    }
    // @ts-ignore
    getEditType(data) {
        return undefined;
    }
    async onDelete({ target }) {
        const rows = await this.table.getRows();
        const index = this.indexByElement(target);
        return [rows[index], index];
    }
    async save() {
        return;
    }
    // @ts-ignore
    async validate(el) {
        return true;
    }
    // @ts-ignore
    async undo(change) { }
}

class RowEditingController extends BaseEditingController {
    constructor(control, table) {
        super(control, table);
    }
    async onCreateStart() {
        this.table.activeRowIndex = 0;
        this.control.activeKey = undefined;
        this.control.createDisabled = true;
        return super.onCreateStart();
    }
    async onCreateEnd(event) {
        const valid = await this.validate(this.rowFromEvent(event.target));
        if (!valid) {
            return undefined;
        }
        this.control.createDisabled = false;
        this.table.activeRowIndex = undefined;
        return super.onCreateEnd(event);
    }
    async onDelete(event) {
        this.control.createDisabled = false;
        this.table.activeRowIndex = undefined;
        return super.onDelete(event);
    }
    onUpdateCancel() {
        this.control.createDisabled = false;
        this.table.activeRowIndex = undefined;
        if (!this.control.activeKey) {
            this.table.dispatchEvent(new CustomEvent('anjDelete', { detail: { index: 0, key: undefined } }));
        }
        else {
            this.control.activeKey = undefined;
        }
    }
    async onUpdateStart(event) {
        this.control.createDisabled = false;
        const removeAdd = this.table.activeRowIndex === 0 && !this.control.activeKey;
        const [index, data] = await super.onUpdateStart(event);
        this.table.activeRowIndex = removeAdd ? index - 1 : index;
        this.control.activeKey = event.detail.key;
        if (removeAdd) {
            this.table.dispatchEvent(new CustomEvent('anjDelete', { detail: { index: 0, key: undefined } }));
        }
        return [index, data];
    }
    async onUpdateEnd(event) {
        const valid = await this.validate(this.rowFromEvent(event.target));
        if (!valid) {
            return undefined;
        }
        const change = await super.onUpdateEnd(event);
        this.table.activeRowIndex = undefined;
        this.control.activeKey = undefined;
        return change;
    }
    // @ts-ignore
    async undo(change) {
        this.control.createDisabled = false;
        this.table.activeRowIndex = undefined;
    }
    async save() {
        return;
    }
    getEditType(data) {
        if (this.control.createDisabled && !data[this.table.rowKey]) {
            return 'create';
        }
        if (data[this.table.rowKey] && data[this.table.rowKey] === this.control.activeKey) {
            return 'update';
        }
        return undefined;
    }
    rowFromEvent(target) {
        return target.closest('tr');
    }
}

var Translations;
(function (Translations) {
    Translations["SAVE_ALL"] = "Save All";
    Translations["UNDO"] = "Undo";
    Translations["CREATE_NEW"] = "Create New";
})(Translations || (Translations = {}));
const Editing = class {
    constructor(hostRef) {
        core.registerInstance(this, hostRef);
        this.rowsWillChangeListener = () => this.onRowsWillChange();
        this.rowsDidChangeListener = () => this.onRowsDidChange();
        this.history = [];
        /**
         * Renderer to use for cell content when the selected cell conditions are met
         */
        this.cell = (props) => core.h(EditCell, Object.assign({}, props));
        /**
         * If true, the user cannot create records
         */
        this.createDisabled = false;
        /**
         * Can the user add a row? Defaults to false.
         */
        this.creating = false;
        /**
         * If true, the user cannot delete records
         */
        this.deleteDisabled = false;
        /**
         * Can the user delete a row? Defaults to false.
         */
        this.deleting = false;
        /**
         * The mode of editing
         * Possible values are: `"batch"`, `"row"`, and `"noop"`. Defaults to "noop"
         */
        this.mode = 'noop';
        /**
         * If true, will emit a cancelable event to signal the table to update the underlying data
         */
        this.saving = false;
        /**
         * Can the user undo, defaults to true
         */
        this.undo = false;
        /**
         * Can the user edit? Defaults to false.
         */
        this.updating = false;
        /**
         * Rules to use for using this as a renderer
         */
        this.when = this.useAsRenderer.bind(this);
        this.anjCellControlDidLoad = core.createEvent(this, "anjCellControlDidLoad", 7);
        this.anjCreateStart = core.createEvent(this, "anjCreateStart", 7);
        this.anjCreateEnd = core.createEvent(this, "anjCreateEnd", 7);
        this.anjDelete = core.createEvent(this, "anjDelete", 7);
        this.anjUpdateStart = core.createEvent(this, "anjUpdateStart", 7);
        this.anjUpdateEnd = core.createEvent(this, "anjUpdateEnd", 7);
        this.anjEditWillSave = core.createEvent(this, "anjEditWillSave", 7);
    }
    componentWillLoad() {
        this.table = this.element.closest('anj-table');
    }
    componentDidLoad() {
        this.anjCellControlDidLoad.emit();
        this.table.addEventListener('anjRowsWillLoad', this.rowsWillChangeListener);
        this.table.addEventListener('anjRowsDidLoad', this.rowsDidChangeListener);
        this.table.addEventListener('anjPage', this.rowsWillChangeListener);
        this.controller = this.initController();
    }
    componentDidUnload() {
        this.table.removeEventListener('anjRowsWillLoad', this.rowsWillChangeListener);
        this.table.removeEventListener('anjRowsDidLoad', this.rowsDidChangeListener);
        this.table.removeEventListener('anjPage', this.rowsWillChangeListener);
    }
    initController() {
        switch (this.mode) {
            case 'batch':
                return new BatchEditingController(this.element, this.table);
            case 'row':
                return new RowEditingController(this.element, this.table);
            default:
                return new NoopEditingController(this.element, this.table);
        }
    }
    useAsRenderer(column, rowIndex) {
        return (column.editable && (this.mode === 'batch' || rowIndex === this.table.activeRowIndex));
    }
    onRowsWillChange() {
        this.table.activeRowIndex = undefined;
    }
    async onRowsDidChange() {
        return new Promise(resolve => {
            window.setTimeout(() => {
                resolve(this.controller.validate(this.table.querySelector('.anj-table tbody')));
            });
        });
    }
    async onCreateStart() {
        const data = await this.controller.onCreateStart();
        this.anjCreateStart.emit({ data, key: undefined, index: 0 });
    }
    async onCreateEnd(event) {
        event.stopPropagation();
        const row = await this.controller.onCreateEnd(event);
        if (!row)
            return;
        this.pushHistory({ delete: [row] });
        this.anjCreateEnd.emit({ data: row, key: undefined });
        this.save({ create: [row] });
    }
    async onUpdateStart(event) {
        event.stopPropagation();
        const [index, data] = await this.controller.onUpdateStart(event);
        this.anjUpdateStart.emit(Object.assign(event.detail, { index, data }));
    }
    async onUpdateEnd(event) {
        event.stopPropagation();
        const change = await this.controller.onUpdateEnd(event);
        if (!change)
            return;
        const [update, row, index] = change;
        const data = Object.assign({}, row, update);
        this.pushHistory({ update: [row] });
        if (this.mode === 'row') {
            this.anjUpdateEnd.emit({ key: event.detail.key, data, index });
            this.save({ update: [data] });
        }
    }
    async onDelete(event) {
        event.stopPropagation();
        const [data, index] = await this.controller.onDelete(event);
        if (data[this.table.rowKey]) {
            this.pushHistory({ create: [data] });
        }
        this.anjDelete.emit({ index, data, key: event.detail.key });
        if (this.mode === 'row') {
            this.save({ delete: [data] });
        }
    }
    async onUndo() {
        const change = this.popHistory();
        await this.controller.undo(change);
        if (this.mode === 'row') {
            this.save(change);
        }
    }
    async onSuperSave() {
        const change = await this.controller.save();
        if (change) {
            this.clearHistory();
            this.save(change);
        }
    }
    save(patch) {
        if (!this.saving)
            return;
        this.anjEditWillSave.emit(patch);
    }
    clearHistory() {
        if (!this.undo)
            return;
        this.history = [];
    }
    pushHistory(change) {
        if (!this.undo)
            return;
        this.history = [change].concat(this.history);
    }
    popHistory() {
        if (!this.undo)
            return;
        const change = this.history[0];
        this.history = this.history.slice(1);
        return change;
    }
    control(data) {
        return core.h("anj-row-edit-control", { rowKey: data[this.table.rowKey], editing: this.controller ? this.controller.getEditType(data) : undefined, mode: this.mode, update: this.updating && this.mode !== 'batch', delete: this.deleting, deleteDisabled: this.deleteDisabled, onAnjCreateEnd: (e) => this.onCreateEnd(e), onAnjUpdateCancel: () => this.controller.onUpdateCancel(), onAnjUpdateStart: e => this.onUpdateStart(e), onAnjUpdateEnd: e => this.onUpdateEnd(e), onAnjDelete: e => this.onDelete(e) });
    }
    calculateColWidth() {
        const btnSize = 40;
        let multiplier = 0;
        if (this.deleting) {
            multiplier++;
        }
        if (this.updating && this.mode !== 'batch') {
            multiplier = 2;
        }
        const padding = multiplier > 0 ? 2 : 0;
        return `${(multiplier * btnSize) + padding}px`;
    }
    render() {
        return [
            core.h("anj-column", { filterable: false, internal: true, pinned: "right", editable: false, sizable: false, pinnable: false, orderable: false, selectable: false, name: "row-edit-control", width: this.calculateColWidth(), cellRenderer: (data) => this.control(data) }),
            this.mode === 'batch' && this.saving ?
                core.h("anj-button", { context: "icon", "aria-label": this.translator(Translations.SAVE_ALL), disabled: this.history.length === 0, onClick: async () => this.onSuperSave() }, core.h("svg", { slot: "icon-only", xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24" }, core.h("path", { d: "M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z" }))) : undefined,
            this.undo ?
                core.h("anj-button", { context: "icon", "aria-label": this.translator(Translations.UNDO), disabled: this.history.length === 0, onClick: () => this.onUndo() }, core.h("svg", { slot: "icon-only", xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24" }, core.h("path", { d: "M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z" }))) : undefined,
            this.creating ?
                core.h("anj-button", { context: "icon", "aria-label": this.translator(Translations.CREATE_NEW), disabled: this.createDisabled, onClick: () => this.onCreateStart() }, core.h("svg", { slot: "icon-only", xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24" }, core.h("path", { d: "M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" }))) : undefined
        ];
    }
    get element() { return core.getElement(this); }
    static get style() { return ".anj-table .anj-col-row-edit-control{padding-left:0;padding-right:0}.anj-table td.anj-form-cell{overflow:visible;padding-bottom:0;padding-top:0;vertical-align:bottom}.anj-table td.anj-form-cell.anj-datatype-boolean{vertical-align:middle}.anj-table td.anj-form-cell anj-date-picker-toggle{margin-top:-5px}.anj-table td.anj-form-cell anj-checkbox{position:relative;top:3px}.anj-table[column-width=auto] .anj-cell anj-form-group{width:auto}anj-scroller:not(.anj-overflow-x) tbody tr .anj-col-row-edit-control anj-button{opacity:0;-webkit-transition:opacity .2s ease;transition:opacity .2s ease}anj-scroller:not(.anj-overflow-x) tbody tr .anj-col-row-edit-control anj-button:focus,anj-scroller:not(.anj-overflow-x) tbody tr .anj-col-row-edit-control anj-button[active]{opacity:1}anj-scroller:not(.anj-overflow-x) tbody tr:focus-within .anj-col-row-edit-control anj-button,anj-scroller:not(.anj-overflow-x) tbody tr:hover .anj-col-row-edit-control anj-button{opacity:1}"; }
};
i18n_provider.Tunnel.injectProps(Editing, ['translator']);

const CONTROL_ICONS = {
    update: 'M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z',
    save: 'M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z',
    cancel: 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'
};
const RowEditControl = class {
    constructor(hostRef) {
        core.registerInstance(this, hostRef);
        this.controlListener = () => this.onControl(table_utils.EditControl.save);
        /**
         * Can the user delete a row? Defaults to false.
         */
        this.delete = false;
        /**
         * If true, the user cannot delete records
         */
        this.deleteDisabled = false;
        /**
         * The mode of editing
         * Possible values are: `"batch"` and `"row`. Defaults to "row"
         */
        this.mode = 'row';
        /**
         * Can the user edit?
         */
        this.update = false;
        this.anjUpdateCancel = core.createEvent(this, "anjUpdateCancel", 7);
        this.anjCreateEnd = core.createEvent(this, "anjCreateEnd", 7);
        this.anjDelete = core.createEvent(this, "anjDelete", 7);
        this.anjUpdateEnd = core.createEvent(this, "anjUpdateEnd", 7);
        this.anjUpdateStart = core.createEvent(this, "anjUpdateStart", 7);
    }
    componentDidLoad() {
        if (this.mode === 'batch') {
            this.element.closest('tr').addEventListener('anjBlur', this.controlListener);
        }
    }
    componentDidUnload() {
        if (this.mode === 'batch') {
            const row = this.element.closest('tr');
            if (row) {
                this.element.closest('tr').removeEventListener('anjBlur', this.controlListener);
            }
        }
    }
    onControl(action) {
        const key = table_utils.keyFromData(this.rowKey);
        const payload = { key };
        switch (action) {
            case table_utils.EditControl.cancel:
                this.anjUpdateCancel.emit(payload);
                break;
            case table_utils.EditControl.save:
                this.editing === 'create' ? this.anjCreateEnd.emit(payload) : this.anjUpdateEnd.emit(payload);
                break;
            case table_utils.RowControl.delete:
                this.anjDelete.emit(payload);
                break;
            case table_utils.RowControl.update:
                this.anjUpdateStart.emit(payload);
                break;
        }
    }
    controlDisabled(control) {
        return this[`${control}Disabled`];
    }
    render() {
        const controls = this.editing ? table_utils.EditControls : table_utils.RowControls.filter(c => this[c]);
        return (core.h("anj-button-group", null, controls.map((c) => core.h("anj-button", { context: "icon", onClick: () => this.onControl(c), onAnjBlur: e => e.stopPropagation(), disabled: this.controlDisabled(c), ariaLabel: c }, core.h("svg", { slot: "icon-only", xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24" }, c === 'delete' ?
            core.h("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM8 9h8v10H8V9zm7.5-5l-1-1h-5l-1 1H5v2h14V4z" }) :
            core.h("path", { d: CONTROL_ICONS[c] }))))));
    }
    get element() { return core.getElement(this); }
    static get style() { return "anj-row-edit-control{display:block}anj-row-edit-control anj-button:not(:last-child){margin-right:.5rem}anj-row-edit-control anj-button .anj-button-link{border-radius:50%}"; }
};

const ValidationRule = class {
    constructor(hostRef) {
        core.registerInstance(this, hostRef);
        this.anjValidationRuleDidLoad = core.createEvent(this, "anjValidationRuleDidLoad", 7);
    }
    componentDidLoad() {
        this.anjValidationRuleDidLoad.emit();
    }
};

exports.anj_editing = Editing;
exports.anj_row_edit_control = RowEditControl;
exports.anj_validation_rule = ValidationRule;
