@shotleybuilder/svelte-table-kit 0.2.0 → 0.5.0
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 +121 -8
- package/dist/TableKit.svelte +255 -50
- package/dist/TableKit.svelte.d.ts +1 -0
- package/dist/components/ColumnMenu.svelte +271 -0
- package/dist/components/ColumnMenu.svelte.d.ts +29 -0
- package/dist/components/FilterBar.svelte +12 -5
- package/dist/components/FilterBar.svelte.d.ts +2 -0
- package/dist/components/GroupBar.svelte +12 -5
- package/dist/components/GroupBar.svelte.d.ts +2 -0
- package/dist/components/SortBar.svelte +239 -0
- package/dist/components/SortBar.svelte.d.ts +22 -0
- package/dist/components/SortCondition.svelte +131 -0
- package/dist/components/SortCondition.svelte.d.ts +25 -0
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,10 +19,11 @@ Svelte Table Kit brings Airtable-like functionality to your Svelte applications
|
|
|
19
19
|
- ↔️ **Column spacing control** - 3 sizes: narrow, normal, wide
|
|
20
20
|
- 🔍 **Advanced filtering** - 12 operators with AND/OR logic
|
|
21
21
|
- 📊 **Multi-level grouping** - Up to 3 nested levels (like Airtable)
|
|
22
|
-
- ⬆️
|
|
22
|
+
- ⬆️ **Flexible sorting** - Column header or Airtable-style sort control
|
|
23
23
|
- 📄 Pagination with customizable page sizes
|
|
24
24
|
- 💾 LocalStorage persistence for all user preferences
|
|
25
25
|
- ✂️ Text truncation with ellipsis for long content
|
|
26
|
+
- 📋 **Column context menu** - Quick access to sort, filter, group, and hide actions
|
|
26
27
|
|
|
27
28
|
**Advanced Filtering:**
|
|
28
29
|
- 12 filter operators: equals, contains, starts with, greater than, etc.
|
|
@@ -31,6 +32,13 @@ Svelte Table Kit brings Airtable-like functionality to your Svelte applications
|
|
|
31
32
|
- Active filter count badge
|
|
32
33
|
- Real-time filtering as you type
|
|
33
34
|
|
|
35
|
+
**Sorting Options:**
|
|
36
|
+
- **Column header mode** (default) - Click headers to sort with ↑↓↕ indicators
|
|
37
|
+
- **Airtable-style control** - Dedicated sort dropdown with multi-level sorting
|
|
38
|
+
- Choose column and direction (A → Z or Z → A)
|
|
39
|
+
- Multiple sort levels applied top to bottom
|
|
40
|
+
- Collapsible SortBar UI
|
|
41
|
+
|
|
34
42
|
**Grouping & Hierarchy:**
|
|
35
43
|
- Group by up to 3 columns simultaneously
|
|
36
44
|
- Expand/collapse groups with chevron buttons
|
|
@@ -38,6 +46,15 @@ Svelte Table Kit brings Airtable-like functionality to your Svelte applications
|
|
|
38
46
|
- Item count per group
|
|
39
47
|
- Collapsible GroupBar UI
|
|
40
48
|
|
|
49
|
+
**Column Context Menu:**
|
|
50
|
+
- Hover over column headers to reveal menu trigger (chevron icon)
|
|
51
|
+
- **Sort A → Z / Sort Z → A** - Quick sort with active state indication
|
|
52
|
+
- **Filter by this field** - Creates pre-filled filter condition
|
|
53
|
+
- **Group by this field** - Adds column to grouping configuration
|
|
54
|
+
- **Hide field** - Remove column from view
|
|
55
|
+
- Actions conditionally shown based on feature flags
|
|
56
|
+
- Seamlessly integrates with existing controls
|
|
57
|
+
|
|
41
58
|
**Developer Experience:**
|
|
42
59
|
- 🎨 Headless design - style it your way
|
|
43
60
|
- 📦 Built on TanStack Table v8 (battle-tested, powerful)
|
|
@@ -49,6 +66,7 @@ Svelte Table Kit brings Airtable-like functionality to your Svelte applications
|
|
|
49
66
|
**AI-Ready:**
|
|
50
67
|
- 🤖 JSON-schema driven configuration
|
|
51
68
|
- 🧠 AI agents can generate table configs from natural language
|
|
69
|
+
- ⚡ **Reactive config prop** - Update table state dynamically without remounting (v0.5.0+)
|
|
52
70
|
- 📋 Preset configurations for common use cases
|
|
53
71
|
- 🔧 Programmatic table setup and state management
|
|
54
72
|
|
|
@@ -107,19 +125,30 @@ The simplest way to use TableKit:
|
|
|
107
125
|
|
|
108
126
|
### With Configuration
|
|
109
127
|
|
|
110
|
-
|
|
128
|
+
Customize initial table state programmatically:
|
|
111
129
|
|
|
112
130
|
```svelte
|
|
113
131
|
<script>
|
|
114
|
-
import { TableKit
|
|
115
|
-
|
|
116
|
-
const config = presets.dashboard; // or generate with AI
|
|
132
|
+
import { TableKit } from '@shotleybuilder/svelte-table-kit';
|
|
117
133
|
</script>
|
|
118
134
|
|
|
119
135
|
<TableKit
|
|
120
136
|
{data}
|
|
121
137
|
{columns}
|
|
122
|
-
{
|
|
138
|
+
config={{
|
|
139
|
+
id: 'my-view-v1',
|
|
140
|
+
version: '1.0',
|
|
141
|
+
defaultColumnOrder: ['name', 'role', 'age', 'id'],
|
|
142
|
+
defaultColumnSizing: { name: 200, role: 150 },
|
|
143
|
+
defaultVisibleColumns: ['name', 'role', 'age'],
|
|
144
|
+
defaultFilters: [
|
|
145
|
+
{ id: 'f1', field: 'role', operator: 'equals', value: 'Developer' }
|
|
146
|
+
],
|
|
147
|
+
defaultSorting: [
|
|
148
|
+
{ columnId: 'name', direction: 'asc' }
|
|
149
|
+
],
|
|
150
|
+
filterLogic: 'and'
|
|
151
|
+
}}
|
|
123
152
|
features={{
|
|
124
153
|
columnVisibility: true,
|
|
125
154
|
filtering: true,
|
|
@@ -129,6 +158,66 @@ Use AI-generated or predefined configurations:
|
|
|
129
158
|
/>
|
|
130
159
|
```
|
|
131
160
|
|
|
161
|
+
### Reactive Configuration (v0.5.0+)
|
|
162
|
+
|
|
163
|
+
**The `config` prop is fully reactive** - update it dynamically to change table state without remounting:
|
|
164
|
+
|
|
165
|
+
```svelte
|
|
166
|
+
<script>
|
|
167
|
+
import { TableKit } from '@shotleybuilder/svelte-table-kit';
|
|
168
|
+
|
|
169
|
+
let tableConfig = $state({
|
|
170
|
+
id: 'query-1',
|
|
171
|
+
version: '1.0',
|
|
172
|
+
defaultFilters: [
|
|
173
|
+
{ id: 'f1', field: 'status', operator: 'equals', value: 'active' }
|
|
174
|
+
]
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Update config - table reacts automatically
|
|
178
|
+
function showPendingItems() {
|
|
179
|
+
tableConfig = {
|
|
180
|
+
id: 'query-2', // New ID triggers update
|
|
181
|
+
version: '1.0',
|
|
182
|
+
defaultFilters: [
|
|
183
|
+
{ id: 'f1', field: 'status', operator: 'equals', value: 'pending' }
|
|
184
|
+
]
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
</script>
|
|
188
|
+
|
|
189
|
+
<button on:click={showPendingItems}>Show Pending</button>
|
|
190
|
+
<TableKit {data} {columns} config={tableConfig} persistState={false} />
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Perfect for AI-driven tables:**
|
|
194
|
+
|
|
195
|
+
```svelte
|
|
196
|
+
<script>
|
|
197
|
+
let aiConfig = $state(undefined);
|
|
198
|
+
|
|
199
|
+
async function askAI(question) {
|
|
200
|
+
const response = await fetch('/api/nl-query', {
|
|
201
|
+
method: 'POST',
|
|
202
|
+
body: JSON.stringify({ question })
|
|
203
|
+
});
|
|
204
|
+
aiConfig = await response.json(); // Table updates automatically
|
|
205
|
+
}
|
|
206
|
+
</script>
|
|
207
|
+
|
|
208
|
+
<input
|
|
209
|
+
placeholder="Ask a question about the data..."
|
|
210
|
+
on:submit={(e) => askAI(e.target.value)}
|
|
211
|
+
/>
|
|
212
|
+
<TableKit {data} {columns} config={aiConfig} persistState={false} />
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Key Points:**
|
|
216
|
+
- Config changes detected by comparing `config.id`
|
|
217
|
+
- Set `persistState={false}` to prevent localStorage conflicts
|
|
218
|
+
- When config is active, localStorage is automatically ignored
|
|
219
|
+
- No `{#key}` blocks needed - updates are smooth and instant
|
|
220
|
+
|
|
132
221
|
### Feature Flags
|
|
133
222
|
|
|
134
223
|
Control which features are enabled:
|
|
@@ -143,6 +232,7 @@ Control which features are enabled:
|
|
|
143
232
|
columnReordering: true,
|
|
144
233
|
filtering: true,
|
|
145
234
|
sorting: true,
|
|
235
|
+
sortingMode: 'control', // 'header' (default) or 'control' (Airtable-style)
|
|
146
236
|
pagination: true,
|
|
147
237
|
rowSelection: false,
|
|
148
238
|
grouping: false
|
|
@@ -150,6 +240,10 @@ Control which features are enabled:
|
|
|
150
240
|
/>
|
|
151
241
|
```
|
|
152
242
|
|
|
243
|
+
**Sorting Modes:**
|
|
244
|
+
- `sortingMode: 'header'` - Click column headers to sort (default behavior)
|
|
245
|
+
- `sortingMode: 'control'` - Use Airtable-style sort dropdown with multi-level support
|
|
246
|
+
|
|
153
247
|
### Event Handlers
|
|
154
248
|
|
|
155
249
|
Listen to table events:
|
|
@@ -201,10 +295,10 @@ TableKit is headless by default. You can:
|
|
|
201
295
|
|------|------|---------|-------------|
|
|
202
296
|
| `data` | `T[]` | `[]` | Table data array |
|
|
203
297
|
| `columns` | `ColumnDef<T>[]` | `[]` | Column definitions |
|
|
204
|
-
| `config` | `TableConfig` | `undefined` |
|
|
298
|
+
| `config` | `TableConfig` | `undefined` | Reactive table configuration (requires `id` and `version`) |
|
|
205
299
|
| `features` | `TableFeatures` | All enabled | Feature flags |
|
|
206
300
|
| `storageKey` | `string` | `undefined` | LocalStorage key for persistence |
|
|
207
|
-
| `persistState` | `boolean` | `true` | Enable state persistence |
|
|
301
|
+
| `persistState` | `boolean` | `true` | Enable state persistence (auto-disabled when config is active) |
|
|
208
302
|
| `theme` | `'light' \| 'dark' \| 'auto'` | `'light'` | Theme mode |
|
|
209
303
|
| `align` | `'left' \| 'center' \| 'right'` | `'left'` | Column text alignment |
|
|
210
304
|
| `rowHeight` | `'short' \| 'medium' \| 'tall' \| 'extra_tall'` | `'medium'` | Row height preset |
|
|
@@ -213,6 +307,25 @@ TableKit is headless by default. You can:
|
|
|
213
307
|
| `onRowSelect` | `(rows: T[]) => void` | `undefined` | Row selection handler |
|
|
214
308
|
| `onStateChange` | `(state: TableState) => void` | `undefined` | State change handler |
|
|
215
309
|
|
|
310
|
+
### TableConfig Type
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
interface TableConfig {
|
|
314
|
+
id: string; // Required: Unique identifier for change detection
|
|
315
|
+
version: string; // Required: Config version
|
|
316
|
+
defaultColumnOrder?: string[]; // Column IDs in display order
|
|
317
|
+
defaultColumnSizing?: Record<string, number>; // Column widths in pixels
|
|
318
|
+
defaultVisibleColumns?: string[]; // Visible column IDs (others hidden)
|
|
319
|
+
defaultFilters?: FilterCondition[]; // Initial filter conditions
|
|
320
|
+
defaultSorting?: SortConfig[]; // Initial sort configuration
|
|
321
|
+
filterLogic?: 'and' | 'or'; // Filter combination logic
|
|
322
|
+
pagination?: {
|
|
323
|
+
pageSize: number;
|
|
324
|
+
pageSizeOptions?: number[];
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
216
329
|
---
|
|
217
330
|
|
|
218
331
|
## 🎯 Use Cases
|
package/dist/TableKit.svelte
CHANGED
|
@@ -23,8 +23,11 @@ import {
|
|
|
23
23
|
import { applyFilters } from "./utils/filters";
|
|
24
24
|
import FilterBar from "./components/FilterBar.svelte";
|
|
25
25
|
import GroupBar from "./components/GroupBar.svelte";
|
|
26
|
+
import SortBar from "./components/SortBar.svelte";
|
|
27
|
+
import ColumnMenu from "./components/ColumnMenu.svelte";
|
|
26
28
|
export let data = [];
|
|
27
29
|
export let columns = [];
|
|
30
|
+
export let config = void 0;
|
|
28
31
|
export let storageKey = "table-kit";
|
|
29
32
|
export let persistState = true;
|
|
30
33
|
export let align = "left";
|
|
@@ -36,38 +39,123 @@ export let features = {
|
|
|
36
39
|
columnReordering: true,
|
|
37
40
|
filtering: true,
|
|
38
41
|
sorting: true,
|
|
42
|
+
sortingMode: "header",
|
|
43
|
+
// 'header' or 'control'
|
|
39
44
|
pagination: true
|
|
40
45
|
};
|
|
41
46
|
export let onRowClick = void 0;
|
|
42
47
|
export let onRowSelect = void 0;
|
|
43
48
|
export let onStateChange = void 0;
|
|
44
49
|
let sorting = writable([]);
|
|
45
|
-
let columnVisibility = writable(
|
|
46
|
-
|
|
47
|
-
);
|
|
48
|
-
let
|
|
49
|
-
persistState && storageKey ? loadColumnSizing(storageKey) : {}
|
|
50
|
-
);
|
|
51
|
-
let columnFilters = writable(
|
|
52
|
-
persistState && storageKey ? loadColumnFilters(storageKey) : []
|
|
53
|
-
);
|
|
54
|
-
let columnOrder = writable(
|
|
55
|
-
persistState && storageKey ? loadColumnOrder(storageKey) : []
|
|
56
|
-
);
|
|
50
|
+
let columnVisibility = writable({});
|
|
51
|
+
let columnSizing = writable({});
|
|
52
|
+
let columnFilters = writable([]);
|
|
53
|
+
let columnOrder = writable([]);
|
|
57
54
|
let filterConditions = writable([]);
|
|
58
55
|
let filterLogic = writable("and");
|
|
56
|
+
let filterBarExpanded = false;
|
|
57
|
+
let previousConfigId = config?.id;
|
|
58
|
+
let configInitialized = false;
|
|
59
59
|
let grouping = writable([]);
|
|
60
60
|
let expanded = writable(true);
|
|
61
|
+
let groupBarExpanded = false;
|
|
62
|
+
$: {
|
|
63
|
+
const configChanged = config?.id && config.id !== previousConfigId;
|
|
64
|
+
const hasConfig = config !== void 0 && config !== null;
|
|
65
|
+
if (hasConfig && config && (configChanged || !configInitialized)) {
|
|
66
|
+
if (config.defaultColumnOrder && config.defaultColumnOrder.length > 0) {
|
|
67
|
+
columnOrder.set(config.defaultColumnOrder);
|
|
68
|
+
}
|
|
69
|
+
if (config.defaultVisibleColumns && columns.length > 0) {
|
|
70
|
+
const visibilityMap = {};
|
|
71
|
+
columns.forEach((col) => {
|
|
72
|
+
const colId = col.accessorKey || col.id;
|
|
73
|
+
if (config && config.defaultVisibleColumns) {
|
|
74
|
+
visibilityMap[colId] = config.defaultVisibleColumns.includes(colId);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
columnVisibility.set(visibilityMap);
|
|
78
|
+
}
|
|
79
|
+
if (config.defaultSorting) {
|
|
80
|
+
sorting.set(config.defaultSorting);
|
|
81
|
+
}
|
|
82
|
+
if (config.defaultFilters) {
|
|
83
|
+
filterConditions.set(config.defaultFilters);
|
|
84
|
+
}
|
|
85
|
+
if (config.filterLogic) {
|
|
86
|
+
filterLogic.set(config.filterLogic);
|
|
87
|
+
}
|
|
88
|
+
if (config.defaultColumnSizing) {
|
|
89
|
+
columnSizing.set(config.defaultColumnSizing);
|
|
90
|
+
}
|
|
91
|
+
previousConfigId = config.id;
|
|
92
|
+
configInitialized = true;
|
|
93
|
+
} else if (!hasConfig && persistState && storageKey && !configInitialized) {
|
|
94
|
+
columnOrder.set(loadColumnOrder(storageKey) || []);
|
|
95
|
+
columnVisibility.set(loadColumnVisibility(storageKey) || {});
|
|
96
|
+
columnSizing.set(loadColumnSizing(storageKey) || {});
|
|
97
|
+
columnFilters.set(loadColumnFilters(storageKey) || []);
|
|
98
|
+
configInitialized = true;
|
|
99
|
+
} else if (!hasConfig && !configInitialized) {
|
|
100
|
+
configInitialized = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
61
103
|
$: horizontalPadding = columnSpacing === "narrow" ? 0.5 : columnSpacing === "wide" ? 2 : 1;
|
|
62
104
|
$: verticalPadding = rowHeight === "short" ? 0.375 : rowHeight === "tall" ? 1 : rowHeight === "extra_tall" ? 1.5 : 0.75;
|
|
63
105
|
$: filteredData = applyFilters(data, $filterConditions, $filterLogic);
|
|
64
|
-
$:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
106
|
+
$: {
|
|
107
|
+
const hasActiveConfig = config !== void 0 && config !== null;
|
|
108
|
+
const shouldPersist = persistState && !hasActiveConfig && storageKey && isBrowser;
|
|
109
|
+
if (shouldPersist) {
|
|
110
|
+
saveColumnVisibility(storageKey, $columnVisibility);
|
|
111
|
+
saveColumnSizing(storageKey, $columnSizing);
|
|
112
|
+
saveColumnFilters(storageKey, $columnFilters);
|
|
113
|
+
saveColumnOrder(storageKey, $columnOrder);
|
|
114
|
+
}
|
|
69
115
|
}
|
|
70
116
|
let showColumnPicker = false;
|
|
117
|
+
let columnPickerButton = null;
|
|
118
|
+
let columnPickerPosition = { top: 0, left: 0 };
|
|
119
|
+
let openColumnMenuId = null;
|
|
120
|
+
function generateFilterId() {
|
|
121
|
+
return `filter-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
122
|
+
}
|
|
123
|
+
function addFilterForColumn(columnId) {
|
|
124
|
+
const newCondition = {
|
|
125
|
+
id: generateFilterId(),
|
|
126
|
+
field: columnId,
|
|
127
|
+
operator: "equals",
|
|
128
|
+
value: ""
|
|
129
|
+
};
|
|
130
|
+
filterConditions.update((conditions) => [...conditions, newCondition]);
|
|
131
|
+
filterBarExpanded = true;
|
|
132
|
+
}
|
|
133
|
+
function addGroupForColumn(columnId) {
|
|
134
|
+
grouping.update((groups) => {
|
|
135
|
+
if (groups.includes(columnId)) {
|
|
136
|
+
return groups;
|
|
137
|
+
}
|
|
138
|
+
if (groups.length >= 3) {
|
|
139
|
+
return groups;
|
|
140
|
+
}
|
|
141
|
+
return [...groups, columnId];
|
|
142
|
+
});
|
|
143
|
+
groupBarExpanded = true;
|
|
144
|
+
}
|
|
145
|
+
function updateColumnPickerPosition() {
|
|
146
|
+
if (columnPickerButton && showColumnPicker) {
|
|
147
|
+
const rect = columnPickerButton.getBoundingClientRect();
|
|
148
|
+
columnPickerPosition = {
|
|
149
|
+
top: rect.bottom + 8,
|
|
150
|
+
// 8px margin (0.5rem)
|
|
151
|
+
left: rect.right - 224
|
|
152
|
+
// 224px = 14rem dropdown width
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
$: if (showColumnPicker) {
|
|
157
|
+
updateColumnPickerPosition();
|
|
158
|
+
}
|
|
71
159
|
let showRowHeightMenu = false;
|
|
72
160
|
let showColumnSpacingMenu = false;
|
|
73
161
|
let draggedColumnId = null;
|
|
@@ -194,6 +282,7 @@ $: if ($columnOrder.length === 0 && columns.length > 0) {
|
|
|
194
282
|
columnOrder.set(columns.map((col) => col.accessorKey || col.id));
|
|
195
283
|
}
|
|
196
284
|
$: hasActiveFilters = $filterConditions.length > 0;
|
|
285
|
+
$: totalTableWidth = $table.getVisibleLeafColumns().reduce((sum, col) => sum + col.getSize(), 0);
|
|
197
286
|
$: if (onStateChange) {
|
|
198
287
|
onStateChange({
|
|
199
288
|
columnVisibility: $columnVisibility,
|
|
@@ -208,7 +297,7 @@ $: if (onStateChange) {
|
|
|
208
297
|
|
|
209
298
|
<div class="table-kit-container align-{align}">
|
|
210
299
|
<!-- Filters and Controls -->
|
|
211
|
-
{#if features.filtering !== false || features.grouping !== false || features.columnVisibility !== false}
|
|
300
|
+
{#if features.filtering !== false || features.grouping !== false || features.columnVisibility !== false || (features.sorting !== false && features.sortingMode === 'control')}
|
|
212
301
|
<div class="table-kit-toolbar">
|
|
213
302
|
<!-- Filter Controls -->
|
|
214
303
|
{#if features.filtering !== false}
|
|
@@ -219,6 +308,19 @@ $: if (onStateChange) {
|
|
|
219
308
|
onConditionsChange={(newConditions) => filterConditions.set(newConditions)}
|
|
220
309
|
logic={$filterLogic}
|
|
221
310
|
onLogicChange={(newLogic) => filterLogic.set(newLogic)}
|
|
311
|
+
isExpanded={filterBarExpanded}
|
|
312
|
+
onExpandedChange={(expanded) => (filterBarExpanded = expanded)}
|
|
313
|
+
/>
|
|
314
|
+
</div>
|
|
315
|
+
{/if}
|
|
316
|
+
|
|
317
|
+
<!-- Sort Controls (when sortingMode is 'control') -->
|
|
318
|
+
{#if features.sorting !== false && features.sortingMode === 'control'}
|
|
319
|
+
<div class="table-kit-sorts">
|
|
320
|
+
<SortBar
|
|
321
|
+
{columns}
|
|
322
|
+
sorting={$sorting}
|
|
323
|
+
onSortingChange={(newSorting) => sorting.set(newSorting)}
|
|
222
324
|
/>
|
|
223
325
|
</div>
|
|
224
326
|
{/if}
|
|
@@ -230,6 +332,8 @@ $: if (onStateChange) {
|
|
|
230
332
|
{columns}
|
|
231
333
|
grouping={$grouping}
|
|
232
334
|
onGroupingChange={(newGrouping) => grouping.set(newGrouping)}
|
|
335
|
+
isExpanded={groupBarExpanded}
|
|
336
|
+
onExpandedChange={(expanded) => (groupBarExpanded = expanded)}
|
|
233
337
|
/>
|
|
234
338
|
</div>
|
|
235
339
|
{/if}
|
|
@@ -405,6 +509,7 @@ $: if (onStateChange) {
|
|
|
405
509
|
<div class="table-kit-column-picker">
|
|
406
510
|
<div class="relative">
|
|
407
511
|
<button
|
|
512
|
+
bind:this={columnPickerButton}
|
|
408
513
|
on:click={() => (showColumnPicker = !showColumnPicker)}
|
|
409
514
|
class="column-picker-btn"
|
|
410
515
|
>
|
|
@@ -423,7 +528,10 @@ $: if (onStateChange) {
|
|
|
423
528
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
424
529
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
425
530
|
<div class="backdrop" on:click={() => (showColumnPicker = false)} />
|
|
426
|
-
<div
|
|
531
|
+
<div
|
|
532
|
+
class="column-picker-dropdown"
|
|
533
|
+
style="top: {columnPickerPosition.top}px; left: {columnPickerPosition.left}px;"
|
|
534
|
+
>
|
|
427
535
|
<div class="dropdown-header">
|
|
428
536
|
<span>Toggle Columns</span>
|
|
429
537
|
<div class="header-actions">
|
|
@@ -465,7 +573,7 @@ $: if (onStateChange) {
|
|
|
465
573
|
</div>
|
|
466
574
|
{:else}
|
|
467
575
|
<div class="table-kit-scroll">
|
|
468
|
-
<table class="table-kit-table">
|
|
576
|
+
<table class="table-kit-table" style="width: {totalTableWidth}px;">
|
|
469
577
|
<thead>
|
|
470
578
|
{#each $table.getHeaderGroups() as headerGroup}
|
|
471
579
|
<tr>
|
|
@@ -477,34 +585,97 @@ $: if (onStateChange) {
|
|
|
477
585
|
style="width: {header.getSize()}px;"
|
|
478
586
|
>
|
|
479
587
|
{#if !header.isPlaceholder}
|
|
480
|
-
<div
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
<button
|
|
490
|
-
class="sort-btn"
|
|
491
|
-
class:sortable={header.column.getCanSort()}
|
|
492
|
-
on:click={header.column.getToggleSortingHandler()}
|
|
588
|
+
<div class="th-wrapper">
|
|
589
|
+
<div
|
|
590
|
+
class="th-content"
|
|
591
|
+
style="padding: {verticalPadding}rem {horizontalPadding}rem; cursor: {features.columnReordering !==
|
|
592
|
+
false
|
|
593
|
+
? 'grab'
|
|
594
|
+
: 'default'};"
|
|
595
|
+
draggable={features.columnReordering !== false}
|
|
596
|
+
on:dragstart={() => handleDragStart(header.column.id)}
|
|
493
597
|
>
|
|
494
|
-
|
|
495
|
-
<
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
598
|
+
{#if features.sorting !== false && features.sortingMode !== 'control' && header.column.getCanSort()}
|
|
599
|
+
<button
|
|
600
|
+
class="sort-btn"
|
|
601
|
+
class:sortable={header.column.getCanSort()}
|
|
602
|
+
on:click={header.column.getToggleSortingHandler()}
|
|
603
|
+
>
|
|
604
|
+
<span class="header-text">
|
|
605
|
+
<svelte:component
|
|
606
|
+
this={flexRender(header.column.columnDef.header, header.getContext())}
|
|
607
|
+
/>
|
|
608
|
+
</span>
|
|
609
|
+
<span class="sort-icon">
|
|
610
|
+
{{
|
|
611
|
+
asc: '↑',
|
|
612
|
+
desc: '↓'
|
|
613
|
+
}[header.column.getIsSorted()] ?? '↕'}
|
|
614
|
+
</span>
|
|
615
|
+
</button>
|
|
616
|
+
{:else}
|
|
617
|
+
<span class="header-text">
|
|
618
|
+
<svelte:component
|
|
619
|
+
this={flexRender(header.column.columnDef.header, header.getContext())}
|
|
620
|
+
/>
|
|
505
621
|
</span>
|
|
506
622
|
{/if}
|
|
507
|
-
|
|
623
|
+
|
|
624
|
+
<!-- Column Menu Trigger -->
|
|
625
|
+
<button
|
|
626
|
+
class="column-menu-trigger"
|
|
627
|
+
on:click|stopPropagation={() => {
|
|
628
|
+
openColumnMenuId =
|
|
629
|
+
openColumnMenuId === header.column.id ? null : header.column.id;
|
|
630
|
+
}}
|
|
631
|
+
aria-label="Column options"
|
|
632
|
+
>
|
|
633
|
+
<svg
|
|
634
|
+
width="12"
|
|
635
|
+
height="12"
|
|
636
|
+
viewBox="0 0 12 12"
|
|
637
|
+
fill="none"
|
|
638
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
639
|
+
>
|
|
640
|
+
<path
|
|
641
|
+
d="M3 5L6 8L9 5"
|
|
642
|
+
stroke="currentColor"
|
|
643
|
+
stroke-width="1.5"
|
|
644
|
+
stroke-linecap="round"
|
|
645
|
+
stroke-linejoin="round"
|
|
646
|
+
/>
|
|
647
|
+
</svg>
|
|
648
|
+
</button>
|
|
649
|
+
</div>
|
|
650
|
+
|
|
651
|
+
<!-- Column Menu -->
|
|
652
|
+
<ColumnMenu
|
|
653
|
+
column={header.column}
|
|
654
|
+
isOpen={openColumnMenuId === header.column.id}
|
|
655
|
+
canSort={features.sorting !== false}
|
|
656
|
+
canFilter={features.filtering !== false}
|
|
657
|
+
canGroup={features.grouping !== false}
|
|
658
|
+
on:sort={(e) => {
|
|
659
|
+
const direction = e.detail.direction;
|
|
660
|
+
header.column.toggleSorting(direction === 'desc');
|
|
661
|
+
openColumnMenuId = null;
|
|
662
|
+
}}
|
|
663
|
+
on:filter={() => {
|
|
664
|
+
addFilterForColumn(header.column.id);
|
|
665
|
+
openColumnMenuId = null;
|
|
666
|
+
}}
|
|
667
|
+
on:group={() => {
|
|
668
|
+
addGroupForColumn(header.column.id);
|
|
669
|
+
openColumnMenuId = null;
|
|
670
|
+
}}
|
|
671
|
+
on:hide={() => {
|
|
672
|
+
header.column.toggleVisibility(false);
|
|
673
|
+
openColumnMenuId = null;
|
|
674
|
+
}}
|
|
675
|
+
on:close={() => {
|
|
676
|
+
openColumnMenuId = null;
|
|
677
|
+
}}
|
|
678
|
+
/>
|
|
508
679
|
</div>
|
|
509
680
|
<!-- Resize Handle -->
|
|
510
681
|
{#if features.columnResizing !== false && header.column.getCanResize()}
|
|
@@ -822,9 +993,9 @@ $: if (onStateChange) {
|
|
|
822
993
|
}
|
|
823
994
|
|
|
824
995
|
.column-picker-dropdown {
|
|
825
|
-
position:
|
|
826
|
-
right:
|
|
827
|
-
z-index:
|
|
996
|
+
position: fixed; /* Use fixed to break out of container constraints */
|
|
997
|
+
right: auto;
|
|
998
|
+
z-index: 30;
|
|
828
999
|
margin-top: 0.5rem;
|
|
829
1000
|
width: 14rem;
|
|
830
1001
|
border-radius: 0.375rem;
|
|
@@ -871,7 +1042,8 @@ $: if (onStateChange) {
|
|
|
871
1042
|
}
|
|
872
1043
|
|
|
873
1044
|
.column-list {
|
|
874
|
-
|
|
1045
|
+
min-height: 12rem; /* Ensure picker stays usable even when all columns hidden */
|
|
1046
|
+
max-height: 20rem;
|
|
875
1047
|
overflow-y: auto;
|
|
876
1048
|
}
|
|
877
1049
|
|
|
@@ -910,9 +1082,9 @@ $: if (onStateChange) {
|
|
|
910
1082
|
}
|
|
911
1083
|
|
|
912
1084
|
.table-kit-table {
|
|
913
|
-
width: auto;
|
|
914
1085
|
border-collapse: collapse;
|
|
915
1086
|
table-layout: fixed;
|
|
1087
|
+
min-width: 100%; /* Ensure table is at least full container width */
|
|
916
1088
|
}
|
|
917
1089
|
|
|
918
1090
|
thead {
|
|
@@ -1006,6 +1178,39 @@ $: if (onStateChange) {
|
|
|
1006
1178
|
color: #9ca3af;
|
|
1007
1179
|
}
|
|
1008
1180
|
|
|
1181
|
+
.th-wrapper {
|
|
1182
|
+
position: relative;
|
|
1183
|
+
display: flex;
|
|
1184
|
+
align-items: center;
|
|
1185
|
+
width: 100%;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
.column-menu-trigger {
|
|
1189
|
+
flex-shrink: 0;
|
|
1190
|
+
display: flex;
|
|
1191
|
+
align-items: center;
|
|
1192
|
+
justify-content: center;
|
|
1193
|
+
padding: 0.25rem;
|
|
1194
|
+
margin-left: 0.25rem;
|
|
1195
|
+
background: transparent;
|
|
1196
|
+
border: none;
|
|
1197
|
+
border-radius: 0.25rem;
|
|
1198
|
+
cursor: pointer;
|
|
1199
|
+
color: #9ca3af;
|
|
1200
|
+
transition: all 0.15s;
|
|
1201
|
+
opacity: 0;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
.th-wrapper:hover .column-menu-trigger,
|
|
1205
|
+
.column-menu-trigger:focus {
|
|
1206
|
+
opacity: 1;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
.column-menu-trigger:hover {
|
|
1210
|
+
background-color: #f3f4f6;
|
|
1211
|
+
color: #374151;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1009
1214
|
.resize-handle {
|
|
1010
1215
|
position: absolute;
|
|
1011
1216
|
top: 0;
|
|
@@ -5,6 +5,7 @@ declare class __sveltets_Render<T> {
|
|
|
5
5
|
props(): {
|
|
6
6
|
data?: T[] | undefined;
|
|
7
7
|
columns?: ColumnDef<T>[] | undefined;
|
|
8
|
+
config?: TableKitProps<T_1>["config"];
|
|
8
9
|
storageKey?: TableKitProps<T_1>["storageKey"];
|
|
9
10
|
persistState?: TableKitProps<T_1>["persistState"];
|
|
10
11
|
align?: TableKitProps<T_1>["align"];
|