import '../css/grid.scss';
import { global, isPositive, isMobile, throttle, truncate } from "../../utility";
import { r as lang } from "../../utility/lgres";
import { nullOrEmpty } from "../../utility/strings";
import { createElement } from "../../functions";
import { createIcon } from "../icon";
import { createCheckbox } from "../checkbox";
import { setTooltip } from "../tooltip";
import { convertCssStyle } from "../extension";
import { GridColumn, GridInputColumn, GridTextColumn, GridDropdownColumn, GridCheckboxColumn, GridIconColumn, GridDateColumn } from "./column";

const ColumnChangedType = {
    Reorder: 'reorder',
    Resize: 'resize',
    Sort: 'sort'
};
const RefreshInterval = isMobile() ? 32 : 0;
const HoverInternal = 200;
const RedumCount = 4;
const MiniDragOffset = 4;
const MiniColumnWidth = 50;
const FilterPanelWidth = 200;

function getClientX(e) {
    if (e == null) {
        return null;
    }
    const cx = e.touches && e.touches[0]?.clientX;
    return cx ?? e.clientX;
}

function getOffsetLeftFromWindow(element) {
    let left = 0;
    while (element != null) {
        left += element.offsetLeft;
        element = element.offsetParent;
    }
    return left;
}

function indexOfParent(target) {
    // return [...target.parentElement.children].indexOf(target);
    return Array.prototype.indexOf.call(target.parentElement.children, target);
}

const ColumnTypes = {
    0: GridColumn,
    1: GridInputColumn,
    2: GridDropdownColumn,
    3: GridCheckboxColumn,
    4: GridIconColumn,
    5: GridTextColumn,
    6: GridDateColumn
};

let r = lang;

export class Grid {
    _var = {
        selectedColumnIndex: -1,
        startIndex: 0,
        rowCount: -1,
        colTypes: {},
        colAttrs: {}
    };
    // _var.source;
    // _var.currentSource;
    // _var.parent;
    // _var.el;
    // _var.refs;
    // _var.rendering;
    // _var.selectedColumnIndex = -1;
    // _var.selectedIndexes;
    // _var.startIndex = 0;
    // _var.needResize;
    // _var.containerHeight;
    // _var.bodyClientWidth;
    // _var.rowCount = -1;
    // _var.scrollTop;
    // _var.scrollLeft;
    // _var.colTypes = {};
    // _var.colAttrs = {};
    // _var.vtable = [];

    columns = [];
    langs = {};
    virtualCount = 100;
    rowHeight = 36;
    lineHeight = 24;
    extraRows = 0;
    filterRowHeight = 30;
    height;
    readonly;
    multiSelect = false;
    fullrowClick = true;
    tooltipDisabled = false;
    headerVisible = true;
    window = global;
    sortIndex = -1;
    sortDirection = 1;

    willSelect;
    cellClicked;

    onSelectedRowChanged;
    onCellDblClicked;
    onRowDblClicked;
    onColumnChanged;
    onBodyScrolled;

    static ColumnTypes = {
        Common: 0,
        Input: 1,
        Dropdown: 2,
        Checkbox: 3,
        Icon: 4,
        Text: 5,
        Date: 6,
        isCheckbox(type) { return type === 3 }
    };

    constructor(container, getText) {
        this._var.parent = typeof container === 'string' ? document.querySelector(container) : container;
        if (typeof getText === 'function') {
            r = getText;
        }
        this.langs = {
            all: r('allItem', '( All )'),
            ok: r('ok', 'OK'),
            reset: r('reset', 'Reset')
        };
    }

    get element() { return this._var.el }

    get changed() {
        const source = this._var.source;
        if (source == null) {
            return false;
        }
        return source.filter(r => r.__changed).length > 0;
    }

    get source() { return this._var.source?.map(s => s.values) }
    set source(list) {
        if (this._var.el == null) {
            throw new Error('grid has not been initialized.')
        }
        if (!Array.isArray(list)) {
            throw new Error('source is not an Array.')
        }
        list = list.map(i => { return { values: i } });
        this._var.source = list;
        this._refreshSource(list);
    }

    setItem(index, item) {
        if (this._var.source == null) {
            throw new Error('no source');
        }
        const it = this._var.source[index];
        delete it.source;
        it.values = item;
        this.refresh();
    }

    addItem(item, index) {
        if (this._var.source == null) {
            throw new Error('no source');
        }
        if (index >= 0) {
            this._var.source.splice(index, 0, { values: item });
        } else {
            this._var.source.push({ values: item });
        }
        this.reload();
    }

    removeItem(index) {
        if (this._var.source == null) {
            throw new Error('no source');
        }
        this._var.source.splice(index, 1);
        this.reload();
    }

    _refreshSource(list) {
        list ??= this._var.source;
        if (this._var.colAttrs.__filtered === true) {
            this._var.currentSource = list.filter(it => {
                for (let col of this.columns) {
                    if (Array.isArray(col.filterValues)) {
                        const v = this._getItemValue(it.values, col.key, col.filter);
                        if (col.filterValues.indexOf(v) < 0) {
                            return false;
                        }
                    }
                }
                return true;
            });
        } else {
            this._var.currentSource = list;
        }
        this._var.selectedColumnIndex = -1;
        this._var.selectedIndexes = [];
        this._var.startIndex = 0;
        this._var.scrollTop = 0;
        this._var.scrollLeft = 0;
        this._var.rowCount = -1;

        if (this.sortIndex >= 0) {
            this.sortColumn();
        }
        this.resize();
    }

    get virtual() { return this._var.currentSource?.length > this.virtualCount }

    get sortKey() {
        if (this.columns == null) {
            return null;
        }
        return this.columns[this.sortIndex]?.key;
    }

    get tableRows() {
        // return [...this._var.refs.body.children];
        return Array.prototype.slice.call(this._var.refs.body.children);
    }

    get selectedIndexes() { return this._var.selectedIndexes }
    set selectedIndexes(indexes) {
        const startIndex = this._var.startIndex;
        this._var.selectedIndexes.splice(0, this._var.selectedIndexes.length, ...indexes);
        if (this.readonly !== true) {
            this.refresh();
        } else {
            this.tableRows.forEach((row, i) => {
                if (indexes.includes(startIndex + i)) {
                    row.classList.add('selected');
                } else if (row.classList.contains('selected')) {
                    row.classList.remove('selected');
                }
            });
        }
        if (typeof this.onSelectedRowChanged === 'function') {
            this.onSelectedRowChanged();
        }
    }

