/*!
 * Custom elements for Nanopore-Digital Web applications
 */
'use strict';

var index = require('./index-DGttnXif.js');
var renderer = require('./renderer-jVpDRMfV.js');
var math = require('./math-DIjJ3V87.js');
var throttle = require('./throttle-CrRDOkln.js');
var scroll = require('./scroll-C6pO9RvO.js');
var index$1 = require('./index-CkLcAUj-.js');

const CSSNAMESPACE = 'nano-tbl';

const isInstanceOf = (value, className) => {
  const C = globalThis[className];
  return C != null && value instanceof C;
};
const getTransferables = (value) => {
  if (value != null) {
    if (
      isInstanceOf(value, "ArrayBuffer") ||
      isInstanceOf(value, "MessagePort") ||
      isInstanceOf(value, "ImageBitmap") ||
      isInstanceOf(value, "OffscreenCanvas")
    ) {
      return [value];
    }
    if (typeof value === "object") {
      if (value.constructor === Object) {
        value = Object.values(value);
      }
      if (Array.isArray(value)) {
        return value.flatMap(getTransferables);
      }
      return getTransferables(value.buffer);
    }
  }
  return [];
};

let pendingIds = 0;
let callbackIds = 0;
const pending = new Map();
const callbacks = new Map();

const createWorker = (workerPath, workerName, workerMsgId) => {
  const worker = new Worker(workerPath, {name:workerName});

  worker.addEventListener('message', ({data}) => {
    if (data) {
      const workerMsg = data[0];
      const id = data[1];
      const value = data[2];

      if (workerMsg === workerMsgId) {
        const err = data[3];
        const [resolve, reject, callbackIds] = pending.get(id);
        pending.delete(id);

        if (err) {
          const errObj = (err.isError)
            ? Object.assign(new Error(err.value.message), err.value)
            : err.value;

          index.consoleError(errObj);
          reject(errObj);
        } else {
          if (callbackIds) {
            callbackIds.forEach(id => callbacks.delete(id));
          }
          resolve(value);
        }
      } else if (workerMsg === workerMsgId + '.cb') {
        try {
          callbacks.get(id)(...value);
        } catch (e) {
          index.consoleError(e);
        }
      }
    }
  });

  return worker;
};

const createWorkerProxy = (worker, workerMsgId, exportedMethod) => (
  (...args) => new Promise((resolve, reject) => {
    let pendingId = pendingIds++;
    let i = 0;
    let argLen = args.length;
    let mainData = [resolve, reject];
    pending.set(pendingId, mainData);

    for (; i < argLen; i++) {
      if (typeof args[i] === 'function') {
        const callbackId = callbackIds++;
        callbacks.set(callbackId, args[i]);
        args[i] = [workerMsgId + '.cb', callbackId];
        (mainData[2] = mainData[2] || []).push(callbackId);
      }
    }
    const postMessage = (w) => (
      w.postMessage(
        [workerMsgId, pendingId, exportedMethod, args],
        getTransferables(args)
      )
    );
    if (worker.then) {
      worker.then(postMessage);
    } else {
      postMessage(worker);
    }
  })
);

const workerPromise = Promise.resolve().then(function () { return require('./table.worker-BXs-g6xI.js'); }).then(m => m.worker);
const createWorkerStore = /*@__PURE__*/createWorkerProxy(workerPromise, 'stencil.table.worker', 'createWorkerStore');
const syncConfigToWorker = /*@__PURE__*/createWorkerProxy(workerPromise, 'stencil.table.worker', 'syncConfigToWorker');
const syncDataToWorker = /*@__PURE__*/createWorkerProxy(workerPromise, 'stencil.table.worker', 'syncDataToWorker');
const workerFilter = /*@__PURE__*/createWorkerProxy(workerPromise, 'stencil.table.worker', 'workerFilter');
const workerSearch = /*@__PURE__*/createWorkerProxy(workerPromise, 'stencil.table.worker', 'workerSearch');
const workerSort = /*@__PURE__*/createWorkerProxy(workerPromise, 'stencil.table.worker', 'workerSort');

function colsToWorker(columns) {
    const safeColumns = JSON.parse(JSON.stringify(columns));
    columns.forEach((c) => {
        if (c?.sortCompareFn) {
            const safeCol = safeColumns.find((sc) => sc.prop === c.prop);
            safeCol.sortCompareFn = c.sortCompareFn.toString();
        }
    });
    return safeColumns;
}
const stores = new WeakMap();
async function generateStore(host, columns, scrollParent, isReady) {
    const store = {
        data: index$1.createStore({ rows: [] }),
        config: index$1.createStore({ columns }),
        general: index$1.createStore({ workerId: null, scrollParent, host, isReady }),
    };
    const id = await createWorkerStore(store.data.state.rows, colsToWorker(store.config.state.columns));
    store.general.state.workerId = id;
    stores.set(host, store);
    // sync data from our store to our worker
    store.data.use({
        reset: () => {
            if (store.general.state.workerId)
                syncDataToWorker(store.general.state.workerId, null);
        },
        dispose: () => {
            if (store.general.state.workerId)
                syncDataToWorker(store.general.state.workerId, null);
        },
    });
    store.config.use({
        reset: () => {
            if (store.general.state.workerId)
                syncConfigToWorker(store.general.state.workerId, null);
        },
        dispose: () => {
            if (store.general.state.workerId)
                syncConfigToWorker(store.general.state.workerId, null);
        },
    });
    return store;
}
function getStore(host) {
    return stores.get(host);
}
function getDataType(value) {
    if (value instanceof Date) {
        return 'date';
    }
    if (['number', 'string', 'boolean'].includes(typeof value)) {
        return typeof value;
    }
    return 'unknown';
}
function storeSetData(host, rows) {
    const store = stores.get(host);
    if (!store)
        return;
    const cols = store.config.state.columns;
    const dateCols = cols
        .filter((col) => col.type === 'date')
        .reduce((prev, curr) => {
        return [...prev, curr.prop];
    }, []);
    const unknownCols = cols.filter((col) => !col.type);
    // augments data with some internal props.
    // discern unknown column types
    // convert dates to numbers for worker
    rows = rows.map((row, i) => {
        // try our best to discern the column type (from first row) if unset
        if (unknownCols.length && i === 0) {
            store.config.state.columns = cols.map((col) => {
                const val = getNestedProperty(row, col.prop);
                if (unknownCols.includes(col) && val && !col.type) {
                    col.type = getDataType(val);
                    if (col.type === 'date')
                        dateCols.push(col.prop);
                }
                return col;
            });
        }
        // convert date columns into numbers to send to our worker
        dateCols.forEach((colName) => {
            // coerce any date type;
            // Date(), timestamp, valid date string
            const cellValue = getNestedProperty(row, colName);
            const coerceDate = new Date(cellValue);
            if (!!coerceDate && Number(coerceDate)) {
                // Store as a flattened property (e.g., row["user.birthDate"])
                // getNestedProperty will check this first before traversing nested structure
                row[colName] = Number(coerceDate);
            }
        });
        row['__index'] = i;
        row['__uuid'] = math.cyrb53(cols.map((c) => getNestedProperty(row, c.prop)).join());
        return row;
    });
    store.data.state.rows = rows;
    if (store.general.state.workerId)
        return syncDataToWorker(store.general.state.workerId, rows);
}
function storeSetConfig(host, columns) {
    const store = stores.get(host);
    if (!store)
        return;
    store.config.state.columns = columns;
    if (store.general.state.workerId)
        return syncConfigToWorker(store.general.state.workerId, colsToWorker(columns));
}
async function storeSearch(host, term) {
    const store = stores.get(host);
    if (!store || !store.general.state.workerId)
        return;
    try {
        store.data.state.rows = await workerSearch(store.general.state.workerId, term);
    }
    catch (e) {
        console.warn(e);
    }
}
async function storeFilter(host, filters) {
    const store = stores.get(host);
    if (!store || !store.general.state.workerId)
        return;
    try {
        store.data.state.rows = await workerFilter(store.general.state.workerId, filters);
    }
    catch (e) {
        console.warn(e);
    }
}
async function storeSort(host, prop, order) {
    const store = stores.get(host);
    if (!store || !store.general.state.workerId)
        return;
    try {
        store.data.state.rows = await workerSort(store.general.state.workerId, prop, order);
    }
    catch (e) {
        console.warn(e);
    }
}

// Separate file (so it's safe to import into the worker)
/**
 * Accesses a nested property using dot notation.
 * @param obj - the object to access
 * @param path - the property path (e.g., 'props.nested.name')
 * @returns the value at the path, or undefined if not found
 */
function getNestedProperty(obj, path) {
    if (!obj || path === null || path === undefined)
        return undefined;
    // Convert to string for consistent handling
    const pathStr = String(path);
    // Check for a flattened property first (optimization for transformed values)
    // e.g., obj["user.birthDate"] takes precedence over obj.user.birthDate
    if (obj[pathStr] !== undefined) {
        return obj[pathStr];
    }
    // If the path doesn't contain a dot, just return the direct property
    if (!pathStr.includes('.')) {
        return obj[path];
    }
    // Split the path and traverse the object
    const keys = pathStr.split('.');
    let result = obj;
    for (const key of keys) {
        if (result === null || result === undefined) {
            return undefined;
        }
        result = result[key];
    }
    return result;
}

/**
 * Get a model object for custom cell / property renderers.
 * @param rowIndex - the current row index being rendered
 * @param colIndex - the current column index being rendered
 * @returns a model object which will be passed to custom renderers
 */
function colDataModel(rowIndex, colIndex) {
    const store = fetchStores();
    const columns = store.config.state.columns;
    const rows = store.data.state.rows;
    const column = columns[colIndex];
    const prop = column?.prop;
    const rowModel = rows[rowIndex];
    const cellModel = rowModel
        ? getNestedProperty(rowModel, columns[colIndex].prop)
        : '';
    return {
        prop,
        cellModel,
        column,
        rowIndex,
        rowModel,
    };
}
/**
 * Get a model object for custom row renderers.
 * @param rowIndex
 * @returns a model object passed to custom row renderers
 */
