@urbicon-ui/table 6.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/dist/cells/ActionButtons.svelte +224 -0
- package/dist/cells/ActionButtons.svelte.d.ts +74 -0
- package/dist/cells/CopyButton.svelte +89 -0
- package/dist/cells/CopyButton.svelte.d.ts +33 -0
- package/dist/cells/CustomCell.svelte +136 -0
- package/dist/cells/CustomCell.svelte.d.ts +44 -0
- package/dist/cells/DateCell.svelte +194 -0
- package/dist/cells/DateCell.svelte.d.ts +39 -0
- package/dist/cells/LinkCell.svelte +240 -0
- package/dist/cells/LinkCell.svelte.d.ts +42 -0
- package/dist/cells/NumberCell.svelte +225 -0
- package/dist/cells/NumberCell.svelte.d.ts +47 -0
- package/dist/cells/StatusBadge.svelte +121 -0
- package/dist/cells/StatusBadge.svelte.d.ts +44 -0
- package/dist/cells/UserAvatar.svelte +71 -0
- package/dist/cells/UserAvatar.svelte.d.ts +37 -0
- package/dist/cells/index.d.ts +8 -0
- package/dist/cells/index.js +9 -0
- package/dist/core/EmptyState.svelte +161 -0
- package/dist/core/EmptyState.svelte.d.ts +16 -0
- package/dist/core/ErrorState.svelte +158 -0
- package/dist/core/ErrorState.svelte.d.ts +15 -0
- package/dist/core/GroupedRow.svelte +239 -0
- package/dist/core/GroupedRow.svelte.d.ts +18 -0
- package/dist/core/LoadingState.svelte +75 -0
- package/dist/core/LoadingState.svelte.d.ts +14 -0
- package/dist/core/MobileCard.svelte +151 -0
- package/dist/core/MobileCard.svelte.d.ts +15 -0
- package/dist/core/TableCell.svelte +105 -0
- package/dist/core/TableCell.svelte.d.ts +14 -0
- package/dist/core/TableDesktop.svelte +480 -0
- package/dist/core/TableDesktop.svelte.d.ts +26 -0
- package/dist/core/TableHead.svelte +314 -0
- package/dist/core/TableHead.svelte.d.ts +7 -0
- package/dist/core/TableMobile.svelte +112 -0
- package/dist/core/TableMobile.svelte.d.ts +13 -0
- package/dist/core/TableProvider.svelte +271 -0
- package/dist/core/TableProvider.svelte.d.ts +40 -0
- package/dist/core/TableRow.svelte +171 -0
- package/dist/core/TableRow.svelte.d.ts +16 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.js +14 -0
- package/dist/core/sticky-context.svelte.d.ts +48 -0
- package/dist/core/sticky-context.svelte.js +88 -0
- package/dist/core/table/Table.svelte +304 -0
- package/dist/core/table/Table.svelte.d.ts +26 -0
- package/dist/core/table/index.d.ts +448 -0
- package/dist/core/table/index.js +1 -0
- package/dist/core/table-style-context.d.ts +66 -0
- package/dist/core/table-style-context.js +26 -0
- package/dist/factories/ColumnValidation.d.ts +49 -0
- package/dist/factories/ColumnValidation.js +188 -0
- package/dist/factories/TableColumns.d.ts +97 -0
- package/dist/factories/TableColumns.js +262 -0
- package/dist/factories/TypedColumnBuilder.d.ts +41 -0
- package/dist/factories/TypedColumnBuilder.js +72 -0
- package/dist/factories/index.d.ts +12 -0
- package/dist/factories/index.js +13 -0
- package/dist/features/HeaderMenu.svelte +236 -0
- package/dist/features/HeaderMenu.svelte.d.ts +8 -0
- package/dist/features/LiveUpdateBanner.svelte +66 -0
- package/dist/features/LiveUpdateBanner.svelte.d.ts +6 -0
- package/dist/features/SearchHighlight.svelte +21 -0
- package/dist/features/SearchHighlight.svelte.d.ts +8 -0
- package/dist/features/SmartFilterBar/ChipsField.svelte +104 -0
- package/dist/features/SmartFilterBar/ChipsField.svelte.d.ts +5 -0
- package/dist/features/SmartFilterBar/ColumnVisibilityMenu.svelte +84 -0
- package/dist/features/SmartFilterBar/ColumnVisibilityMenu.svelte.d.ts +3 -0
- package/dist/features/SmartFilterBar/FilterMenu.svelte +367 -0
- package/dist/features/SmartFilterBar/FilterMenu.svelte.d.ts +3 -0
- package/dist/features/SmartFilterBar/GroupingMenu.svelte +82 -0
- package/dist/features/SmartFilterBar/GroupingMenu.svelte.d.ts +3 -0
- package/dist/features/SmartFilterBar/SmartFilterBar.svelte +109 -0
- package/dist/features/SmartFilterBar/SmartFilterBar.svelte.d.ts +11 -0
- package/dist/features/SmartFilterBar/SummaryMenu.svelte +118 -0
- package/dist/features/SmartFilterBar/SummaryMenu.svelte.d.ts +3 -0
- package/dist/features/SummaryRow.svelte +97 -0
- package/dist/features/SummaryRow.svelte.d.ts +8 -0
- package/dist/features/index.d.ts +4 -0
- package/dist/features/index.js +4 -0
- package/dist/i18n/index.d.ts +366 -0
- package/dist/i18n/index.js +21 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +41 -0
- package/dist/stores/TableStore.svelte.d.ts +192 -0
- package/dist/stores/TableStore.svelte.js +362 -0
- package/dist/stores/concerns/index.d.ts +15 -0
- package/dist/stores/concerns/index.js +14 -0
- package/dist/stores/concerns/types.d.ts +31 -0
- package/dist/stores/concerns/types.js +1 -0
- package/dist/stores/concerns/useColumnOrder.svelte.d.ts +16 -0
- package/dist/stores/concerns/useColumnOrder.svelte.js +81 -0
- package/dist/stores/concerns/useColumnVisibility.svelte.d.ts +16 -0
- package/dist/stores/concerns/useColumnVisibility.svelte.js +58 -0
- package/dist/stores/concerns/useExpansion.svelte.d.ts +9 -0
- package/dist/stores/concerns/useExpansion.svelte.js +32 -0
- package/dist/stores/concerns/useFiltering.svelte.d.ts +20 -0
- package/dist/stores/concerns/useFiltering.svelte.js +109 -0
- package/dist/stores/concerns/useFocusManagement.svelte.d.ts +15 -0
- package/dist/stores/concerns/useFocusManagement.svelte.js +52 -0
- package/dist/stores/concerns/useGrouping.svelte.d.ts +15 -0
- package/dist/stores/concerns/useGrouping.svelte.js +86 -0
- package/dist/stores/concerns/useLiveUpdates.svelte.d.ts +45 -0
- package/dist/stores/concerns/useLiveUpdates.svelte.js +175 -0
- package/dist/stores/concerns/usePagination.svelte.d.ts +18 -0
- package/dist/stores/concerns/usePagination.svelte.js +54 -0
- package/dist/stores/concerns/usePersistence.svelte.d.ts +36 -0
- package/dist/stores/concerns/usePersistence.svelte.js +167 -0
- package/dist/stores/concerns/useRemoteData.svelte.d.ts +21 -0
- package/dist/stores/concerns/useRemoteData.svelte.js +64 -0
- package/dist/stores/concerns/useSearch.svelte.d.ts +8 -0
- package/dist/stores/concerns/useSearch.svelte.js +16 -0
- package/dist/stores/concerns/useSelection.svelte.d.ts +21 -0
- package/dist/stores/concerns/useSelection.svelte.js +110 -0
- package/dist/stores/concerns/useSorting.svelte.d.ts +11 -0
- package/dist/stores/concerns/useSorting.svelte.js +70 -0
- package/dist/stores/concerns/useSummary.svelte.d.ts +18 -0
- package/dist/stores/concerns/useSummary.svelte.js +96 -0
- package/dist/stores/index.d.ts +1 -0
- package/dist/stores/index.js +1 -0
- package/dist/style/index.css +137 -0
- package/dist/style/index.d.ts +2 -0
- package/dist/style/index.js +2 -0
- package/dist/style/table-theme.css +131 -0
- package/dist/style/themes/comfortable.css +20 -0
- package/dist/style/themes/compact.css +20 -0
- package/dist/translations/de.d.ts +177 -0
- package/dist/translations/de.js +176 -0
- package/dist/translations/en.d.ts +177 -0
- package/dist/translations/en.js +176 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/tableTypes.d.ts +262 -0
- package/dist/types/tableTypes.js +1 -0
- package/dist/utils/index.d.ts +165 -0
- package/dist/utils/index.js +330 -0
- package/dist/utils/sticky-measure.d.ts +54 -0
- package/dist/utils/sticky-measure.js +107 -0
- package/dist/utils/virtualizer.d.ts +43 -0
- package/dist/utils/virtualizer.js +43 -0
- package/dist/variants/index.d.ts +11 -0
- package/dist/variants/index.js +15 -0
- package/dist/variants/table-cells.variants.d.ts +827 -0
- package/dist/variants/table-cells.variants.js +627 -0
- package/dist/variants/table-features.variants.d.ts +547 -0
- package/dist/variants/table-features.variants.js +412 -0
- package/dist/variants/table-states.variants.d.ts +594 -0
- package/dist/variants/table-states.variants.js +394 -0
- package/dist/variants/table.system.d.ts +301 -0
- package/dist/variants/table.system.js +314 -0
- package/dist/variants/table.variants.d.ts +428 -0
- package/dist/variants/table.variants.js +360 -0
- package/package.json +93 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
2
|
+
import { resolveColumnId } from '../../utils';
|
|
3
|
+
/**
|
|
4
|
+
* Column order concern: manages column reordering.
|
|
5
|
+
* Maintains an ordered list of column ids and provides reorder operations.
|
|
6
|
+
* @param state - Shared table state (reads `columns` for initial order).
|
|
7
|
+
*/
|
|
8
|
+
export function useColumnOrder(state) {
|
|
9
|
+
let columnOrder = $state([]);
|
|
10
|
+
/** Columns sorted by the current order. Falls back to original order for unknown ids. */
|
|
11
|
+
const orderedColumns = $derived.by(() => {
|
|
12
|
+
if (columnOrder.length === 0)
|
|
13
|
+
return state.columns;
|
|
14
|
+
const columnMap = new SvelteMap(state.columns.map((col) => [resolveColumnId(col), col]));
|
|
15
|
+
const ordered = [];
|
|
16
|
+
// Add columns in the specified order
|
|
17
|
+
for (const id of columnOrder) {
|
|
18
|
+
const col = columnMap.get(id);
|
|
19
|
+
if (col) {
|
|
20
|
+
ordered.push(col);
|
|
21
|
+
columnMap.delete(id);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Append any remaining columns not in the order (newly added columns)
|
|
25
|
+
for (const col of columnMap.values()) {
|
|
26
|
+
ordered.push(col);
|
|
27
|
+
}
|
|
28
|
+
return ordered;
|
|
29
|
+
});
|
|
30
|
+
/** Initialize order from current columns (called when columns change). */
|
|
31
|
+
function initOrder(columns) {
|
|
32
|
+
columnOrder = columns.map((col) => resolveColumnId(col));
|
|
33
|
+
}
|
|
34
|
+
/** Move a column from one index to another. */
|
|
35
|
+
function reorderColumn(fromIndex, toIndex) {
|
|
36
|
+
if (fromIndex === toIndex)
|
|
37
|
+
return;
|
|
38
|
+
if (columnOrder.length === 0) {
|
|
39
|
+
columnOrder = state.columns.map((col) => resolveColumnId(col));
|
|
40
|
+
}
|
|
41
|
+
const next = [...columnOrder];
|
|
42
|
+
if (fromIndex < 0 || fromIndex >= next.length || toIndex < 0 || toIndex > next.length)
|
|
43
|
+
return;
|
|
44
|
+
const [moved] = next.splice(fromIndex, 1);
|
|
45
|
+
next.splice(toIndex, 0, moved);
|
|
46
|
+
columnOrder = next;
|
|
47
|
+
}
|
|
48
|
+
/** Reset column order to the original (as defined by `columns` prop). */
|
|
49
|
+
function resetColumnOrder() {
|
|
50
|
+
columnOrder = [];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Replace the order wholesale with a persisted snapshot. Unknown ids
|
|
54
|
+
* are kept as-is — the derived `orderedColumns` will gracefully drop
|
|
55
|
+
* any id that no longer matches a known column, and newly added
|
|
56
|
+
* columns (not in the snapshot) are appended at the end.
|
|
57
|
+
*/
|
|
58
|
+
function applyOrder(order) {
|
|
59
|
+
columnOrder = [...order];
|
|
60
|
+
}
|
|
61
|
+
/** Get the current order index for a column id. Returns -1 if not found. */
|
|
62
|
+
function getColumnIndex(id) {
|
|
63
|
+
if (columnOrder.length === 0) {
|
|
64
|
+
return state.columns.findIndex((col) => resolveColumnId(col) === id);
|
|
65
|
+
}
|
|
66
|
+
return columnOrder.indexOf(id);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
get orderedColumns() {
|
|
70
|
+
return orderedColumns;
|
|
71
|
+
},
|
|
72
|
+
get columnOrder() {
|
|
73
|
+
return columnOrder;
|
|
74
|
+
},
|
|
75
|
+
initOrder,
|
|
76
|
+
reorderColumn,
|
|
77
|
+
resetColumnOrder,
|
|
78
|
+
applyOrder,
|
|
79
|
+
getColumnIndex
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
import type { Column } from '../../types/tableTypes';
|
|
3
|
+
import type { TableState } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Column visibility concern: manages which columns are shown or hidden.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useColumnVisibility(state: TableState): {
|
|
8
|
+
readonly allColumns: Column[];
|
|
9
|
+
readonly hiddenColumnKeys: SvelteSet<string>;
|
|
10
|
+
setColumns: (newColumns: Column[]) => void;
|
|
11
|
+
setHiddenIds: (ids: Iterable<string>) => void;
|
|
12
|
+
hideColumn: (id: string) => void;
|
|
13
|
+
showColumn: (id: string) => void;
|
|
14
|
+
toggleColumnVisibility: (id: string) => void;
|
|
15
|
+
showAllColumns: () => void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
import { resolveColumnId } from '../../utils';
|
|
3
|
+
/**
|
|
4
|
+
* Column visibility concern: manages which columns are shown or hidden.
|
|
5
|
+
*/
|
|
6
|
+
export function useColumnVisibility(state) {
|
|
7
|
+
let allColumns = $state([]);
|
|
8
|
+
let hiddenColumnKeys = new SvelteSet();
|
|
9
|
+
function setColumns(newColumns) {
|
|
10
|
+
allColumns = [...newColumns];
|
|
11
|
+
state.columns = newColumns.filter((col) => !hiddenColumnKeys.has(resolveColumnId(col)));
|
|
12
|
+
}
|
|
13
|
+
function hideColumn(id) {
|
|
14
|
+
hiddenColumnKeys = new SvelteSet([...hiddenColumnKeys, id]);
|
|
15
|
+
state.columns = allColumns.filter((col) => !hiddenColumnKeys.has(resolveColumnId(col)));
|
|
16
|
+
}
|
|
17
|
+
function showColumn(id) {
|
|
18
|
+
const next = new SvelteSet(hiddenColumnKeys);
|
|
19
|
+
next.delete(id);
|
|
20
|
+
hiddenColumnKeys = next;
|
|
21
|
+
state.columns = allColumns.filter((col) => !hiddenColumnKeys.has(resolveColumnId(col)));
|
|
22
|
+
}
|
|
23
|
+
function toggleColumnVisibility(id) {
|
|
24
|
+
if (hiddenColumnKeys.has(id)) {
|
|
25
|
+
showColumn(id);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
hideColumn(id);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function showAllColumns() {
|
|
32
|
+
hiddenColumnKeys = new SvelteSet();
|
|
33
|
+
state.columns = [...allColumns];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Replace the hidden-column set wholesale. Used by the host store to
|
|
37
|
+
* apply a persisted snapshot before the consumer's `columns` prop
|
|
38
|
+
* reaches `setColumns`. Filtering of `state.columns` happens lazily on
|
|
39
|
+
* the next `setColumns` call.
|
|
40
|
+
*/
|
|
41
|
+
function setHiddenIds(ids) {
|
|
42
|
+
hiddenColumnKeys = new SvelteSet(ids);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
get allColumns() {
|
|
46
|
+
return allColumns;
|
|
47
|
+
},
|
|
48
|
+
get hiddenColumnKeys() {
|
|
49
|
+
return hiddenColumnKeys;
|
|
50
|
+
},
|
|
51
|
+
setColumns,
|
|
52
|
+
setHiddenIds,
|
|
53
|
+
hideColumn,
|
|
54
|
+
showColumn,
|
|
55
|
+
toggleColumnVisibility,
|
|
56
|
+
showAllColumns
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TableState } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Expansion concern: manages row expand/collapse state.
|
|
4
|
+
* Supports both single-expand and multi-expand modes.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useExpansion(state: TableState): {
|
|
7
|
+
toggleExpand: (itemId: string | number) => void;
|
|
8
|
+
isItemExpanded: (itemId: string | number) => boolean;
|
|
9
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
/**
|
|
3
|
+
* Expansion concern: manages row expand/collapse state.
|
|
4
|
+
* Supports both single-expand and multi-expand modes.
|
|
5
|
+
*/
|
|
6
|
+
export function useExpansion(state) {
|
|
7
|
+
function toggleExpand(itemId) {
|
|
8
|
+
if (state.multiExpand) {
|
|
9
|
+
const next = new SvelteSet(state.expandedItemIds);
|
|
10
|
+
if (next.has(itemId)) {
|
|
11
|
+
next.delete(itemId);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
next.add(itemId);
|
|
15
|
+
}
|
|
16
|
+
state.expandedItemIds = next;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
state.expandedItemId = state.expandedItemId === itemId ? null : itemId;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function isItemExpanded(itemId) {
|
|
23
|
+
if (state.multiExpand) {
|
|
24
|
+
return state.expandedItemIds.has(itemId);
|
|
25
|
+
}
|
|
26
|
+
return state.expandedItemId === itemId;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
toggleExpand,
|
|
30
|
+
isItemExpanded
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Filter, FilterOperator, TableItem } from '../../types/tableTypes';
|
|
2
|
+
import { resolveColumnId } from '../../utils';
|
|
3
|
+
import type { TableState } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Filtering concern: manages active filters and computes filtered items.
|
|
6
|
+
* Combines search-term matching and advanced filter matching.
|
|
7
|
+
*
|
|
8
|
+
* Values for both search and filter matching come from the column's
|
|
9
|
+
* accessor (string property or function), not a raw `getNestedValue` —
|
|
10
|
+
* which keeps object-typed properties and computed columns honest.
|
|
11
|
+
*/
|
|
12
|
+
export declare function useFiltering(state: TableState): {
|
|
13
|
+
readonly filteredItems: TableItem[];
|
|
14
|
+
addFilter: (filter: Filter) => void;
|
|
15
|
+
removeFilter: (index: number) => void;
|
|
16
|
+
removeFiltersByColumn: (column: string, operator?: FilterOperator, value?: string) => void;
|
|
17
|
+
clearAllFilters: () => void;
|
|
18
|
+
hasFilterForColumn: (column: string, operator?: FilterOperator, value?: string) => boolean;
|
|
19
|
+
};
|
|
20
|
+
export { resolveColumnId };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { resolveColumnId, resolveColumnValue, resolveValueById } from '../../utils';
|
|
2
|
+
/**
|
|
3
|
+
* Filtering concern: manages active filters and computes filtered items.
|
|
4
|
+
* Combines search-term matching and advanced filter matching.
|
|
5
|
+
*
|
|
6
|
+
* Values for both search and filter matching come from the column's
|
|
7
|
+
* accessor (string property or function), not a raw `getNestedValue` —
|
|
8
|
+
* which keeps object-typed properties and computed columns honest.
|
|
9
|
+
*/
|
|
10
|
+
export function useFiltering(state) {
|
|
11
|
+
const filteredItems = $derived.by(() => {
|
|
12
|
+
if (!state.items.length)
|
|
13
|
+
return [];
|
|
14
|
+
// In server mode, items are already filtered by the server
|
|
15
|
+
if (state.mode === 'server')
|
|
16
|
+
return state.items;
|
|
17
|
+
return state.items.filter((item) => {
|
|
18
|
+
const matchesSearchTerm = state.searchTerm === '' ||
|
|
19
|
+
state.columns
|
|
20
|
+
// Discriminate synthetic columns first — afterwards TS narrows to
|
|
21
|
+
// DataColumnString | DataColumnFunction, which carries `searchable`.
|
|
22
|
+
.filter((col) => col.accessor !== undefined && col.searchable !== false)
|
|
23
|
+
.some((column) => {
|
|
24
|
+
const raw = resolveColumnValue(column, item);
|
|
25
|
+
// Object/array values stringify to "[object Object]" and produce
|
|
26
|
+
// misleading matches. Treat them as no-match unless the column
|
|
27
|
+
// explicitly opts in via a string-returning accessor/formatter.
|
|
28
|
+
if (raw !== null && typeof raw === 'object' && !(raw instanceof Date)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const value = raw === null || raw === undefined ? '' : String(raw);
|
|
32
|
+
return value.toLowerCase().includes(state.searchTerm.toLowerCase());
|
|
33
|
+
});
|
|
34
|
+
const matchesFilters = state.activeFilters.length === 0 ||
|
|
35
|
+
state.activeFilters.every((filter) => {
|
|
36
|
+
const raw = resolveValueById(state.columns, item, filter.column);
|
|
37
|
+
const value = String(raw ?? '').toLowerCase();
|
|
38
|
+
const filterValue = filter.value.toLowerCase();
|
|
39
|
+
switch (filter.operator) {
|
|
40
|
+
case 'contains':
|
|
41
|
+
return value.includes(filterValue);
|
|
42
|
+
case 'equals':
|
|
43
|
+
return value === filterValue;
|
|
44
|
+
case 'startsWith':
|
|
45
|
+
return value.startsWith(filterValue);
|
|
46
|
+
case 'endsWith':
|
|
47
|
+
return value.endsWith(filterValue);
|
|
48
|
+
case 'greaterThan':
|
|
49
|
+
return Number(value) > Number(filterValue);
|
|
50
|
+
case 'lessThan':
|
|
51
|
+
return Number(value) < Number(filterValue);
|
|
52
|
+
default:
|
|
53
|
+
if (import.meta.env?.DEV)
|
|
54
|
+
console.warn(`[Table] Unknown filter operator "${filter.operator}" — row excluded.`);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
return matchesSearchTerm && matchesFilters;
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
function addFilter(filter) {
|
|
62
|
+
state.activeFilters = [...state.activeFilters, filter];
|
|
63
|
+
state.currentPage = 1;
|
|
64
|
+
}
|
|
65
|
+
function removeFilter(index) {
|
|
66
|
+
state.activeFilters = state.activeFilters.filter((_, i) => i !== index);
|
|
67
|
+
state.currentPage = 1;
|
|
68
|
+
}
|
|
69
|
+
function removeFiltersByColumn(column, operator, value) {
|
|
70
|
+
state.activeFilters = state.activeFilters.filter((filter) => {
|
|
71
|
+
if (filter.column !== column)
|
|
72
|
+
return true;
|
|
73
|
+
if (operator && filter.operator !== operator)
|
|
74
|
+
return true;
|
|
75
|
+
if (value && filter.value !== value)
|
|
76
|
+
return true;
|
|
77
|
+
return false;
|
|
78
|
+
});
|
|
79
|
+
state.currentPage = 1;
|
|
80
|
+
}
|
|
81
|
+
function clearAllFilters() {
|
|
82
|
+
state.activeFilters = [];
|
|
83
|
+
state.currentPage = 1;
|
|
84
|
+
}
|
|
85
|
+
function hasFilterForColumn(column, operator, value) {
|
|
86
|
+
return state.activeFilters.some((filter) => {
|
|
87
|
+
if (filter.column !== column)
|
|
88
|
+
return false;
|
|
89
|
+
if (operator && filter.operator !== operator)
|
|
90
|
+
return false;
|
|
91
|
+
if (value && filter.value !== value)
|
|
92
|
+
return false;
|
|
93
|
+
return true;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
get filteredItems() {
|
|
98
|
+
return filteredItems;
|
|
99
|
+
},
|
|
100
|
+
addFilter,
|
|
101
|
+
removeFilter,
|
|
102
|
+
removeFiltersByColumn,
|
|
103
|
+
clearAllFilters,
|
|
104
|
+
hasFilterForColumn
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// Re-exported so concerns that compose useFiltering can keep their column-id
|
|
108
|
+
// resolution consistent without re-importing the util path.
|
|
109
|
+
export { resolveColumnId };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TableState } from './types';
|
|
2
|
+
export type FocusDirection = 'up' | 'down' | 'first' | 'last';
|
|
3
|
+
/**
|
|
4
|
+
* Focus management concern: tracks which row is focused for keyboard navigation.
|
|
5
|
+
* Uses the roving tabindex pattern (WAI-ARIA Grid).
|
|
6
|
+
* @param _state - Shared table state (unused here; kept for concern-signature parity).
|
|
7
|
+
* @param getVisibleItemCount - Getter for the number of currently visible rows.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useFocusManagement(_state: TableState, getVisibleItemCount: () => number): {
|
|
10
|
+
readonly focusedRowIndex: number;
|
|
11
|
+
resetFocus: () => void;
|
|
12
|
+
setFocusedRow: (index: number) => void;
|
|
13
|
+
moveFocus: (direction: FocusDirection) => void;
|
|
14
|
+
isFocusedRow: (index: number) => boolean;
|
|
15
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Focus management concern: tracks which row is focused for keyboard navigation.
|
|
3
|
+
* Uses the roving tabindex pattern (WAI-ARIA Grid).
|
|
4
|
+
* @param _state - Shared table state (unused here; kept for concern-signature parity).
|
|
5
|
+
* @param getVisibleItemCount - Getter for the number of currently visible rows.
|
|
6
|
+
*/
|
|
7
|
+
export function useFocusManagement(_state, getVisibleItemCount) {
|
|
8
|
+
let focusedRowIndex = $state(0);
|
|
9
|
+
/** Reset focus to first row when data changes */
|
|
10
|
+
function resetFocus() {
|
|
11
|
+
focusedRowIndex = 0;
|
|
12
|
+
}
|
|
13
|
+
function setFocusedRow(index) {
|
|
14
|
+
const count = getVisibleItemCount();
|
|
15
|
+
if (count === 0) {
|
|
16
|
+
focusedRowIndex = 0;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
focusedRowIndex = Math.max(0, Math.min(index, count - 1));
|
|
20
|
+
}
|
|
21
|
+
function moveFocus(direction) {
|
|
22
|
+
const count = getVisibleItemCount();
|
|
23
|
+
if (count === 0)
|
|
24
|
+
return;
|
|
25
|
+
switch (direction) {
|
|
26
|
+
case 'up':
|
|
27
|
+
setFocusedRow(focusedRowIndex - 1);
|
|
28
|
+
break;
|
|
29
|
+
case 'down':
|
|
30
|
+
setFocusedRow(focusedRowIndex + 1);
|
|
31
|
+
break;
|
|
32
|
+
case 'first':
|
|
33
|
+
setFocusedRow(0);
|
|
34
|
+
break;
|
|
35
|
+
case 'last':
|
|
36
|
+
setFocusedRow(count - 1);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function isFocusedRow(index) {
|
|
41
|
+
return focusedRowIndex === index;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
get focusedRowIndex() {
|
|
45
|
+
return focusedRowIndex;
|
|
46
|
+
},
|
|
47
|
+
resetFocus,
|
|
48
|
+
setFocusedRow,
|
|
49
|
+
moveFocus,
|
|
50
|
+
isFocusedRow
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TableItem } from '../../types/tableTypes';
|
|
2
|
+
import type { TableState } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Grouping concern: manages group-by key, group order, collapse state,
|
|
5
|
+
* and computes grouped items.
|
|
6
|
+
* @param state - Shared table state.
|
|
7
|
+
* @param getSortedItems - Getter for upstream sorted items.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useGrouping(state: TableState, getSortedItems: () => TableItem[]): {
|
|
10
|
+
readonly grouped: Record<string, TableItem[]>;
|
|
11
|
+
setGroupByKey: (key: string | null) => void;
|
|
12
|
+
setGroupOrder: (order: string[]) => void;
|
|
13
|
+
toggleGroup: (groupName: string) => void;
|
|
14
|
+
toggleAllGroups: () => void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
import { findColumnById, resolveValueById } from '../../utils';
|
|
3
|
+
/**
|
|
4
|
+
* Grouping concern: manages group-by key, group order, collapse state,
|
|
5
|
+
* and computes grouped items.
|
|
6
|
+
* @param state - Shared table state.
|
|
7
|
+
* @param getSortedItems - Getter for upstream sorted items.
|
|
8
|
+
*/
|
|
9
|
+
export function useGrouping(state, getSortedItems) {
|
|
10
|
+
const grouped = $derived.by(() => {
|
|
11
|
+
const items = getSortedItems();
|
|
12
|
+
if (!state.groupByKey)
|
|
13
|
+
return { ungrouped: items };
|
|
14
|
+
// Synthetic columns have no accessor — grouping by them would bucket
|
|
15
|
+
// every row under 'Unassigned'. Fall back to ungrouped instead.
|
|
16
|
+
const groupColumn = findColumnById(state.columns, state.groupByKey);
|
|
17
|
+
if (groupColumn && groupColumn.accessor === undefined)
|
|
18
|
+
return { ungrouped: items };
|
|
19
|
+
const result = {};
|
|
20
|
+
for (const item of items) {
|
|
21
|
+
const groupValue = resolveValueById(state.columns, item, state.groupByKey);
|
|
22
|
+
const groupKey = groupValue !== undefined && groupValue !== null ? String(groupValue) : 'Unassigned';
|
|
23
|
+
if (!result[groupKey]) {
|
|
24
|
+
result[groupKey] = [];
|
|
25
|
+
}
|
|
26
|
+
result[groupKey].push(item);
|
|
27
|
+
}
|
|
28
|
+
if (state.groupOrder && state.groupOrder.length > 0) {
|
|
29
|
+
const ordered = {};
|
|
30
|
+
state.groupOrder.forEach((groupKey) => {
|
|
31
|
+
if (result[groupKey]) {
|
|
32
|
+
ordered[groupKey] = result[groupKey];
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
Object.keys(result).forEach((groupKey) => {
|
|
36
|
+
if (!ordered[groupKey]) {
|
|
37
|
+
ordered[groupKey] = result[groupKey];
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return ordered;
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
});
|
|
44
|
+
function setGroupByKey(key) {
|
|
45
|
+
state.groupByKey = key;
|
|
46
|
+
state.collapsedGroups = new SvelteSet();
|
|
47
|
+
state.allGroupsExpanded = true;
|
|
48
|
+
state.currentPage = 1;
|
|
49
|
+
}
|
|
50
|
+
function setGroupOrder(order) {
|
|
51
|
+
state.groupOrder = order;
|
|
52
|
+
}
|
|
53
|
+
function toggleGroup(groupName) {
|
|
54
|
+
const newGroups = new SvelteSet(state.collapsedGroups);
|
|
55
|
+
if (newGroups.has(groupName)) {
|
|
56
|
+
newGroups.delete(groupName);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
newGroups.add(groupName);
|
|
60
|
+
}
|
|
61
|
+
state.collapsedGroups = newGroups;
|
|
62
|
+
const groupKeys = Object.keys(grouped);
|
|
63
|
+
if (groupKeys.length > 0) {
|
|
64
|
+
state.allGroupsExpanded = state.collapsedGroups.size === 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function toggleAllGroups() {
|
|
68
|
+
const isExpanded = state.allGroupsExpanded;
|
|
69
|
+
if (isExpanded) {
|
|
70
|
+
state.collapsedGroups = new SvelteSet(Object.keys(grouped));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
state.collapsedGroups = new SvelteSet();
|
|
74
|
+
}
|
|
75
|
+
state.allGroupsExpanded = !state.allGroupsExpanded;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
get grouped() {
|
|
79
|
+
return grouped;
|
|
80
|
+
},
|
|
81
|
+
setGroupByKey,
|
|
82
|
+
setGroupOrder,
|
|
83
|
+
toggleGroup,
|
|
84
|
+
toggleAllGroups
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { TableItem } from '../../types/tableTypes';
|
|
2
|
+
import type { TableState } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Summary of pending live changes.
|
|
5
|
+
*/
|
|
6
|
+
export interface LiveUpdateCounts {
|
|
7
|
+
inserts: number;
|
|
8
|
+
updates: number;
|
|
9
|
+
deletes: number;
|
|
10
|
+
total: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A pending update: the item ID and the changed fields.
|
|
14
|
+
*/
|
|
15
|
+
export interface PendingUpdate {
|
|
16
|
+
id: string | number;
|
|
17
|
+
changes: Partial<TableItem>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Live updates concern: manages a pending buffer of inserts, updates, and deletes
|
|
21
|
+
* that are NOT immediately applied to the visible table.
|
|
22
|
+
*
|
|
23
|
+
* Instead, the UI shows a notification (e.g. "3 new, 2 updated") and the user
|
|
24
|
+
* decides when to merge them. This avoids disorienting UX issues like rows
|
|
25
|
+
* jumping, disappearing, or conflicting with active selection/editing.
|
|
26
|
+
*
|
|
27
|
+
* @param state - Shared table state (items are modified on apply).
|
|
28
|
+
*/
|
|
29
|
+
export declare function useLiveUpdates(state: TableState): {
|
|
30
|
+
readonly counts: LiveUpdateCounts;
|
|
31
|
+
readonly hasPending: boolean;
|
|
32
|
+
readonly pendingInserts: TableItem[];
|
|
33
|
+
readonly pendingUpdates: PendingUpdate[];
|
|
34
|
+
readonly pendingDeletes: Set<string | number>;
|
|
35
|
+
pushInsert: (item: TableItem) => void;
|
|
36
|
+
pushUpdate: (id: string | number, changes: Partial<TableItem>) => void;
|
|
37
|
+
pushDelete: (id: string | number) => void;
|
|
38
|
+
applyAll: () => void;
|
|
39
|
+
applyInserts: () => void;
|
|
40
|
+
applyUpdates: () => void;
|
|
41
|
+
applyDeletes: () => void;
|
|
42
|
+
dismissAll: () => void;
|
|
43
|
+
isRecentlyUpdated: (id: string | number) => boolean;
|
|
44
|
+
isPendingDelete: (id: string | number) => boolean;
|
|
45
|
+
};
|