    get selectedIndex() { return (this._var.selectedIndexes && this._var.selectedIndexes[0]) ?? -1 }

    get loading() { return this._var.refs.loading?.style?.visibility === 'visible' }
    set loading(flag) {
        if (this._var.refs.loading == null) {
            return;
        }
        if (flag === false) {
            this._var.refs.loading.style.visibility = 'hidden';
            this._var.refs.loading.style.opacity = 0;
        } else {
            this._var.refs.loading.style.visibility = 'visible';
            this._var.refs.loading.style.opacity = 1;
        }
    }

    get scrollTop() { return this._var.el?.scrollTop; }
    set scrollTop(top) {
        if (this._var.el == null) {
            return;
        }
        this._var.el.scrollTop = top;
        this.reload(true);
    }

    init(container = this._var.parent) {
        this._var.el = null;
        this._var.refs = {};
        this._var.rendering = true;
        if (container == null) {
            throw new Error('no specified parent.');
        }
        if (!(container instanceof HTMLElement)) {
            const ele = container[0];
            if (!(ele instanceof HTMLElement)) {
                throw new Error(`parent type not supported. ${JSON.stringify(Object.getPrototypeOf(container))}`);
            }
            container = ele;
        }
        this._var.parent = container;
        const grid = createElement('div', 'ui-grid');
        grid.setAttribute('tabindex', 0);
        grid.addEventListener('keydown', e => {
            let index = this.selectedIndex;
            let flag = false;
            if (e.key === 'ArrowUp') {
                // up
                if (index > 0) {
                    flag = true;
                    index -= 1;
                }
            } else if (e.key === 'ArrowDown') {
                // down
                const count = this._var.currentSource?.length ?? 0;
                if (index < count - 1) {
                    flag = true;
                    index += 1;
                }
            }
            if (flag) {
                this._var.selectedIndexes = [index];
                this.scrollToIndex(index);
                this.refresh();
                if (typeof this.onSelectedRowChanged === 'function') {
                    this.onSelectedRowChanged(index);
                }
                e.stopPropagation();
            }
        });
        container.replaceChildren(grid);
        const sizer = createElement('span', 'ui-grid-sizer');
        grid.appendChild(sizer);
        this._var.refs.sizer = sizer;

        grid.addEventListener('scroll', e => throttle(this._onScroll, RefreshInterval, this, e), { passive: true });

        // header & body
        const wrapper = createElement('div', 'ui-grid-wrapper');
        this._var.refs.wrapper = wrapper;
        grid.appendChild(wrapper);
        const table = createElement('table', 'ui-grid-table');
        this._var.refs.table = table;
        this._createHeader(table);
        const body = this._createBody(table);
        wrapper.appendChild(table);
        // tooltip
        if (!this.tooltipDisabled) {
            const holder = createElement('div', 'ui-grid-hover-holder');
            holder.addEventListener('mousedown', e => {
                const holder = e.currentTarget;
                const row = Number(holder.dataset.row);
                const col = Number(holder.dataset.col);
                if (holder.classList.contains('active')) {
                    holder.classList.remove('active');
                }
                return this._onRowClicked(e, row, col);
            });
            holder.addEventListener('dblclick', e => this._onRowDblClicked(e));
            wrapper.appendChild(holder);
            body.addEventListener('mousemove', e => throttle(this._onBodyMouseMove, HoverInternal, this, e, holder), { passive: true });
        }

        // loading
        const loading = createElement('div', 'ui-grid-loading',
            createElement('div', null, createIcon('fa-regular', 'spinner-third'))
        );
        this._var.refs.loading = loading;
        grid.appendChild(loading);
        this._var.el = grid;

        this._var.rendering = false;
        if (this._var.source != null && this.sortIndex >= 0) {
            this.sortColumn();
        }
    }

    setData(source) {
        this.source = source;
    }

    scrollToIndex(index) {
        const top = this._scrollToTop(index * (this.rowHeight + 1), true);
        this._var.el.scrollTop = top;
    }

    resize(force, keep) {
        if (this._var.rendering || this._var.el == null) {
            return;
        }
        const body = this._var.refs.body;
        const top = this.headerVisible === false ? 0 : this._var.refs.header.offsetHeight;

        let height = this.height;
        if (height === 0) {
            height = this._var.containerHeight;
        } else if (isNaN(height) || height < 0) {
            height = this._var.el.offsetHeight - top;
        }
        const count = truncate((height - 1) / (this.rowHeight + 1)) + (RedumCount * 2) + 1;
        if (force || count !== this._var.rowCount) {
            this._var.rowCount = count;
            this.reload(keep);
        }
        this._var.bodyClientWidth = body.clientWidth;
    }

    reload(keep) {
        const filtered = this.columns.some(c => c.filterValues != null);
        if ((filtered ^ this._var.colAttrs.__filtered) === 1) {
            this._var.colAttrs.__filtered = filtered;
            const headers = this._var.refs.header.children;
            for (let i = 0; i < this.columns.length; ++i) {
                const ele = headers[i].querySelector('.filter');
                if (ele == null) {
                    continue;
                }
                if (this.columns[i].filterValues != null) {
                    ele.classList.add('active');
                } else {
                    ele.classList.remove('active');
                }
            }
            this._refreshSource();
            return;
        }
        let length = this._var.currentSource?.length ?? 0;
        if (this.extraRows > 0) {
            length += this.extraRows;
        }
        this._var.containerHeight = length * (this.rowHeight + 1);
        if (!keep) {
            this._var.el.scrollTop = 0;
            // this._var.el.scrollLeft = 0;
            this._var.refs.table.style.top = '0px';
        }
        this._var.refs.wrapper.style.height = `${this._var.containerHeight}px`;
        this._adjustRows(this._var.refs.body);
        this.refresh();
    }

    refresh() {
        if (this._var.refs.body == null) {
            throw new Error('body has not been created.');
        }
        const widths = {};
        this._fillRows(this.tableRows, this.columns, widths);
        if (this._var.needResize && widths.flag) {
            this._var.needResize = false;
            this.columns.forEach((col, i) => {
                if (!this._get(col.key, 'autoResize')) {
                    return;
                }
                let width = widths[i];
                if (width < col.width) {
                    width = col.width;
                }
                if (width > 0) {
                    this._changeColumnWidth(i, width);
                }
            });
        }
    }

    resetChange() {
        if (this._var.source == null) {
            return;
        }
        for (let row of this._var.source) {
            delete row.__changed;
        }
    }

