chunk-017-table.js

I’ll build this as a safe, theme-friendly static chunk file: no dependency, no jQuery requirement, auto-detects tables, improves accessibility, responsive behavior, search/filter/sort helpers, copy/export options, and WordPress-friendly initialization.

Yes friend — for this file:

assets/static-js/static-chunks-js/chunk-017-table.js

use it as an advanced table utility chunk for RX Theme. It will improve normal WordPress/medical article tables with responsive wrapping, sorting, searching, copy buttons, CSV export, sticky header, accessibility, mobile card view, and safe auto-init.

/*!
 * RX Theme - Table Advanced Static Chunk
 * File: assets/static-js/static-chunks-js/chunk-017-table.js
 * Version: 1.0.0
 *
 * Purpose:
 * - Advanced responsive table system for RX Theme
 * - Works with normal WordPress content tables
 * - No jQuery required
 * - Safe for medical article tables, comparison tables, dosage tables, FAQ tables
 */

(function () {
  "use strict";

  /**
   * --------------------------------------------------------------------------
   * RX Table Global Namespace
   * --------------------------------------------------------------------------
   */

  window.RXTheme = window.RXTheme || {};
  window.RXTheme.table = window.RXTheme.table || {};

  const RXTable = window.RXTheme.table;

  /**
   * --------------------------------------------------------------------------
   * Default Configuration
   * --------------------------------------------------------------------------
   */

  const DEFAULTS = {
    tableSelector:
      ".entry-content table, .rx-content table, .rx-table, table.rx-advanced-table",

    enhancedClass: "rx-table-enhanced",
    wrapperClass: "rx-table-wrapper",
    toolbarClass: "rx-table-toolbar",
    responsiveClass: "rx-table-responsive",
    cardModeClass: "rx-table-card-mode",
    sortableClass: "rx-table-sortable",
    stickyClass: "rx-table-sticky",
    searchableClass: "rx-table-searchable",

    enableResponsive: true,
    enableSorting: true,
    enableSearch: true,
    enableCopy: true,
    enableCSV: true,
    enableStickyHeader: true,
    enableMobileLabels: true,
    enableRowCount: true,
    enableKeyboardSupport: true,
    enablePrintButton: true,

    mobileBreakpoint: 768,

    searchPlaceholder: "Search table...",
    noResultText: "No matching rows found.",
    copyText: "Copy",
    copiedText: "Copied",
    csvText: "CSV",
    printText: "Print",
    clearText: "Clear",

    skipClass: "rx-table-no-enhance",
    debug: false,
  };

  RXTable.config = Object.assign({}, DEFAULTS, RXTable.config || {});

  /**
   * --------------------------------------------------------------------------
   * Small Utilities
   * --------------------------------------------------------------------------
   */

  function log() {
    if (RXTable.config.debug && window.console) {
      console.log.apply(console, arguments);
    }
  }

  function toArray(value) {
    return Array.prototype.slice.call(value || []);
  }

  function sanitizeText(value) {
    return String(value || "")
      .replace(/\s+/g, " ")
      .trim();
  }

  function createElement(tag, className, text) {
    const el = document.createElement(tag);

    if (className) {
      el.className = className;
    }

    if (typeof text === "string") {
      el.textContent = text;
    }

    return el;
  }

  function getTableText(table) {
    return sanitizeText(table.innerText || table.textContent || "");
  }

  function isHidden(el) {
    return el.style.display === "none" || el.hidden === true;
  }

  function debounce(fn, delay) {
    let timer = null;

    return function () {
      const args = arguments;
      const context = this;

      window.clearTimeout(timer);

      timer = window.setTimeout(function () {
        fn.apply(context, args);
      }, delay);
    };
  }

  function escapeCSV(value) {
    const text = String(value || "").replace(/"/g, '""');

    if (/[",\n\r]/.test(text)) {
      return '"' + text + '"';
    }

    return text;
  }

  function downloadFile(filename, content, mimeType) {
    const blob = new Blob([content], {
      type: mimeType || "text/plain;charset=utf-8",
    });

    const url = URL.createObjectURL(blob);
    const link = document.createElement("a");

    link.href = url;
    link.download = filename;
    document.body.appendChild(link);
    link.click();

    window.setTimeout(function () {
      URL.revokeObjectURL(url);
      link.remove();
    }, 100);
  }

  function getTableName(table, index) {
    const caption = table.querySelector("caption");

    if (caption && sanitizeText(caption.textContent)) {
      return sanitizeText(caption.textContent)
        .toLowerCase()
        .replace(/[^a-z0-9]+/gi, "-")
        .replace(/^-|-$/g, "");
    }

    return "rx-table-" + (index + 1);
  }

  function getHeaderCells(table) {
    const theadHeaders = table.querySelectorAll("thead th");

    if (theadHeaders.length) {
      return toArray(theadHeaders);
    }

    const firstRowHeaders = table.querySelectorAll("tr:first-child th");

    if (firstRowHeaders.length) {
      return toArray(firstRowHeaders);
    }

    return [];
  }

  function getBodyRows(table) {
    const tbodyRows = table.querySelectorAll("tbody tr");

    if (tbodyRows.length) {
      return toArray(tbodyRows);
    }

    const rows = toArray(table.querySelectorAll("tr"));

    if (!rows.length) {
      return [];
    }

    const firstRowHasHeaders = rows[0].querySelectorAll("th").length > 0;

    return firstRowHasHeaders ? rows.slice(1) : rows;
  }

  function getCellValue(row, index) {
    const cells = row.children;
    const cell = cells[index];

    if (!cell) {
      return "";
    }

    return sanitizeText(cell.textContent);
  }

  function parseValue(value) {
    const text = sanitizeText(value);

    if (!text) {
      return "";
    }

    const numeric = Number(
      text
        .replace(/,/g, "")
        .replace(/%/g, "")
        .replace(/[^\d.-]/g, "")
    );

    if (!Number.isNaN(numeric) && /\d/.test(text)) {
      return numeric;
    }

    const date = Date.parse(text);

    if (!Number.isNaN(date) && /\d{2,4}/.test(text)) {
      return date;
    }

    return text.toLowerCase();
  }

  function compareValues(a, b, direction) {
    if (typeof a === "number" && typeof b === "number") {
      return direction === "asc" ? a - b : b - a;
    }

    const result = String(a).localeCompare(String(b), undefined, {
      numeric: true,
      sensitivity: "base",
    });

    return direction === "asc" ? result : -result;
  }

  /**
   * --------------------------------------------------------------------------
   * Wrapper
   * --------------------------------------------------------------------------
   */

  function wrapTable(table) {
    if (table.parentElement && table.parentElement.classList.contains(RXTable.config.wrapperClass)) {
      return table.parentElement;
    }

    const wrapper = createElement("div", RXTable.config.wrapperClass);

    table.parentNode.insertBefore(wrapper, table);
    wrapper.appendChild(table);

    wrapper.setAttribute("role", "region");
    wrapper.setAttribute("aria-label", "Scrollable table");
    wrapper.setAttribute("tabindex", "0");

    return wrapper;
  }

  /**
   * --------------------------------------------------------------------------
   * Toolbar
   * --------------------------------------------------------------------------
   */

  function createToolbar(table, wrapper, index) {
    if (wrapper.querySelector(":scope > ." + RXTable.config.toolbarClass)) {
      return wrapper.querySelector(":scope > ." + RXTable.config.toolbarClass);
    }

    const toolbar = createElement("div", RXTable.config.toolbarClass);
    const tableName = getTableName(table, index);

    toolbar.setAttribute("data-rx-table-toolbar", tableName);

    wrapper.insertBefore(toolbar, table);

    return toolbar;
  }

  /**
   * --------------------------------------------------------------------------
   * Search
   * --------------------------------------------------------------------------
   */

  function addSearch(table, toolbar) {
    if (!RXTable.config.enableSearch) {
      return;
    }

    if (table.classList.contains("rx-table-no-search")) {
      return;
    }

    const rows = getBodyRows(table);

    if (rows.length < 3) {
      return;
    }

    table.classList.add(RXTable.config.searchableClass);

    const searchWrap = createElement("div", "rx-table-search-wrap");
    const input = createElement("input", "rx-table-search-input");
    const clearButton = createElement("button", "rx-table-clear-btn", RXTable.config.clearText);
    const status = createElement("span", "rx-table-search-status");

    input.type = "search";
    input.placeholder = RXTable.config.searchPlaceholder;
    input.setAttribute("aria-label", "Search inside this table");

    clearButton.type = "button";
    clearButton.hidden = true;

    status.setAttribute("aria-live", "polite");

    searchWrap.appendChild(input);
    searchWrap.appendChild(clearButton);
    searchWrap.appendChild(status);
    toolbar.appendChild(searchWrap);

    let noResultRow = null;

    function ensureNoResultRow() {
      if (noResultRow) {
        return noResultRow;
      }

      const colCount = Math.max(1, table.querySelectorAll("tr:first-child > *").length);
      const tbody = table.tBodies[0] || table.createTBody();

      noResultRow = document.createElement("tr");
      noResultRow.className = "rx-table-no-result-row";
      noResultRow.hidden = true;

      const cell = document.createElement("td");
      cell.colSpan = colCount;
      cell.textContent = RXTable.config.noResultText;

      noResultRow.appendChild(cell);
      tbody.appendChild(noResultRow);

      return noResultRow;
    }

    function filterRows() {
      const query = sanitizeText(input.value).toLowerCase();
      let visibleCount = 0;

      rows.forEach(function (row) {
        const text = sanitizeText(row.textContent).toLowerCase();
        const matched = !query || text.indexOf(query) !== -1;

        row.hidden = !matched;

        if (matched) {
          visibleCount += 1;
        }
      });

      const noRow = ensureNoResultRow();
      noRow.hidden = visibleCount !== 0;

      clearButton.hidden = !query;
      status.textContent = query
        ? visibleCount + " row" + (visibleCount === 1 ? "" : "s") + " found"
        : "";

      updateRowCount(table);
    }

    input.addEventListener("input", debounce(filterRows, 120));

    clearButton.addEventListener("click", function () {
      input.value = "";
      filterRows();
      input.focus();
    });
  }

  /**
   * --------------------------------------------------------------------------
   * Sorting
   * --------------------------------------------------------------------------
   */

  function addSorting(table) {
    if (!RXTable.config.enableSorting) {
      return;
    }

    if (table.classList.contains("rx-table-no-sort")) {
      return;
    }

    const headers = getHeaderCells(table);
    const rows = getBodyRows(table);

    if (!headers.length || rows.length < 2) {
      return;
    }

    table.classList.add(RXTable.config.sortableClass);

    headers.forEach(function (header, index) {
      if (header.classList.contains("rx-no-sort")) {
        return;
      }

      const originalText = sanitizeText(header.textContent);
      const button = createElement("button", "rx-table-sort-btn");

      button.type = "button";
      button.textContent = originalText;
      button.setAttribute("aria-label", "Sort by " + originalText);
      button.setAttribute("aria-sort", "none");
      button.dataset.direction = "none";

      header.textContent = "";
      header.appendChild(button);
      header.classList.add("rx-table-sort-header");

      button.addEventListener("click", function () {
        const currentDirection = button.dataset.direction;
        const nextDirection = currentDirection === "asc" ? "desc" : "asc";

        headers.forEach(function (otherHeader) {
          const otherButton = otherHeader.querySelector(".rx-table-sort-btn");

          if (otherButton && otherButton !== button) {
            otherButton.dataset.direction = "none";
            otherButton.setAttribute("aria-sort", "none");
            otherHeader.classList.remove("rx-sorted-asc", "rx-sorted-desc");
          }
        });

        button.dataset.direction = nextDirection;
        button.setAttribute("aria-sort", nextDirection === "asc" ? "ascending" : "descending");

        header.classList.remove("rx-sorted-asc", "rx-sorted-desc");
        header.classList.add(nextDirection === "asc" ? "rx-sorted-asc" : "rx-sorted-desc");

        sortRows(table, index, nextDirection);
      });
    });
  }

  function sortRows(table, columnIndex, direction) {
    const tbody = table.tBodies[0];

    if (!tbody) {
      return;
    }

    const rows = getBodyRows(table).filter(function (row) {
      return !row.classList.contains("rx-table-no-result-row");
    });

    rows.sort(function (rowA, rowB) {
      const a = parseValue(getCellValue(rowA, columnIndex));
      const b = parseValue(getCellValue(rowB, columnIndex));

      return compareValues(a, b, direction);
    });

    rows.forEach(function (row) {
      tbody.appendChild(row);
    });
  }

  /**
   * --------------------------------------------------------------------------
   * Mobile Labels
   * --------------------------------------------------------------------------
   */

  function addMobileLabels(table) {
    if (!RXTable.config.enableMobileLabels) {
      return;
    }

    if (table.classList.contains("rx-table-no-mobile-label")) {
      return;
    }

    const headers = getHeaderCells(table).map(function (header) {
      return sanitizeText(header.textContent);
    });

    if (!headers.length) {
      return;
    }

    const rows = getBodyRows(table);

    rows.forEach(function (row) {
      const cells = toArray(row.children);

      cells.forEach(function (cell, index) {
        if (!cell.hasAttribute("data-rx-label")) {
          cell.setAttribute("data-rx-label", headers[index] || "Column " + (index + 1));
        }
      });
    });
  }

  /**
   * --------------------------------------------------------------------------
   * Copy Table
   * --------------------------------------------------------------------------
   */

  function addCopyButton(table, toolbar) {
    if (!RXTable.config.enableCopy) {
      return;
    }

    if (table.classList.contains("rx-table-no-copy")) {
      return;
    }

    const button = createElement("button", "rx-table-copy-btn", RXTable.config.copyText);
    button.type = "button";

    toolbar.appendChild(button);

    button.addEventListener("click", function () {
      const text = tableToPlainText(table);

      copyText(text)
        .then(function () {
          const oldText = button.textContent;
          button.textContent = RXTable.config.copiedText;
          button.classList.add("is-copied");

          window.setTimeout(function () {
            button.textContent = oldText;
            button.classList.remove("is-copied");
          }, 1400);
        })
        .catch(function () {
          log("RX Table copy failed.");
        });
    });
  }

  function copyText(text) {
    if (navigator.clipboard && navigator.clipboard.writeText) {
      return navigator.clipboard.writeText(text);
    }

    return new Promise(function (resolve, reject) {
      const textarea = document.createElement("textarea");

      textarea.value = text;
      textarea.setAttribute("readonly", "");
      textarea.style.position = "absolute";
      textarea.style.left = "-9999px";

      document.body.appendChild(textarea);
      textarea.select();

      try {
        document.execCommand("copy");
        textarea.remove();
        resolve();
      } catch (error) {
        textarea.remove();
        reject(error);
      }
    });
  }

  function tableToPlainText(table) {
    const rows = toArray(table.querySelectorAll("tr")).filter(function (row) {
      return !isHidden(row);
    });

    return rows
      .map(function (row) {
        return toArray(row.children)
          .map(function (cell) {
            return sanitizeText(cell.textContent);
          })
          .join("\t");
      })
      .join("\n");
  }

  /**
   * --------------------------------------------------------------------------
   * CSV Export
   * --------------------------------------------------------------------------
   */

  function addCSVButton(table, toolbar, index) {
    if (!RXTable.config.enableCSV) {
      return;
    }

    if (table.classList.contains("rx-table-no-csv")) {
      return;
    }

    const button = createElement("button", "rx-table-csv-btn", RXTable.config.csvText);
    button.type = "button";

    toolbar.appendChild(button);

    button.addEventListener("click", function () {
      const csv = tableToCSV(table);
      const filename = getTableName(table, index) + ".csv";

      downloadFile(filename, csv, "text/csv;charset=utf-8");
    });
  }

  function tableToCSV(table) {
    const rows = toArray(table.querySelectorAll("tr")).filter(function (row) {
      return !isHidden(row);
    });

    return rows
      .map(function (row) {
        return toArray(row.children)
          .map(function (cell) {
            return escapeCSV(sanitizeText(cell.textContent));
          })
          .join(",");
      })
      .join("\n");
  }

  /**
   * --------------------------------------------------------------------------
   * Print Table
   * --------------------------------------------------------------------------
   */

  function addPrintButton(table, toolbar) {
    if (!RXTable.config.enablePrintButton) {
      return;
    }

    if (table.classList.contains("rx-table-no-print")) {
      return;
    }

    const button = createElement("button", "rx-table-print-btn", RXTable.config.printText);
    button.type = "button";

    toolbar.appendChild(button);

    button.addEventListener("click", function () {
      printSingleTable(table);
    });
  }

  function printSingleTable(table) {
    const printWindow = window.open("", "_blank", "width=900,height=700");

    if (!printWindow) {
      window.print();
      return;
    }

    const title = document.title || "RX Table";
    const tableHTML = table.outerHTML;

    printWindow.document.open();
    printWindow.document.write(
      "<!doctype html>" +
        "<html>" +
        "<head>" +
        "<meta charset='utf-8'>" +
        "<title>" +
        title +
        "</title>" +
        "<style>" +
        "body{font-family:Arial,sans-serif;padding:24px;color:#111;}" +
        "table{width:100%;border-collapse:collapse;}" +
        "th,td{border:1px solid #ddd;padding:8px;text-align:left;vertical-align:top;}" +
        "th{background:#f5f5f5;}" +
        "caption{font-weight:700;margin-bottom:12px;text-align:left;}" +
        "</style>" +
        "</head>" +
        "<body>" +
        tableHTML +
        "</body>" +
        "</html>"
    );
    printWindow.document.close();

    printWindow.focus();

    window.setTimeout(function () {
      printWindow.print();
      printWindow.close();
    }, 300);
  }

  /**
   * --------------------------------------------------------------------------
   * Sticky Header
   * --------------------------------------------------------------------------
   */

  function addStickyHeader(table) {
    if (!RXTable.config.enableStickyHeader) {
      return;
    }

    if (table.classList.contains("rx-table-no-sticky")) {
      return;
    }

    const thead = table.querySelector("thead");

    if (!thead) {
      return;
    }

    table.classList.add(RXTable.config.stickyClass);
  }

  /**
   * --------------------------------------------------------------------------
   * Row Count
   * --------------------------------------------------------------------------
   */

  function addRowCount(table, toolbar) {
    if (!RXTable.config.enableRowCount) {
      return;
    }

    if (table.classList.contains("rx-table-no-count")) {
      return;
    }

    const count = createElement("span", "rx-table-row-count");
    count.setAttribute("aria-live", "polite");

    toolbar.appendChild(count);

    updateRowCount(table);
  }

  function updateRowCount(table) {
    const wrapper = table.closest("." + RXTable.config.wrapperClass);

    if (!wrapper) {
      return;
    }

    const countEl = wrapper.querySelector(".rx-table-row-count");

    if (!countEl) {
      return;
    }

    const rows = getBodyRows(table).filter(function (row) {
      return !row.classList.contains("rx-table-no-result-row");
    });

    const visibleRows = rows.filter(function (row) {
      return !row.hidden;
    });

    countEl.textContent =
      visibleRows.length + " / " + rows.length + " row" + (rows.length === 1 ? "" : "s");
  }

  /**
   * --------------------------------------------------------------------------
   * Keyboard Support
   * --------------------------------------------------------------------------
   */

  function addKeyboardSupport(wrapper) {
    if (!RXTable.config.enableKeyboardSupport) {
      return;
    }

    wrapper.addEventListener("keydown", function (event) {
      const step = 80;

      if (event.key === "ArrowRight") {
        wrapper.scrollLeft += step;
      }

      if (event.key === "ArrowLeft") {
        wrapper.scrollLeft -= step;
      }
    });
  }

  /**
   * --------------------------------------------------------------------------
   * Responsive State
   * --------------------------------------------------------------------------
   */

  function updateResponsiveState(table, wrapper) {
    if (!RXTable.config.enableResponsive) {
      return;
    }

    table.classList.add(RXTable.config.responsiveClass);

    const shouldCard =
      window.innerWidth <= RXTable.config.mobileBreakpoint &&
      table.classList.contains("rx-table-card");

    table.classList.toggle(RXTable.config.cardModeClass, shouldCard);
    wrapper.classList.toggle("has-horizontal-scroll", table.scrollWidth > wrapper.clientWidth);
  }

  function bindResponsiveWatcher(table, wrapper) {
    const update = debounce(function () {
      updateResponsiveState(table, wrapper);
    }, 120);

    window.addEventListener("resize", update, { passive: true });
    updateResponsiveState(table, wrapper);
  }

  /**
   * --------------------------------------------------------------------------
   * Accessibility Improvements
   * --------------------------------------------------------------------------
   */

  function improveAccessibility(table, index) {
    if (!table.hasAttribute("role")) {
      table.setAttribute("role", "table");
    }

    if (!table.id) {
      table.id = "rx-table-" + (index + 1);
    }

    const caption = table.querySelector("caption");

    if (caption) {
      const captionId = table.id + "-caption";
      caption.id = caption.id || captionId;
      table.setAttribute("aria-labelledby", caption.id);
    }

    const headers = getHeaderCells(table);

    headers.forEach(function (header) {
      if (!header.hasAttribute("scope")) {
        header.setAttribute("scope", "col");
      }
    });
  }

  /**
   * --------------------------------------------------------------------------
   * Table Enhancement Main Function
   * --------------------------------------------------------------------------
   */

  RXTable.enhanceTable = function (table, index) {
    if (!table || table.nodeType !== 1) {
      return;
    }

    if (table.classList.contains(RXTable.config.skipClass)) {
      return;
    }

    if (table.dataset.rxTableEnhanced === "true") {
      return;
    }

    table.dataset.rxTableEnhanced = "true";
    table.classList.add(RXTable.config.enhancedClass);

    improveAccessibility(table, index);
    addMobileLabels(table);
    addSorting(table);
    addStickyHeader(table);

    const wrapper = wrapTable(table);
    const toolbar = createToolbar(table, wrapper, index);

    addSearch(table, toolbar);
    addCopyButton(table, toolbar);
    addCSVButton(table, toolbar, index);
    addPrintButton(table, toolbar);
    addRowCount(table, toolbar);
    addKeyboardSupport(wrapper);
    bindResponsiveWatcher(table, wrapper);

    log("RX Table enhanced:", table);
  };

  /**
   * --------------------------------------------------------------------------
   * Init All Tables
   * --------------------------------------------------------------------------
   */

  RXTable.init = function (customConfig) {
    RXTable.config = Object.assign({}, RXTable.config, customConfig || {});

    const tables = toArray(document.querySelectorAll(RXTable.config.tableSelector));

    tables.forEach(function (table, index) {
      RXTable.enhanceTable(table, index);
    });
  };

  /**
   * --------------------------------------------------------------------------
   * Re-init for AJAX / Dynamic WordPress Content
   * --------------------------------------------------------------------------
   */

  RXTable.refresh = function (container) {
    const root = container || document;
    const tables = toArray(root.querySelectorAll(RXTable.config.tableSelector));

    tables.forEach(function (table, index) {
      RXTable.enhanceTable(table, index);
    });
  };

  /**
   * --------------------------------------------------------------------------
   * Mutation Observer
   * Useful for lazy loaded content, AJAX posts, Gutenberg blocks, tabs, etc.
   * --------------------------------------------------------------------------
   */

  RXTable.observe = function () {
    if (!("MutationObserver" in window)) {
      return;
    }

    const observer = new MutationObserver(
      debounce(function (mutations) {
        mutations.forEach(function (mutation) {
          toArray(mutation.addedNodes).forEach(function (node) {
            if (!node || node.nodeType !== 1) {
              return;
            }

            if (node.matches && node.matches(RXTable.config.tableSelector)) {
              RXTable.enhanceTable(node, 0);
              return;
            }

            if (node.querySelectorAll) {
              RXTable.refresh(node);
            }
          });
        });
      }, 200)
    );

    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });

    RXTable.observer = observer;
  };

  /**
   * --------------------------------------------------------------------------
   * Public Helper: Destroy Enhancement
   * --------------------------------------------------------------------------
   */

  RXTable.destroy = function (table) {
    if (!table) {
      return;
    }

    table.dataset.rxTableEnhanced = "false";
    table.classList.remove(
      RXTable.config.enhancedClass,
      RXTable.config.responsiveClass,
      RXTable.config.cardModeClass,
      RXTable.config.sortableClass,
      RXTable.config.stickyClass,
      RXTable.config.searchableClass
    );

    const wrapper = table.closest("." + RXTable.config.wrapperClass);

    if (wrapper) {
      const parent = wrapper.parentNode;
      parent.insertBefore(table, wrapper);
      wrapper.remove();
    }
  };

  /**
   * --------------------------------------------------------------------------
   * Auto Init
   * --------------------------------------------------------------------------
   */

  function ready(callback) {
    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", callback);
    } else {
      callback();
    }
  }

  ready(function () {
    RXTable.init();
    RXTable.observe();

    document.dispatchEvent(
      new CustomEvent("rxTableReady", {
        detail: {
          module: "chunk-017-table",
          version: "1.0.0",
        },
      })
    );
  });
})();

