@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 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
- - ⬆️ Multi-column sorting with visual indicators
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
- Use AI-generated or predefined configurations:
128
+ Customize initial table state programmatically:
111
129
 
112
130
  ```svelte
113
131
  <script>
114
- import { TableKit, presets } from '@shotleybuilder/svelte-table-kit';
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
- {config}
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` | AI-generated or preset config |
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
@@ -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
- persistState && storageKey ? loadColumnVisibility(storageKey) : {}
47
- );
48
- let columnSizing = writable(
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
- $: if (persistState && storageKey && isBrowser) {
65
- saveColumnVisibility(storageKey, $columnVisibility);
66
- saveColumnSizing(storageKey, $columnSizing);
67
- saveColumnFilters(storageKey, $columnFilters);
68
- saveColumnOrder(storageKey, $columnOrder);
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 class="column-picker-dropdown">
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
- class="th-content"
482
- style="padding: {verticalPadding}rem {horizontalPadding}rem; cursor: {features.columnReordering !==
483
- false
484
- ? 'grab'
485
- : 'default'};"
486
- draggable={features.columnReordering !== false}
487
- on:dragstart={() => handleDragStart(header.column.id)}
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
- <span class="header-text">
495
- <svelte:component
496
- this={flexRender(header.column.columnDef.header, header.getContext())}
497
- />
498
- </span>
499
- {#if features.sorting !== false && header.column.getCanSort()}
500
- <span class="sort-icon">
501
- {{
502
- asc: '↑',
503
- desc: '↓'
504
- }[header.column.getIsSorted()] ?? '↕'}
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
- </button>
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: absolute;
826
- right: 0;
827
- z-index: 20;
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
- max-height: 16rem;
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"];