UNPKG

@opensig/opendesign

Version:

251 lines (250 loc) 7.83 kB
import { shallowRef, onBeforeUnmount } from "vue"; import { resolveHtmlElement } from "../_utils/vue-utils.mjs"; import { debounce } from "../_utils/helper.mjs"; import { log } from "../_utils/log.mjs"; const DEFAULT_CELL_FIRST_COL_MARKER = "o-cell-first-col"; const DEFAULT_CELL_LAST_COL_MARKER = "o-cell-last-col"; const DEFAULT_CELL_LAST_ROW_MARKER = "o-cell-last-row"; const DEFAULT_ROW_LAST_MARKER = "o-row-last"; function fillGrid(grid, cellMeta) { for (let r = cellMeta.rowStart; r < cellMeta.rowEnd; r++) { if (!grid[r]) grid[r] = []; for (let c = cellMeta.colStart; c < cellMeta.colEnd; c++) { grid[r][c] = cellMeta; } } } function processSection(section, scope, cellMap) { const rows = section.rows; const grid = []; const rtn = { data: grid, totalRows: 0, totalCols: 0, rows: Array.from(rows), sectionEl: section, scope }; let maxCols = 0; for (let i = 0; i < rows.length; i++) { grid.push([]); } for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) { const row = rows[rowIndex]; let colIndex = 0; let colEnd = 0; for (const cell of Array.from(row.cells)) { const colspan = cell.colSpan || 1; const rowspan = cell.rowSpan || 1; while (grid[rowIndex][colIndex] !== void 0) { colIndex++; } colEnd = colIndex + colspan; const cellMeta = { el: cell, colStart: colIndex, rowStart: rowIndex, colEnd, rowEnd: rowIndex + rowspan, lastCol: false, lastRow: false, section: rtn }; cellMap.set(cell, cellMeta); fillGrid(grid, cellMeta); colIndex += colspan; } if (colEnd > maxCols && rowIndex > 0) { log.warn( `The row ${rowIndex + 1} has ${colEnd} columns, exceeding previous row's ${maxCols} columns. This may indicate inconsistent cell count or incorrect colspan/rowspan settings.`, row ); } maxCols = Math.max(maxCols, colEnd); } rtn.totalCols = maxCols; rtn.totalRows = rows.length; return rtn; } function normalizeSection(section, maxCols) { section.data.forEach((row) => { if (row.length < maxCols) { row.push(...Array(maxCols - row.length).fill(null)); } }); section.totalCols = maxCols; return section; } function markCellEl(cell, colMarker, rowMarker) { if (colMarker) { if (cell.lastCol) { cell.el.classList.add(colMarker); } else { cell.el.classList.remove(colMarker); } } if (rowMarker) { if (cell.lastRow) { cell.el.classList.add(rowMarker); } else { cell.el.classList.remove(rowMarker); } } } function markSection(section, isLastSection, marker) { const { totalCols, totalRows, data } = section; for (let rowIndex = 0; rowIndex < totalRows; rowIndex++) { const isLastRow = rowIndex === totalRows - 1 && isLastSection; const rowEl = section.rows[rowIndex]; if (marker.rowMarker && rowEl) { if (isLastRow) { rowEl.classList.add(marker.rowMarker); } else { rowEl.classList.remove(marker.rowMarker); } } for (let colIndex = 0; colIndex < totalCols; colIndex++) { const cell = data[rowIndex][colIndex]; if (cell && rowIndex === cell.rowStart && colIndex === cell.colStart) { if (cell.colEnd === totalCols) { cell.lastCol = true; } if (cell.rowEnd === totalRows && isLastSection) { cell.lastRow = true; } markCellEl(cell, marker.cellColMarker, marker.cellRowMarker); } } } } function markTable(sections, options) { const { markCellLastCol, markCellLastRow, markRowLast, splitBySection } = options; const marker = { cellColMarker: markCellLastCol === true ? DEFAULT_CELL_LAST_COL_MARKER : markCellLastCol, cellRowMarker: markCellLastRow === true ? DEFAULT_CELL_LAST_ROW_MARKER : markCellLastRow, rowMarker: markRowLast === true ? DEFAULT_ROW_LAST_MARKER : markRowLast }; const validSections = sections.filter(Boolean); const lastSectionIdx = validSections.length - 1; validSections.forEach((section, sectionIndex) => { const isLastSection = splitBySection || sectionIndex === lastSectionIdx; markSection(section, isLastSection, marker); }); } const processTable = (el, cellMap, options) => { let head = null; let foot = null; let maxCols = 0; if (el.tHead) { head = processSection(el.tHead, "head", cellMap); maxCols = Math.max(maxCols, head.totalCols); } const bodies = Array.from(el.tBodies).map((tbody, index) => { const body = processSection(tbody, "body", cellMap); if (body.totalCols > maxCols && maxCols > 0) { log.warn( `The tbody ${index + 1} has ${body.totalCols} columns, exceeding previous section's ${maxCols} columns. This may indicate inconsistent cell count or incorrect colspan/rowspan settings.`, tbody ); } maxCols = Math.max(maxCols, body.totalCols); return body; }); if (el.tFoot) { foot = processSection(el.tFoot, "foot", cellMap); if (foot.totalCols > maxCols && maxCols > 0) { log.warn( `The tfoot section has ${foot.totalCols} columns, exceeding the ${maxCols} columns in thead or tbody. This may indicate inconsistent cell count or incorrect colspan/rowspan settings.`, el.tFoot ); } maxCols = Math.max(maxCols, foot.totalCols); } if (head) { normalizeSection(head, maxCols); } bodies.map((section) => normalizeSection(section, maxCols)); if (foot) { normalizeSection(foot, maxCols); } markTable([head, ...bodies, foot], options); return { head, bodies, foot }; }; function isSpanChange(record) { return record.type === "attributes" && record.target instanceof HTMLTableCellElement; } function isTableChildChange(record) { if (record.type === "childList") { for (const node of record.addedNodes) { if (node instanceof HTMLTableCellElement || node instanceof HTMLTableRowElement || node instanceof HTMLTableSectionElement) { return true; } } for (const node of record.removedNodes) { if (node instanceof HTMLTableCellElement || node instanceof HTMLTableRowElement || node instanceof HTMLTableSectionElement) { return true; } } } return false; } function shouldRefactorTableMeta(records) { for (const record of records) { if (isSpanChange(record)) { return true; } if (isTableChildChange(record)) { return true; } } return false; } function useTableMeta(elRef, options = {}) { const cellMap = /* @__PURE__ */ new WeakMap(); const head = shallowRef(null); const bodies = shallowRef([]); const foot = shallowRef(null); let mutationObserver = null; const updateMeta = (el) => { const _rtn = processTable(el, cellMap, options); head.value = _rtn.head; bodies.value = _rtn.bodies; foot.value = _rtn.foot; }; resolveHtmlElement(elRef).then((el) => { if (!(el instanceof HTMLTableElement)) { return; } updateMeta(el); const deBounceUpdateMeta = debounce(updateMeta.bind(null, el), 16, false); mutationObserver = new MutationObserver((mRecords) => { if (shouldRefactorTableMeta(mRecords)) { deBounceUpdateMeta(); } }); mutationObserver.observe(el, { childList: true, subtree: true, attributes: true, attributeFilter: ["colspan", "rowspan"] }); }); onBeforeUnmount(() => { mutationObserver == null ? void 0 : mutationObserver.disconnect(); }); function getMeta(cellEl) { return cellMap.get(cellEl) || null; } return { head, bodies, foot, getMeta }; } export { DEFAULT_CELL_FIRST_COL_MARKER, DEFAULT_CELL_LAST_COL_MARKER, DEFAULT_CELL_LAST_ROW_MARKER, DEFAULT_ROW_LAST_MARKER, useTableMeta };