compote-ui
Version:
An opinionated UI component library for Svelte, built on top of [Ark UI](https://ark-ui.com) with additional components and features not available in the core Ark UI library.
183 lines (182 loc) • 6.98 kB
JavaScript
import { getColumnMeta } from './data-table-utils';
/**
* Shared derived state for the standard and virtualized table roots.
*
* Writable slice atoms are rune-backed, so reading them inside a $derived
* registers the dependency. Derived table APIs (getRowModel, header groups,
* getVisibleLeafColumns, …) are not reliably tracked by the beta adapter, so
* each derived below first reads the slices its result depends on.
*/
export function createTableViewState(getTable) {
const table = $derived.by(getTable);
const columnPinning = $derived.by(() => table.atoms.columnPinning.get());
const columnResizing = $derived.by(() => table.atoms.columnResizing.get());
const columnSizing = $derived.by(() => table.atoms.columnSizing.get());
const columnVisibility = $derived.by(() => table.atoms.columnVisibility.get());
const rowSelection = $derived.by(() => table.atoms.rowSelection.get());
const sorting = $derived.by(() => table.atoms.sorting.get());
const columnFilters = $derived.by(() => table.atoms.columnFilters.get());
const globalFilter = $derived.by(() => table.atoms.globalFilter.get());
const rowModel = $derived.by(() => {
void columnFilters;
void globalFilter;
void sorting;
return table.getRowModel();
});
const headerGroupsData = $derived.by(() => {
void columnPinning;
void columnSizing;
void columnVisibility;
return buildHeaderGroups(table);
});
const visibleLeafColumns = $derived.by(() => {
void columnSizing;
void columnVisibility;
return table.getVisibleLeafColumns();
});
const growColumn = $derived(visibleLeafColumns.find((col) => getColumnMeta(col.columnDef)?.grow));
const isRowSelectionEnabled = $derived(Boolean(table.options.enableRowSelection));
const isMultiRowSelectionEnabled = $derived(table.options.enableMultiRowSelection !== false);
const allRowsSelectionState = $derived.by(() => {
void rowSelection;
void rowModel;
return table.getIsAllRowsSelected()
? true
: table.getIsSomeRowsSelected()
? 'indeterminate'
: false;
});
const selectedRowCount = $derived.by(() => {
void rowSelection;
void rowModel;
return table.getSelectedRowModel().rows.length;
});
const isColumnResizing = $derived(columnResizing.isResizingColumn !== false);
const hasFooter = $derived(visibleLeafColumns.some((col) => {
const meta = getColumnMeta(col.columnDef);
return !!(meta?.sum || meta?.footer);
}));
return {
get table() {
return table;
},
get columnPinning() {
return columnPinning;
},
get columnResizing() {
return columnResizing;
},
get columnSizing() {
return columnSizing;
},
get columnVisibility() {
return columnVisibility;
},
get rowSelection() {
return rowSelection;
},
get sorting() {
return sorting;
},
get rowModel() {
return rowModel;
},
get headerGroups() {
return headerGroupsData.groups;
},
getHeaderSection(header) {
return headerGroupsData.sections.get(header);
},
get visibleLeafColumns() {
return visibleLeafColumns;
},
get growColumn() {
return growColumn;
},
get hasGrowColumn() {
return growColumn !== undefined;
},
get isRowSelectionEnabled() {
return isRowSelectionEnabled;
},
get isMultiRowSelectionEnabled() {
return isMultiRowSelectionEnabled;
},
get allRowsSelectionState() {
return allRowsSelectionState;
},
get selectedRowCount() {
return selectedRowCount;
},
get isColumnResizing() {
return isColumnResizing;
},
get hasFooter() {
return hasFooter;
}
};
}
/**
* Concatenate the left/center/right header-group sections into single rows.
*
* When a column group spans pinned and unpinned columns, TanStack splits it
* into separate header objects (one per section). The fragments are kept
* separate — the pinned fragment gets sticky positioning so the group label
* stays visible during horizontal scroll — but the label renders only once:
* on the pinned fragment, with the other fragments cloned as placeholders.
* The clone preserves the header's prototype so its methods (getContext,
* getSize, …) keep working.
*
* The returned WeakMap records which section each header came from; the head
* component needs it because a fragment's `column` spans all sections and
* can't identify the fragment's own section.
*/
function buildHeaderGroups(table) {
const sections = new WeakMap();
const leftHeaderGroups = table.getLeftHeaderGroups();
const centerHeaderGroups = table.getCenterHeaderGroups();
const rightHeaderGroups = table.getRightHeaderGroups();
const groups = centerHeaderGroups.map((headerGroup, index) => {
const parts = [
...(leftHeaderGroups[index]?.headers ?? []).map((header) => ({
header,
section: 'left'
})),
...headerGroup.headers.map((header) => ({ header, section: 'center' })),
...(rightHeaderGroups[index]?.headers ?? []).map((header) => ({
header,
section: 'right'
}))
];
const headers = [];
let i = 0;
while (i < parts.length) {
let j = i;
while (j + 1 < parts.length &&
parts[j + 1].header.column.id === parts[i].header.column.id) {
j++;
}
const run = parts.slice(i, j + 1);
// The label lives on the pinned fragment so it stays visible; left wins
// over right when a group somehow spans both pinned sections.
const labelIndex = Math.max(0, run.findIndex((part) => part.section !== 'center'));
run.forEach((part, k) => {
let header = part.header;
if (run.length > 1 && k !== labelIndex && !header.isPlaceholder) {
const clone = Object.assign(Object.create(Object.getPrototypeOf(header)), header);
clone.isPlaceholder = true;
header = clone;
}
sections.set(header, part.section);
headers.push(header);
});
i = j + 1;
}
return {
...headerGroup,
id: `${leftHeaderGroups[index]?.id ?? ''}|${headerGroup.id}|${rightHeaderGroups[index]?.id ?? ''}`,
headers
};
});
return { groups, sections };
}