fix table with handling, icons implemented
This commit is contained in:
+230
-20
@@ -302,18 +302,48 @@
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function freezeTableGeometry() {
|
||||
if (table.id === 'asset-table') return;
|
||||
const widths = [...headerRow.cells].map(cell => Math.max(minimumColumnWidth, Math.round(cell.getBoundingClientRect().width || minimumColumnWidth)));
|
||||
const total = widths.reduce((sum, width) => sum + width, 0);
|
||||
widths.forEach((width, columnIndex) => {
|
||||
if (headerRow.cells[columnIndex]?.dataset.rowNumberColumn === '1') return;
|
||||
applyColumnWidth(columnIndex, width);
|
||||
});
|
||||
table.style.tableLayout = 'fixed';
|
||||
table.style.width = `${total}px`;
|
||||
table.style.minWidth = `${total}px`;
|
||||
}
|
||||
|
||||
function synchronizeTableWidth() {
|
||||
if (table.id === 'asset-table') return;
|
||||
const total = [...headerRow.cells].reduce((sum, cell) => sum + Math.round(cell.getBoundingClientRect().width || 0), 0);
|
||||
if (total > 0) {
|
||||
table.style.width = `${total}px`;
|
||||
table.style.minWidth = `${total}px`;
|
||||
}
|
||||
}
|
||||
|
||||
function applyStoredColumnWidths() {
|
||||
let restored = false;
|
||||
[...headerRow.cells].forEach((header, columnIndex) => {
|
||||
if (header.dataset.rowNumberColumn === '1') return;
|
||||
const stored = readStoredColumnWidth(header, columnIndex);
|
||||
if (stored !== null) applyColumnWidth(columnIndex, stored);
|
||||
if (stored !== null) {
|
||||
applyColumnWidth(columnIndex, stored);
|
||||
restored = true;
|
||||
}
|
||||
});
|
||||
if (restored && table.id !== 'asset-table') {
|
||||
table.style.tableLayout = 'fixed';
|
||||
requestAnimationFrame(synchronizeTableWidth);
|
||||
}
|
||||
}
|
||||
|
||||
function installColumnResizers() {
|
||||
[...headerRow.cells].forEach((header, columnIndex) => {
|
||||
if (header.dataset.rowNumberColumn === '1') return;
|
||||
if (header.dataset.noResize === '1') return;
|
||||
if (header.dataset.noResize === '1' || header.dataset.rowNumberColumn === '1') return;
|
||||
if (header.querySelector(':scope > .table-column-resizer')) return;
|
||||
|
||||
header.classList.add('table-resizable-column-header');
|
||||
@@ -334,34 +364,72 @@
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const startX = event.clientX;
|
||||
const measured = header.getBoundingClientRect().width;
|
||||
const startWidth = measured > 0 ? measured : minimumColumnWidth;
|
||||
handle.setPointerCapture?.(event.pointerId);
|
||||
freezeTableGeometry();
|
||||
|
||||
const headers = [...headerRow.cells];
|
||||
const rightmostIndex = headers.length - 1;
|
||||
const startWidth = header.getBoundingClientRect().width || minimumColumnWidth;
|
||||
const rightmostHeader = headers[rightmostIndex];
|
||||
const rightmostStartWidth = rightmostHeader?.getBoundingClientRect().width || minimumColumnWidth;
|
||||
const keepTableWidth = columnIndex !== rightmostIndex;
|
||||
|
||||
document.documentElement.classList.add('table-column-resizing');
|
||||
|
||||
const move = moveEvent => {
|
||||
applyColumnWidth(columnIndex, startWidth + moveEvent.clientX - startX);
|
||||
const resizePair = delta => {
|
||||
if (!keepTableWidth) {
|
||||
applyColumnWidth(columnIndex, startWidth + delta);
|
||||
synchronizeTableWidth();
|
||||
return;
|
||||
}
|
||||
|
||||
// All columns between the dragged column and the far-right column
|
||||
// stay unchanged. The far-right column alone absorbs the delta.
|
||||
const minDelta = minimumColumnWidth - startWidth;
|
||||
const maxDelta = rightmostStartWidth - minimumColumnWidth;
|
||||
const effectiveDelta = Math.max(minDelta, Math.min(maxDelta, delta));
|
||||
applyColumnWidth(columnIndex, startWidth + effectiveDelta);
|
||||
applyColumnWidth(rightmostIndex, rightmostStartWidth - effectiveDelta);
|
||||
};
|
||||
const finish = finishEvent => {
|
||||
const current = header.getBoundingClientRect().width || startWidth;
|
||||
saveColumnWidth(header, columnIndex, current);
|
||||
handle.releasePointerCapture?.(finishEvent.pointerId);
|
||||
|
||||
const move = moveEvent => resizePair(moveEvent.clientX - startX);
|
||||
const finish = () => {
|
||||
saveColumnWidth(header, columnIndex, header.getBoundingClientRect().width || startWidth);
|
||||
if (keepTableWidth && rightmostHeader) {
|
||||
saveColumnWidth(rightmostHeader, rightmostIndex, rightmostHeader.getBoundingClientRect().width || rightmostStartWidth);
|
||||
} else {
|
||||
synchronizeTableWidth();
|
||||
}
|
||||
document.documentElement.classList.remove('table-column-resizing');
|
||||
handle.removeEventListener('pointermove', move);
|
||||
handle.removeEventListener('pointerup', finish);
|
||||
handle.removeEventListener('pointercancel', finish);
|
||||
window.removeEventListener('pointermove', move, true);
|
||||
window.removeEventListener('pointerup', finish, true);
|
||||
window.removeEventListener('pointercancel', finish, true);
|
||||
};
|
||||
handle.addEventListener('pointermove', move);
|
||||
handle.addEventListener('pointerup', finish);
|
||||
handle.addEventListener('pointercancel', finish);
|
||||
window.addEventListener('pointermove', move, true);
|
||||
window.addEventListener('pointerup', finish, true);
|
||||
window.addEventListener('pointercancel', finish, true);
|
||||
});
|
||||
|
||||
handle.addEventListener('keydown', event => {
|
||||
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
freezeTableGeometry();
|
||||
const headers = [...headerRow.cells];
|
||||
const rightmostIndex = headers.length - 1;
|
||||
const step = event.key === 'ArrowRight' ? 12 : -12;
|
||||
const current = header.getBoundingClientRect().width || minimumColumnWidth;
|
||||
saveColumnWidth(header, columnIndex, current + (event.key === 'ArrowRight' ? 12 : -12));
|
||||
if (columnIndex === rightmostIndex) {
|
||||
saveColumnWidth(header, columnIndex, current + step);
|
||||
synchronizeTableWidth();
|
||||
return;
|
||||
}
|
||||
const rightmostHeader = headers[rightmostIndex];
|
||||
const rightmostWidth = rightmostHeader?.getBoundingClientRect().width || minimumColumnWidth;
|
||||
const minDelta = minimumColumnWidth - current;
|
||||
const maxDelta = rightmostWidth - minimumColumnWidth;
|
||||
const effectiveDelta = Math.max(minDelta, Math.min(maxDelta, step));
|
||||
saveColumnWidth(header, columnIndex, current + effectiveDelta);
|
||||
if (rightmostHeader) saveColumnWidth(rightmostHeader, rightmostIndex, rightmostWidth - effectiveDelta);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -796,8 +864,10 @@
|
||||
});
|
||||
|
||||
thead.appendChild(filterRow);
|
||||
installColumnResizers();
|
||||
applyStoredColumnWidths();
|
||||
if (isAssetTable) {
|
||||
installColumnResizers();
|
||||
applyStoredColumnWidths();
|
||||
}
|
||||
|
||||
// Editable cells are always read live, so changed values immediately
|
||||
// participate in filtering without a stale cache.
|
||||
@@ -840,7 +910,147 @@
|
||||
}));
|
||||
}
|
||||
|
||||
function initUniversalTableResize(table) {
|
||||
if (!table || table.id === 'asset-table' || table.dataset.columnResizeReady === '1') return;
|
||||
const thead = table.tHead;
|
||||
const tbody = table.tBodies?.[0];
|
||||
if (!thead || !tbody || !thead.rows.length) return;
|
||||
|
||||
const headerRow = thead.rows[0];
|
||||
const storagePart = table.dataset.storageKey || table.id || `${window.location.pathname}:table-${[...document.querySelectorAll('main table')].indexOf(table)}`;
|
||||
const storagePrefix = `assetmanager:table:${storagePart}:column-width:`;
|
||||
const minWidth = 56;
|
||||
const maxWidth = 1200;
|
||||
const normalize = width => Math.min(maxWidth, Math.max(minWidth, Math.round(width)));
|
||||
|
||||
const columnKey = (header, index) => header.dataset.field || `index-${index}`;
|
||||
const applyWidth = (index, width) => {
|
||||
const normalized = normalize(width);
|
||||
[...thead.rows, ...tbody.rows].forEach(row => {
|
||||
const cell = row.cells?.[index];
|
||||
if (!cell) return;
|
||||
cell.style.width = `${normalized}px`;
|
||||
cell.style.minWidth = `${normalized}px`;
|
||||
cell.style.maxWidth = `${normalized}px`;
|
||||
});
|
||||
return normalized;
|
||||
};
|
||||
|
||||
const freezeGeometry = () => {
|
||||
const widths = [...headerRow.cells].map(cell => normalize(cell.getBoundingClientRect().width || minWidth));
|
||||
const total = widths.reduce((sum, width) => sum + width, 0);
|
||||
widths.forEach((width, index) => applyWidth(index, width));
|
||||
table.style.tableLayout = 'fixed';
|
||||
table.style.width = `${total}px`;
|
||||
table.style.minWidth = `${total}px`;
|
||||
};
|
||||
|
||||
const syncTableWidth = () => {
|
||||
const total = [...headerRow.cells].reduce((sum, cell) => sum + Math.round(cell.getBoundingClientRect().width || 0), 0);
|
||||
if (total > 0) {
|
||||
table.style.width = `${total}px`;
|
||||
table.style.minWidth = `${total}px`;
|
||||
}
|
||||
};
|
||||
|
||||
[...headerRow.cells].forEach((header, index) => {
|
||||
if (header.dataset.noResize === '1' || header.dataset.rowNumberColumn === '1') return;
|
||||
const key = `${storagePrefix}${columnKey(header, index)}`;
|
||||
try {
|
||||
const stored = Number.parseInt(localStorage.getItem(key) || '', 10);
|
||||
if (Number.isFinite(stored)) applyWidth(index, stored);
|
||||
} catch (_) {}
|
||||
|
||||
if (header.querySelector(':scope > .table-column-resizer')) return;
|
||||
header.classList.add('table-resizable-column-header');
|
||||
const handle = document.createElement('span');
|
||||
handle.className = 'table-column-resizer';
|
||||
handle.setAttribute('role', 'separator');
|
||||
handle.setAttribute('aria-orientation', 'vertical');
|
||||
handle.tabIndex = 0;
|
||||
handle.title = document.body.dataset.columnResizeLabel || 'Drag to change the column width';
|
||||
header.appendChild(handle);
|
||||
|
||||
const save = width => {
|
||||
const normalized = applyWidth(index, width);
|
||||
try { localStorage.setItem(key, String(normalized)); } catch (_) {}
|
||||
};
|
||||
handle.addEventListener('click', event => { event.preventDefault(); event.stopPropagation(); });
|
||||
handle.addEventListener('pointerdown', event => {
|
||||
event.preventDefault(); event.stopPropagation();
|
||||
const startX = event.clientX;
|
||||
freezeGeometry();
|
||||
const headers = [...headerRow.cells];
|
||||
const rightmostIndex = headers.length - 1;
|
||||
const startWidth = header.getBoundingClientRect().width || minWidth;
|
||||
const rightmostHeader = headers[rightmostIndex];
|
||||
const rightmostStartWidth = rightmostHeader?.getBoundingClientRect().width || minWidth;
|
||||
const keepTableWidth = index !== rightmostIndex;
|
||||
document.documentElement.classList.add('table-column-resizing');
|
||||
|
||||
const resizePair = delta => {
|
||||
if (!keepTableWidth) {
|
||||
applyWidth(index, startWidth + delta);
|
||||
syncTableWidth();
|
||||
return;
|
||||
}
|
||||
const minDelta = minWidth - startWidth;
|
||||
const maxDelta = rightmostStartWidth - minWidth;
|
||||
const effectiveDelta = Math.max(minDelta, Math.min(maxDelta, delta));
|
||||
applyWidth(index, startWidth + effectiveDelta);
|
||||
applyWidth(rightmostIndex, rightmostStartWidth - effectiveDelta);
|
||||
};
|
||||
|
||||
const move = e => resizePair(e.clientX - startX);
|
||||
const finish = () => {
|
||||
save(header.getBoundingClientRect().width || startWidth);
|
||||
if (keepTableWidth && rightmostHeader) {
|
||||
const rightKey = `${storagePrefix}${columnKey(rightmostHeader, rightmostIndex)}`;
|
||||
const rightWidth = applyWidth(rightmostIndex, rightmostHeader.getBoundingClientRect().width || rightmostStartWidth);
|
||||
try { localStorage.setItem(rightKey, String(rightWidth)); } catch (_) {}
|
||||
} else {
|
||||
syncTableWidth();
|
||||
}
|
||||
document.documentElement.classList.remove('table-column-resizing');
|
||||
window.removeEventListener('pointermove', move, true);
|
||||
window.removeEventListener('pointerup', finish, true);
|
||||
window.removeEventListener('pointercancel', finish, true);
|
||||
};
|
||||
window.addEventListener('pointermove', move, true);
|
||||
window.addEventListener('pointerup', finish, true);
|
||||
window.addEventListener('pointercancel', finish, true);
|
||||
});
|
||||
handle.addEventListener('keydown', event => {
|
||||
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
|
||||
event.preventDefault(); event.stopPropagation();
|
||||
freezeGeometry();
|
||||
const headers = [...headerRow.cells];
|
||||
const rightmostIndex = headers.length - 1;
|
||||
const step = event.key === 'ArrowRight' ? 12 : -12;
|
||||
const current = header.getBoundingClientRect().width || minWidth;
|
||||
if (index === rightmostIndex) {
|
||||
save(current + step);
|
||||
syncTableWidth();
|
||||
return;
|
||||
}
|
||||
const rightmostHeader = headers[rightmostIndex];
|
||||
const rightmostWidth = rightmostHeader?.getBoundingClientRect().width || minWidth;
|
||||
const minDelta = minWidth - current;
|
||||
const maxDelta = rightmostWidth - minWidth;
|
||||
const effectiveDelta = Math.max(minDelta, Math.min(maxDelta, step));
|
||||
save(current + effectiveDelta);
|
||||
if (rightmostHeader) {
|
||||
const rightKey = `${storagePrefix}${columnKey(rightmostHeader, rightmostIndex)}`;
|
||||
const rightWidth = applyWidth(rightmostIndex, rightmostWidth - effectiveDelta);
|
||||
try { localStorage.setItem(rightKey, String(rightWidth)); } catch (_) {}
|
||||
}
|
||||
});
|
||||
});
|
||||
table.dataset.columnResizeReady = '1';
|
||||
}
|
||||
|
||||
document.querySelectorAll('table.data-table').forEach(initTable);
|
||||
document.querySelectorAll('main table:not(#asset-table):not([data-column-resize="off"])').forEach(initUniversalTableResize);
|
||||
|
||||
window.addEventListener('asset-table-content-reloaded', event => {
|
||||
const table = event.detail?.table;
|
||||
|
||||
Reference in New Issue
Block a user