function rowDataModel(rowIndex) {
    const store = fetchStores();
    const rows = store.data.state.rows;
    const rowModel = rows[rowIndex];
    return {
        rowModel,
        rowIndex,
    };
}
/**
 * Merges 2 objects of properties together
 * @param current - property object
 * @param extra - additional object property
 * @returns - merged properties that can be applied to a node
 */
function mergeProperties(current, extra) {
    if (!extra)
        return current;
    // top level merge
    const props = { ...extra, ...current };
    // deeper merge
    // merge classes maps or strings
    if (extra.class) {
        if (typeof extra.class === 'object' && typeof props.class === 'object') {
            props.class = { ...extra.class, ...props.class };
        }
        else if (typeof extra.class === 'string' &&
            typeof props.class === 'object') {
            props.class[extra.class] = true;
        }
        else if (typeof props.class === 'string' &&
            typeof extra.class === 'string') {
            props.class += ' ' + extra.class;
        }
    }
    // merge style
    if (extra.style) {
        props.style = { ...extra.style, ...props.style };
    }
    return props;
}
/**
 * Returns the current nano-data-table's stores.
 * @returns the current nano-data-table stores
 */
function fetchStores() {
    return getStore(index.getElement(index.getRenderingRef()));
}
/**
 * Merges any defined cell properties with properties
 * required by `nano-data-table` functionality
 * @param rowIndex - the current row index being rendered
 * @param colIndex = the current column index being rendered
 * @param defaultProps - default properties required by `nano-data-table`
 * @returns - the merged properties that will be applied to a node
 */
function mergeCellProperties(rowIndex, colIndex, defaultProps) {
    const props = { ...defaultProps };
    const extraPropsFunc = fetchStores().config.state.columns[colIndex]?.cellProperties;
    if (!extraPropsFunc)
        return props;
    const data = colDataModel(rowIndex, colIndex);
    const extra = extraPropsFunc(data);
    if (!extra)
        return props;
    return mergeProperties(props, extra);
}
/**
 * Renders a table header (within a thead) using a custom template if set.
 * @param col - the current column config object
 * @returns - a JSX node
 */
function colheadFootRender(col) {
    const tpl = col?.columnTemplate;
    return tpl ? tpl(index.h, col) : (index.h(index.Fragment, null, col.title));
}
function headerPinClasses(type, vPinned, toString = false) {
    const classes = {
        [`${CSSNAMESPACE}__${type}`]: true,
        [`${CSSNAMESPACE}__pin`]: !!vPinned,
        [`${CSSNAMESPACE}__pin--top`]: vPinned === 'top',
        [`${CSSNAMESPACE}__pin--bottom`]: vPinned === 'bottom',
    };
    if (toString)
        return classListToStr(classes);
    return classes;
}
/**
 * Turns a class map {'string': boolean} to class string
 * @param classes - the class map to convert
 * @returns a class string
 */
function classListToStr(classes) {
    let classString = '';
    Object.entries(classes).forEach(([className, on]) => {
        if (on)
            classString += className + ' ';
    });
    return classString;
}

const TableColHead = ({ column, onColumnSortClick, defaults, }) => {
    if (column.hidden) {
        return renderer.h(index.Fragment, null);
    }
    // Sort handling
    function handleColumnSortClick(e) {
        let order;
        switch (column.order) {
            case 'asc':
                order = 'desc';
                break;
            case 'desc':
                order = null;
                break;
            default:
                order = 'asc';
        }
        onColumnSortClick(order, column.prop, e.target.closest('th'));
    }
    function isSortable() {
        return ((!!defaults.sortable && column.sortable !== false) ||
            (!defaults.sortable && column.sortable === true));
    }
    let extraProps = {};
    if (column.columnProperties) {
        extraProps = column.columnProperties(column) || extraProps;
    }
    const baseProps = {
        class: {
            ...headerPinClasses('th', column.pinned),
            [`${CSSNAMESPACE}__pin--start`]: column.pinned === 'start',
            [`${CSSNAMESPACE}__pin--end`]: column.pinned === 'end',
            [`${CSSNAMESPACE}__ordered`]: !!column.order,
            [`${CSSNAMESPACE}__filtered`]: column.filter !== undefined && column.filter !== null,
        },
    };
    let props = extraProps ? mergeProperties(baseProps, extraProps) : baseProps;
    const content = colheadFootRender(column);
    if (!content)
        return renderer.h(index.Fragment, null);
    props =
        Number(props.colSpan) > 1
            ? { ...props, scope: 'colgroup' }
            : { ...props, scope: 'col' };
    if (isSortable()) {
        const sort = column.order
            ? column.order === 'asc'
                ? 'ascending'
                : 'descending'
            : 'none';
        props = { ...props, 'aria-sort': sort };
    }
    return (renderer.h("th", { ...props, key: column.prop }, isSortable() ? (renderer.h("button", { class: {
            [`${CSSNAMESPACE}__order-btn`]: true,
            [`${CSSNAMESPACE}__cell-content`]: true,
        }, onClick: handleColumnSortClick },
        colheadFootRender(column),
        column.filter !== undefined && column.filter !== null && (renderer.h("nano-icon", { name: "light/filter" })),
        !!column.order &&
            (column.order === 'desc' ? (renderer.h("nano-icon", { name: "solid/arrow-down-long" })) : (renderer.h("nano-icon", { name: "solid/arrow-up-long" }))),
        renderer.h("div", { class: `${CSSNAMESPACE}__status-icons` },
            renderer.h("nano-icon", { name: "light/chevron-down" })))) : (renderer.h("div", { class: `${CSSNAMESPACE}__cell-content` },
        colheadFootRender(column),
        column.filter !== undefined && column.filter !== null && (renderer.h("nano-icon", { name: "light/bars-filter" }))))));
};

// TABLE CELL
// (tbody > tr > td, tbody > tr > th)
/**
 * Renders a cell using a custom renderer if set.
 * @param rowIndex - the current row index being rendered
 * @param colIndex - the current column index being rendered
 * @returns - a JSX node
 */
function cellRender(rowIndex, colIndex) {
    const store = fetchStores();
    const columns = store.config.state.columns;
    const tpl = columns[colIndex]?.cellTemplate;
    const model = colDataModel(rowIndex, colIndex);
    const tableInstance = index.getRenderingRef();
    if (!!model.cellModel && columns[colIndex].type === 'date') {
        const d = new Date(model.cellModel);
        if (d instanceof Date && !isNaN(d)) {
            model.cellModel = !tpl
                ? `${new Date(model.cellModel).toLocaleDateString()} ${new Date(model.cellModel).toLocaleTimeString()}`
                : d;
        }
    }
    // Wrap the h pragma, then we can know if we're using Stencil's
    // jsx renderer or a different one
    let hCalled = false;
    const hWrap = (...args) => {
        hCalled = true;
        // @ts-expect-error - h mismatch
        return renderer.h(...args);
    };
    let tplResult = tpl ? tpl(hWrap, model) : undefined;
    if (tplResult &&
        tableInstance.customRenderer &&
        tplResult['$attrs$'] === undefined &&
        !(tplResult instanceof Element) &&
        typeof tplResult !== 'string' &&
        !hCalled) {
        // template result is jsx *not* from Stencil.
        // Render it now and output it later.
        const templateEle = document.createElement('template');
        const result = tableInstance.customRenderer(tplResult, templateEle.content);
        // custom rendering can render to the templateEle OR return a promise<string>
        tplResult = result && result['then'] ? result : templateEle;
    }
    return tplResult ? tplResult : model.cellModel !== undefined && model.cellModel !== null ? (renderer.h(index.Fragment, null, model.cellModel?.toString())) : ('');
}
const baseCellClasses = (colIndex, toString = false) => {
    const store = fetchStores();
    const column = store.config.state.columns[colIndex];
    const classes = {
        [`${CSSNAMESPACE}__td`]: true,
        [`${CSSNAMESPACE}__ordered`]: !!column.order,
        [`${CSSNAMESPACE}__pin`]: !!column.pinned,
        [`${CSSNAMESPACE}__pin--start`]: column.pinned === 'start',
        [`${CSSNAMESPACE}__pin--end`]: column.pinned === 'end',
    };
    if (toString)
        return classListToStr(classes);
    return classes;
};
const TableCell = ({ rowIndex, colIndex, nestedContent, }) => {
    const Content = () => nestedContent
        ? nestedContent()
        : cellRender(rowIndex, colIndex) || (renderer.h("span", { class: "placeholder" }, "\u00A0"));
    let CellType = 'td';
    const store = fetchStores();
    const column = store.config.state.columns[colIndex];
    if (column.hidden) {
        return renderer.h(index.Fragment, null);
    }
    let props = mergeCellProperties(rowIndex, colIndex, {
        class: baseCellClasses(colIndex),
    });
    if (column.rowHeader) {
        props =
            Number(props.rowSpan) > 1
                ? { ...props, scope: 'rowgroup' }
                : { ...props, scope: 'row' };
        CellType = 'th';
    }
    const ContentWrap = (props) => {
        const content = Content();
        return (renderer.h("div", { ref: (d) => {
                if (!d)
                    return;
                if (content instanceof Element) {
                    // custom rendered an element
                    d.replaceChildren();
                    d.append(content['content'] || content);
                }
                else if (content['then']) {
                    // custom rendered a promise. Most likely within SSR.
                    content.then((res) => (d.innerHTML = res));
                }
            }, ...props, class: {
                [`${CSSNAMESPACE}__cell-content`]: true,
                [`${CSSNAMESPACE}__cell-content--wrap`]: !!column.wrap,
            }, innerHTML: typeof content === 'string' && content.includes('<')
                ? content
                : undefined }, (typeof content !== 'string' || !content.includes('<')) &&
            !content['then'] &&
            !(content instanceof Element) &&
            content));
    };
    return (renderer.h(CellType, { ...props }, column.autoTooltip && !column.wrap ? (renderer.h("nano-resize-observe", { notifyContentFit: "x", onNanoResizeContentFitChange: (e) => (e.target.firstElementChild.disabled =
            e.detail.x) },
        renderer.h("nano-tooltip", { disabled: true, placement: "top", onNanoShow: (e) => (e.target.closest(CellType).style.zIndex = '100'), onNanoHide: (e) => (e.target.closest(CellType).style.zIndex = '') },
            renderer.h(ContentWrap, null),
            renderer.h("span", { slot: "content" },
                renderer.h(Content, null))))) : (renderer.h(ContentWrap, null))));
};