    sortColumn(reload) {
        const index = this.sortIndex;
        const col = this.columns[index];
        if (col == null) {
            return;
        }
        const direction = this.sortDirection;
        [...this._var.refs.header.children].forEach((th, i) => {
            const arrow = th.querySelector('.arrow');
            if (arrow == null) {
                return;
            }
            if (i === index) {
                arrow.className = `arrow ${(direction !== 1 ? 'desc' : 'asc')}`;
            } else if (arrow.className !== 'arrow') {
                arrow.className = 'arrow';
            }
        });
        let comparer;
        if (typeof col.sortFilter !== 'function') {
            let direction = this.sortDirection;
            if (isNaN(direction)) {
                direction = 1;
            }
            comparer = (a, b) => {
                a = this._getItemValue(a.values, col.key, col.filter);
                b = this._getItemValue(b.values, col.key, col.filter);
                if (a == null && typeof b === 'number') {
                    a = 0;
                } else if (typeof a === 'number' && b == null) {
                    b = 0;
                } else if (a != null && b == null) {
                    return direction;
                } else if (typeof a === 'string' && typeof b === 'string') {
                    a = a.toLowerCase();
                    b = b.toLowerCase();
                }
                return a === b ? 0 : (a > b ? 1 : -1) * direction;
            };
        } else {
            comparer = (a, b) => col.sortFilter(a.values, b.values) * direction;
        }
        this._var.source.sort(comparer);
        if (this._var.colAttrs.__filtered === true) {
            this._var.currentSource.sort(comparer);
        }
        if (this._var.rowCount < 0) {
            return;
        }
        if (reload) {
            this.reload();
        } else {
            this.refresh();
        }
    }

    clearHeaderCheckbox() {
        const boxes = this._var.refs.header.querySelectorAll('.ui-check-wrapper>input');
        boxes.forEach(box => box.checked = false);
    }

    _createHeader(table) {
        const thead = createElement('thead');
        if (this.headerVisible === false) {
            thead.style.display = 'none';
        }
        table.appendChild(thead);
        const header = createElement('tr');
        thead.appendChild(header);
        const sizer = this._var.refs.sizer;
        let left = 0;
        for (let col of this.columns) {
            if (col.visible === false) {
                const hidden = createElement('th');
                hidden.style.display = 'none';
                if (col.sortable !== false) {
                    hidden.dataset.key = col.key;
                    hidden.addEventListener('click', e => this._onHeaderClicked(e, col, true));
                }
                header.appendChild(hidden);
                continue;
            }
            // style
            const isCheckbox = Grid.ColumnTypes.isCheckbox(col.type);
            if (col.width > 0) {
                // col.autoResize = false;
            } else {
                this._set(col.key, 'autoResize', true);
                this._var.needResize = true;
                sizer.innerText = col.caption ?? '';
                let width = sizer.offsetWidth + 22;
                if (!this.readonly && col.enabled !== false && col.allcheck && isCheckbox) {
                    width += 32;
                }
                if (col.allowFilter === true) {
                    width += 14;
                }
                if (width < MiniColumnWidth) {
                    width = MiniColumnWidth;
                }
                col.width = width;
            }
            col.align ??= isCheckbox ? 'center' : 'left';
            if (col.sortable !== false) {
                col.sortable = true;
            }
            const w = `${col.width}px`;
            const style = {
                'width': w,
                'max-width': w,
                'min-width': w,
                'text-align': col.align
            };
            this._set(col.key, 'style', style);
            // element
            const th = createElement('th', 'column');
            const thStyle = { ...style };
            if (col.isfixed) {
                th.classList.add('sticky');
                thStyle.left = `${left}px`;
            }
            left += col.width;
            th.dataset.key = col.key;
            if (col.sortable) {
                thStyle.cursor = 'pointer';
                th.addEventListener('click', e => this._onHeaderClicked(e, col));
            }
            th.style.cssText = convertCssStyle(thStyle);
            if (col.orderable !== false) {
                col.orderable = true;
                th.addEventListener('mousedown', e => this._onDragStart(e, col));
            }
            const wrapper = createElement('div');
            th.appendChild(wrapper);
            if (!this.readonly && col.enabled !== false && col.allcheck && isCheckbox) {
                const check = createCheckbox({
                    onchange: e => this._onColumnAllChecked(col, e.target.checked)
                });
                wrapper.appendChild(check);
            }
            const caption = createElement('span');
            if (col.captionStyle != null) {
                caption.style.cssText = convertCssStyle(col.captionStyle);
            }
            caption.innerText = col.caption ?? '';
            wrapper.appendChild(caption);
            // order arrow
            if (col.sortable) {
                th.appendChild(createElement('layer', 'arrow'));
            }
            // filter
            if (col.allowFilter === true) {
                const filter = createElement('layer', 'filter');
                filter.appendChild(createIcon('fa-solid', 'filter'));
                filter.addEventListener('mousedown', e => this._onFilter(e, col));
                th.classList.add('header-filter');
                th.appendChild(filter);
            }
            // resize spliter
            if (col.resizable !== false) {
                const spliter = createElement('layer', 'spliter');
                spliter.addEventListener('mousedown', e => this._onResizeStart(e, col));
                spliter.addEventListener('dblclick', e => this._onAutoResize(e, col));
                th.appendChild(spliter);
            }
            // tooltip
            // !nullOrEmpty(col.tooltip) && setTooltip(th, col.tooltip);
            header.appendChild(th);
        }
        const dragger = createElement('div', 'dragger');
        const draggerCursor = createElement('layer', 'dragger-cursor');
        header.appendChild(
            createElement('th', null, dragger, draggerCursor, createElement('div')));

        sizer.replaceChildren();
        this._var.refs.header = header;
        this._var.refs.dragger = dragger;
        this._var.refs.draggerCursor = draggerCursor;
        return thead;
    }

    _createBody(table) {
        const body = createElement('tbody');
        table.appendChild(body);
        const cols = this.columns;
        let width = 1;
        for (let col of cols) {
            if (col.visible !== false && !isNaN(col.width)) {
                width += col.width + 1;
            }
        }
        if (width > 0) {
            table.style.width = `${width}px`;
        }
        // body content
        body.addEventListener('mousedown', e => {
            let [parent, target] = this._getRowTarget(e.target);
            const rowIndex = indexOfParent(parent);
            let colIndex = indexOfParent(target);
            if (colIndex >= this.columns.length) {
                colIndex = -1;
            }
            this._onRowClicked(e, rowIndex, colIndex);
        });
        body.addEventListener('dblclick', e => this._onRowDblClicked(e));
        // this._adjustRows();
        this._var.refs.body = body;

        // this.refresh();
        return body;
    }

