buildHtmlTable(data, opts = {}) {
    let rowKeys;
    const dataType = Object.prototype.toString.apply(data);
    if (dataType === "[object Array]" || dataType === "[object Object]") {
      rowKeys = Object.keys(data);
      data = Object.values(data);
    } else {
      const out = _a.doc.createElement("p");
      out.textContent = "Input data is not an array or an object, cannot create a table.";
      return out;
    }
    if (rowKeys.length > 1e3) _a.log(1, "Uib:buildHtmlTable", `Warning, data is ${rowKeys.length} rows. Anything over 1,000 can get very slow to complete.`)();
    const tbl = _a.doc.createElement("table");
    const thead = _a.doc.createElement("thead");
    const headerRow = _a.doc.createElement("tr");
    if (!opts.cols) {
      if (data.length < 1) throw new Error("[ui.js:buildHtmlTable] When no opts.cols is provided, data must contain at least 1 row");
      const hasName = Object.prototype.toString.apply(data[0]) !== "[object Array]";
      headerRow.dataset.colReference = "";
      opts.cols = [];
      Object.keys(data[0]).forEach((col, i) => {
        opts.cols.push({
          "index": i,
          "hasName": hasName,
          "name": hasName ? col : void 0,
          "key": col ?? i,
          "title": col
        });
      });
    }
    tbl.cols = opts.cols;
    opts.cols.forEach((col) => {
      const thEl = _a.doc.createElement("th");
      thEl.textContent = col.title;
      if (col.hasName === true) thEl.dataset.colName = name;
      headerRow.appendChild(thEl);
    });
    thead.appendChild(headerRow);
    tbl.appendChild(thead);
    const tbody = _a.doc.createElement("tbody");
    tbl.appendChild(tbody);
    const rowOpts = {
      allowHTML: true,
      cols: opts.cols
      // we only want to get this once
    };
    data.forEach((item, i) => {
      if (isNaN(Number(rowKeys[i]))) rowOpts.rowId = rowKeys[i];
      else rowOpts.rowId = void 0;
      this.tblAddRow(tbl, item, rowOpts);
    });
    if (opts.parent) {
      let parentEl;
      if (typeof opts.parent === "string") {
        parentEl = _a.doc.querySelector(opts.parent);
      } else {
        parentEl = opts.parent;
      }
      try {
        parentEl.appendChild(tbl);
      } catch (e) {
        throw new Error(`[ui.js:buildHtmlTable] Could not add table to parent. ${e.message}`);
      }
      return;
    }
    return tbl;
  }
Builds & returns an HTML table element from an array (or object) of objects
1st row is used for columns unless you pass opts.cols to describe them.
If an object of objects, inner keys are used to populate th/td `data-col-name` attribs. Outer keys applied as row ID's.

TODO
- Allow optional caption, heading, footers, optional collapse
- Multiple headings, footers
- colspans, rowspans
- multiple tbody

@param {Array<object>|Array<Array>|object} data Input data array or object. Object of objects gives named rows. Array of objects named cols. Array of arrays no naming.
@param {object} opts Table options
  @param {Array<columnDefinition>=} opts.cols Column metadata. If not provided will be derived from 1st row of data
  @param {HTMLElement|string=} opts.parent If provided, the table will be added as a child instead of returned. May be an actual HTML element or a CSS Selector
  @param {boolean=} opts.allowHTML Optional, default=false. If true, allows HTML cell content, otherwise only allows text. Always sanitise HTML inputs
@returns {HTMLTableElement|HTMLParagraphElement} Output HTML Element