const tableCellContent = (props, children, ctx) => {
    const cell = (renderer.h("div", { ...props.wrapperProps, class: {
            [`${CSSNAMESPACE}__cell-content`]: true,
            [`${CSSNAMESPACE}__cell-content--wrap`]: props.wrap,
        } }, children));
    return props.header ? (renderer.h("th", { scope: ctx, ...props.cellProps }, cell)) : (renderer.h("td", { ...props.cellProps }, cell));
};
const TableRow = ({ rowRenderer, rowIndex, rowModel }, children, utils) => {
    // helper, generates <td> or <th>
    const TableCell = ({ header, wrap, cellProps, wrapperProps }, children) => {
        return tableCellContent({ header, wrap, cellProps, wrapperProps }, children, 'row');
    };
    let extraProps = {};
    if (!rowModel) {
        const model = rowDataModel(rowIndex);
        rowModel = model.rowModel;
    }
    if (rowRenderer?.rowProperties) {
        extraProps =
            rowRenderer.rowProperties({ rowModel, rowIndex }) || extraProps;
    }
    let rowPinned;
    if (rowRenderer?.pinned && typeof rowRenderer.pinned === 'function') {
        rowPinned = rowRenderer.pinned({ rowModel, rowIndex });
    }
    const props = mergeProperties({ class: headerPinClasses('tr', rowPinned, true) }, extraProps);
    const tpl = rowRenderer?.template;
    /**
     * Applies appropriate classes to td / th VNodes;
     * which can be supplied by user defined templates
     * @param children virtual / jsx node array
     * @returns virtual / jsx node array
     */
    const applyCellClasses = (children) => {
        return utils.map(children, (cNode, i) => {
            if (['td', 'th'].includes(cNode.vtag.toString())) {
                cNode.vattrs = mergeProperties({
                    class: baseCellClasses(i, true),
                }, cNode.vattrs);
            }
            return cNode;
        });
    };
    if (tpl) {
        let toRender = tpl(renderer.h, {
            renderedRow: (renderer.h("tr", { ...props, key: rowModel.__uuid }, children)),
            rowModel,
            rowIndex,
        }, TableCell);
        if (Array.isArray(toRender)) {
            toRender = utils.map(toRender, (node, i) => {
                if (node.vtag === 'tr') {
                    if (!node.vkey)
                        node.vkey = `${rowModel.__uuid}_${i}`;
                    node.vattrs = mergeProperties({ class: headerPinClasses('tr', rowPinned, true) }, node.vattrs);
                    if (node.vchildren) {
                        node.vchildren = applyCellClasses(node.vchildren);
                    }
                }
                return node;
            });
        }
        return toRender;
    }
    return (renderer.h("tr", { ...props, key: rowModel.__uuid }, applyCellClasses(children)));
};
const TableHeadFootRow = ({ rowRenderer }, // onRowPinned, onColPinned
children, utils) => {
    let extraProps = {};
    if (rowRenderer.rowProperties) {
        extraProps = rowRenderer.rowProperties() || {};
    }
    const TableHeadFootCell = ({ header, wrap, cellProps, wrapperProps }, children) => {
        return tableCellContent({ header, wrap, cellProps, wrapperProps }, children, 'col');
    };
    const pinned = rowRenderer.pinned || null;
    const baseProps = { class: headerPinClasses('tr', pinned) };
    const props = extraProps ? mergeProperties(baseProps, extraProps) : baseProps;
    const tpl = rowRenderer?.template;
    if (tpl) {
        let toRender = tpl(renderer.h, {
            renderedRow: renderer.h("tr", { ...props }, children),
        }, TableHeadFootCell);
        if (Array.isArray(toRender)) {
            toRender = utils.map(toRender, (node) => {
                if (node.vtag === 'tr') {
                    node.vattrs = mergeProperties({ class: headerPinClasses('tr', pinned, true) }, node.vattrs);
                    if (node.vchildren) {
                        node.vchildren = utils.map(node.vchildren, (cNode) => {
                            if (['td', 'th'].includes(cNode.vtag.toString())) {
                                cNode.vattrs = mergeProperties({
                                    class: headerPinClasses(cNode.vtag.toString(), pinned, true),
                                }, cNode.vattrs);
                            }
                            return cNode;
                        });
                    }
                }
                return node;
            });
        }
        return toRender;
    }
    return renderer.h("tr", { ...props }, children);
};

/**
 * Manages the complex business of table 'pinning'; sticking columns and rows to the scrolling parent element.
 *
 * *knowing* when an element is pinned is tricky.
 * Managing the display of multiple, side-by-side pinned elements is trickier still.
 *
 * Pinning table columns are very different from pinning table rows:
 * - Rows are actual elements we can select, measure and apply styles to.
 * - Columns are disparate collections of elements that are much harder to select, measure and apply styles to.
 *
 * With this in mind, how columns and rows are pinned is different:
 * Rows can have changes applied directly, Columns have changes applied via dynamic stylesheets.
 *
 * The service is slightly opinionated on how it pins rows and columns (with some room for override):
 * - Pinned columns are stuck consecutively, without overlapping.
 * e.g. If column 'name' and 'surname' are both `pin: 'start'`; 'surname' will display **next** to name.
 * Both columns are important for context
 *
 * - Pinned rows are set to overlap *when* they have the same ancestor,
 * and stuck consecutively when they have a different ancestor.
 * e.g. `tbody > tr.pin ~ tr.pin` the second row will **overlap** the first; it's unlikely both rows will be important for context.
 * `thead > tr.pin`, `tbody > tr.pin`. Both rows are required for context so will require next to each other.
 *
 * Devs can override this behaviour by setting `--pin-start`, `--pin-end`, `--pin-top`, `--pin-bottom` custom vars.
 */
