@beeblock/svelar-datatable 0.1.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.
Files changed (44) hide show
  1. package/README.md +149 -0
  2. package/dist/SvelarDatatablePlugin.d.ts +13 -0
  3. package/dist/export/ExportManager.d.ts +4 -0
  4. package/dist/export/clipboard.d.ts +2 -0
  5. package/dist/export/csv.d.ts +4 -0
  6. package/dist/export/excel.d.ts +2 -0
  7. package/dist/export/index.d.ts +6 -0
  8. package/dist/export/pdf.d.ts +2 -0
  9. package/dist/export/print.d.ts +2 -0
  10. package/dist/index.d.ts +5 -0
  11. package/dist/index.js +19 -0
  12. package/dist/server/DataTableController.d.ts +4 -0
  13. package/dist/server/DataTableRequest.d.ts +3 -0
  14. package/dist/server/DataTableService.d.ts +25 -0
  15. package/dist/server/index.d.ts +3 -0
  16. package/dist/server/index.js +1 -0
  17. package/dist/state/DataTableStore.d.ts +64 -0
  18. package/dist/state/ServerDataTableStore.d.ts +23 -0
  19. package/dist/state/index.d.ts +2 -0
  20. package/dist/types.d.ts +208 -0
  21. package/dist/types.js +0 -0
  22. package/dist/ui/index.d.ts +20 -0
  23. package/package.json +45 -0
  24. package/src/ui/DataTable.svelte +385 -0
  25. package/src/ui/DataTableBody.svelte +180 -0
  26. package/src/ui/DataTableBubbleEditor.svelte +93 -0
  27. package/src/ui/DataTableButtons.svelte +139 -0
  28. package/src/ui/DataTableCell.svelte +381 -0
  29. package/src/ui/DataTableColumnToggle.svelte +111 -0
  30. package/src/ui/DataTableEditor.svelte +27 -0
  31. package/src/ui/DataTableEditorField.svelte +190 -0
  32. package/src/ui/DataTableEditorForm.svelte +94 -0
  33. package/src/ui/DataTableEmpty.svelte +40 -0
  34. package/src/ui/DataTableExpandedRow.svelte +37 -0
  35. package/src/ui/DataTableFooter.svelte +65 -0
  36. package/src/ui/DataTableHead.svelte +169 -0
  37. package/src/ui/DataTableLoading.svelte +44 -0
  38. package/src/ui/DataTableModalEditor.svelte +126 -0
  39. package/src/ui/DataTablePagination.svelte +205 -0
  40. package/src/ui/DataTableRow.svelte +192 -0
  41. package/src/ui/DataTableSearch.svelte +95 -0
  42. package/src/ui/DataTableToolbar.svelte +164 -0
  43. package/src/ui/index.ts +20 -0
  44. package/src/ui/virtual/VirtualScroller.svelte +62 -0
