Make large table widths fluid
This commit is contained in:
1 parent
0165b6c1da
commit
d88540d859
3 files changed
+58
-65
No files matched your search
@@ -77,7 +77,7 @@
|
|||||||
|
|
||||||
<p class="sg-preview-label">Component: Large Table</p>
|
<p class="sg-preview-label">Component: Large Table</p>
|
||||||
<p>Suche, Sortierung und „Mehr laden“ sind hier Demo-Markup. Produktions-Implementierungen müssen Treffer serverseitig neu rendern.</p>
|
<p>Suche, Sortierung und „Mehr laden“ sind hier Demo-Markup. Produktions-Implementierungen müssen Treffer serverseitig neu rendern.</p>
|
||||||
<p>Optionale Spaltenbreiten-Anpassung: <code>data-column-resize="true"</code> aktiviert die Funktion. Die Tabelle benötigt dafür eine stabile, portalweit eindeutige <code>data-column-resize-key</code>. Die Header-Zellen können mit <code>data-default-width</code> (Prozent, Pixel oder relative <code>fr</code>-Werte) und die Tabelle mit <code>data-column-min-width</code> konfiguriert werden. Die Runtime wird nach dynamischem Nachladen über <code>window.sgInitLargeTableColumnResize({ root })</code> erneut aufgerufen. Gespeicherte Werte liegen als <code>{ version: 1, widths: number[] }</code> unter <code>sg:large-table:column-widths:<key></code> in <code>localStorage</code>.</p>
|
<p>Optionale Spaltenbreiten-Anpassung: <code>data-column-resize="true"</code> aktiviert die Funktion. Die Tabelle benötigt dafür eine stabile, portalweit eindeutige <code>data-column-resize-key</code>. Die Header-Zellen können mit <code>data-default-width</code> (Prozent, Pixel oder relative <code>fr</code>-Werte) und die Tabelle mit <code>data-column-min-width</code> konfiguriert werden. Die Runtime wird nach dynamischem Nachladen über <code>window.sgInitLargeTableColumnResize({ root })</code> erneut aufgerufen. Gespeicherte Werte liegen als <code>{ version: 3, ratios: number[] }</code> unter <code>sg:large-table:column-widths:<key></code> in <code>localStorage</code>; die Werte sind proportionale Anteile der verfügbaren Tabellenbreite.</p>
|
||||||
|
|
||||||
<article id="styleguide-large-table" class="sg-card sg-large-table" data-component="large-table" data-column-resize="true" data-column-resize-key="styleguide-large-table" data-column-min-width="8rem" role="table" aria-label="Beispiel Large Table">
|
<article id="styleguide-large-table" class="sg-card sg-large-table" data-component="large-table" data-column-resize="true" data-column-resize-key="styleguide-large-table" data-column-min-width="8rem" role="table" aria-label="Beispiel Large Table">
|
||||||
<div class="sg-large-table__viewport" data-component-part="large-table-viewport">
|
<div class="sg-large-table__viewport" data-component-part="large-table-viewport">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
(function initLargeTableColumnResizeModule() {
|
(function initLargeTableColumnResizeModule() {
|
||||||
const STORAGE_PREFIX = 'sg:large-table:column-widths:';
|
const STORAGE_PREFIX = 'sg:large-table:column-widths:';
|
||||||
const STORAGE_VERSION = 2;
|
const STORAGE_VERSION = 3;
|
||||||
const DEFAULT_MIN_WIDTH = 128;
|
const DEFAULT_MIN_WIDTH = 128;
|
||||||
const KEYBOARD_STEP = 8;
|
const KEYBOARD_STEP = 8;
|
||||||
const KEYBOARD_LARGE_STEP = 40;
|
const KEYBOARD_LARGE_STEP = 40;
|
||||||
@@ -44,41 +44,27 @@
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getGridTrackWidth = (table) => {
|
const normalizeRatios = (values) => {
|
||||||
const row = table.querySelector('[data-component-part="large-table-header-row"]');
|
const safeValues = values.map((value) => (Number.isFinite(value) && value > 0 ? value : 0));
|
||||||
if (!row) {
|
const total = safeValues.reduce((sum, value) => sum + value, 0);
|
||||||
return table.clientWidth;
|
|
||||||
}
|
|
||||||
const rowStyles = getComputedStyle(row);
|
|
||||||
const horizontalPadding = (parseFloat(rowStyles.paddingLeft) || 0)
|
|
||||||
+ (parseFloat(rowStyles.paddingRight) || 0);
|
|
||||||
return Math.max(0, row.clientWidth - horizontalPadding);
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeWidths = (widths, tableWidth, minWidth) => {
|
|
||||||
const safeWidths = widths.map((width) => Math.max(minWidth, width));
|
|
||||||
const total = safeWidths.reduce((sum, width) => sum + width, 0);
|
|
||||||
if (total <= 0) {
|
if (total <= 0) {
|
||||||
return Array(widths.length).fill(Math.max(minWidth, tableWidth / widths.length));
|
return Array(values.length).fill(1 / values.length);
|
||||||
}
|
}
|
||||||
if (widths.some((width) => width !== width)) {
|
return safeValues.map((value) => value / total);
|
||||||
return Array(widths.length).fill(Math.max(minWidth, tableWidth / widths.length));
|
|
||||||
}
|
|
||||||
return safeWidths.map((width) => width * tableWidth / total);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDefaultWidths = (table, headers, minWidth) => {
|
const getDefaultRatios = (table, headers, minWidth) => {
|
||||||
const tableWidth = Math.max(getGridTrackWidth(table), minWidth * headers.length);
|
const tableWidth = Math.max(table.querySelector('[data-component-part="large-table-viewport"]')?.clientWidth || 0, minWidth * headers.length);
|
||||||
const configured = headers.map((header) => parseDefaultWidth(header.dataset.defaultWidth, tableWidth));
|
const configured = headers.map((header) => parseDefaultWidth(header.dataset.defaultWidth, tableWidth));
|
||||||
if (configured.some((width) => width === null)) {
|
if (configured.some((width) => width === null)) {
|
||||||
return Array(headers.length).fill(Math.max(minWidth, tableWidth / headers.length));
|
return Array(headers.length).fill(1 / headers.length);
|
||||||
}
|
}
|
||||||
return normalizeWidths(configured, tableWidth, minWidth);
|
return normalizeRatios(configured);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStorageKey = (table) => `${STORAGE_PREFIX}${table.dataset.columnResizeKey || ''}`;
|
const getStorageKey = (table) => `${STORAGE_PREFIX}${table.dataset.columnResizeKey || ''}`;
|
||||||
|
|
||||||
const readStoredWidths = (table, count, minWidth) => {
|
const readStoredRatios = (table, count) => {
|
||||||
const key = getStorageKey(table);
|
const key = getStorageKey(table);
|
||||||
if (!table.dataset.columnResizeKey) {
|
if (!table.dataset.columnResizeKey) {
|
||||||
return null;
|
return null;
|
||||||
@@ -88,19 +74,19 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const stored = JSON.parse(window.localStorage.getItem(key));
|
const stored = JSON.parse(window.localStorage.getItem(key));
|
||||||
if (stored?.version !== STORAGE_VERSION || !Array.isArray(stored.widths) || stored.widths.length !== count) {
|
if (stored?.version !== STORAGE_VERSION || !Array.isArray(stored.ratios) || stored.ratios.length !== count) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (stored.widths.some((width) => !Number.isFinite(width) || width < minWidth)) {
|
if (stored.ratios.some((ratio) => !Number.isFinite(ratio) || ratio <= 0)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return stored.widths;
|
return normalizeRatios(stored.ratios);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const writeStoredWidths = (table, widths) => {
|
const writeStoredRatios = (table, ratios) => {
|
||||||
if (!table.dataset.columnResizeKey) {
|
if (!table.dataset.columnResizeKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -110,7 +96,7 @@
|
|||||||
}
|
}
|
||||||
window.localStorage.setItem(getStorageKey(table), JSON.stringify({
|
window.localStorage.setItem(getStorageKey(table), JSON.stringify({
|
||||||
version: STORAGE_VERSION,
|
version: STORAGE_VERSION,
|
||||||
widths: widths.map((width) => Math.round(width)),
|
ratios: normalizeRatios(ratios),
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Storage can be unavailable in privacy-restricted browser contexts.
|
// Storage can be unavailable in privacy-restricted browser contexts.
|
||||||
@@ -133,35 +119,37 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyWidths = (table, widths, minWidth) => {
|
const getRenderedWidths = (table, count, minWidth) => {
|
||||||
const safeWidths = widths.map((width) => Math.max(minWidth, width));
|
const headers = getColumnHeaders(table);
|
||||||
const baseTotal = safeWidths.reduce((sum, width) => sum + width, 0);
|
const widths = headers.map((header) => header.getBoundingClientRect().width);
|
||||||
const effectiveTotal = Math.max(baseTotal, getGridTrackWidth(table));
|
if (widths.length === count && widths.every((width) => width > 0)) {
|
||||||
const effectiveWidths = safeWidths.map((width, index) => (
|
return widths;
|
||||||
index === safeWidths.length - 1 ? width + effectiveTotal - baseTotal : width
|
}
|
||||||
));
|
return Array(count).fill(minWidth);
|
||||||
const total = effectiveWidths.reduce((sum, width) => sum + width, 0);
|
|
||||||
const columnTemplate = effectiveWidths.map((width, index) => (
|
|
||||||
index === safeWidths.length - 1
|
|
||||||
? `minmax(${Math.round(width)}px, 1fr)`
|
|
||||||
: `${Math.round(width)}px`
|
|
||||||
)).join(' ');
|
|
||||||
table.style.setProperty('--large-table-grid-template-columns', columnTemplate);
|
|
||||||
table.style.setProperty('--large-table-grid-min-width', `${Math.ceil(total)}px`);
|
|
||||||
table._largeTableColumnWidths = effectiveWidths;
|
|
||||||
updateHandleState(table, effectiveWidths, minWidth);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const resizeColumn = (table, index, delta, persist) => {
|
const applyRatios = (table, ratios, minWidth) => {
|
||||||
const widths = [...table._largeTableColumnWidths];
|
const safeRatios = normalizeRatios(ratios);
|
||||||
|
const columnTemplate = safeRatios
|
||||||
|
.map((ratio) => `minmax(${Math.ceil(minWidth)}px, ${ratio}fr)`)
|
||||||
|
.join(' ');
|
||||||
|
table.style.setProperty('--large-table-grid-template-columns', columnTemplate);
|
||||||
|
table.style.setProperty('--large-table-grid-min-width', `${Math.ceil(minWidth * safeRatios.length)}px`);
|
||||||
|
table._largeTableColumnRatios = safeRatios;
|
||||||
|
table._largeTableColumnWidths = getRenderedWidths(table, safeRatios.length, minWidth);
|
||||||
|
updateHandleState(table, table._largeTableColumnWidths, minWidth);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeColumn = (table, index, delta, persist, baseWidths = table._largeTableColumnWidths) => {
|
||||||
|
const widths = [...baseWidths];
|
||||||
const minWidth = getMinWidth(table);
|
const minWidth = getMinWidth(table);
|
||||||
const nextLeft = Math.max(minWidth, Math.min(widths[index] + widths[index + 1] - minWidth, widths[index] + delta));
|
const nextLeft = Math.max(minWidth, Math.min(widths[index] + widths[index + 1] - minWidth, widths[index] + delta));
|
||||||
const appliedDelta = nextLeft - widths[index];
|
const appliedDelta = nextLeft - widths[index];
|
||||||
widths[index] = nextLeft;
|
widths[index] = nextLeft;
|
||||||
widths[index + 1] -= appliedDelta;
|
widths[index + 1] -= appliedDelta;
|
||||||
applyWidths(table, widths, minWidth);
|
applyRatios(table, widths.map((width) => width / widths.reduce((sum, value) => sum + value, 0)), minWidth);
|
||||||
if (persist) {
|
if (persist) {
|
||||||
writeStoredWidths(table, widths);
|
writeStoredRatios(table, table._largeTableColumnRatios);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -171,8 +159,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const minWidth = getMinWidth(table);
|
const minWidth = getMinWidth(table);
|
||||||
const stored = readStoredWidths(table, headers.length, minWidth);
|
const stored = readStoredRatios(table, headers.length);
|
||||||
applyWidths(table, stored || getDefaultWidths(table, headers, minWidth), minWidth);
|
applyRatios(table, stored || getDefaultRatios(table, headers, minWidth), minWidth);
|
||||||
|
|
||||||
if (table.dataset.largeTableResizeInitialized === 'true') {
|
if (table.dataset.largeTableResizeInitialized === 'true') {
|
||||||
return;
|
return;
|
||||||
@@ -201,20 +189,32 @@
|
|||||||
if (!activeResize || activeResize.table !== table) {
|
if (!activeResize || activeResize.table !== table) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
applyWidths(table, activeResize.startWidths, getMinWidth(table));
|
resizeColumn(
|
||||||
resizeColumn(table, activeResize.index, event.clientX - activeResize.startX, false);
|
table,
|
||||||
|
activeResize.index,
|
||||||
|
event.clientX - activeResize.startX,
|
||||||
|
false,
|
||||||
|
activeResize.startWidths,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
table.addEventListener('pointerup', () => {
|
table.addEventListener('pointerup', () => {
|
||||||
if (!activeResize || activeResize.table !== table) {
|
if (!activeResize || activeResize.table !== table) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writeStoredWidths(table, table._largeTableColumnWidths);
|
writeStoredRatios(table, table._largeTableColumnRatios);
|
||||||
activeResize.handle.dataset.largeTableResizeActive = 'false';
|
activeResize.handle.dataset.largeTableResizeActive = 'false';
|
||||||
table.dataset.columnResizeState = 'idle';
|
table.dataset.columnResizeState = 'idle';
|
||||||
activeResize = null;
|
activeResize = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (typeof ResizeObserver === 'function') {
|
||||||
|
table._largeTableResizeObserver = new ResizeObserver(() => {
|
||||||
|
applyRatios(table, table._largeTableColumnRatios, getMinWidth(table));
|
||||||
|
});
|
||||||
|
table._largeTableResizeObserver.observe(table.querySelector('[data-component-part="large-table-viewport"]'));
|
||||||
|
}
|
||||||
|
|
||||||
table.addEventListener('keydown', (event) => {
|
table.addEventListener('keydown', (event) => {
|
||||||
const handle = event.target.closest('[data-large-table-resize-handle]');
|
const handle = event.target.closest('[data-large-table-resize-handle]');
|
||||||
if (!handle || !table.contains(handle)) {
|
if (!handle || !table.contains(handle)) {
|
||||||
|
|||||||
@@ -106,14 +106,7 @@
|
|||||||
|
|
||||||
.sg-large-table[data-column-resize="true"] .sg-large-table__row,
|
.sg-large-table[data-column-resize="true"] .sg-large-table__row,
|
||||||
.sg-large-table[data-column-resize="true"] .sg-large-table__title-segment {
|
.sg-large-table[data-column-resize="true"] .sg-large-table__title-segment {
|
||||||
width: max(
|
width: max(100%, var(--large-table-grid-min-width, 0px));
|
||||||
100%,
|
|
||||||
calc(
|
|
||||||
var(--large-table-grid-min-width, 0px) +
|
|
||||||
var(--card-segment-padding-horizontal) +
|
|
||||||
var(--card-segment-padding-horizontal)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sg-large-table__row--hidden {
|
.sg-large-table__row--hidden {
|
||||||
|
|||||||
Reference in new issue
Block a user