class TablePinService {
    // Private state
    cachedColMeta = new WeakMap();
    tableId;
    tableEle;
    scrollElement;
    startColumns;
    endColumns;
    topRows;
    bottomRows;
    tableDims;
    constructor(table, scrollElement) {
        this.tableEle = table;
        this.tableId = this.tableEle.id;
        this.scrollElement = scrollElement;
        // Secret sauce - `getElementsByClassName` is a live collection.
        // An HTMLCollection will keep itself up-to-date as elements come and go
        // So we can keep assessing on scroll
        this.startColumns = table
            .querySelector('thead')
            .getElementsByClassName(`${CSSNAMESPACE}__pin--start`);
        this.endColumns = table
            .querySelector('thead')
            .getElementsByClassName(`${CSSNAMESPACE}__pin--end`);
        this.topRows = table.getElementsByClassName(`${CSSNAMESPACE}__pin--top`);
        this.bottomRows = table.getElementsByClassName(`${CSSNAMESPACE}__pin--bottom`);
        this.onResize();
    }
    // Pinned cols & change detection
    get pinnedStart() {
        return this._pinnedStart;
    }
    set pinnedStart(cols) {
        this._pinnedStart = cols;
        this.handlePinnedStartChange();
    }
    _pinnedStart = [];
    addStyleSheet(id, css) {
        const root = this.tableEle.getRootNode();
        const styleSheet = root.getElementById(id) ||
            document.createElement('style');
        styleSheet.id = id;
        styleSheet.innerHTML = css;
        if (!styleSheet.isConnected) {
            if (root.head) {
                root.head.append(styleSheet);
            }
            else {
                root.append(styleSheet);
            }
        }
    }
    /**
     * Called when columns are either pinned or unpinned.
     * Attaches a tiny stylesheet to target the 'last' start column.
     * (e.g. We only want to apply drop shadow on last pinned start column - not all pinned columns)
     */
    handlePinnedStartChange() {
        index.writeTask(() => {
            if (this.pinnedStart.length) {
                this.tableEle.classList.add(`${CSSNAMESPACE}__pinned--start`);
                const lastActiveCol = this.cachedColMeta.get(this.pinnedStart[this.pinnedStart.length - 1]);
                this.addStyleSheet(`${this.tableId}-col-start-active-style`, 
                /* css */ `
          #${this.tableId} tr > :nth-child(${lastActiveCol.idx + 1}) {
            --pin-start-active: 1;
          }
        `);
            }
            else {
                this.tableEle.classList.remove(`${CSSNAMESPACE}__pinned--start`);
                this.addStyleSheet(`${this.tableId}-col-start-active-style`, ``);
            }
        });
    }
    get pinnedEnd() {
        return this._pinnedEnd;
    }
    set pinnedEnd(cols) {
        this._pinnedEnd = cols;
        this.handlePinnedEndChange();
    }
    _pinnedEnd = [];
    /**
     * Called when columns are either pinned or unpinned.
     * Attaches a tiny stylesheet to target the 'first' end column.
     * (e.g. We only want to apply drop shadow on first pinned end column - not all pinned columns)
     */
    handlePinnedEndChange() {
        index.writeTask(() => {
            if (this.pinnedEnd.length) {
                this.tableEle.classList.add(`${CSSNAMESPACE}__pinned--end`);
                const firstActiveCol = this.cachedColMeta.get(this.pinnedEnd[0]);
                this.addStyleSheet(`${this.tableId}-col-end-active-style`, 
                /* css */ `
          #${this.tableId} tr > :nth-child(${firstActiveCol.idx + 1}) { --pin-end-active: 1; }
        `);
            }
            else {
                this.tableEle.classList.remove(`${CSSNAMESPACE}__pinned--end`);
                this.addStyleSheet(`${this.tableId}-col-end-active-style`, ``);
            }
        });
    }
    get cssColDimensionCacheKey() {
        return this._cssColDimensionCacheKey;
    }
    set cssColDimensionCacheKey(key) {
        if (key === this._cssColDimensionCacheKey)
            return;
        this._cssColDimensionCacheKey = key;
        this.createPinnedColDimensionStyles();
    }
    _cssColDimensionCacheKey = '';
    /**
     * To only generate column dimension styles when necessary we
     * maintain a cache key string via serialised column meta.
     * Only when this key changes do we generate a new stylesheet
     */
    generateCssCacheKey() {
        let key = '';
        for (const col of this.startColumns) {
            const colMeta = this.cachedColMeta.get(col);
            key += `${colMeta.idx}-start-${colMeta.width}`;
        }
        for (const col of this.endColumns) {
            const colMeta = this.cachedColMeta.get(col);
            key += `${colMeta.idx}-start-${colMeta.width}`;
        }
        this.cssColDimensionCacheKey = key;
    }
    /**
     * Generates pinned column width offset styles
     * so pinned columns can appear stuck together,
     * then attaches a stylesheet.
     */
    createPinnedColDimensionStyles() {
        let widthS = 0;
        let widthE = 0;
        const startCols = Array.from(this.startColumns);
        const endCols = Array.from(this.endColumns).reverse();
        const css = /* css */ `
      ${startCols
            .map((col) => {
            const colMeta = this.cachedColMeta.get(col);
            widthS += colMeta.width - 1 || -1;
            return /* css */ `
            #${this.tableId} tr > :nth-child(${colMeta.idx + 1}) ~ td,
            #${this.tableId} tr > :nth-child(${colMeta.idx + 1}) ~ th {
              --pin-start: ${widthS - 1}px;
            }
          `;
        })
            .join('')}
      ${endCols
            .map((col) => {
            const colMeta = this.cachedColMeta.get(col);
            widthE += colMeta.width - 1 || -1;
            return /* css */ `
            #${this.tableId} tr > td:has(~ :nth-child(${colMeta.idx + 1})),
            #${this.tableId} tr > th:has(~ :nth-child(${colMeta.idx + 1})) {
              --pin-end: ${widthE - 1}px;
            }
          `;
        })
            .join('')}
    `;
        this.addStyleSheet(`${this.tableId}-dimension-style`, css);
    }
    getParentOffsets() {
        const { x, y } = this.scrollElement.getBoundingClientRect();
        let offsetX = x;
        let offsetY = y;
        if (this.scrollElement === document.documentElement) {
            offsetX = this.scrollElement.offsetLeft;
            offsetY = this.scrollElement.offsetTop;
        }
        return { offsetX, offsetY };
    }
    /**
     * Loops through all 'top' & 'bottom' rows (on scroll or resize)
     * Manages their visual state by applying classes on stuck / unstuck
     * And their pinned offset / distance
     */
    assessRows() {
        if (!this.topRows.length && !this.bottomRows.length)
            return;
        // top rows
        if (this.topRows.length) {
            index.readTask(async () => {
                let heightAggregate = 0;
                let cacheParent;
                const { offsetY } = this.getParentOffsets();
                for (const topRow of this.topRows) {
                    const { y, height } = topRow.getBoundingClientRect();
                    const currParent = topRow.parentElement;
                    // we'll use the applied `--pin-top` css var to decide row offset.
                    // This allows devs to override this behaviour through selector specificity
                    const pinTop = getComputedStyle(topRow).getPropertyValue('--pin-top');
                    const offset = pinTop !== '' ? parseFloat(pinTop) : heightAggregate;
                    // we need to wait for the row to finish sticking
                    // and generating it's offset (`--pin-top`) so we can see, on
                    // subsequent rows *if* the offset was applied
                    index.writeTask(() => {
                        if (y - offsetY <= offset) {
                            topRow.classList.add(`${CSSNAMESPACE}__pinned`, `${CSSNAMESPACE}__pinned--top`);
                        }
                        else {
                            topRow.classList.remove(`${CSSNAMESPACE}__pinned`, `${CSSNAMESPACE}__pinned--top`);
                        }
                        // by default, we only want to stick one row from each parental block (thead, tbody, tfoot)
                        // so only aggregate height / offset when cacheParent is different from current parent.
                        // Devs can override this behaviour by manually setting `--pin-bottom` on the table row
                        if (cacheParent !== currParent) {
                            currParent.style.setProperty('--pin-top', `${heightAggregate - 1}px`);
                            heightAggregate += height - 1;
                            cacheParent = currParent;
                        }
                    });
                }
            });
        }
        // bottom rows
        if (this.bottomRows.length) {
            const bottomRows = Array.from(this.bottomRows).reverse();
            index.readTask(async () => {
                let cacheParent;
                let heightAggregate = 0;
                const { offsetY } = this.getParentOffsets();
                for (const bottomRow of bottomRows) {
                    if (!bottomRow.isConnected)
                        continue;
                    const { y, height } = bottomRow.getBoundingClientRect();
                    const currParent = bottomRow.parentElement;
                    // we'll use the applied `--pin-bottom` css var to decide row offset.
                    // This allows devs to override this behaviour through selector specificity
                    const pinBottom = getComputedStyle(bottomRow).getPropertyValue('--pin-bottom');
                    const offset = pinBottom !== '' ? parseFloat(pinBottom) : heightAggregate;
                    // we need to wait for the row to finish sticking
                    // and generating it's offset (`--pin-bottom`) to we can see, on
                    // subsequent rows *if* the offset was applied
                    await new Promise((resolve) => index.writeTask(() => {
                        if (this.tableDims.height + offsetY - (y + height) <= offset) {
                            bottomRow.classList.add(`${CSSNAMESPACE}__pinned`, `${CSSNAMESPACE}__pinned--bottom`);
                        }
                        else {
                            bottomRow.classList.remove(`${CSSNAMESPACE}__pinned`, `${CSSNAMESPACE}__pinned--bottom`);
                        }
                        // by default, we only want to stick one row from each parental block (thead, tbody, tfoot)
                        // so only aggregate height / offset when cacheParent is different from current parent.
                        // Devs can override this behaviour by manually setting `--pin-bottom` on the table row
                        if (cacheParent !== currParent) {
                            currParent.style.setProperty('--pin-bottom', `${heightAggregate - 1}px`);
                            heightAggregate += height - 1;
                            cacheParent = currParent;
                        }
                        resolve();
                    }));
                }
            });
        }
    }
    /**
     * Loops through all 'start' & 'end' columns (on scroll or resize)
     * Caches meta about each column (e.g. size, position-index)
     * and decides which columns are pinned
     */
    async assessCols() {
        if (!this.startColumns.length && !this.endColumns.length)
            return;
        let boundBox;
        let currPinned;
        let parentEles;
        const done = new Promise((resolve) => {
            // start cols
            if (this.startColumns.length) {
                index.readTask(() => {
                    const { offsetX } = this.getParentOffsets();
                    // cumulatively add widths of columns together
                    // 'cos columns stick together
                    let widthAggregate = offsetX;
                    parentEles = Array.from(this.startColumns[0].parentElement.children);
                    for (const startCol of this.startColumns) {
                        boundBox = startCol.getBoundingClientRect();
                        // cache meta for later
                        this.cachedColMeta.set(startCol, {
                            width: boundBox.width,
                            idx: parentEles.indexOf(startCol),
                        });
                        currPinned = this.pinnedStart.find((c) => c === startCol);
                        if (boundBox.x < widthAggregate) {
                            // this column is pinned
                            if (!currPinned)
                                this.pinnedStart = [
                                    ...this.pinnedStart,
                                    startCol,
                                ];
                        }
                        else if (currPinned) {
                            // this column is unpinned
                            this.pinnedStart = this.pinnedStart.filter((c) => c !== startCol);
                        }
                        widthAggregate += boundBox.width;
                    }
                    if (!this.endColumns.length)
                        resolve();
                });
            }
            // end cols
            if (this.endColumns.length) {
                index.readTask(() => {
                    const endCols = Array.from(this.endColumns).reverse();
                    parentEles = Array.from(this.endColumns[0].parentElement.children);
                    const { offsetX } = this.getParentOffsets();
                    // cumulatively add widths of columns together
                    // 'cos columns stick together
                    let widthAggregate = 0;
                    for (const endCol of endCols) {
                        boundBox = endCol.getBoundingClientRect();
                        // cache meta for later
                        this.cachedColMeta.set(endCol, {
                            width: boundBox.width,
                            idx: parentEles.indexOf(endCol),
                        });
                        currPinned = this.pinnedEnd.find((c) => c === endCol);
                        if (this.tableDims.width + offsetX - boundBox.right <=
                            widthAggregate) {
                            // this column is pinned
                            if (!currPinned)
                                this.pinnedEnd = [endCol, ...this.pinnedEnd];
                        }
                        else if (currPinned) {
                            // this column is unpinned
                            this.pinnedEnd = this.pinnedEnd.filter((c) => c !== endCol);
                        }
                        widthAggregate += boundBox.width;
                    }
                    resolve();
                });
            }
        });
        await done;
        // potentially generate a new css stylesheet if anything changed
        this.generateCssCacheKey();
    }
    cacheX = 0;
    cacheY = 0;
    onScroll(pos) {
        if (this.cacheX !== pos.x) {
            this.cacheX = pos.x;
            this.assessCols();
        }
        if (this.cacheY !== pos.y) {
            this.cacheY = pos.y;
            this.assessRows();
        }
    }
    onResize() {
        const width = this.scrollElement.clientWidth;
        const height = this.scrollElement.clientHeight;
        this.tableDims = { width, height };
        this.assessCols();
        this.assessRows();
    }
}