@@ -0,0 +1,139 @@
1
+ <script lang="ts">
2
+ import type { ButtonDef, ExportFormat, DataTableStore, ColumnDef } from '../index.js';
3
+ import { ExportManager } from '../export/ExportManager.js';
4
+
5
+ interface Props {
6
+ buttons: (ButtonDef | ExportFormat)[];
7
+ store: DataTableStore;
8
+ columns: ColumnDef[];
9
+ }
10
+ let { buttons, store, columns }: Props = $props();
11
+
12
+ const exportManager = new ExportManager();
13
+
14
+ const exportLabels: Record<ExportFormat, string> = {
15
+ csv: 'CSV',
16
+ excel: 'Excel',
17
+ pdf: 'PDF',
18
+ clipboard: 'Copy',
19
+ print: 'Print',
20
+ };
21
+
22
+ function isExportFormat(btn: ButtonDef | ExportFormat): btn is ExportFormat {
23
+ return typeof btn === 'string';
24
+ }
25
+
26
+ async function handleExport(format: ExportFormat) {
27
+ const state = store.getState();
28
+ const data = state.filteredRows;
29
+ const visibleCols = columns.filter((c) => state.columnVisibility[c.key] !== false);
30
+ await exportManager.export(format, data, visibleCols);
31
+ }
32
+
33
+ async function handleAction(btn: ButtonDef) {
34
+ if (typeof btn.action === 'function') {
35
+ const selected = store.getSelectedRows();
36
+ const state = store.getState();
37
+ await btn.action(selected, state.allRows);
38
+ }
39
+ }
40
+
41
+ function isDisabled(btn: ButtonDef): boolean {
42
+ if (typeof btn.disabled === 'function') {
43
+ return btn.disabled(store.getSelectedRows());
44
+ }
45
+ return btn.disabled ?? false;
46
+ }
47
+ </script>
48
+
49
+ <div class="sdt-buttons">
50
+ {#each buttons as btn}
51
+ {#if isExportFormat(btn)}
52
+ <button type="button" class="sdt-btn sdt-btn-outline" onclick={() => handleExport(btn)}>
53
+ {exportLabels[btn]}
54
+ </button>
55
+ {:else if btn.collection}
56
+ <div class="sdt-btn-group">
57
+ <button type="button" class="sdt-btn sdt-btn-{btn.variant ?? 'default'}" disabled={isDisabled(btn)}>
58
+ {btn.label}
59
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="12" height="12">
60
+ <path d="m6 9 6 6 6-6" />
61
+ </svg>
62
+ </button>
63
+ </div>
64
+ {:else}
65
+ <button
66
+ type="button"
67
+ class="sdt-btn sdt-btn-{btn.variant ?? 'default'}"
68
+ disabled={isDisabled(btn)}
69
+ onclick={() => handleAction(btn)}
70
+ >
71
+ {btn.label}
72
+ </button>
73
+ {/if}
74
+ {/each}
75
+ </div>
76
+
77
+ <style>
78
+ .sdt-buttons {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 0.375rem;
82
+ flex-wrap: wrap;
83
+ }
84
+ .sdt-btn {
85
+ display: inline-flex;
86
+ align-items: center;
87
+ gap: 0.25rem;
88
+ padding: 0.5rem 0.75rem;
89
+ border-radius: 0.375rem;
90
+ font-size: 0.8125rem;
91
+ font-weight: 500;
92
+ cursor: pointer;
93
+ transition: background-color 0.15s, border-color 0.15s;
94
+ border: 1px solid transparent;
95
+ }
96
+ .sdt-btn:disabled {
97
+ opacity: 0.4;
98
+ cursor: not-allowed;
99
+ }
100
+ .sdt-btn-default {
101
+ background: var(--sdt-primary, #3b82f6);
102
+ color: #fff;
103
+ }
104
+ .sdt-btn-default:hover:not(:disabled) {
105
+ background: var(--sdt-primary-hover, #2563eb);
106
+ }
107
+ .sdt-btn-destructive {
108
+ background: #ef4444;
109
+ color: #fff;
110
+ }
111
+ .sdt-btn-destructive:hover:not(:disabled) {
112
+ background: #dc2626;
113
+ }
114
+ .sdt-btn-outline {
115
+ background: var(--sdt-input-bg, #fff);
116
+ border-color: var(--sdt-border, #e5e7eb);
117
+ color: var(--sdt-text, #111827);
118
+ }
119
+ .sdt-btn-outline:hover:not(:disabled) {
120
+ background: var(--sdt-hover, #f3f4f6);
121
+ }
122
+ .sdt-btn-secondary {
123
+ background: var(--sdt-hover, #f3f4f6);
124
+ color: var(--sdt-text, #111827);
125
+ }
126
+ .sdt-btn-secondary:hover:not(:disabled) {
127
+ background: #e5e7eb;
128
+ }
129
+ .sdt-btn-ghost {
130
+ background: transparent;
131
+ color: var(--sdt-text, #111827);
132
+ }
133
+ .sdt-btn-ghost:hover:not(:disabled) {
134
+ background: var(--sdt-hover, #f3f4f6);
135
+ }
136
+ .sdt-btn-group {
137
+ position: relative;
138
+ }
139
+ </style>
@@ -0,0 +1,381 @@
1
+ <script lang="ts">
2
+ import type { ColumnDef, DataTableStore } from '../index.js';
3
+ import type { Snippet } from 'svelte';
4
+
5
+ interface Props {
6
+ row: any;
7
+ rowIndex: number;
8
+ column: ColumnDef;
9
+ store: DataTableStore;
10
+ editorMode?: string | null;
11
+ customCell?: Snippet<[{ row: any; column: ColumnDef; value: any }]>;
12
+ onExcelNavigate?: (direction: 'up' | 'down' | 'left' | 'right') => void;
13
+ onExcelCommit?: () => void;
14
+ onInlineCommit?: (row: any, columnKey: string, newValue: any, oldValue: any) => void;
15
+ tdClass?: string;
16
+ }
17
+ let {
18
+ row, rowIndex, column, store, editorMode = null, customCell,
19
+ onExcelNavigate, onExcelCommit, onInlineCommit, tdClass = '',
20
+ }: Props = $props();
21
+
22
+ let value = $derived(row[column.key]);
23
+
24
+ let state = $state(store.getState());
25
+ $effect(() => {
26
+ return store.subscribe(() => {
27
+ state = store.getState();
28
+ });
29
+ });
30
+
31
+ let isExcelMode = $derived(editorMode === 'excel');
32
+ let isFocused = $derived(
33
+ isExcelMode
34
+ && state.excelFocusedCell?.rowIndex === rowIndex
35
+ && state.excelFocusedCell?.columnKey === column.key
36
+ );
37
+ let isEditing = $derived(
38
+ isExcelMode
39
+ && state.excelEditingCell?.rowIndex === rowIndex
40
+ && state.excelEditingCell?.columnKey === column.key
41
+ );
42
+
43
+ // Inline editor state
44
+ let isInlineEditing = $derived(
45
+ editorMode === 'inline'
46
+ && state.editorMode === 'inline'
47
+ && state.editingRowId === store.getRowId(row)
48
+ && state.editingColumn === column.key
49
+ );
50
+ let inlineValue = $state('');
51
+
52
+ $effect(() => {
53
+ if (isInlineEditing) {
54
+ inlineValue = value != null ? String(value) : '';
55
+ }
56
+ });
57
+
58
+ let cellEl: HTMLTableCellElement | undefined = $state();
59
+ let inputEl: HTMLInputElement | HTMLSelectElement | undefined = $state();
60
+
61
+ let inlineInputEl: HTMLInputElement | HTMLSelectElement | undefined = $state();
62
+
63
+ // Auto-focus input when entering edit mode (excel or inline)
64
+ $effect(() => {
65
+ if (isEditing && inputEl) {
66
+ inputEl.focus();
67
+ if (inputEl instanceof HTMLInputElement) {
68
+ inputEl.select();
69
+ }
70
+ }
71
+ });
72
+
73
+ $effect(() => {
74
+ if (isInlineEditing && inlineInputEl) {
75
+ inlineInputEl.focus();
76
+ if (inlineInputEl instanceof HTMLInputElement) {
77
+ inlineInputEl.select();
78
+ }
79
+ }
80
+ });
81
+
82
+ function commitInlineEdit() {
83
+ if (!isInlineEditing) return;
84
+ const col = column;
85
+ const oldVal = value;
86
+ let newVal: any = inlineValue;
87
+ if (col.type === 'number') newVal = newVal === '' ? null : Number(newVal);
88
+ else if (col.type === 'boolean') newVal = newVal === 'true' || newVal === '1';
89
+ else if (newVal === '') newVal = null;
90
+
91
+ store.closeEditor();
92
+ if (newVal !== oldVal) {
93
+ onInlineCommit?.(row, col.key, newVal, oldVal);
94
+ }
95
+ }
96
+
97
+ function cancelInlineEdit() {
98
+ store.closeEditor();
99
+ }
100
+
101
+ function handleInlineKeydown(e: KeyboardEvent) {
102
+ if (e.key === 'Enter') {
103
+ e.preventDefault();
104
+ commitInlineEdit();
105
+ } else if (e.key === 'Escape') {
106
+ e.preventDefault();
107
+ cancelInlineEdit();
108
+ }
109
+ }
110
+
111
+ function formatValue(val: any, type?: string): string {
112
+ if (val === null || val === undefined) return '';
113
+ if (type === 'boolean') return val ? 'Yes' : 'No';
114
+ if (type === 'date' && val) {
115
+ try { return new Date(val).toLocaleDateString(); } catch { return String(val); }
116
+ }
117
+ if (type === 'number') return String(val);
118
+ return String(val);
119
+ }
120
+
121
+ function handleDblClick() {
122
+ if (column.editable && editorMode === 'inline') {
123
+ store.openEditor(store.getRowId(row), column.key, 'inline');
124
+ }
125
+ }
126
+
127
+ function handleCellClick() {
128
+ if (isExcelMode && column.editable !== false) {
129
+ store.focusCell(rowIndex, column.key);
130
+ }
131
+ }
132
+
133
+ function handleCellKeydown(e: KeyboardEvent) {
134
+ if (!isExcelMode || !isFocused) return;
135
+
136
+ if (isEditing) {
137
+ switch (e.key) {
138
+ case 'Enter':
139
+ e.preventDefault();
140
+ onExcelCommit?.();
141
+ break;
142
+ case 'Escape':
143
+ e.preventDefault();
144
+ store.cancelCellEdit();
145
+ cellEl?.focus();
146
+ break;
147
+ case 'Tab':
148
+ e.preventDefault();
149
+ onExcelCommit?.();
150
+ onExcelNavigate?.(e.shiftKey ? 'left' : 'right');
151
+ break;
152
+ case 'ArrowUp':
153
+ e.preventDefault();
154
+ onExcelCommit?.();
155
+ onExcelNavigate?.('up');
156
+ break;
157
+ case 'ArrowDown':
158
+ e.preventDefault();
159
+ onExcelCommit?.();
160
+ onExcelNavigate?.('down');
161
+ break;
162
+ case 'ArrowLeft':
163
+ case 'ArrowRight':
164
+ // Allow cursor movement inside text inputs
165
+ break;
166
+ }
167
+ return;
168
+ }
169
+
170
+ // While focused (not editing)
171
+ switch (e.key) {
172
+ case 'Enter':
173
+ case 'F2':
174
+ e.preventDefault();
175
+ if (column.editable !== false) {
176
+ store.startCellEdit();
177
+ }
178
+ break;
179
+ case 'ArrowUp':
180
+ e.preventDefault();
181
+ onExcelNavigate?.('up');
182
+ break;
183
+ case 'ArrowDown':
184
+ e.preventDefault();
185
+ onExcelNavigate?.('down');
186
+ break;
187
+ case 'ArrowLeft':
188
+ e.preventDefault();
189
+ onExcelNavigate?.('left');
190
+ break;
191
+ case 'ArrowRight':
192
+ case 'Tab':
193
+ e.preventDefault();
194
+ onExcelNavigate?.(e.shiftKey ? 'left' : 'right');
195
+ break;
196
+ case 'Escape':
197
+ e.preventDefault();
198
+ store.clearExcelFocus();
199
+ break;
200
+ default:
201
+ // Typing a character starts editing
202
+ if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey && column.editable !== false) {
203
+ store.startCellEdit();
204
+ // Let the character through to the input
205
+ store.setExcelEditValue(e.key);
206
+ }
207
+ break;
208
+ }
209
+ }
210
+
211
+ function handleInputKeydown(e: KeyboardEvent) {
212
+ // For select/boolean: left/right arrows also commit+navigate (no cursor to move)
213
+ if (column.type === 'boolean' || column.editorField?.type === 'select') {
214
+ if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
215
+ e.preventDefault();
216
+ onExcelCommit?.();
217
+ onExcelNavigate?.(e.key === 'ArrowLeft' ? 'left' : 'right');
218
+ return;
219
+ }
220
+ }
221
+ handleCellKeydown(e);
222
+ }
223
+
224
+ // Auto-focus cell element when it becomes focused in excel mode
225
+ $effect(() => {
226
+ if (isFocused && !isEditing && cellEl) {
227
+ cellEl.focus();
228
+ }
229
+ });
230
+ </script>
231
+
232
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
233
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
234
+ <td
235
+ bind:this={cellEl}
236
+ class="sdt-cell {column.className ?? ''} {tdClass}"
237
+ class:sdt-cell-editable={column.editable !== false}
238
+ class:sdt-excel-focused={isFocused && !isEditing}
239
+ class:sdt-excel-editing={isEditing}
240
+ style:width={column.width}
241
+ style:min-width={column.minWidth}
242
+ style:max-width={column.maxWidth}
243
+ ondblclick={handleDblClick}
244
+ onclick={handleCellClick}
245
+ onkeydown={handleCellKeydown}
246
+ tabindex={isExcelMode && column.editable !== false ? 0 : undefined}
247
+ >
248
+ {#if isInlineEditing}
249
+ {#if column.type === 'boolean'}
250
+ <select
251
+ bind:this={inlineInputEl}
252
+ class="sdt-inline-input"
253
+ value={inlineValue}
254
+ onchange={(e) => { inlineValue = e.currentTarget.value; commitInlineEdit(); }}
255
+ onkeydown={handleInlineKeydown}
256
+ onblur={commitInlineEdit}
257
+ >
258
+ <option value="true">Yes</option>
259
+ <option value="false">No</option>
260
+ </select>
261
+ {:else if column.editorField?.type === 'select' && column.editorField.options}
262
+ <select
263
+ bind:this={inlineInputEl}
264
+ class="sdt-inline-input"
265
+ value={inlineValue}
266
+ onchange={(e) => { inlineValue = e.currentTarget.value; commitInlineEdit(); }}
267
+ onkeydown={handleInlineKeydown}
268
+ onblur={commitInlineEdit}
269
+ >
270
+ {#each column.editorField.options as opt}
271
+ <option value={opt.value}>{opt.label}</option>
272
+ {/each}
273
+ </select>
274
+ {:else}
275
+ <input
276
+ bind:this={inlineInputEl}
277
+ class="sdt-inline-input"
278
+ type={column.type === 'number' ? 'number' : 'text'}
279
+ bind:value={inlineValue}
280
+ onkeydown={handleInlineKeydown}
281
+ onblur={commitInlineEdit}
282
+ />
283
+ {/if}
284
+ {:else if isEditing}
285
+ {#if column.type === 'boolean'}
286
+ <select
287
+ bind:this={inputEl}
288
+ class="sdt-excel-input"
289
+ value={state.excelEditValue}
290
+ onchange={(e) => store.setExcelEditValue(e.currentTarget.value)}
291
+ onkeydown={handleInputKeydown}
292
+ >
293
+ <option value="true">Yes</option>
294
+ <option value="false">No</option>
295
+ </select>
296
+ {:else if column.editorField?.type === 'select' && column.editorField.options}
297
+ <select
298
+ bind:this={inputEl}
299
+ class="sdt-excel-input"
300
+ value={state.excelEditValue}
301
+ onchange={(e) => store.setExcelEditValue(e.currentTarget.value)}
302
+ onkeydown={handleInputKeydown}
303
+ >
304
+ {#each column.editorField.options as opt}
305
+ <option value={opt.value}>{opt.label}</option>
306
+ {/each}
307
+ </select>
308
+ {:else}
309
+ <input
310
+ bind:this={inputEl}
311
+ class="sdt-excel-input"
312
+ type={column.type === 'number' ? 'number' : 'text'}
313
+ value={state.excelEditValue}
314
+ oninput={(e) => store.setExcelEditValue(e.currentTarget.value)}
315
+ onkeydown={handleInputKeydown}
316
+ />
317
+ {/if}
318
+ {:else if customCell}
319
+ {@render customCell({ row, column, value })}
320
+ {:else if column.type === 'html'}
321
+ {@html value}
322
+ {:else}
323
+ {formatValue(value, column.type)}
324
+ {/if}
325
+ </td>
326
+
327
+ <style>
328
+ .sdt-cell {
329
+ padding: var(--sdt-cell-padding, 0.625rem 0.75rem);
330
+ border-bottom: 1px solid var(--sdt-border, #e5e7eb);
331
+ font-size: 0.875rem;
332
+ white-space: nowrap;
333
+ overflow: hidden;
334
+ text-overflow: ellipsis;
335
+ }
336
+ .sdt-cell-editable {
337
+ cursor: pointer;
338
+ }
339
+ .sdt-cell-editable:hover {
340
+ background-color: var(--sdt-cell-edit-hover, rgba(59, 130, 246, 0.05));
341
+ }
342
+ .sdt-excel-focused {
343
+ outline: 2px solid var(--sdt-primary, #3b82f6);
344
+ outline-offset: -2px;
345
+ background-color: var(--sdt-excel-focus-bg, rgba(59, 130, 246, 0.06));
346
+ }
347
+ .sdt-excel-editing {
348
+ padding: 0.125rem;
349
+ background-color: var(--sdt-excel-edit-bg, #fff);
350
+ outline: 2px solid var(--sdt-primary, #3b82f6);
351
+ outline-offset: -2px;
352
+ }
353
+ .sdt-excel-input {
354
+ width: 100%;
355
+ height: 100%;
356
+ border: none;
357
+ outline: none;
358
+ background: transparent;
359
+ font-size: 0.875rem;
360
+ font-family: inherit;
361
+ padding: 0.375rem 0.5rem;
362
+ box-sizing: border-box;
363
+ }
364
+ .sdt-excel-input:focus {
365
+ outline: none;
366
+ }
367
+ .sdt-inline-input {
368
+ width: 100%;
369
+ border: 1px solid var(--sdt-primary, #3b82f6);
370
+ border-radius: 0.25rem;
371
+ outline: none;
372
+ background: var(--sdt-bg, #fff);
373
+ font-size: 0.875rem;
374
+ font-family: inherit;
375
+ padding: 0.25rem 0.375rem;
376
+ box-sizing: border-box;
377
+ }
378
+ .sdt-inline-input:focus {
379
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
380
+ }
381
+ </style>
@@ -0,0 +1,111 @@
1
+ <script lang="ts">
2
+ import type { ColumnDef, DataTableStore } from '../index.js';
3
+
4
+ interface Props {
5
+ store: DataTableStore;
6
+ columns: ColumnDef[];
7
+ }
8
+ let { store, columns }: Props = $props();
9
+
10
+ let open = $state(false);
11
+ let state = $state(store.getState());
12
+
13
+ $effect(() => {
14
+ return store.subscribe(() => {
15
+ state = store.getState();
16
+ });
17
+ });
18
+
19
+ function toggle(key: string) {
20
+ store.toggleColumnVisibility(key);
21
+ }
22
+
23
+ function handleClickOutside(e: MouseEvent) {
24
+ const target = e.target as HTMLElement;
25
+ if (!target.closest('.sdt-col-toggle')) {
26
+ open = false;
27
+ }
28
+ }
29
+
30
+ $effect(() => {
31
+ if (open) {
32
+ document.addEventListener('click', handleClickOutside);
33
+ return () => document.removeEventListener('click', handleClickOutside);
34
+ }
35
+ });
36
+ </script>
37
+
38
+ <div class="sdt-col-toggle">
39
+ <button type="button" class="sdt-col-toggle-btn" onclick={() => (open = !open)} aria-label="Toggle columns">
40
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
41
+ <path d="M12 3v18M3 12h18M9 3h6M9 21h6M3 9v6M21 9v6" />
42
+ </svg>
43
+ Columns
44
+ </button>
45
+
46
+ {#if open}
47
+ <div class="sdt-col-toggle-menu">
48
+ {#each columns as col}
49
+ <label class="sdt-col-toggle-item">
50
+ <input
51
+ type="checkbox"
52
+ checked={state.columnVisibility[col.key] !== false}
53
+ onchange={() => toggle(col.key)}
54
+ />
55
+ <span>{col.header}</span>
56
+ </label>
57
+ {/each}
58
+ </div>
59
+ {/if}
60
+ </div>
61
+
62
+ <style>
63
+ .sdt-col-toggle {
64
+ position: relative;
65
+ }
66
+ .sdt-col-toggle-btn {
67
+ display: inline-flex;
68
+ align-items: center;
69
+ gap: 0.375rem;
70
+ padding: 0.5rem 0.75rem;
71
+ border: 1px solid var(--sdt-border, #e5e7eb);
72
+ border-radius: 0.375rem;
73
+ background: var(--sdt-input-bg, #fff);
74
+ color: var(--sdt-text, #111827);
75
+ font-size: 0.8125rem;
76
+ cursor: pointer;
77
+ transition: background-color 0.15s;
78
+ }
79
+ .sdt-col-toggle-btn:hover {
80
+ background-color: var(--sdt-hover, #f3f4f6);
81
+ }
82
+ .sdt-col-toggle-menu {
83
+ position: absolute;
84
+ top: 100%;
85
+ right: 0;
86
+ margin-top: 0.25rem;
87
+ min-width: 180px;
88
+ padding: 0.5rem;
89
+ background: var(--sdt-bg, #fff);
90
+ border: 1px solid var(--sdt-border, #e5e7eb);
91
+ border-radius: 0.375rem;
92
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
93
+ z-index: 20;
94
+ }
95
+ .sdt-col-toggle-item {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 0.5rem;
99
+ padding: 0.375rem 0.5rem;
100
+ font-size: 0.8125rem;
101
+ border-radius: 0.25rem;
102
+ cursor: pointer;
103
+ color: var(--sdt-text, #111827);
104
+ }
105
+ .sdt-col-toggle-item:hover {
106
+ background-color: var(--sdt-hover, #f3f4f6);
107
+ }
108
+ .sdt-col-toggle-item input {
109
+ accent-color: var(--sdt-primary, #3b82f6);
110
+ }
111
+ </style>
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import type { EditorFieldDef, DataTableStore, DataTableClassNames } from '../index.js';
3
+ import DataTableModalEditor from './DataTableModalEditor.svelte';
4
+ import DataTableBubbleEditor from './DataTableBubbleEditor.svelte';
5
+
6
+ interface Props {
7
+ fields: EditorFieldDef[];
8
+ store: DataTableStore;
9
+ classNames?: DataTableClassNames;
10
+ onsubmit: (formData: Record<string, any>) => void | Promise<void>;
11
+ anchorEl?: HTMLElement | null;
12
+ }
13
+ let { fields, store, classNames = {}, onsubmit, anchorEl = null }: Props = $props();
14
+
15
+ let state = $state(store.getState());
16
+ $effect(() => {
17
+ return store.subscribe(() => {
18
+ state = store.getState();
19
+ });
20
+ });
21
+ </script>
22
+
23
+ {#if state.editorMode === 'modal'}
24
+ <DataTableModalEditor {fields} {store} {onsubmit} {classNames} />
25
+ {:else if state.editorMode === 'bubble'}
26
+ <DataTableBubbleEditor {fields} {store} {onsubmit} {anchorEl} {classNames} />
27
+ {/if}