    _adjustRows() {
        let count = this._var.rowCount;
        if (isNaN(count) || count < 0 || !this.virtual) {
            count = this._var.currentSource?.length ?? 0;
        }
        const cols = this.columns;
        const content = this._var.refs.body;
        const exists = content.children.length;
        count -= exists;
        if (count > 0) {
            for (let i = 0; i < count; i += 1) {
                const row = createElement('tr', 'ui-grid-row');
                let left = 0;
                cols.forEach((col, j) => {
                    const cell = createElement('td');
                    if (col.visible !== false) {
                        let style = this._get(col.key, 'style') ?? {};
                        if (col.isfixed) {
                            cell.classList.add('sticky');
                            style.left = `${left}px`;
                        }
                        left += col.width;
                        cell.dataset.row = String(exists + i);
                        cell.dataset.col = String(j);
                        if (col.css != null) {
                            style = { ...style, ...col.css };
                        }
                        style = convertCssStyle(style);
                        if (style !== '') {
                            cell.style.cssText = style;
                        }
                        if (Grid.ColumnTypes.isCheckbox(col.type)) {
                            cell.appendChild(GridCheckboxColumn.createEdit(e => this._onRowChanged(e, exists + i, col, e.target.checked, cell)));
                            // this._var.colTypes[col.key] = GridCheckboxColumn;
                        } else {
                            let type = this._var.colTypes[col.key];
                            if (type == null) {
                                if (isNaN(col.type)) {
                                    type = col.type;
                                } else {
                                    type = ColumnTypes[col.type];
                                }
                                type ??= GridColumn;
                                this._var.colTypes[col.key] = type;
                            }
                            cell.appendChild(type.create(col));
                        }
                    } else {
                        cell.style.display = 'none';
                    }
                    row.appendChild(cell);
                });
                row.appendChild(createElement('td', td => td.innerText = '\u00a0'));
                content.appendChild(row);
            }
        } else if (count < 0) {
            for (let i = -1; i >= count; i -= 1) {
                // content.removeChild(content.children[exists + i]);
                content.children[exists + i].remove();
            }
        }
    }

    _fillRows(rows, cols, widths) {
        const startIndex = this._var.startIndex;
        const selectedIndexes = this._var.selectedIndexes;
        [...rows].forEach((row, i) => {
            const vals = this._var.currentSource[startIndex + i];
            if (vals == null) {
                return;
            }
            if (!isPositive(row.children.length)) {
                return;
            }
            const item = vals.values;
            const selected = selectedIndexes.includes(startIndex + i);
            if (selected) {
                row.classList.add('selected');
            } else if (row.classList.contains('selected')) {
                row.classList.remove('selected');
            }
            // data
            cols.forEach((col, j) => {
                if (col.visible === false) {
                    return;
                }
                const cell = row.children[j];
                if (cell == null) {
                    return;
                }
                let val;
                if (col.text != null) {
                    val = col.text;
                } else if (typeof col.filter === 'function') {
                    val = col.filter(item, selected, this._var.refs.body);
                } else {
                    val = item[col.key];
                    if (val?.DisplayValue != null) {
                        val = val.DisplayValue;
                    }
                }
                val ??= '';
                // fill
                if (typeof col.bgFilter === 'function') {
                    const bgColor = col.bgFilter(item);
                    cell.style.backgroundColor = bgColor ?? '';
                }
                const isCheckbox = Grid.ColumnTypes.isCheckbox(col.type);
                const type = isCheckbox ? GridCheckboxColumn : this._var.colTypes[col.key] ?? GridColumn;
                let element;
                if (!isCheckbox && typeof type.createEdit === 'function') {
                    if (vals.__editing?.[col.key]) {
                        if (typeof type.leaveEdit === 'function') {
                            type.leaveEdit(cell.children[0], this._var.el);
                        }
                        if (type.editing) {
                            val = type.getValue({ target: cell.children[0] }, col);
                            this._onRowChanged(null, i, col, val, cell, true);
                        }
                    }
                    element = selected ?
                        type.createEdit(e => this._onRowChanged(e, i, col, type.getValue(e, col), cell), col, this._var.el, vals) :
                        type.create(col);
                    cell.replaceChildren(element);
                } else {
                    element = cell.children[0];
                }
                let enabled;
                if (this.readonly) {
                    enabled = false;
                } else {
                    enabled = col.enabled;
                    if (typeof enabled === 'function') {
                        enabled = enabled.call(col, item);
                    } else if (typeof enabled === 'string') {
                        enabled = item[enabled];
                    }
                }
                type.setValue(element, val, vals, col, this);
                let tip = col.tooltip;
                if (typeof tip === 'function') {
                    tip = tip.call(col, item);
                }
                if (nullOrEmpty(tip)) {
                    element.querySelector('.ui-tooltip-wrapper')?.remove();
                } else {
                    setTooltip(element, tip, false, this.element);
                }
                if (typeof type.setEnabled === 'function') {
                    type.setEnabled(element, enabled);
                }
                // auto resize
                if (this._var.needResize && this._get(col.key, 'autoResize')) {
                    const width = element.scrollWidth + 12;
                    if (width > 0 && widths != null && (isNaN(widths[j]) || widths[j] < width)) {
                        widths[j] = width;
                        widths.flag = true;
                    }
                }
                if (typeof col.styleFilter === 'function') {
                    const style = col.styleFilter(item);
                    if (style != null) {
                        type.setStyle(element, style);
                    }
                }
                if (col.events != null) {
                    for (let ev of Object.entries(col.events)) {
                        element[ev[0]] = ev[1].bind(item);
                    }
                }
                if (col.attrs != null) {
                    let attrs = col.attrs;
                    if (typeof attrs === 'function') {
                        attrs = attrs(item);
                    }
                    for (let attr of Object.entries(attrs)) {
                        element.setAttribute(attr[0], attr[1]);
                    }
                }
            });
            if (vals.__editing != null) {
                delete vals.__editing;
            }
        });
    }

