@opensig/opendesign
Version:
251 lines (250 loc) • 7.83 kB
JavaScript
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
};