const tableCss = ":host,\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n[hidden] {\n  display: none !important;\n}\n@media (prefers-reduced-motion: reduce) {\n    :host,\n    *,\n    *::before,\n    *::after {\n      animation-duration: 0.01ms !important;\n      animation-iteration-count: 1 !important;\n      transition-duration: 0.01ms !important;\n      scroll-behavior: auto !important;\n    }\n  }:host,\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n[hidden] {\n  display: none !important;\n}\n@media (prefers-reduced-motion: reduce) {\n    :host,\n    *,\n    *::before,\n    *::after {\n      animation-duration: 0.01ms !important;\n      animation-iteration-count: 1 !important;\n      transition-duration: 0.01ms !important;\n      scroll-behavior: auto !important;\n    }\n  }.nano-data-table {\n  /**\n   * @prop --table-padding. The padding of the table cells. Defaults to `var(--table-compact-padding)`\n   * @prop --table-border-color. The border color of the table cells. Defaults to `var(--nano-color-neutral-200)`\n   * @prop --table-border-color-header. The border color of the table header and footer cells. Defaults to `var(--nano-color-primary-1000)`\n   * @prop --table-background. The background color of the table cells. Defaults to `var(--nano-color-base-0)`\n   * @prop --table-max-col-width. The maximum width of the table columns. Defaults to `clamp(200px, 500px, 50vw)`\n   * @prop --table-ordered-bg. The background color of the ordered table rows. Defaults to `var(--nano-color-neutral-100)`\n   * @prop --table-wrapper-max-height: maximum height of the table when table is scrollable. Defaults to 80vh;\n   * @prop --table-wrapper-max-width: maximum width of the table when table is scrollable. Defaults to 100%;\n   */\n  --table-padding: var(--nano-spacing-sm) calc(var(--nano-spacing-md) - (var(--nano-spacing-md) / 3));\n  --table-border-color: var(--nano-color-neutral-200);\n  --table-border-color-header: var(--nano-color-primary-1000);\n  --table-background: var(--nano-color-base-0);\n  --table-max-col-width: clamp(200px, 500px, 50vw);\n  --table-ordered-bg: var(--nano-color-neutral-100);\n  --table-wrapper-max-height: 80vh;\n  --table-wrapper-max-width: 100%;\n  display: block;\n  inline-size: 100%;\n  color: var(--nano-color-neutral-1400);\n  container-type: inline-size;\n  --isLtR: 1;\n}\n.nano-data-table:dir(rtl) {\n  --isLtR: -1;\n}\n.nano-data-table .nano-sortable__dragged {\n  background: var(--nano-color-base-0);\n  opacity: 0.9;\n  box-shadow: var(--nano-shadow-l0);\n  z-index: 10 !important;\n}\n\n.nano-tbl__wrap {\n  display: table;\n  min-inline-size: 100%;\n}\n.nano-data-table:not([scrollable=false]) .nano-tbl__wrap {\n  max-inline-size: var(--table-wrapper-max-width);\n  max-block-size: var(--table-wrapper-max-height);\n  position: relative;\n  display: block;\n}\n.nano-data-table:not([scrollable=false]) .nano-tbl__wrap table {\n  margin-block-end: var(--nano-spacing-l);\n}\n\n.nano-data-table:not([scrollable=false]) .nano-masked-overflow {\n  --fade-size: 30px;\n}\n.nano-data-table:not([scrollable=false]):has(.nano-tbl__pin--start, .nano-tbl__pin--end) .nano-masked-overflow {\n  --fade-size: 0;\n}\n\n.nano-tbl {\n  text-align: start;\n  inline-size: 100%;\n  border-spacing: 0 0;\n  border-collapse: separate;\n  border-inline-end: 1px solid transparent;\n  border-block-start: 1px solid transparent;\n  position: relative;\n  z-index: 1;\n}\n.nano-tbl__top-anchor {\n  clip-path: inset(50%);\n  block-size: 1px;\n  overflow: hidden;\n  position: absolute;\n  white-space: nowrap;\n  inline-size: 1px;\n  position: relative;\n}\n.nano-tbl__ordered {\n  background-color: var(--table-ordered-bg);\n  --table-border-color: var(--nano-color-neutral-300);\n}\n.nano-tbl__order-btn {\n  padding: 0;\n  border: none;\n  outline: none;\n  font: inherit;\n  background: none;\n  appearance: none;\n  color: inherit;\n  display: flex;\n  gap: var(--nano-spacing-sm);\n  align-items: center;\n  inline-size: 100%;\n}\n.nano-tbl__order-btn:focus-visible {\n  outline: var(--nano-focus-ring);\n  outline-offset: var(--nano-focus-ring-offset);\n}\n.nano-tbl__status-icons {\n  margin-inline: auto 10px;\n  display: flex;\n  gap: 10px;\n}\n.nano-tbl__progress-bar {\n  --height: 0.2125rem;\n  position: sticky;\n  inset-block-start: 0;\n  inset-inline: 0;\n  z-index: 10;\n  transition: transform 0.25s;\n  transform: scale(0);\n  inline-size: 100%;\n}\n.nano-tbl__progress-bar--show {\n  transform: scale(1);\n}\n.nano-tbl__caption--hide {\n  clip-path: inset(50%);\n  block-size: 1px;\n  overflow: hidden;\n  position: absolute;\n  white-space: nowrap;\n  inline-size: 1px;\n}\n.nano-tbl__td, .nano-tbl__th {\n  max-inline-size: var(--table-max-col-width);\n}\nthead .nano-tbl__td .nano-sortable__keyboard-handle, thead .nano-tbl__th .nano-sortable__keyboard-handle {\n  position: absolute;\n  inset-inline-end: 5px;\n  inset-block-start: 50%;\n  transform: translateY(-50%);\n  background: white;\n  z-index: 10;\n}\n.nano-tbl__td.nano-tbl__ordered, .nano-tbl__th.nano-tbl__ordered {\n  background-color: var(--table-ordered-bg) !important;\n  --table-border-color: var(--nano-color-neutral-300);\n}\n.nano-tbl__cell-content {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n.nano-tbl__cell-content--no-result {\n  padding-block: var(--nano-spacing-md);\n}\n.nano-tbl__cell-content--wrap {\n  white-space: normal;\n  overflow: visible;\n}\n.nano-tbl tbody {\n  opacity: 1;\n  transition: 0.1s ease opacity;\n}\n.nano-tbl tbody.nano-tbl__loading {\n  z-index: -1;\n  position: relative;\n}\n.nano-tbl tbody.nano-tbl__inactive {\n  opacity: 0;\n}\n.nano-tbl tbody .nano-tbl__tr:has(~ .nano-tbl__tr--placeholder) {\n  display: none;\n}\n.nano-tbl tbody .nano-tbl__tr--placeholder ~ .nano-tbl__tr {\n  display: none;\n}\n.nano-tbl th[scope=row] {\n  margin: 0;\n}\n.nano-tbl__tr {\n  --base-z: 0;\n}\n.nano-tbl__pin {\n  z-index: var(--z, var(--base-z, 0));\n}\n.nano-tbl__pin .nano-tbl__th, .nano-tbl__pin .nano-tbl__td {\n  z-index: var(--z, var(--base-z, 0));\n}\n.nano-tbl__pin--start {\n  position: sticky;\n  inset-inline-start: var(--pin-start, -1px);\n}\n.nano-tbl__pin--start::after {\n  content: \"\";\n  position: absolute;\n  inset: 0;\n  box-shadow: calc(4px * var(--isLtR)) 0 4px -3px rgb(var(--nano-color-base-rgb-1000)/25%);\n  opacity: var(--pin-start-active, 0);\n  z-index: -1;\n}\n.nano-tbl__pinned--start .nano-tbl__pin--start {\n  --z: calc(var(--base-z) + 3) !important;\n}\n.nano-tbl__pin--end {\n  position: sticky;\n  inset-inline-end: var(--pin-end, -1px);\n  max-inline-size: min(50vw, 200px);\n}\n@container (width <= 500px) {\n  .nano-tbl__pin--start ~ .nano-tbl__pin--end {\n    --z: 0 !important;\n    inset-inline-end: auto !important;\n  }\n  .nano-tbl__pin--start ~ .nano-tbl__pin--end::after {\n    display: none !important;\n  }\n}\n@media (width <= 500px) {\n  .nano-tbl__pin--start ~ .nano-tbl__pin--end {\n    --z: 0 !important;\n    inset-inline-end: auto !important;\n  }\n  .nano-tbl__pin--start ~ .nano-tbl__pin--end::after {\n    display: none !important;\n  }\n}\n.nano-tbl__pin--end::after {\n  display: block;\n  content: \"\";\n  position: absolute;\n  inset: 0;\n  box-shadow: calc(-4px * var(--isLtR)) 0 4px -3px rgb(var(--nano-color-base-rgb-1000)/25%);\n  opacity: var(--pin-end-active, 0);\n  z-index: -1;\n}\n.nano-tbl__pinned--end .nano-tbl__pin--end {\n  --z: calc(var(--base-z) + 2);\n}\n.nano-tbl__pin--top .nano-tbl__th, .nano-tbl__pin--top .nano-tbl__td {\n  position: sticky;\n  inset-block-start: var(--pin-top, -1px);\n}\n.nano-tbl__pin--top .nano-tbl__pin--end, .nano-tbl__pin--top .nano-tbl__pin--start {\n  --z: calc(var(--base-z) + 1);\n}\n.nano-tbl__pin--top.nano-tbl__pinned--top {\n  --base-z: 4;\n}\ntbody .nano-tbl__pin--top.nano-tbl__pinned--top .nano-tbl__th, tbody .nano-tbl__pin--top.nano-tbl__pinned--top .nano-tbl__td {\n  border-block-end: 1px solid var(--table-border-color-header);\n}\n.nano-tbl__pin--bottom .nano-tbl__th, .nano-tbl__pin--bottom .nano-tbl__td {\n  position: sticky;\n  inset-block-end: var(--pin-bottom, -1px);\n}\n.nano-tbl__pin--bottom .nano-tbl__pin--end, .nano-tbl__pin--bottom .nano-tbl__pin--start {\n  --z: calc(var(--base-z) + 1);\n}\n.nano-tbl__pin--bottom.nano-tbl__pinned--bottom {\n  --base-z: 5;\n}\n.nano-tbl__pin--bottom.nano-tbl__pinned--bottom:has(.nano-tbl__pin--bottom.nano-tbl__pinned--bottom) .nano-tbl__pin {\n  --base-z: 6;\n}\ntbody .nano-tbl__pin--bottom.nano-tbl__pinned--bottom .nano-tbl__th, tbody .nano-tbl__pin--bottom.nano-tbl__pinned--bottom .nano-tbl__td {\n  border-block-start: 1px solid var(--table-border-color-header);\n}\n.nano-tbl thead tr:last-of-type td,\n.nano-tbl thead tr:last-of-type th {\n  border-block-end: 2px solid var(--table-border-color-header);\n}\n.nano-tbl tfoot tr:first-of-type td,\n.nano-tbl tfoot tr:first-of-type th {\n  border-block-start: none;\n}\n.nano-tbl tfoot tr:last-of-type td,\n.nano-tbl tfoot tr:last-of-type th {\n  border-block-end: 2px solid var(--table-border-color-header);\n  border-block-start: 2px solid var(--table-border-color-header);\n}\n.nano-tbl .unlimited-width {\n  max-inline-size: none;\n}\n.nano-tbl__spinner {\n  font-size: 1.5rem;\n  transition: scale 0.25s;\n  scale: 0;\n  padding: 0.5rem;\n  position: absolute;\n  inset-block-end: 0;\n  inset-inline-start: calc(50% - 0.75rem);\n  z-index: 0;\n}\n.nano-tbl__spinner--show {\n  scale: 1;\n  position: sticky;\n}";

