@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,13 @@
|
|
|
1
|
+
// === TABLE FACTORIES ===
|
|
2
|
+
// === CELL COMPONENTS ===
|
|
3
|
+
export { default as ActionButtons } from '../cells/ActionButtons.svelte';
|
|
4
|
+
export { default as CopyButton } from '../cells/CopyButton.svelte';
|
|
5
|
+
export { default as CustomCell } from '../cells/CustomCell.svelte';
|
|
6
|
+
export { default as DateCell } from '../cells/DateCell.svelte';
|
|
7
|
+
export { default as LinkCell } from '../cells/LinkCell.svelte';
|
|
8
|
+
export { default as NumberCell } from '../cells/NumberCell.svelte';
|
|
9
|
+
export { default as StatusBadge } from '../cells/StatusBadge.svelte';
|
|
10
|
+
export { default as UserAvatar } from '../cells/UserAvatar.svelte';
|
|
11
|
+
export { ColumnValidation, ValidationHelpers } from './ColumnValidation';
|
|
12
|
+
export { TableColumns } from './TableColumns';
|
|
13
|
+
export { TypedColumnBuilder } from './TypedColumnBuilder';
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getTableContext } from '../stores/TableStore.svelte';
|
|
3
|
+
import { headerMenuItemVariants, headerMenuVariants } from '../variants';
|
|
4
|
+
import {
|
|
5
|
+
Button,
|
|
6
|
+
Popover,
|
|
7
|
+
resolveIcon,
|
|
8
|
+
ArrowDownIcon as ArrowDownIconDefault,
|
|
9
|
+
ArrowUpIcon as ArrowUpIconDefault,
|
|
10
|
+
CalculatorIcon as CalculatorIconDefault,
|
|
11
|
+
EyeIcon as EyeIconDefault,
|
|
12
|
+
EyeOffIcon as EyeOffIconDefault,
|
|
13
|
+
FilterXIcon as FilterXIconDefault,
|
|
14
|
+
MoreVerticalIcon as MoreVerticalIconDefault,
|
|
15
|
+
UsersIcon as UsersIconDefault
|
|
16
|
+
} from '@urbicon-ui/blocks';
|
|
17
|
+
import { useTableI18n } from '../i18n';
|
|
18
|
+
import { resolveColumnId, resolveColumnLabel } from '../utils';
|
|
19
|
+
import type { Column } from '../types/tableTypes';
|
|
20
|
+
|
|
21
|
+
const tt = useTableI18n();
|
|
22
|
+
|
|
23
|
+
type HeaderMenuProps = {
|
|
24
|
+
column: Column;
|
|
25
|
+
isActive?: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const ArrowDownIcon = resolveIcon('arrowDown', ArrowDownIconDefault);
|
|
29
|
+
const ArrowUpIcon = resolveIcon('arrowUp', ArrowUpIconDefault);
|
|
30
|
+
const CalculatorIcon = resolveIcon('calculator', CalculatorIconDefault);
|
|
31
|
+
const EyeIcon = resolveIcon('eye', EyeIconDefault);
|
|
32
|
+
const EyeOffIcon = resolveIcon('eyeOff', EyeOffIconDefault);
|
|
33
|
+
const FilterXIcon = resolveIcon('filterX', FilterXIconDefault);
|
|
34
|
+
const MoreVerticalIcon = resolveIcon('moreVertical', MoreVerticalIconDefault);
|
|
35
|
+
const UsersIcon = resolveIcon('users', UsersIconDefault);
|
|
36
|
+
|
|
37
|
+
let { column, isActive = false }: HeaderMenuProps = $props();
|
|
38
|
+
|
|
39
|
+
const tableContext = getTableContext();
|
|
40
|
+
const {
|
|
41
|
+
state: tableState,
|
|
42
|
+
setGroupByKey,
|
|
43
|
+
addSummaryConfig,
|
|
44
|
+
removeSummaryConfig,
|
|
45
|
+
removeFiltersByColumn,
|
|
46
|
+
hideColumn,
|
|
47
|
+
showColumn
|
|
48
|
+
} = tableContext;
|
|
49
|
+
|
|
50
|
+
let menuOpen = $state(false);
|
|
51
|
+
|
|
52
|
+
const columnId = $derived(resolveColumnId(column));
|
|
53
|
+
// Synthetic columns (no accessor) cannot participate in derived ops.
|
|
54
|
+
const canSort = $derived(column.accessor !== undefined && column.sortable !== false);
|
|
55
|
+
const canGroup = $derived(column.accessor !== undefined && column.groupable !== false);
|
|
56
|
+
|
|
57
|
+
const styles = $derived(headerMenuVariants({ active: isActive }));
|
|
58
|
+
|
|
59
|
+
function handleSortAsc() {
|
|
60
|
+
tableState.sortColumn = columnId;
|
|
61
|
+
tableState.sortDirection = 'asc';
|
|
62
|
+
menuOpen = false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function handleSortDesc() {
|
|
66
|
+
tableState.sortColumn = columnId;
|
|
67
|
+
tableState.sortDirection = 'desc';
|
|
68
|
+
menuOpen = false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function handleGroupBy() {
|
|
72
|
+
if (tableState.groupByKey === columnId) {
|
|
73
|
+
setGroupByKey(null);
|
|
74
|
+
} else {
|
|
75
|
+
setGroupByKey(columnId);
|
|
76
|
+
}
|
|
77
|
+
menuOpen = false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function handleToggleSummary() {
|
|
81
|
+
const hasSummary = tableState.summaryConfigs.some((c) => c.column === columnId);
|
|
82
|
+
|
|
83
|
+
if (hasSummary) {
|
|
84
|
+
removeSummaryConfig(columnId);
|
|
85
|
+
} else {
|
|
86
|
+
// Synthetic columns can't reach this handler (gated by isColumnSummable),
|
|
87
|
+
// so `dataType` is reachable on the narrowed data-column shape.
|
|
88
|
+
const dataType = 'dataType' in column ? column.dataType : undefined;
|
|
89
|
+
const summaryType = dataType === 'number' ? 'sum' : 'count';
|
|
90
|
+
addSummaryConfig({
|
|
91
|
+
column: columnId,
|
|
92
|
+
type: summaryType
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
menuOpen = false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handleRemoveFilters() {
|
|
99
|
+
removeFiltersByColumn(columnId);
|
|
100
|
+
menuOpen = false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function handleHideColumn() {
|
|
104
|
+
hideColumn(columnId);
|
|
105
|
+
menuOpen = false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function itemClass(
|
|
109
|
+
intent: 'default' | 'filter' | 'group' | 'summary' | 'danger',
|
|
110
|
+
active = false
|
|
111
|
+
) {
|
|
112
|
+
return headerMenuItemVariants({ intent, active });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isColumnSummable(col: Column): boolean {
|
|
116
|
+
// Synthetic columns have no source value — they cannot be summed regardless of name.
|
|
117
|
+
if (col.accessor === undefined) return false;
|
|
118
|
+
// After the accessor check, TS narrows `col` to the derivable shapes.
|
|
119
|
+
const dataCol = col as Exclude<Column, { accessor?: never }>;
|
|
120
|
+
if (dataCol.summable !== undefined) return dataCol.summable === true;
|
|
121
|
+
return (
|
|
122
|
+
dataCol.dataType === 'number' ||
|
|
123
|
+
/^(age|salary|price|amount|count|number|projectsCompleted|rating|score)$/i.test(
|
|
124
|
+
resolveColumnId(col)
|
|
125
|
+
)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let isSorted = $derived(tableState.sortColumn === columnId);
|
|
130
|
+
let isGrouped = $derived(tableState.groupByKey === columnId);
|
|
131
|
+
let hasSummary = $derived(tableState.summaryConfigs.some((c) => c.column === columnId));
|
|
132
|
+
let hasFilter = $derived(tableState.activeFilters.some((f) => f.column === columnId));
|
|
133
|
+
|
|
134
|
+
const hiddenColumns = $derived.by(() =>
|
|
135
|
+
tableContext.allColumns.filter((col) => tableContext.hiddenColumnKeys.has(resolveColumnId(col)))
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
function handleShowColumn(key: string) {
|
|
139
|
+
showColumn(key);
|
|
140
|
+
menuOpen = false;
|
|
141
|
+
}
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<div class={styles.container()} data-testid={`header-menu-${columnId}`}>
|
|
145
|
+
<Popover bind:open={menuOpen} placement="bottom-start">
|
|
146
|
+
{#snippet trigger()}
|
|
147
|
+
<Button
|
|
148
|
+
variant="ghost"
|
|
149
|
+
size="sm"
|
|
150
|
+
class={styles.trigger()}
|
|
151
|
+
aria-label="{tt('headerMenu.columnOptions')} {resolveColumnLabel(column)}"
|
|
152
|
+
data-testid={`header-menu-trigger-${columnId}`}
|
|
153
|
+
>
|
|
154
|
+
<MoreVerticalIcon class="h-4 w-4" />
|
|
155
|
+
</Button>
|
|
156
|
+
{/snippet}
|
|
157
|
+
|
|
158
|
+
<div class={styles.menu()}>
|
|
159
|
+
{#if canSort}
|
|
160
|
+
<Button
|
|
161
|
+
variant="ghost"
|
|
162
|
+
size="sm"
|
|
163
|
+
class={itemClass('default', isSorted && tableState.sortDirection === 'asc')}
|
|
164
|
+
onclick={handleSortAsc}
|
|
165
|
+
>
|
|
166
|
+
<ArrowUpIcon class="h-4 w-4" />
|
|
167
|
+
{tt('headerMenu.sortAscending')}
|
|
168
|
+
</Button>
|
|
169
|
+
|
|
170
|
+
<Button
|
|
171
|
+
variant="ghost"
|
|
172
|
+
size="sm"
|
|
173
|
+
class={itemClass('default', isSorted && tableState.sortDirection === 'desc')}
|
|
174
|
+
onclick={handleSortDesc}
|
|
175
|
+
>
|
|
176
|
+
<ArrowDownIcon class="h-4 w-4" />
|
|
177
|
+
{tt('headerMenu.sortDescending')}
|
|
178
|
+
</Button>
|
|
179
|
+
|
|
180
|
+
<div class={styles.separator()}></div>
|
|
181
|
+
{/if}
|
|
182
|
+
|
|
183
|
+
{#if hasFilter}
|
|
184
|
+
<Button variant="ghost" size="sm" class={itemClass('filter')} onclick={handleRemoveFilters}>
|
|
185
|
+
<FilterXIcon class="h-4 w-4" />
|
|
186
|
+
{tt('headerMenu.removeFilter')}
|
|
187
|
+
</Button>
|
|
188
|
+
{/if}
|
|
189
|
+
|
|
190
|
+
{#if canGroup}
|
|
191
|
+
<Button
|
|
192
|
+
variant="ghost"
|
|
193
|
+
size="sm"
|
|
194
|
+
class={itemClass(isGrouped ? 'group' : 'default', isGrouped)}
|
|
195
|
+
onclick={handleGroupBy}
|
|
196
|
+
>
|
|
197
|
+
<UsersIcon class="h-4 w-4" />
|
|
198
|
+
{isGrouped ? tt('headerMenu.removeGrouping') : tt('headerMenu.groupByColumn')}
|
|
199
|
+
</Button>
|
|
200
|
+
{/if}
|
|
201
|
+
|
|
202
|
+
{#if isColumnSummable(column)}
|
|
203
|
+
<Button
|
|
204
|
+
variant="ghost"
|
|
205
|
+
size="sm"
|
|
206
|
+
class={itemClass(hasSummary ? 'summary' : 'default', hasSummary)}
|
|
207
|
+
onclick={handleToggleSummary}
|
|
208
|
+
>
|
|
209
|
+
<CalculatorIcon class="h-4 w-4" />
|
|
210
|
+
{hasSummary ? tt('headerMenu.removeSummary') : tt('headerMenu.addSummary')}
|
|
211
|
+
</Button>
|
|
212
|
+
{/if}
|
|
213
|
+
|
|
214
|
+
<div class={styles.separator()}></div>
|
|
215
|
+
<Button variant="ghost" size="sm" class={itemClass('danger')} onclick={handleHideColumn}>
|
|
216
|
+
<EyeOffIcon class="h-4 w-4" />
|
|
217
|
+
{tt('headerMenu.hideColumn')}
|
|
218
|
+
</Button>
|
|
219
|
+
|
|
220
|
+
{#if hiddenColumns.length > 0}
|
|
221
|
+
<div class={styles.separator()}></div>
|
|
222
|
+
{#each hiddenColumns as col (resolveColumnId(col))}
|
|
223
|
+
<Button
|
|
224
|
+
variant="ghost"
|
|
225
|
+
size="sm"
|
|
226
|
+
class={itemClass('default')}
|
|
227
|
+
onclick={() => handleShowColumn(resolveColumnId(col))}
|
|
228
|
+
>
|
|
229
|
+
<EyeIcon class="h-4 w-4" />
|
|
230
|
+
{tt('headerMenu.showColumn')} "{resolveColumnLabel(col)}"
|
|
231
|
+
</Button>
|
|
232
|
+
{/each}
|
|
233
|
+
{/if}
|
|
234
|
+
</div>
|
|
235
|
+
</Popover>
|
|
236
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Column } from '../types/tableTypes';
|
|
2
|
+
type HeaderMenuProps = {
|
|
3
|
+
column: Column;
|
|
4
|
+
isActive?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare const HeaderMenu: import("svelte").Component<HeaderMenuProps, {}, "">;
|
|
7
|
+
type HeaderMenu = ReturnType<typeof HeaderMenu>;
|
|
8
|
+
export default HeaderMenu;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from '@urbicon-ui/blocks';
|
|
3
|
+
import { getTableContext } from '../stores/TableStore.svelte';
|
|
4
|
+
import { useTableI18n } from '../i18n';
|
|
5
|
+
|
|
6
|
+
const tt = useTableI18n();
|
|
7
|
+
|
|
8
|
+
let { class: className = '' }: { class?: string } = $props();
|
|
9
|
+
|
|
10
|
+
const tableContext = getTableContext();
|
|
11
|
+
const counts = $derived(tableContext.liveUpdateCounts);
|
|
12
|
+
const hasPending = $derived(tableContext.hasPendingUpdates);
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
{#if hasPending}
|
|
16
|
+
<div
|
|
17
|
+
class="border-primary/20 bg-primary-subtle text-text-primary rounded-contain flex items-center justify-between gap-3 border px-4 py-2.5 text-sm {className}"
|
|
18
|
+
role="status"
|
|
19
|
+
aria-live="polite"
|
|
20
|
+
data-testid="live-update-banner"
|
|
21
|
+
>
|
|
22
|
+
<div class="flex items-center gap-2">
|
|
23
|
+
<span class="relative flex h-2.5 w-2.5">
|
|
24
|
+
<span
|
|
25
|
+
class="bg-primary absolute inline-flex h-full w-full animate-ping rounded-full opacity-75"
|
|
26
|
+
></span>
|
|
27
|
+
<span class="bg-primary relative inline-flex h-2.5 w-2.5 rounded-full"></span>
|
|
28
|
+
</span>
|
|
29
|
+
|
|
30
|
+
<span>
|
|
31
|
+
{#if counts.inserts > 0}
|
|
32
|
+
<strong>{counts.inserts}</strong> {tt('liveUpdates.newItems')}
|
|
33
|
+
{/if}
|
|
34
|
+
{#if counts.inserts > 0 && (counts.updates > 0 || counts.deletes > 0)},
|
|
35
|
+
{/if}
|
|
36
|
+
{#if counts.updates > 0}
|
|
37
|
+
<strong>{counts.updates}</strong> {tt('liveUpdates.updatedItems')}
|
|
38
|
+
{/if}
|
|
39
|
+
{#if counts.updates > 0 && counts.deletes > 0},
|
|
40
|
+
{/if}
|
|
41
|
+
{#if counts.deletes > 0}
|
|
42
|
+
<strong>{counts.deletes}</strong> {tt('liveUpdates.deletedItems')}
|
|
43
|
+
{/if}
|
|
44
|
+
</span>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="flex items-center gap-2">
|
|
48
|
+
<Button
|
|
49
|
+
size="sm"
|
|
50
|
+
variant="ghost"
|
|
51
|
+
onclick={() => tableContext.dismissAllUpdates()}
|
|
52
|
+
data-testid="live-update-dismiss"
|
|
53
|
+
>
|
|
54
|
+
{tt('liveUpdates.dismiss')}
|
|
55
|
+
</Button>
|
|
56
|
+
<Button
|
|
57
|
+
size="sm"
|
|
58
|
+
intent="primary"
|
|
59
|
+
onclick={() => tableContext.applyAllUpdates()}
|
|
60
|
+
data-testid="live-update-apply"
|
|
61
|
+
>
|
|
62
|
+
{tt('liveUpdates.apply')}
|
|
63
|
+
</Button>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
{/if}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { splitSearchSegments } from '../utils';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
text = '',
|
|
6
|
+
searchTerm = '',
|
|
7
|
+
highlightClass = 'table-search-highlight'
|
|
8
|
+
}: {
|
|
9
|
+
text: string;
|
|
10
|
+
searchTerm?: string;
|
|
11
|
+
highlightClass?: string;
|
|
12
|
+
} = $props();
|
|
13
|
+
|
|
14
|
+
let segments = $derived(splitSearchSegments(text, searchTerm));
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<span class="inline"
|
|
18
|
+
>{#each segments as segment, i (i)}{#if segment.highlighted}<mark class={highlightClass}
|
|
19
|
+
>{segment.text}</mark
|
|
20
|
+
>{:else}{segment.text}{/if}{/each}</span
|
|
21
|
+
>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
text: string;
|
|
3
|
+
searchTerm?: string;
|
|
4
|
+
highlightClass?: string;
|
|
5
|
+
};
|
|
6
|
+
declare const SearchHighlight: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type SearchHighlight = ReturnType<typeof SearchHighlight>;
|
|
8
|
+
export default SearchHighlight;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getTableContext, type TableAction, useTableI18n } from '../..';
|
|
3
|
+
import type { SummaryConfig } from '../../stores/TableStore.svelte';
|
|
4
|
+
import { findColumnById, resolveColumnLabel } from '../../utils';
|
|
5
|
+
import { Badge } from '@urbicon-ui/blocks';
|
|
6
|
+
|
|
7
|
+
const tt = useTableI18n();
|
|
8
|
+
|
|
9
|
+
interface ChipItem {
|
|
10
|
+
type: TableAction;
|
|
11
|
+
id: string;
|
|
12
|
+
content: string;
|
|
13
|
+
onRemove: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let { class: className = '' } = $props();
|
|
17
|
+
|
|
18
|
+
const tableContext = getTableContext();
|
|
19
|
+
const { state: tableState, removeFilter, setGroupByKey, removeSummaryConfig } = tableContext;
|
|
20
|
+
|
|
21
|
+
function getColumnTitle(id: string): string {
|
|
22
|
+
// Raw-id fallback for persisted state that references a removed column.
|
|
23
|
+
const column = findColumnById(tableState.columns, id);
|
|
24
|
+
return column ? resolveColumnLabel(column) : id;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Aggregation codes (avg/min/max) differ from their translation keys
|
|
28
|
+
// (average/minimum/maximum). Map explicitly — interpolating the raw code into
|
|
29
|
+
// `summary.types.${type}` produced missing keys ("summary.types.avg") for
|
|
30
|
+
// avg/min/max. The map is type-checked against the translation keys and the
|
|
31
|
+
// SummaryConfig union, so a drift on either side is now a compile error.
|
|
32
|
+
const SUMMARY_TYPE_KEY = {
|
|
33
|
+
sum: 'summary.types.sum',
|
|
34
|
+
avg: 'summary.types.average',
|
|
35
|
+
count: 'summary.types.count',
|
|
36
|
+
min: 'summary.types.minimum',
|
|
37
|
+
max: 'summary.types.maximum'
|
|
38
|
+
} as const satisfies Record<SummaryConfig['type'], string>;
|
|
39
|
+
|
|
40
|
+
function getSummaryLabel(config: SummaryConfig): string {
|
|
41
|
+
const columnTitle = getColumnTitle(config.column);
|
|
42
|
+
const typeLabel = tt(SUMMARY_TYPE_KEY[config.type]);
|
|
43
|
+
return `${typeLabel}: ${columnTitle}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const allChips = $derived.by((): ChipItem[] => {
|
|
47
|
+
const chips: ChipItem[] = [];
|
|
48
|
+
|
|
49
|
+
// Filter Chips
|
|
50
|
+
tableState.activeFilters.forEach((filter, index) => {
|
|
51
|
+
chips.push({
|
|
52
|
+
type: 'filter',
|
|
53
|
+
id: `filter-${index}`,
|
|
54
|
+
content: `${getColumnTitle(filter.column)}: ${filter.value}`,
|
|
55
|
+
onRemove: () => removeFilter(index)
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (tableState.groupByKey) {
|
|
60
|
+
chips.push({
|
|
61
|
+
type: 'group',
|
|
62
|
+
id: 'group',
|
|
63
|
+
content: getColumnTitle(tableState.groupByKey),
|
|
64
|
+
onRemove: () => setGroupByKey(null)
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
tableState.summaryConfigs.forEach((config, index) => {
|
|
69
|
+
chips.push({
|
|
70
|
+
type: 'summary',
|
|
71
|
+
id: `summary-${index}`,
|
|
72
|
+
content: getSummaryLabel(config),
|
|
73
|
+
onRemove: () => removeSummaryConfig(config.column)
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return chips;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const hasChips = $derived(allChips.length > 0);
|
|
81
|
+
|
|
82
|
+
const CHIP_COLORS: Record<string, string> = {
|
|
83
|
+
filter: 'bg-filter-subtle text-filter border-filter/30',
|
|
84
|
+
group: 'bg-group-subtle text-group border-group/30',
|
|
85
|
+
summary: 'bg-summary-subtle text-summary border-summary/30'
|
|
86
|
+
};
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
{#if hasChips}
|
|
90
|
+
<div class="flex flex-wrap gap-1.5 {className}">
|
|
91
|
+
{#each allChips as chip (chip.id)}
|
|
92
|
+
<Badge
|
|
93
|
+
removable={true}
|
|
94
|
+
onRemove={chip.onRemove}
|
|
95
|
+
variant="outlined"
|
|
96
|
+
size="sm"
|
|
97
|
+
class={CHIP_COLORS[chip.type] ?? ''}
|
|
98
|
+
aria-label={tt('aria.removeItem', { content: chip.content })}
|
|
99
|
+
>
|
|
100
|
+
{chip.content}
|
|
101
|
+
</Badge>
|
|
102
|
+
{/each}
|
|
103
|
+
</div>
|
|
104
|
+
{/if}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getTableContext, useTableI18n } from '../..';
|
|
3
|
+
import { resolveColumnId, resolveColumnLabel } from '../../utils';
|
|
4
|
+
import {
|
|
5
|
+
Badge,
|
|
6
|
+
Button,
|
|
7
|
+
Select,
|
|
8
|
+
Tooltip,
|
|
9
|
+
resolveIcon,
|
|
10
|
+
EyeIcon as EyeIconDefault
|
|
11
|
+
} from '@urbicon-ui/blocks';
|
|
12
|
+
|
|
13
|
+
const tt = useTableI18n();
|
|
14
|
+
|
|
15
|
+
const EyeIcon = resolveIcon('eye', EyeIconDefault);
|
|
16
|
+
|
|
17
|
+
const tableContext = getTableContext();
|
|
18
|
+
const { toggleColumnVisibility } = tableContext;
|
|
19
|
+
|
|
20
|
+
let menuOpen = $state(false);
|
|
21
|
+
|
|
22
|
+
const hiddenCount = $derived(tableContext.hiddenColumnKeys.size);
|
|
23
|
+
|
|
24
|
+
const columnItems = $derived.by(() =>
|
|
25
|
+
tableContext.allColumns.map((col) => ({
|
|
26
|
+
label: resolveColumnLabel(col),
|
|
27
|
+
value: resolveColumnId(col)
|
|
28
|
+
}))
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const visibleValues = $derived.by(() =>
|
|
32
|
+
tableContext.allColumns
|
|
33
|
+
.filter((col) => !tableContext.hiddenColumnKeys.has(resolveColumnId(col)))
|
|
34
|
+
.map((col) => resolveColumnId(col))
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
function handleValueChange(values: string | string[] | null) {
|
|
38
|
+
if (!Array.isArray(values)) return;
|
|
39
|
+
const newVisible = new Set(values);
|
|
40
|
+
for (const col of tableContext.allColumns) {
|
|
41
|
+
const id = resolveColumnId(col);
|
|
42
|
+
const isCurrentlyHidden = tableContext.hiddenColumnKeys.has(id);
|
|
43
|
+
const shouldBeVisible = newVisible.has(id);
|
|
44
|
+
if (isCurrentlyHidden && shouldBeVisible) {
|
|
45
|
+
toggleColumnVisibility(id);
|
|
46
|
+
} else if (!isCurrentlyHidden && !shouldBeVisible) {
|
|
47
|
+
toggleColumnVisibility(id);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
{#snippet customTrigger(_selected: unknown[], _open: boolean, _clear: () => void)}
|
|
54
|
+
<Tooltip label={tt('columns.visibility')}>
|
|
55
|
+
<Button
|
|
56
|
+
variant="ghost"
|
|
57
|
+
intent="neutral"
|
|
58
|
+
size="sm"
|
|
59
|
+
active={hiddenCount > 0}
|
|
60
|
+
aria-expanded={menuOpen}
|
|
61
|
+
aria-haspopup="listbox"
|
|
62
|
+
onclick={() => (menuOpen = !menuOpen)}
|
|
63
|
+
>
|
|
64
|
+
<EyeIcon class="h-4 w-4" />
|
|
65
|
+
{#if hiddenCount > 0}
|
|
66
|
+
<Badge variant="filled" intent="primary" size="xs" counter class="ml-1">
|
|
67
|
+
{hiddenCount}
|
|
68
|
+
</Badge>
|
|
69
|
+
{/if}
|
|
70
|
+
</Button>
|
|
71
|
+
</Tooltip>
|
|
72
|
+
{/snippet}
|
|
73
|
+
|
|
74
|
+
<Select
|
|
75
|
+
options={columnItems}
|
|
76
|
+
multiple
|
|
77
|
+
value={visibleValues}
|
|
78
|
+
bind:open={menuOpen}
|
|
79
|
+
onValueChange={handleValueChange}
|
|
80
|
+
size="sm"
|
|
81
|
+
syncWidth={false}
|
|
82
|
+
selectionIndicator="checkmark"
|
|
83
|
+
{customTrigger}
|
|
84
|
+
/>
|