    _changeColumnWidth(index, width) {
        const col = this.columns[index];
        // const oldwidth = col.width;
        const w = `${width}px`;
        col.width = width;
        const style = this._get(col.key, 'style');
        style.width = w;
        style['max-width'] = w;
        style['min-width'] = w;
        const headerCells = this._var.refs.header.children;
        let element = headerCells[index];
        element.style.width = w;
        element.style.maxWidth = w;
        element.style.minWidth = w;
        let left = 0;
        if (col.isfixed) {
            left = element.offsetLeft + width;
            let l = left;
            for (let i = index + 1; i < this.columns.length; ++i) {
                if (this.columns[i].isfixed) {
                    headerCells[i].style.left = `${l}px`;
                    l += this.columns[i].width;
                } else {
                    break;
                }
            }
        }
        for (let row of this.tableRows) {
            element = row.children[index];
            if (element != null) {
                element.style.width = w;
                element.style.maxWidth = w;
                element.style.minWidth = w;
                if (col.isfixed) {
                    let l = left;
                    for (let i = index + 1; i < this.columns.length; ++i) {
                        if (this.columns[i].isfixed) {
                            row.children[i].style.left = `${l}px`;
                            l += this.columns[i].width;
                        } else {
                            break;
                        }
                    }
                }
            }
        }
    }

    _changingColumnOrder(index, offset, x, offsetLeft, scrollLeft) {
        const children = this._var.refs.header.children;
        let element = children[index];
        this._var.refs.dragger.style.cssText = `left: ${element.offsetLeft - offsetLeft + offset}px; width: ${element.style.width}; display: block`;
        offset = x + scrollLeft - element.offsetLeft; // getOffsetLeftFromWindow(element);
        let idx;
        if (offset < 0) {
            offset = -offset;
            for (let i = index - 1; i >= 0 && offset >= 0; i -= 1) {
                element = children[i];
                if (element == null || !element.className || element.classList.contains('sticky')) {
                    idx = i + 1;
                    break;
                }
                if (offset < element.offsetWidth) {
                    idx = (offset > element.offsetWidth / 2) ? i : i + 1;
                    break;
                }
                offset -= element.offsetWidth;
            }
            idx ??= 0;
        } else {
            const count = children.length;
            for (let i = index; i < count - 1 && offset >= 0; i += 1) {
                element = children[i];
                if (element == null || !element.className || element.classList.contains('sticky')) {
                    idx = i;
                    break;
                }
                if (offset < element.offsetWidth) {
                    idx = (offset > element.offsetWidth / 2) ? i + 1 : i;
                    break;
                }
                offset -= element.offsetWidth;
            }
            idx ??= count - 1;
        }
        if (idx !== this._var.colAttrs.__orderIndex) {
            this._var.colAttrs.__orderIndex = idx;
            element = children[idx];
            if (element == null) {
                return;
            }
            this._var.refs.draggerCursor.style.cssText = `left: ${element.offsetLeft - offsetLeft}px; display: block`;
        }
    }

    _changeColumnOrder(index) {
        this._var.refs.dragger.style.display = '';
        this._var.refs.draggerCursor.style.display = '';
        const orderIndex = this._var.colAttrs.__orderIndex;
        if (orderIndex >= 0 && orderIndex !== index) {
            let targetIndex = orderIndex - index;
            if (targetIndex >= 0 && targetIndex <= 1) {
                return;
            }
            const header = this._var.refs.header;
            const children = header.children;
            const rows = this.tableRows;
            const columns = this.columns;
            if (targetIndex > 1) {
                targetIndex = orderIndex - 1;
                // const current = columns[index];
                // for (let i = index; i < targetIndex; i += 1) {
                //     columns[i] = columns[i + 1];
                // }
                // columns[targetIndex] = current;
                const current = columns.splice(index, 1)[0];
                columns.splice(targetIndex, 0, current);
                header.insertBefore(children[index], children[targetIndex].nextElementSibling);
                for (let row of rows) {
                    row.insertBefore(row.children[index], row.children[targetIndex].nextElementSibling);
                }
            } else {
                targetIndex = orderIndex;
                // const current = columns[index];
                // for (let i = index; i > targetIndex; i -= 1) {
                //     columns[i] = columns[i - 1];
                // }
                // columns[targetIndex] = current;
                const current = columns.splice(index, 1)[0];
                columns.splice(targetIndex, 0, current);
                header.insertBefore(children[index], children[targetIndex]);
                for (let row of rows) {
                    row.insertBefore(row.children[index], row.children[targetIndex]);
                }
            }
            // refresh sortIndex
            [...children].forEach((th, i) => {
                const arrow = th.querySelector('.arrow');
                if (arrow == null) {
                    return;
                }
                if (arrow.className !== 'arrow') {
                    this.sortIndex = i;
                }
            });

            if (typeof this.onColumnChanged === 'function') {
                this.onColumnChanged(ColumnChangedType.Reorder, index, targetIndex);
            }
        }
    }

    _scrollToTop(top, reload) {
        const rowHeight = (this.rowHeight + 1);
        top -= (top % (rowHeight * 2)) + (RedumCount * rowHeight);
        if (top < 0) {
            top = 0;
        } else {
            let bottomTop = this._var.containerHeight - (reload ? 0 : this._var.rowCount * rowHeight);
            if (bottomTop < 0) {
                bottomTop = 0;
            }
            if (top > bottomTop) {
                top = bottomTop;
            }
        }
        if (this._var.scrollTop !== top) {
            this._var.scrollTop = top;
            if (this.virtual) {
                this._var.startIndex = top / rowHeight;
            }
            this.refresh();
            if (this.virtual) {
                this._var.refs.table.style.top = `${top}px`;
            }
        } else if (reload) {
            this.refresh();
        }

        return top;
    }

    _get(key, name) {
        const attr = this._var.colAttrs[key];
        if (attr == null) {
            return null;
        }
        return attr[name];
    }

    _set(key, name, value) {
        const attr = this._var.colAttrs[key];
        if (attr == null) {
            this._var.colAttrs[key] = { [name]: value };
        } else {
            attr[name] = value;
        }
    }

    _getItemValue(item, key, filter) {
        let value;
        if (typeof filter === 'function') {
            value = filter(item, false, this._var.refs.body);
        } else {
            value = item[key];
        }
        return value?.Value ?? value;
    }

    _getRowTarget(target) {
        let parent;
        while ((parent = target.parentElement) != null && !parent.classList.contains('ui-grid-row')) {
            target = parent;
        }
        return [parent, target];
    }

    _notHeader(tagName) {
        return /^(input|label|layer|svg|use)$/i.test(tagName);
    }