Also add this CSS later in your table CSS file, for example:

assets/static-css/components/table.css

.rx-table-wrapper {
  width: 100%;
  overflow-x: auto;
  margin: 1.5rem 0;
  border-radius: 12px;
  -webkit-overflow-scrolling: touch;
}

.rx-table-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.75rem;
}

.rx-table-search-wrap {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex: 1 1 260px;
}

.rx-table-search-input {
  width: 100%;
  max-width: 360px;
  padding: 0.65rem 0.8rem;
  border: 1px solid var(--rx-border-color, #dcdcdc);
  border-radius: 8px;
  font-size: 0.95rem;
}

.rx-table-toolbar button {
  cursor: pointer;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--rx-border-color, #dcdcdc);
  border-radius: 8px;
  background: var(--rx-surface-color, #fff);
  color: var(--rx-text-color, #111);
  font-size: 0.9rem;
}

.rx-table-toolbar button:hover {
  background: var(--rx-soft-bg, #f5f7f9);
}

.rx-table-enhanced {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.96rem;
}

.rx-table-enhanced th,
.rx-table-enhanced td {
  padding: 0.75rem;
  border: 1px solid var(--rx-border-color, #e5e7eb);
  text-align: left;
  vertical-align: top;
}

.rx-table-enhanced th {
  font-weight: 700;
  background: var(--rx-table-head-bg, #f8fafc);
}

.rx-table-sticky thead th {
  position: sticky;
  top: 0;
  z-index: 2;
}

.rx-table-sort-btn {
  width: 100%;
  border: 0 !important;
  background: transparent !important;
  padding: 0 !important;
  text-align: left;
  font: inherit;
  font-weight: inherit;
}

.rx-table-sort-header.rx-sorted-asc .rx-table-sort-btn::after {
  content: " ▲";
  font-size: 0.75em;
}

.rx-table-sort-header.rx-sorted-desc .rx-table-sort-btn::after {
  content: " ▼";
  font-size: 0.75em;
}

.rx-table-row-count,
.rx-table-search-status {
  font-size: 0.85rem;
  opacity: 0.75;
}

.rx-table-no-result-row td {
  text-align: center;
  font-weight: 600;
}

@media (max-width: 768px) {
  .rx-table-wrapper {
    border: 1px solid var(--rx-border-color, #e5e7eb);
  }

  .rx-table-card-mode,
  .rx-table-card-mode thead,
  .rx-table-card-mode tbody,
  .rx-table-card-mode th,
  .rx-table-card-mode td,
  .rx-table-card-mode tr {
    display: block;
    width: 100%;
  }

  .rx-table-card-mode thead {
    position: absolute;
    left: -9999px;
  }

  .rx-table-card-mode tr {
    margin: 0 0 1rem;
    border-bottom: 1px solid var(--rx-border-color, #e5e7eb);
  }

  .rx-table-card-mode td {
    display: grid;
    grid-template-columns: 42% 1fr;
    gap: 0.75rem;
    border-left: 0;
    border-right: 0;
  }

  .rx-table-card-mode td::before {
    content: attr(data-rx-label);
    font-weight: 700;
  }
}

To use mobile card mode for a special table, add this class in your HTML:

<table class="rx-table rx-table-card">
  ...
</table>

To stop enhancement for any table:

<table class="rx-table-no-enhance">
  ...
</table>

To disable only one feature:

<table class="rx-table rx-table-no-sort rx-table-no-search rx-table-no-csv">
  ...
</table>

This file is safe as a static chunk because it only runs after DOM ready, does not block rendering, and can be loaded with defer.

Leave a Reply

Your email address will not be published. Required fields are marked *