let id = 0;
const NanoDataTable = class {
    constructor(hostRef) {
        index.registerInstance(this, hostRef);
        this.nanoTblReady = index.createEvent(this, "nanoTblReady", 7);
        this.nanoTblBlockRendered = index.createEvent(this, "nanoTblBlockRendered", 7);
        this.nanoTblBeforeSort = index.createEvent(this, "nanoTblBeforeSort", 7);
        this.nanoTblAfterSort = index.createEvent(this, "nanoTblAfterSort", 7);
        this.nanoTblBeforeFilter = index.createEvent(this, "nanoTblBeforeFilter", 7);
        this.nanoTblAfterFilter = index.createEvent(this, "nanoTblAfterFilter", 7);
        this.nanoTblBeforeSearch = index.createEvent(this, "nanoTblBeforeSearch", 7);
        this.nanoTblAfterSearch = index.createEvent(this, "nanoTblAfterSearch", 7);
        this.nanoTblBeforeEdit = index.createEvent(this, "nanoTblBeforeEdit", 7);
        this.debounceSetLoading = throttle.debounce(this.debounceSetLoading.bind(this), 50);
    }
    // Public API
    // Props
    /** Function called whenever `col.cellTemplate` renders an unknown object.
     * The function should render a valid HTMLElement to the cell arg.
     * Example usage - render JSX from a 3rd party lib and append the result to the cell element. */
    customRenderer;
    /** If true, will render any nested table in a scrollable container (using `overflow: auto`) */
    scrollable = false;
    /** A descriptive title of the table.
     * You must use this or the `slot="caption"` for accessibility */
    caption;
    /** Show the caption on-screen */
    showCaption = false;
    /** Will show a loading state when set to true.
     * By default, will be shown automatically if `rows` is a promise waiting to resolve / or falsy
     * *or* when performing custom filtering or sorting.
     * *Note* when set manually, will overwrite any internal loading state.
     * Set to 'undefined' to revert to default behaviour. */
    loading = undefined;
    get _loading() {
        return this.loading !== undefined ? this.loading : this.internalLoading;
    }
    set _loading(l) {
        if (this.loading !== undefined)
            return;
        this.debounceSetLoading(l);
    }
    debounceSetLoading(l) {
        this.internalLoading = l;
    }
    internalLoading = true;
    /** When loading rows asynchronously render this number of placeholder rows */
    placeholderSize = 10;
    /** The data to place in the table */
    rows;
    handleRowsChange() {
        if (!this.rows) {
            this._loading = true;
            return;
        }
        this._loading = true;
        Promise.resolve(this.rows).then(async (rows) => {
            await storeSetData(this.host, rows);
            // reset everything
            if (!this.customFilterApplied) {
                // if we have a custom filter applied (which causes new rows),
                // we don't want to reset filters as this will cause a loop
                this.currentFilters = '';
            }
            else {
                this.customFilterApplied = false;
            }
            this.currentSort = '';
            await this.columnInit();
            if (!this.isReady)
                requestAnimationFrame(() => this.setInitialBlockDimension());
            this._loading = false;
            if (this.tablePinnedService)
                this.tablePinnedService.assessRows();
        });
    }
    /** The column config used to present the rows of data */
    columns = [];
    async handleColsChange() {
        await storeSetConfig(this.host, this.columns);
        if (this.isReady)
            this.columnInit();
    }
    /** Used for custom thead row rendering */
    headRender = { pinned: 'top' };
    /** Used for custom row rendering */
    rowRender;
    /** Used for custom tfoot row rendering */
    footRender = { pinned: 'bottom' };
    /** Whether to show a `<tfoot>` component */
    showFooter = false;
    /** To improve performance, data is split into blocks
     * which are shown / hidden and rendered as they become visible.
     * Use this to control the number of hits within a block -
     * A block should fill a scrolling viewport.
     * The less perBlock the better for performance */
    perBlock = 50;
    /** The number of total blocks currently rendered in the table. @readonly */
    get blocksLength() {
        return this.blocks.length;
    }
    /** Term to search for in the data */
    searchTerm;
    /** A custom filtering function. Should return a promise.
     * If the promise resolves as `true` the column UI will be updated.
     * If the promise resolves as falsy, the sort will be performed by the component.
     * A good use-case would be performing the filter on a server / via fetch.
     * Then on success, updating the table's data via the `rows` property */
    customFilterFn;
    /** A custom sorting function. Should return a promise.
     * If the promise resolves as `true` the column UI will be updated.
     * If the promise resolves as falsey, the sort will be performed by the component.
     * A good use-case would be performing the sort on a server / via fetch.
     * Then on success, updating the table's data via the `rows` property */
    customSortFn;
    /**
     * The default sortable option for all columns.
     * `true` will enable column sorting unless you set `sortable: false` on a column
     * `false` will disable column sorting unless you set `sortable: true` on a column
     */
    defaultSort = true;
    handleSearchTermChange() {
        this.searchStart();
    }
    /** If true, will show a spinner at the bottom of the table when loading */
    showPaginateSpinner = false;
    /** Use this to render the table with roughly the correct dimensions.
     * a use-case might be; fetch a small initial dataset to minimise load-time,
     * render the table with the correct dimensions, the table becomes interactive,
     * load the rest of the data
     */
    virtualTotalItems = 0;
    virtualTotalItemsChangeHandler() {
        this.setBlocks();
    }
    /** @readonly - shows the currently applied filters */
    get appliedFilters() {
        return this.filters;
    }
    /** Fired when the table has done it's first complete render */
    nanoTblReady;
    /** Fired whenever a block is activated by scrolling into view / becoming visible
     * This could be leveraged for infinite scrolling / to fetch more data.
     */
    nanoTblBlockRendered;
    /** Fired before a column is sorted.
     * `event.preventDefault()` to stop sorting.  */
    nanoTblBeforeSort;
    /** Fired after a column is sorted */
    nanoTblAfterSort;
    /** Fired before a column is filtered.
     * `event.preventDefault()` to stop filtering. */
    nanoTblBeforeFilter;
    /** Fired after a column is sorted */
    nanoTblAfterFilter;
    /** Fired before a general search.
     * `event.preventDefault()` to stop searching.  */
    nanoTblBeforeSearch;
    /** Fired after a general / cross column search */
    nanoTblAfterSearch;
    /** Fired before a general search.
     * `event.preventDefault()` to stop searching.  */
    nanoTblBeforeEdit;
    /** Remove any column sorts currently applied
     * @returns a promise which resolves when complete */
    async resetSorting() {
        const col = this.columns.find((c) => !!c.order);
        if (!col)
            return;
        return this.sortStart(null, col.prop);
    }
    /** Apply a sort on a column
     * @returns a promise which resolves when complete */
    async addSort(column, order) {
        const col = this.columns.find((c) => c.prop === column);
        if (!col)
            throw new Error('Cannot find column with ' + column);
        return this.sortStart(order, col.prop);
    }
    /** Remove any column filters currently applied
     * @returns a promise which resolves when complete */
    async resetFilters() {
        this.filters = [];
        return this.filterStart();
    }
    /** Apply a filter on a column
     * @param filters - the filters to apply
     * @param additive - if true, will add the filters to any currently applied
     * @returns a promise which resolves when complete
     */
    async addFilters(filters, additive = true) {
        if (!additive)
            this.filters = [];
        return this.filterStart(filters, additive);
    }
    /** Remove filters from a column
     * @param columnNames - the filters to apply
     * @returns a promise which resolves when complete
     */
    async removeFilters(columnNames) {
        this.filters = this.filters.filter((f) => !columnNames.includes(f.prop));
        return this.filterStart();
    }
    /** Updates a row model at a given index
     * @param row - the row to update.
     * @param rowIndex - the row index to insert this row
     */
    async updateRow(row, rowIndex) {
        this.rows.splice(rowIndex, 1, row);
        this.handleRowsChange();
    }
    get host() { return index.getElement(this); }
    renderId = 'tbl-' + id++;
    store;
    filters = [];
    currentFilters = '[]';
    currentSort = '';
    tableEle;
    topAnchorEle;
    activeWatcherIo;
    tablePinnedService;
    customFilterApplied = false;
    // Block view management
    /** `tr` elements split into units - defined by `perBlock`
      These are show / hidden for perf */
    blocks = [];
    activeBlocks = [0, 1];
    measureHeight = 0;
    blockElements;
    blockHeights = [];
    // uses the first 'tr' of an active block as our yard stick
    set measureEle(el) {
        if (!el)
            return;
        const potentialHeight = el.getBoundingClientRect().height;
        this.measureHeight =
            Math.abs(this.measureHeight - potentialHeight) < 5
                ? this.measureHeight
                : potentialHeight;
        this.unitHeight =
            el.querySelector('tr')?.getBoundingClientRect().height || this.unitHeight;
    }
    unitHeight = 0;
    get scrollParent() {
        return this._scrollParent;
    }
    set scrollParent(ele) {
        if (ele === this._scrollParent)
            return;
        if (this._scrollParent) {
            (this._scrollParent === document.documentElement
                ? document
                : this._scrollParent).removeEventListener('scroll', this.scrollHandler);
        }
        (ele === document.documentElement ? document : ele).addEventListener('scroll', this.scrollHandler);
        this._scrollParent = ele;
    }
    _scrollParent;
    // used to fire `nanoTblBlockRendered` on block render change
    get primaryBlockIndex() {
        return this._primaryBlockIndex;
    }
    set primaryBlockIndex(blockIndex) {
        if (this._primaryBlockIndex === blockIndex)
            return;
        this._primaryBlockIndex = blockIndex;
        // fire block change event
        this.nanoTblBlockRendered.emit({
            block: blockIndex,
            totalBlocks: this.blockElements.length,
        });
    }
    _primaryBlockIndex;
    // Misc.
    get isReady() {
        return this._isReady;
    }
    set isReady(ready) {
        if (ready === this._isReady)
            return;
        this._isReady = ready;
        if (this.isReady)
            requestAnimationFrame(() => this.handleReady());
    }
    _isReady = false;
    // Private Logic
    // Listeners
    handleReady() {
        const hash = window.location.hash;
        if (hash.length > 1) {
            try {
                const idRow = document.querySelector(hash);
                if (idRow)
                    idRow.scrollIntoView();
                // eslint-disable-next-line no-empty
            }
            catch (_e) { }
        }
        this.nanoTblReady.emit();
    }
    /**
     * Start a sort - can be cancelled by `preventDefault`
     * @param order - column order
     * @param column - column config object
     * @returns A promise
     */
    sortStart = async (order, column, element) => {
        // did order change?
        if (this.currentSort === order + ':' + column)
            return;
        this._loading = true;
        const sortEvent = this.nanoTblBeforeSort.emit({ column: column, order });
        if (sortEvent.defaultPrevented)
            return;
        this.currentSort = order + ':' + column;
        // doesn't make sense to leave user in place for a sort
        this.scrollToTop(element);
        if (this.customSortFn) {
            try {
                const res = await this.customSortFn(column, order);
                // if the response is 'true', the custom sort did it's thing
                // handover to finish and stop loading state.
                // if response is falsey, carry on to do a FE sort
                if (res === true) {
                    this.sortComplete(order, column);
                    this._loading = false;
                    return;
                }
            }
            catch (e) {
                // if response errored, stop loading state
                // clear current sort cache
                console.warn('custom sort failed', e);
                this.currentSort = '';
                this._loading = false;
                return;
            }
        }
        try {
            await storeSort(this.host, column, order);
            this.sortComplete(order, column);
        }
        catch (e) {
            console.warn('sort failed', e);
            this.currentSort = '';
        }
        finally {
            if (this.blocks.length)
                this._loading = false;
        }
    };
    /**
     * Complete a sort. Reflects the order to ui.
     * @param order - column order
     * @param column - column config object
     */
    sortComplete(order, column) {
        this.columns = this.columns.map((c) => {
            if (c.prop === column)
                return { ...c, order };
            return { ...c, order: null };
        });
        this.nanoTblAfterSort.emit({ column: column, order });
    }
    async searchStart() {
        this._loading = true;
        const sortEvent = this.nanoTblBeforeSearch.emit({ term: this.searchTerm });
        if (sortEvent.defaultPrevented)
            return;
        // doesn't make sense to leave user in place for a search
        this.scrollToTop();
        try {
            await storeSearch(this.host, this.searchTerm);
            this.nanoTblAfterSearch.emit({ term: this.searchTerm });
        }
        catch (e) {
            console.warn('search failed', e);
        }
        finally {
            this._loading = false;
        }
    }
    async filterStart(filters, additive = true) {
        if (filters) {
            if (additive) {
                this.filters = [
                    ...this.filters.filter((f) => !filters.find((ff) => ff.prop === f.prop)),
                    ...filters,
                ];
            }
            else {
                this.filters = filters;
            }
        }
        if (this.currentFilters === JSON.stringify(this.filters))
            return;
        this._loading = true;
        const sortEvent = this.nanoTblBeforeFilter.emit({ filters: this.filters });
        if (sortEvent.defaultPrevented)
            return;
        this.currentFilters = JSON.stringify(this.filters);
        // doesn't make sense to leave user in place for a search
        this.scrollToTop();
        if (this.customFilterFn) {
            try {
                const res = await this.customFilterFn(this.filters);
                // if the response is 'true', the custom filter did it's thing
                // handover to finish and stop loading state.
                // if response is falsy, carry on to do a FE filter
                if (res === true) {
                    this.customFilterApplied = true;
                    await this.filterComplete();
                    this._loading = false;
                    return;
                }
            }
            catch (e) {
                // if response errored, stop loading state
                // clear current sort cache
                console.warn('custom filter failed', e);
                this.currentFilters = '';
                this._loading = false;
                return;
            }
        }
        try {
            await this.filterComplete();
        }
        catch (e) {
            console.warn('filter failed', e);
        }
        finally {
            this._loading = false;
        }
    }
    async filterComplete() {
        this.columns = this.columns.map((c) => {
            const cFilter = this.filters.find((f) => f.prop === c.prop);
            // if we found a filter AND
            // it isn't true / false and has a length OR
            // it is true or false
            if (cFilter &&
                ((typeof cFilter.filter !== 'boolean' && cFilter.filter.length) ||
                    typeof cFilter.filter === 'boolean'))
                c.filter = cFilter.filter;
            // if a filter value is not set OR
            // it isn't true / false and has no length
            else if ((c.filter !== null && c.filter !== undefined) ||
                (!!c.filter && typeof c.filter !== 'boolean' && !c.filter.length))
                c.filter = undefined;
            return c;
        });
        await storeFilter(this.host, this.filters);
        this.nanoTblAfterFilter.emit({ filters: this.filters });
    }
    /** Scrolls to the top immediately - used whilst sorting / filtering */
    scrollToTop(element) {
        if (!this.scrollParent)
            return;
        const scrollBehaviour = this.scrollParent.style?.scrollBehavior;
        const scrollX = this.scrollParent.scrollLeft;
        this.scrollParent.style.scrollBehavior = 'auto';
        if (this.topAnchorEle && !scroll.isInViewport(this.topAnchorEle, 0.1)) {
            this.host.scrollIntoView();
        }
        if (element && !scroll.isInViewport(element, 1))
            setTimeout(() => element.scrollIntoView({ block: 'start' }), 500);
        if (scrollX)
            this.scrollParent.scrollLeft = scrollX;
        if (scrollBehaviour)
            this.scrollParent.style.scrollBehavior = scrollBehaviour;
        this.scrollHandler();
    }
    setMeasureElement() {
        return new Promise((resolve) => {
            index.readTask(() => {
                this.measureEle = this.blockElements.find((b) => !b?.classList?.contains(`${CSSNAMESPACE}__inactive`));
                resolve();
            });
        });
    }
    /**
     * Makes sure we have dimensions on at least one, active tbody element.
     * We'll use this is a yard stick for inactive tbody elements in future.
     * These elements have no natural height (on account of all their rows being hidden)
     */
    setInitialBlockDimension() {
        if (!this.blockElements?.length)
            return;
        const testForDimensions = async () => {
            await this.setMeasureElement();
            if (this.unitHeight)
                return true;
            return false;
        };
        const dimensionsReady = new Promise((resolve) => {
            testForDimensions().then(async (ready) => {
                if (ready)
                    resolve();
                else {
                    // it's likely this table is hidden in the dom.
                    // we need to wait until it's visible
                    const observer = new IntersectionObserver(async () => {
                        if (await testForDimensions()) {
                            resolve();
                            observer.disconnect();
                        }
                    }, { root: this.scrollParent });
                    observer.observe(this.tableEle);
                }
            });
        });
        // we're all finished.
        dimensionsReady.then(() => {
            requestAnimationFrame(() => (this.isReady = true));
        });
    }
    /** Apply initial columns settings */
    async columnInit() {
        this.filters = this.columns
            .filter((c) => c.filter !== undefined && c.filter !== null)
            .map((c) => {
            const { filter, prop } = c;
            return { prop, filter };
        });
        if (this.searchTerm) {
            await this.searchStart();
        }
        if (this.filters.length) {
            await this.filterStart();
        }
        // apply sort
        const col = this.columns.find((c) => !!c.order);
        if (col) {
            await this.sortStart(col.order, col.prop);
        }
        if (this.tablePinnedService)
            this.tablePinnedService.assessCols();
    }
    /** Split up all incoming rows into 'blocks' split amongst tbody elements.
     * These can then be hidden / shown to improve performance.
     */
    setBlocks() {
        const dRows = this.store.data.state.rows;
        if (!dRows.length) {
            this.blocks = [];
            return;
        }
        // this.ignoreIO = true;
        let i = 1;
        const l = this.virtualTotalItems > dRows.length
            ? this.virtualTotalItems
            : dRows.length;
        let rows = [];
        const blocks = [];
        // old skool loop for perf
        for (i; i <= l; i++) {
            rows.push(this.store.data.state.rows[i - 1] || { __uuid: '' });
            if (i % this.perBlock === 0) {
                // assign a UUID for the whole block
                // for block diffing
                blocks.push({ rows, __uuid: math.cyrb53(rows.map((b) => b.__uuid).join()) });
                rows = [];
            }
        }
        // any leftover rows
        if (rows.length) {
            blocks.push({ rows, __uuid: math.cyrb53(rows.map((b) => b.__uuid).join()) });
        }
        this.blocks = blocks;
    }
    /**
     * Returns a block render height.
     * If it's currently active - let auto do it's thing
     * If we've rendered it before - return that
     * If all else fails, let's guess it
     * @param blockIndex
     * @returns a height string (incl px)
     */
    getBlockHeight(blockIndex) {
        if (this.blockHeights.length) {
            const cachedBlockHeight = this.blockHeights.find((bh) => bh.blockIndex === blockIndex);
            if (cachedBlockHeight && cachedBlockHeight.height)
                return cachedBlockHeight.height;
        }
        const blockLength = this.blocks[blockIndex].rows.length;
        if (blockLength === this.perBlock && this.measureHeight) {
            return this.measureHeight;
        }
        return this.unitHeight ? this.unitHeight * blockLength : 100;
    }
    /** cache the height for all active blocks for later renders */
    setBlockHeight() {
        this.activeBlocks.forEach((blockIndex) => {
            const el = this.blockElements[blockIndex];
            if (!el)
                return;
            index.readTask(() => {
                if (el.classList.contains(`${CSSNAMESPACE}__inactive`))
                    return;
                const height = el.getBoundingClientRect().height;
                // cache height to our block heights array
                // for subsequent renders
                const fBhIdx = this.blockHeights.findIndex((bh) => bh.blockIndex === blockIndex);
                if (fBhIdx > -1) {
                    this.blockHeights[fBhIdx] = { height, blockIndex };
                }
                else
                    this.blockHeights.push({ height, blockIndex });
            });
        });
    }
    /**
     * On scroll, loop through all blocks' heights and cumulatively, add them together.
     * When we find that the scroll position is less than this cumulative block height -
     * stop loop - it's on the current active block.
     */
    scrollHandler = () => {
        let scrollPos = 0;
        // don't listen if this table isn't in the viewport
        if (!this.store?.general.state.isActive || !this.rows)
            return;
        if (this.primaryBlockIndex === undefined)
            this.primaryBlockIndex = 0;
        index.readTask(() => {
            scrollPos =
                typeof this.scrollParent.scrollTop !== 'undefined'
                    ? this.scrollParent.scrollTop
                    : window.scrollY;
            scrollPos += this.host.offsetTop;
            if (this.tablePinnedService) {
                this.tablePinnedService.onScroll({
                    x: typeof this.scrollParent.scrollLeft !== 'undefined'
                        ? this.scrollParent.scrollLeft
                        : window.scrollX,
                    y: scrollPos,
                });
            }
            let cumulativeHeight = this.host.offsetTop;
            let blockIndex = 0;
            const blockLen = this.blocks.length;
            while (blockIndex < blockLen && scrollPos >= cumulativeHeight) {
                cumulativeHeight += this.getBlockHeight(blockIndex);
                if (scrollPos < cumulativeHeight) {
                    const potentialBlocks = [
                        blockIndex,
                        blockIndex + 1,
                        // Math.max(0, blockIndex - 1),
                    ];
                    if (potentialBlocks.toString() !== this.activeBlocks.toString()) {
                        this.activeBlocks = potentialBlocks;
                    }
                    this.primaryBlockIndex = blockIndex;
                }
                blockIndex++;
            }
        });
    };
    /** Process slotted content */
    processSlots() {
        // see if we have slot content
        if (!this.caption && !this.host.querySelector('[slot="caption"]')) {
            console.error('For accessibility you must set a `caption` prop or use the `caption` slot');
        }
    }
    /** Adds an IO. Makes sure our scroll listener is only active when
     * the table is in viewport */
    setupActiveWatcher() {
        if (!this.host || !this.scrollParent || !this.store)
            return;
        if (this.activeWatcherIo) {
            this.activeWatcherIo.disconnect();
            this.activeWatcherIo = undefined;
        }
        const io = (this.activeWatcherIo = new IntersectionObserver(([e]) => {
            if (e.isIntersecting)
                this.store.general.state.isActive = true;
            else
                this.store.general.state.isActive = false;
        }, { root: this.scrollParent, threshold: 0 }));
        io.observe(this.scrollable ? this.tableEle : this.host);
    }
    // Component lifecycle
    async componentWillLoad() {
        // setup stores
        this.store = await generateStore(this.host, this.columns, this.scrollParent, this.isReady);
        this.handleRowsChange();
        this.store.general.onChange('isActive', () => {
            this.scrollHandler();
        });
        this.store.data.onChange('rows', () => this.setBlocks());
        // setup dom and attach listeners
        this.processSlots();
        this.setBlocks();
        if (!this.scrollable) {
            this.scrollParent = scroll.findScrollParent(this.host);
            this.setupActiveWatcher();
        }
    }
    connectedCallback() {
        if (!this.scrollable) {
            this.scrollParent = scroll.findScrollParent(this.host);
            this.setupActiveWatcher();
        }
    }
    componentDidLoad() {
        this.setInitialBlockDimension();
        if (this.scrollable) {
            this.scrollParent = this.host
                .querySelector(renderer.transformTag('nano-masked-overflow'))
                .shadowRoot.querySelector('.onav__scroller');
            this.setupActiveWatcher();
        }
        if (!this.tablePinnedService) {
            this.tablePinnedService = new TablePinService(this.tableEle, this.scrollParent);
        }
    }
    componentShouldUpdate(_newVal, _oldVal, stateName) {
        // stop double rendering - we use the store for rendering internally
        // the public facing props are kept in-sync with the store
        // but we don't want it to cause renders
        if (['rows', 'columns'].includes(stateName))
            return false;
    }
    componentWillRender() {
    }
    componentDidRender() {
        this.setMeasureElement().then(() => this.setBlockHeight());
    }
    disconnectedCallback() {
        if (!this.activeWatcherIo)
            return;
        this.activeWatcherIo.disconnect();
        this.activeWatcherIo = undefined;
        (this.scrollParent === document.documentElement
            ? document
            : this.scrollParent).removeEventListener('scroll', this.scrollHandler);
    }
    render() {
        this.blockElements = [];
        const Wrap = (content) => {
            return this.scrollable ? (renderer.h("nano-masked-overflow", { hideScrollbars: false, scrollControls: false, label: this.caption }, content)) : (renderer.h("div", null, content));
        };
        return (renderer.h(index.Host, { class: "nano-data-table" }, renderer.h("div", { class: `${CSSNAMESPACE}__top-anchor`, ref: (a) => (this.topAnchorEle = a) }, "\u00A0"), renderer.h("nano-resize-observe", { onNanoResize: () => {
                if (this.tablePinnedService)
                    this.tablePinnedService.onResize();
            } }), Wrap(renderer.h("div", { class: `${CSSNAMESPACE}__wrap` }, renderer.h("nano-progress-bar", {
            // indeterminate
            class: {
                [`${CSSNAMESPACE}__progress-bar`]: true,
                [`${CSSNAMESPACE}__progress-bar--show`]: this._loading,
            }
        }, renderer.h("progress", null)), renderer.h("table", { "aria-rowcount": this.store.data.state.rows.length, "aria-colcount": this.store.config.state.columns.length, class: `${CSSNAMESPACE}`, ref: (tbl) => (this.tableEle = tbl), id: 'nano-data-table-' + this.renderId, "aria-busy": this._loading || undefined }, renderer.h("caption", { class: {
                [`${CSSNAMESPACE}__caption`]: true,
                [`${CSSNAMESPACE}__caption--hide`]: !this.showCaption,
            } }, renderer.h("slot", { name: "caption" }, this.caption)), renderer.h("thead", null, renderer.h(TableHeadFootRow, { rowRenderer: this.headRender }, this.store.config.state.columns.map((colModel) => [
            renderer.h(TableColHead, { column: colModel, headRenderer: this.headRender, onColumnSortClick: this.sortStart, defaults: {
                    sortable: this.defaultSort,
                } }),
        ]))), this._loading && !this.blocks.length && (renderer.h("tbody", { class: `${CSSNAMESPACE}__active ${CSSNAMESPACE}__loading` }, [...Array(this.placeholderSize).keys()].map((rowIndex) => (renderer.h("tr", null, this.store.config.state.columns.map((_colModel, colIndex) => (renderer.h(TableCell, { rowIndex: rowIndex, colIndex: colIndex, nestedContent: () => (renderer.h("span", { class: "n-loader-skeleton" }, renderer.h("span", null, "Content loading"))) })))))))), renderer.h("tr", { hidden: !!this._loading || !!this.blocks.length }, renderer.h("th", { class: `${CSSNAMESPACE}__th`, colSpan: this.store.config.state.columns.length }, renderer.h("div", { class: "nano-tbl__cell-content nano-tbl__cell-content--no-result" }, renderer.h("slot", { name: "no-results" }, "No results found")))), this.blocks.map((block, blockIndex) => (renderer.h("tbody", { key: block.__uuid, id: `tbody-${this.renderId}-${blockIndex}`, ref: (tb) => {
                this.blockElements.push(tb);
            }, class: {
                [`${CSSNAMESPACE}__inactive`]: !this.activeBlocks.includes(blockIndex),
                [`${CSSNAMESPACE}__active`]: this.activeBlocks.includes(blockIndex),
            } }, this.activeBlocks.includes(blockIndex) ? (block.rows.map((row, i) => {
            const rowIndex = blockIndex > 0 ? blockIndex * this.perBlock + i : i;
            return (renderer.h(TableRow, { rowRenderer: this.rowRender, rowModel: row, rowIndex: rowIndex }, this.store.config.state.columns.map((_colModel, colIndex) => (renderer.h(TableCell, { rowIndex: rowIndex, colIndex: colIndex })))));
        })) : (renderer.h("tr", { class: `${CSSNAMESPACE}__tr--placeholder` }, renderer.h("td", { colSpan: this.store.config.state.columns.length, style: {
                height: this.getBlockHeight(blockIndex) + 'px',
            } })))))), this.showFooter && (renderer.h("tfoot", null, renderer.h(TableHeadFootRow, { rowRenderer: this.footRender }, this.store.config.state.columns.map((colModel) => [
            renderer.h(TableColHead, { column: colModel, headRenderer: this.footRender, onColumnSortClick: this.sortStart, defaults: {
                    sortable: this.defaultSort,
                } }),
        ]))))), !!this.blocks.length && this.showPaginateSpinner && (renderer.h("nano-spinner", { class: {
                [`${CSSNAMESPACE}__spinner`]: true,
                [`${CSSNAMESPACE}__spinner--show`]: this._loading,
            } }))))));
    }
    static get watchers() { return {
        "rows": ["handleRowsChange"],
        "columns": ["handleColsChange"],
        "searchTerm": ["handleSearchTermChange"],
        "virtualTotalItems": ["virtualTotalItemsChangeHandler"]
    }; }
};
NanoDataTable.style = tableCss;

exports.NanoDataTable = NanoDataTable;
exports.createWorker = createWorker;