    _onHeaderClicked(e, col, force) {
        if (!force && (this._get(col.key, 'resizing') || this._get(col.key, 'dragging'))) {
            return;
        }
        if (!this._notHeader(e.target.tagName)) {
            const index = this.columns.indexOf(col);
            if (index < 0) {
                return;
            }
            if (this.sortIndex === index) {
                this.sortDirection = this.sortDirection === 1 ? -1 : 1;
            } else {
                this.sortIndex = index;
            }
            this.sortColumn();
            if (typeof this.onColumnChanged === 'function') {
                this.onColumnChanged(ColumnChangedType.Sort, index, this.sortDirection);
            }
        }
    }

    _onCloseFilter(e) {
        if (e != null) {
            if ((e.target.tagName === 'LAYER' && e.target.classList.contains('filter')) ||
                e.target.tagName === 'use') {
                return false;
            }
        }
        const panels = this._var.el.querySelectorAll('.filter-panel.active');
        if (panels.length > 0) {
            panels.forEach(el => el.classList.remove('active'));
            setTimeout(() => this._var.el.querySelectorAll('.filter-panel').forEach(el => el.remove()), 120);
            const filtering = this._var.colAttrs.__filtering;
            if (filtering instanceof HTMLElement) {
                filtering.classList.remove('hover');
            }
            delete this._var.colAttrs.__filtering;
            document.removeEventListener('mousedown', this._onCloseFilter);
            return true;
        }
        return false;
    }

    _onFilter(e, col) {
        if (this._onCloseFilter()) {
            return;
        }
        document.addEventListener('mousedown', this._onCloseFilter.bind(this));
        const panel = createElement('div', 'filter-panel');
        panel.addEventListener('mousedown', e => e.stopPropagation());
        const filter = e.currentTarget;
        const th = filter.parentElement;
        const width = th.offsetWidth;
        panel.style.top = `${th.offsetHeight + this._var.el.scrollTop}px`;
        panel.style.left = (th.offsetLeft + (width > FilterPanelWidth ? width - FilterPanelWidth : 0)) + 'px';

        // search
        let searchbox;
        if (col.allowSearch !== false) {
            const searchholder = createElement('div', 'filter-search-holder');
            searchbox = createElement('input', 'filter-search-box ui-text');
            searchbox.type = 'text';
            const searchicon = createIcon('fa-regular', 'search');
            searchicon.addEventListener('mousedown', e => {
                searchbox.focus();
                e.preventDefault();
            });
            searchholder.append(searchbox, searchicon);
            panel.append(searchholder);
        }
        // list
        const itemlist = createElement('div', 'filter-item-list');
        itemlist.addEventListener('scroll', e => throttle(this._onFilterScroll, RefreshInterval, this, col, itemlist, e.target.scrollTop), { passive: true });
        // - all
        const itemall = createElement('div', 'filter-item filter-all');
        itemall.appendChild(createCheckbox({
            label: this.langs.all,
            onchange: e => {
                const checked = e.target.checked;
                itemlist.querySelectorAll('.filter-content input').forEach(box => box.checked = checked);
                for (let it of this._get(col.key, 'filterSource')) {
                    it.__checked = checked;
                }
            }
        }));
        itemlist.appendChild(itemall);
        // - items
        let array;
        if (Array.isArray(col.filterSource)) {
            array = col.filterSource;
        } else if (typeof col.filterSource === 'function') {
            array = col.filterSource.call(this, col);
        } else {
            const dict = Object.create(null);
            for (let item of this._var.source) {
                const val = this._getItemValue(item.values, col.key, col.filter);
                if (!Object.hasOwnProperty.call(dict, val)) {
                    const v = item.values[col.key];
                    dict[val] = {
                        Value: val,
                        DisplayValue: typeof col.filter === 'function' ? col.filter(item.values) : v?.DisplayValue ?? v
                    };
                }
            }
            array = Object.values(dict)
                .sort((a, b) => {
                    a = a?.Value ?? a;
                    b = b?.Value ?? b;
                    return a > b ? 1 : a < b ? -1 : 0;
                });
        }
        array = array.map(i => {
            if (Object.prototype.hasOwnProperty.call(i, 'Value') &&
                Object.prototype.hasOwnProperty.call(i, 'DisplayValue')) {
                return i;
            }
            return {
                Value: i,
                DisplayValue: i == null ? '' : i
            };
        });
        this._fillFilterList(col, itemlist, array, itemall);
        itemall.querySelector('input').checked = ![...itemlist.querySelectorAll('.filter-content input')].some(i => !i.checked);
        panel.appendChild(itemlist);
        if (searchbox != null) {
            searchbox.addEventListener('input', e => {
                const key = e.currentTarget.value.toLowerCase();
                const items = key.length === 0 ? array : array.filter(i => {
                    const displayValue = i?.DisplayValue ?? i;
                    return String(displayValue ?? '').toLowerCase().includes(key);
                });
                this._fillFilterList(col, itemlist, items, itemall);
            });
        }
        // function
        const functions = createElement('div', 'filter-function');
        functions.append(
            createElement('button', ok => {
                ok.innerText = this.langs.ok;
                ok.addEventListener('click', () => {
                    const array = this._get(col.key, 'filterSource').filter(i => i.__checked !== false);
                    if (typeof col.onFilterOk === 'function') {
                        col.onFilterOk.call(this, col, array);
                    } else {
                        col.filterValues = array.map(a => a.Value);
                    }
                    this._var.colAttrs.__filtered = true;
                    this._refreshSource();
                    if (typeof col.onFiltered === 'function') {
                        col.onFiltered.call(this, col);
                    }
                    filter.classList.add('active');
                    this._onCloseFilter();
                });
            }),
            createElement('button', reset => {
                reset.innerText = this.langs.reset;
                reset.addEventListener('click', () => {
                    delete col.filterValues;
                    this._var.colAttrs.__filtered = this.columns.some(c => c.filterValues != null)
                    this._refreshSource();
                    if (typeof col.onFiltered === 'function') {
                        col.onFiltered.call(this, col);
                    }
                    filter.classList.remove('active');
                    this._onCloseFilter();
                });
            })
        );
        panel.appendChild(functions);

        this._var.el.appendChild(panel);
        setTimeout(() => panel.classList.add('active'), 0);
        this._var.colAttrs.__filtering = filter;
        filter.classList.add('hover');
    }

    _fillFilterList(col, list, array, all) {
        list.querySelector('.filter-holder')?.remove();
        list.querySelector('.filter-content')?.remove();
        const rowHeight = this.filterRowHeight;
        const height = array.length * rowHeight;
        this._set(col.key, 'filterHeight', height);
        const holder = createElement('div', 'filter-holder');
        holder.style.height = `${height}px`;
        const content = createElement('div', 'filter-content');
        content.style.top = `${rowHeight}px`;
        this._set(col.key, 'filterSource', array);
        for (let item of array) {
            item.__checked = !Array.isArray(col.filterValues) || col.filterValues.includes(item.Value ?? item);
        }
        if (array.length > 12) {
            array = array.slice(0, 12);
        }
        this._doFillFilterList(content, array, all);
        list.append(holder, content);
    }

    _doFillFilterList(content, array, all) {
        for (let item of array) {
            const div = createElement('div', 'filter-item');
            div.appendChild(createCheckbox({
                checked: item.__checked,
                label: item?.DisplayValue ?? item,
                onchange: e => {
                    item.__checked = e.target.checked;
                    all.querySelector('input').checked = ![...content.querySelectorAll('input')].some(i => !i.checked);
                }
            }));
            content.appendChild(div);
        }
    }

    _onFilterScroll(col, list, top) {
        const rowHeight = this.filterRowHeight;
        top -= (top % (rowHeight * 2)) + rowHeight;
        if (top < 0) {
            top = 0;
        } else {
            let bottomTop = this._get(col.key, 'filterHeight') - (12 * rowHeight);
            if (bottomTop < 0) {
                bottomTop = 0;
            }
            if (top > bottomTop) {
                top = bottomTop;
            }
        }
        if (this._get(col.key, 'filterTop') !== top) {
            this._set(col.key, 'filterTop', top);
            const startIndex = top / rowHeight;
            let array = this._get(col.key, 'filterSource');
            if (startIndex + 12 < array.length) {
                array = array.slice(startIndex, startIndex + 12);
            } else {
                array = array.slice(-12);
            }
            const content = list.querySelector('.filter-content');
            content.replaceChildren();
            this._doFillFilterList(content, array, list.querySelector('.filter-all'));
            content.style.top = `${top + rowHeight}px`;
        }
    }

    _onDragStart(e, col) {
        if (this._notHeader(e.target.tagName)) {
            return;
        }
        if (e.currentTarget.classList.contains('sticky')) {
            return;
        }
        const index = indexOfParent(e.currentTarget);
        const cx = getClientX(e);
        const clearEvents = attr => {
            for (let event of ['mousemove', 'mouseup']) {
                if (attr.hasOwnProperty(event)) {
                    window.removeEventListener(event, attr[event]);
                    delete attr[event];
                }
            }
        };
        let attr = this._var.colAttrs[col.key];
        if (attr == null) {
            attr = this._var.colAttrs[col.key] = {};
        } else {
            clearEvents(attr);
        }
        attr.dragging = true;
        const offsetLeft = this._var.refs.header.querySelector('th:last-child').offsetLeft;
        const scrollLeft = this._var.el.scrollLeft;
        const dragmove = e => {
            const cx2 = getClientX(e);
            const offset = cx2 - cx;
            let pos = attr.offset;
            let dragging;
            if (pos == null && (offset > MiniDragOffset || offset < -MiniDragOffset)) {
                dragging = true;
            } else if (pos !== offset) {
                dragging = true;
            }
            if (dragging) {
                this._changingColumnOrder(index, offset, cx2, offsetLeft, scrollLeft);
                attr.offset = offset;
            }
        };
        attr.mousemove = e => throttle(dragmove, RefreshInterval, this, e);
        attr.mouseup = () => {
            clearEvents(attr);
            if (attr.offset == null) {
                delete attr.dragging;
            } else {
                setTimeout(() => {
                    delete attr.dragging;
                    delete attr.offset;
                });
                this._changeColumnOrder(index);
            }
        };
        ['mousemove', 'mouseup'].forEach(event => window.addEventListener(event, attr[event]));
    }

    _onResizeStart(e, col) {
        const cx = getClientX(e);
        const width = col.width;
        const index = indexOfParent(e.currentTarget.parentElement);
        const window = this.window ?? global;
        const clearEvents = attr => {
            for (let event of ['mousemove', 'mouseup']) {
                if (attr.hasOwnProperty(event)) {
                    window.removeEventListener(event, attr[event]);
                    delete attr[event];
                }
            }
        };
        let attr = this._var.colAttrs[col.key];
        if (attr == null) {
            attr = this._var.colAttrs[col.key] = {};
        } else {
            clearEvents(attr);
        }
        attr.resizing = width;
        const resizemove = e => {
            const cx2 = getClientX(e);
            const val = width + (cx2 - cx);
            if (val < MiniColumnWidth) {
                return;
            }
            attr.resizing = val;
            attr.sizing = true;
            this._changeColumnWidth(index, val);
        };
        attr.mousemove = e => throttle(resizemove, RefreshInterval, this, e);
        attr.mouseup = e => {
            clearEvents(attr);
            const width = attr.resizing;
            if (width != null) {
                setTimeout(() => delete attr.resizing);
                if (attr.sizing) {
                    delete attr.sizing;
                    delete attr.autoResize;
                    this._changeColumnWidth(index, width);
                    if (typeof this.onColumnChanged === 'function') {
                        this.onColumnChanged(ColumnChangedType.Resize, index, width);
                    }
                }
            }
            e.stopPropagation();
            e.preventDefault();
        };
        ['mousemove', 'mouseup'].forEach(event => window.addEventListener(event, attr[event]));
    }

    _onAutoResize(e, col) {
        const th = e.currentTarget.parentElement;
        const index = indexOfParent(th);
        let width = th.querySelector('div:first-child').scrollWidth;
        for (let row of this.tableRows) {
            const element = row.children[index].children[0];
            const w = element.scrollWidth;
            if (w > width) {
                width = w;
            }
        }
        if (width < MiniColumnWidth) {
            width = MiniColumnWidth;
        }
        if (width > 0 && width !== col.width) {
            width += 12;
            this._changeColumnWidth(index, width);
            if (typeof this.onColumnChanged === 'function') {
                this.onColumnChanged(ColumnChangedType.Resize, index, width);
            }
        }
    }

    _onColumnAllChecked(col, flag) {
        if (this._var.currentSource == null) {
            return;
        }
        const key = col.key;
        const isFunction = typeof col.enabled === 'function';
        const isString = typeof col.enabled === 'string';
        if (typeof col.onAllChecked === 'function') {
            col.onAllChecked.call(this, col, flag);
        } else {
            for (let row of this._var.currentSource) {
                const item = row.values;
                if (item == null) {
                    continue;
                }
                const enabled = isFunction ? col.enabled(item) : isString ? item[col.enabled] : col.enabled;
                if (enabled !== false) {
                    item[key] = flag;
                    row.__changed = true;
                    if (typeof col.onChanged === 'function') {
                        col.onChanged.call(this, item, flag);
                    }
                }
            }
            this.refresh();
        }
    }

    _onScroll(e) {
        if (this._var.colAttrs.__filtering != null) {
            this._onCloseFilter();
        }
        if (this.onBodyScrolled === 'function') {
            this.onBodyScrolled(e);
        }
        if (!this.virtual) {
            return;
        }
        const top = e.target.scrollTop;
        this._scrollToTop(top);
    }

    _onBodyMouseMove(e, holder) {
        if (e.target.classList.contains('ui-grid-hover-holder')) {
            return;
        }
        let [parent, target] = this._getRowTarget(e.target);
        if (parent == null) {
            delete holder.dataset.row;
            delete holder.dataset.col;
            if (holder.classList.contains('active')) {
                holder.classList.remove('active');
            }
            return;
        }
        const element = target.children[0];
        if (element?.tagName !== 'SPAN') {
            if (holder.classList.contains('active')) {
                delete holder.dataset.row;
                delete holder.dataset.col;
                holder.classList.remove('active');
            }
            return;
        }
        const row = target.dataset.row;
        const col = target.dataset.col;
        if (holder.dataset.row === row &&
            holder.dataset.col === col) {
            return;
        }
        if (element.scrollWidth > element.offsetWidth) {
            holder.dataset.row = row;
            holder.dataset.col = col;
            holder.innerText = element.innerText;
            const top = target.offsetTop + this._var.refs.table.offsetTop;
            let left = target.offsetLeft;
            let width = holder.offsetWidth;
            if (width > this._var.bodyClientWidth) {
                width = this._var.bodyClientWidth;
            }
            const maxleft = this._var.bodyClientWidth + this._var.scrollLeft - width;
            if (left > maxleft) {
                left = maxleft;
            }
            const height = target.offsetHeight;
            holder.style.cssText = `top: ${top}px; left: ${left}px; max-width: ${this._var.bodyClientWidth}px; height: ${height - 2}px`;
            holder.classList.add('active');
        } else if (holder.classList.contains('active')) {
            delete holder.dataset.row;
            delete holder.dataset.col;
            holder.classList.remove('active');
        }
    }

    _onRowClicked(e, index, colIndex) {
        const startIndex = this._var.startIndex;
        const selectedIndex = startIndex + index;
        if (typeof this.willSelect === 'function' && !this.willSelect(selectedIndex, colIndex)) {
            return;
        }
        // multi-select
        let flag = false;
        const selectedIndexes = this._var.selectedIndexes;
        if (this.multiSelect) {
            if (e.ctrlKey) {
                const i = selectedIndexes.indexOf(selectedIndex);
                if (i < 0) {
                    selectedIndexes.push(selectedIndex);
                } else {
                    selectedIndexes.splice(i, 1);
                }
                flag = true;
            } else if (e.shiftKey && selectedIndexes.length > 0) {
                if (selectedIndexes.length > 1 || selectedIndexes[0] !== selectedIndex) {
                    let start = selectedIndexes[selectedIndexes.length - 1];
                    let end;
                    if (start > selectedIndex) {
                        end = start;
                        start = selectedIndex;
                    } else {
                        end = selectedIndex;
                    }
                    selectedIndexes.splice(0);
                    for (let i = start; i <= end; i += 1) {
                        selectedIndexes.push(i);
                    }
                    flag = true;
                }
            }
        }
        if (!flag && (selectedIndexes.length !== 1 || selectedIndexes[0] !== selectedIndex)) {
            selectedIndexes.splice(0, selectedIndexes.length, selectedIndex);
            flag = true;
        }
        // apply style
        if (flag) {
            if (this.readonly !== true) {
                this.refresh();
            } else {
                this.tableRows.forEach((row, i) => {
                    if (selectedIndexes.includes(startIndex + i)) {
                        row.classList.add('selected');
                    } else if (row.classList.contains('selected')) {
                        row.classList.remove('selected');
                    }
                });
            }
            if (typeof this.onSelectedRowChanged === 'function') {
                this.onSelectedRowChanged(selectedIndex);
            }
        }
        this._var.selectedColumnIndex = colIndex;
        if ((this.fullrowClick || colIndex >= 0) && e.buttons === 1 && typeof this.cellClicked === 'function') {
            if (this.cellClicked(selectedIndex, colIndex) === false) {
                e.stopPropagation();
                e.preventDefault();
            }
        }
    }

    _onRowDblClicked(e) {
        if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'LAYER' && e.target.className === 'ui-check-inner' || e.target.tagName === 'LABEL' && (e.target.className === 'ui-drop-text' || e.target.className === 'ui-drop-caret')) {
            return;
        }
        const index = this.selectedIndex;
        if (typeof this.onRowDblClicked === 'function') {
            this.onRowDblClicked(index);
        }
        if (typeof this.onCellDblClicked === 'function') {
            const colIndex = this._var.selectedColumnIndex;
            if (this.fullrowClick || colIndex >= 0) {
                this.onCellDblClicked(index, colIndex);
            }
        }
    }

    _onRowChanged(e, index, col, value, cell, blur) {
        if (this._var.currentSource == null) {
            return;
        }
        const row = this._var.currentSource[this._var.startIndex + index];
        delete row.source;
        const item = row.values;
        if (item == null) {
            return;
        }
        let enabled = col.enabled;
        if (typeof enabled === 'function') {
            enabled = enabled.call(col, item);
        } else if (typeof enabled === 'string') {
            enabled = item[enabled];
        }
        if (enabled !== false) {
            const val = item[col.key];
            let oldValue;
            if (val?.Value != null) {
                oldValue = val.Value;
                val.Value = value;
            } else {
                oldValue = val;
                item[col.key] = value;
            }
            let tip = col.tooltip;
            if (typeof tip === 'function') {
                tip = tip.call(col, item);
            }
            if (nullOrEmpty(tip)) {
                cell.querySelector('.ui-tooltip-wrapper')?.remove();
            } else {
                setTooltip(cell.children[0], tip, false, this.element);
            }
            row.__changed = true;
            if (blur) {
                if (typeof col.onInputEnded === 'function') {
                    col.onInputEnded.call(this, item, value);
                }
            } else {
                if (typeof col.onChanged === 'function') {
                    col.onChanged.call(this, item, value, oldValue, e);
                }
            }
        }
    }
}