@shotleybuilder/svelte-table-kit 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.
@@ -0,0 +1,283 @@
1
+ <script>export let columns;
2
+ export let grouping = [];
3
+ export let onGroupingChange;
4
+ const MAX_LEVELS = 3;
5
+ let isExpanded = false;
6
+ function addGroup() {
7
+ if (grouping.length >= MAX_LEVELS) return;
8
+ onGroupingChange([...grouping, ""]);
9
+ isExpanded = true;
10
+ }
11
+ function updateGroup(index, columnId) {
12
+ const newGrouping = [...grouping];
13
+ newGrouping[index] = columnId;
14
+ onGroupingChange(newGrouping);
15
+ }
16
+ function removeGroup(index) {
17
+ const newGrouping = grouping.filter((_, i) => i !== index);
18
+ onGroupingChange(newGrouping);
19
+ if (newGrouping.length === 0) {
20
+ isExpanded = false;
21
+ }
22
+ }
23
+ function clearAllGroups() {
24
+ onGroupingChange([]);
25
+ isExpanded = false;
26
+ }
27
+ $: availableColumns = columns.filter((col) => {
28
+ const columnId = col.accessorKey || col.id;
29
+ return columnId && col.enableGrouping !== false;
30
+ });
31
+ $: hasGroups = grouping.length > 0;
32
+ $: validGroupCount = grouping.filter((g) => g !== "").length;
33
+ $: canAddMore = grouping.length < MAX_LEVELS;
34
+ </script>
35
+
36
+ <div class="group-bar">
37
+ <!-- Compact Group Button -->
38
+ <button class="group-toggle-btn" on:click={() => (isExpanded = !isExpanded)}>
39
+ <svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
40
+ <path
41
+ stroke-linecap="round"
42
+ stroke-linejoin="round"
43
+ stroke-width="2"
44
+ d="M4 6h16M4 10h16M4 14h16M4 18h16"
45
+ />
46
+ </svg>
47
+ Group
48
+ {#if validGroupCount > 0}
49
+ <span class="group-badge">{validGroupCount}</span>
50
+ {/if}
51
+ <svg
52
+ class="chevron"
53
+ class:expanded={isExpanded}
54
+ fill="none"
55
+ stroke="currentColor"
56
+ viewBox="0 0 24 24"
57
+ >
58
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
59
+ </svg>
60
+ </button>
61
+
62
+ <!-- Expandable Group Panel -->
63
+ {#if isExpanded}
64
+ <div class="group-panel">
65
+ {#if hasGroups}
66
+ <div class="group-header">
67
+ <span class="group-label">Group by</span>
68
+ <button class="clear-all-btn" on:click={clearAllGroups}> Clear all </button>
69
+ </div>
70
+
71
+ <div class="group-levels">
72
+ {#each grouping as group, index (index)}
73
+ <div class="group-level">
74
+ <select
75
+ class="field-select"
76
+ value={group}
77
+ on:change={(e) => updateGroup(index, e.currentTarget.value)}
78
+ >
79
+ <option value="">Select field...</option>
80
+ {#each availableColumns as column}
81
+ {@const columnId = column.accessorKey || column.id}
82
+ <option value={columnId}>
83
+ {column.header || columnId}
84
+ </option>
85
+ {/each}
86
+ </select>
87
+ <button class="remove-btn" on:click={() => removeGroup(index)} title="Remove group">
88
+ <svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
89
+ <path
90
+ stroke-linecap="round"
91
+ stroke-linejoin="round"
92
+ stroke-width="2"
93
+ d="M6 18L18 6M6 6l12 12"
94
+ />
95
+ </svg>
96
+ </button>
97
+ </div>
98
+ {/each}
99
+ </div>
100
+ {/if}
101
+
102
+ {#if canAddMore}
103
+ <button class="add-group-btn" on:click={addGroup}>
104
+ <svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
105
+ <path
106
+ stroke-linecap="round"
107
+ stroke-linejoin="round"
108
+ stroke-width="2"
109
+ d="M12 6v6m0 0v6m0-6h6m-6 0H6"
110
+ />
111
+ </svg>
112
+ {hasGroups ? 'Add subgroup' : 'Add group'}
113
+ </button>
114
+ {/if}
115
+ </div>
116
+ {/if}
117
+ </div>
118
+
119
+ <style>
120
+ .group-bar {
121
+ position: relative;
122
+ }
123
+
124
+ /* Compact Group Toggle Button */
125
+ .group-toggle-btn {
126
+ display: inline-flex;
127
+ align-items: center;
128
+ gap: 0.5rem;
129
+ padding: 0.5rem 1rem;
130
+ font-size: 0.875rem;
131
+ font-weight: 500;
132
+ color: #374151;
133
+ background: white;
134
+ border: 1px solid #d1d5db;
135
+ border-radius: 0.375rem;
136
+ cursor: pointer;
137
+ transition: all 0.2s;
138
+ }
139
+
140
+ .group-toggle-btn:hover {
141
+ background: #f9fafb;
142
+ border-color: #9ca3af;
143
+ }
144
+
145
+ .group-badge {
146
+ display: inline-flex;
147
+ align-items: center;
148
+ justify-content: center;
149
+ min-width: 1.25rem;
150
+ height: 1.25rem;
151
+ padding: 0 0.375rem;
152
+ font-size: 0.75rem;
153
+ font-weight: 600;
154
+ color: white;
155
+ background: #059669;
156
+ border-radius: 0.75rem;
157
+ }
158
+
159
+ .chevron {
160
+ width: 1rem;
161
+ height: 1rem;
162
+ transition: transform 0.2s;
163
+ }
164
+
165
+ .chevron.expanded {
166
+ transform: rotate(180deg);
167
+ }
168
+
169
+ /* Expandable Group Panel */
170
+ .group-panel {
171
+ position: absolute;
172
+ top: calc(100% + 0.5rem);
173
+ left: 0;
174
+ z-index: 20;
175
+ min-width: 400px;
176
+ padding: 1rem;
177
+ background: white;
178
+ border: 1px solid #e5e7eb;
179
+ border-radius: 0.5rem;
180
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
181
+ }
182
+
183
+ .group-header {
184
+ display: flex;
185
+ justify-content: space-between;
186
+ align-items: center;
187
+ margin-bottom: 0.75rem;
188
+ }
189
+
190
+ .group-label {
191
+ font-size: 0.875rem;
192
+ font-weight: 600;
193
+ color: #374151;
194
+ }
195
+
196
+ .clear-all-btn {
197
+ font-size: 0.75rem;
198
+ color: #6b7280;
199
+ background: none;
200
+ border: none;
201
+ cursor: pointer;
202
+ padding: 0.25rem 0.5rem;
203
+ border-radius: 0.25rem;
204
+ transition: all 0.2s;
205
+ }
206
+
207
+ .clear-all-btn:hover {
208
+ color: #dc2626;
209
+ background: #fee2e2;
210
+ }
211
+
212
+ .group-levels {
213
+ display: flex;
214
+ flex-direction: column;
215
+ gap: 0.5rem;
216
+ margin-bottom: 0.75rem;
217
+ }
218
+
219
+ .group-level {
220
+ display: flex;
221
+ align-items: center;
222
+ gap: 0.5rem;
223
+ padding: 0.5rem;
224
+ background: #f9fafb;
225
+ border-radius: 0.375rem;
226
+ }
227
+
228
+ .field-select {
229
+ flex: 1;
230
+ padding: 0.375rem 0.75rem;
231
+ font-size: 0.875rem;
232
+ border: 1px solid #d1d5db;
233
+ border-radius: 0.375rem;
234
+ background: white;
235
+ }
236
+
237
+ .field-select:focus {
238
+ outline: none;
239
+ border-color: #059669;
240
+ box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.1);
241
+ }
242
+
243
+ .remove-btn {
244
+ flex-shrink: 0;
245
+ padding: 0.375rem;
246
+ background: none;
247
+ border: none;
248
+ color: #6b7280;
249
+ cursor: pointer;
250
+ border-radius: 0.25rem;
251
+ transition: all 0.2s;
252
+ }
253
+
254
+ .remove-btn:hover {
255
+ background: #fee2e2;
256
+ color: #dc2626;
257
+ }
258
+
259
+ .add-group-btn {
260
+ display: inline-flex;
261
+ align-items: center;
262
+ gap: 0.5rem;
263
+ padding: 0.5rem 0.75rem;
264
+ font-size: 0.875rem;
265
+ font-weight: 500;
266
+ color: #059669;
267
+ background: white;
268
+ border: 1px dashed #059669;
269
+ border-radius: 0.375rem;
270
+ cursor: pointer;
271
+ transition: all 0.2s;
272
+ }
273
+
274
+ .add-group-btn:hover {
275
+ background: #d1fae5;
276
+ border-style: solid;
277
+ }
278
+
279
+ .icon {
280
+ width: 1rem;
281
+ height: 1rem;
282
+ }
283
+ </style>
@@ -0,0 +1,21 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { ColumnDef } from '@tanstack/svelte-table';
3
+ declare const __propDef: {
4
+ props: {
5
+ columns: ColumnDef<any>[];
6
+ grouping?: string[];
7
+ onGroupingChange: (grouping: string[]) => void;
8
+ };
9
+ events: {
10
+ [evt: string]: CustomEvent<any>;
11
+ };
12
+ slots: {};
13
+ exports?: {} | undefined;
14
+ bindings?: string | undefined;
15
+ };
16
+ export type GroupBarProps = typeof __propDef.props;
17
+ export type GroupBarEvents = typeof __propDef.events;
18
+ export type GroupBarSlots = typeof __propDef.slots;
19
+ export default class GroupBar extends SvelteComponent<GroupBarProps, GroupBarEvents, GroupBarSlots> {
20
+ }
21
+ export {};
@@ -0,0 +1,9 @@
1
+ export { default as TableKit } from './TableKit.svelte';
2
+ export { default as FilterBar } from './components/FilterBar.svelte';
3
+ export { default as FilterCondition } from './components/FilterCondition.svelte';
4
+ export { default as GroupBar } from './components/GroupBar.svelte';
5
+ export type { TableKitProps, TableConfig, ViewPreset, FilterCondition, FilterOperator, FilterLogic, SortConfig, ClassNameMap, TableFeatures, TableState } from './types';
6
+ export { presets } from './presets';
7
+ export { generateTableConfig, validateTableConfig, mergeConfigs } from './utils/config';
8
+ export { createTextFilter, createSelectFilter, createNumericFilter, evaluateCondition, applyFilters } from './utils/filters';
9
+ export { formatDate, formatCurrency, formatNumber, formatPercent } from './utils/formatters';
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ // Main exports for @sertantai/svelte-table-kit
2
+ // Main component
3
+ export { default as TableKit } from './TableKit.svelte';
4
+ // Sub-components
5
+ export { default as FilterBar } from './components/FilterBar.svelte';
6
+ export { default as FilterCondition } from './components/FilterCondition.svelte';
7
+ export { default as GroupBar } from './components/GroupBar.svelte';
8
+ // Presets
9
+ export { presets } from './presets';
10
+ // Utilities for AI configuration
11
+ export { generateTableConfig, validateTableConfig, mergeConfigs } from './utils/config';
12
+ // Filter utilities
13
+ export { createTextFilter, createSelectFilter, createNumericFilter, evaluateCondition, applyFilters } from './utils/filters';
14
+ // Formatters
15
+ export { formatDate, formatCurrency, formatNumber, formatPercent } from './utils/formatters';
@@ -0,0 +1,6 @@
1
+ import type { TableConfig } from '../types';
2
+ export declare const presets: {
3
+ dashboard: TableConfig;
4
+ dataGrid: TableConfig;
5
+ readonly: TableConfig;
6
+ };
@@ -0,0 +1,27 @@
1
+ // Preset configurations for common use cases
2
+ export const presets = {
3
+ dashboard: {
4
+ id: 'dashboard',
5
+ version: '1.0.0',
6
+ pagination: {
7
+ pageSize: 10,
8
+ pageSizeOptions: [10, 25, 50]
9
+ }
10
+ },
11
+ dataGrid: {
12
+ id: 'data-grid',
13
+ version: '1.0.0',
14
+ pagination: {
15
+ pageSize: 50,
16
+ pageSizeOptions: [25, 50, 100, 200]
17
+ }
18
+ },
19
+ readonly: {
20
+ id: 'readonly',
21
+ version: '1.0.0',
22
+ pagination: {
23
+ pageSize: 25,
24
+ pageSizeOptions: [25, 50, 100]
25
+ }
26
+ }
27
+ };
@@ -0,0 +1,58 @@
1
+ import type { VisibilityState, ColumnSizingState, ColumnFiltersState, ColumnOrderState, SortingState, PaginationState } from '@tanstack/svelte-table';
2
+ /**
3
+ * Check if we're in a browser environment
4
+ * Note: In SvelteKit apps, import from '$app/environment'
5
+ */
6
+ export declare const isBrowser: boolean;
7
+ /**
8
+ * Load column visibility state from localStorage
9
+ */
10
+ export declare function loadColumnVisibility(storageKey: string): VisibilityState;
11
+ /**
12
+ * Save column visibility state to localStorage
13
+ */
14
+ export declare function saveColumnVisibility(storageKey: string, state: VisibilityState): void;
15
+ /**
16
+ * Load column sizing state from localStorage
17
+ */
18
+ export declare function loadColumnSizing(storageKey: string): ColumnSizingState;
19
+ /**
20
+ * Save column sizing state to localStorage
21
+ */
22
+ export declare function saveColumnSizing(storageKey: string, state: ColumnSizingState): void;
23
+ /**
24
+ * Load column filters state from localStorage
25
+ */
26
+ export declare function loadColumnFilters(storageKey: string): ColumnFiltersState;
27
+ /**
28
+ * Save column filters state to localStorage
29
+ */
30
+ export declare function saveColumnFilters(storageKey: string, state: ColumnFiltersState): void;
31
+ /**
32
+ * Load column order state from localStorage
33
+ */
34
+ export declare function loadColumnOrder(storageKey: string): ColumnOrderState;
35
+ /**
36
+ * Save column order state to localStorage
37
+ */
38
+ export declare function saveColumnOrder(storageKey: string, state: ColumnOrderState): void;
39
+ /**
40
+ * Load sorting state from localStorage
41
+ */
42
+ export declare function loadSorting(storageKey: string): SortingState;
43
+ /**
44
+ * Save sorting state to localStorage
45
+ */
46
+ export declare function saveSorting(storageKey: string, state: SortingState): void;
47
+ /**
48
+ * Load pagination state from localStorage
49
+ */
50
+ export declare function loadPagination(storageKey: string, defaultPageSize?: number): PaginationState;
51
+ /**
52
+ * Save pagination state to localStorage
53
+ */
54
+ export declare function savePagination(storageKey: string, state: PaginationState): void;
55
+ /**
56
+ * Clear all table state from localStorage
57
+ */
58
+ export declare function clearTableState(storageKey: string): void;
@@ -0,0 +1,127 @@
1
+ // LocalStorage persistence utilities
2
+ /**
3
+ * Check if we're in a browser environment
4
+ * Note: In SvelteKit apps, import from '$app/environment'
5
+ */
6
+ export const isBrowser = typeof window !== 'undefined' && typeof localStorage !== 'undefined';
7
+ /**
8
+ * Generic localStorage loader with error handling
9
+ */
10
+ function loadFromStorage(key, defaultValue) {
11
+ if (!isBrowser)
12
+ return defaultValue;
13
+ try {
14
+ const saved = localStorage.getItem(key);
15
+ return saved ? JSON.parse(saved) : defaultValue;
16
+ }
17
+ catch (error) {
18
+ console.warn(`Failed to load ${key} from localStorage:`, error);
19
+ return defaultValue;
20
+ }
21
+ }
22
+ /**
23
+ * Generic localStorage saver with error handling
24
+ */
25
+ function saveToStorage(key, value) {
26
+ if (!isBrowser)
27
+ return;
28
+ try {
29
+ localStorage.setItem(key, JSON.stringify(value));
30
+ }
31
+ catch (error) {
32
+ console.error(`Failed to save ${key} to localStorage:`, error);
33
+ }
34
+ }
35
+ /**
36
+ * Load column visibility state from localStorage
37
+ */
38
+ export function loadColumnVisibility(storageKey) {
39
+ return loadFromStorage(`${storageKey}_column_visibility`, {});
40
+ }
41
+ /**
42
+ * Save column visibility state to localStorage
43
+ */
44
+ export function saveColumnVisibility(storageKey, state) {
45
+ saveToStorage(`${storageKey}_column_visibility`, state);
46
+ }
47
+ /**
48
+ * Load column sizing state from localStorage
49
+ */
50
+ export function loadColumnSizing(storageKey) {
51
+ return loadFromStorage(`${storageKey}_column_sizing`, {});
52
+ }
53
+ /**
54
+ * Save column sizing state to localStorage
55
+ */
56
+ export function saveColumnSizing(storageKey, state) {
57
+ saveToStorage(`${storageKey}_column_sizing`, state);
58
+ }
59
+ /**
60
+ * Load column filters state from localStorage
61
+ */
62
+ export function loadColumnFilters(storageKey) {
63
+ return loadFromStorage(`${storageKey}_column_filters`, []);
64
+ }
65
+ /**
66
+ * Save column filters state to localStorage
67
+ */
68
+ export function saveColumnFilters(storageKey, state) {
69
+ saveToStorage(`${storageKey}_column_filters`, state);
70
+ }
71
+ /**
72
+ * Load column order state from localStorage
73
+ */
74
+ export function loadColumnOrder(storageKey) {
75
+ return loadFromStorage(`${storageKey}_column_order`, []);
76
+ }
77
+ /**
78
+ * Save column order state to localStorage
79
+ */
80
+ export function saveColumnOrder(storageKey, state) {
81
+ saveToStorage(`${storageKey}_column_order`, state);
82
+ }
83
+ /**
84
+ * Load sorting state from localStorage
85
+ */
86
+ export function loadSorting(storageKey) {
87
+ return loadFromStorage(`${storageKey}_sorting`, []);
88
+ }
89
+ /**
90
+ * Save sorting state to localStorage
91
+ */
92
+ export function saveSorting(storageKey, state) {
93
+ saveToStorage(`${storageKey}_sorting`, state);
94
+ }
95
+ /**
96
+ * Load pagination state from localStorage
97
+ */
98
+ export function loadPagination(storageKey, defaultPageSize = 10) {
99
+ return loadFromStorage(`${storageKey}_pagination`, {
100
+ pageIndex: 0,
101
+ pageSize: defaultPageSize
102
+ });
103
+ }
104
+ /**
105
+ * Save pagination state to localStorage
106
+ */
107
+ export function savePagination(storageKey, state) {
108
+ saveToStorage(`${storageKey}_pagination`, state);
109
+ }
110
+ /**
111
+ * Clear all table state from localStorage
112
+ */
113
+ export function clearTableState(storageKey) {
114
+ if (!isBrowser)
115
+ return;
116
+ try {
117
+ localStorage.removeItem(`${storageKey}_column_visibility`);
118
+ localStorage.removeItem(`${storageKey}_column_sizing`);
119
+ localStorage.removeItem(`${storageKey}_column_filters`);
120
+ localStorage.removeItem(`${storageKey}_column_order`);
121
+ localStorage.removeItem(`${storageKey}_sorting`);
122
+ localStorage.removeItem(`${storageKey}_pagination`);
123
+ }
124
+ catch (error) {
125
+ console.error('Failed to clear table state:', error);
126
+ }
127
+ }
@@ -0,0 +1,85 @@
1
+ import type { ColumnDef } from '@tanstack/svelte-table';
2
+ export interface TableKitProps<T = any> {
3
+ data: T[];
4
+ columns: ColumnDef<T>[];
5
+ config?: TableConfig;
6
+ features?: TableFeatures;
7
+ storageKey?: string;
8
+ persistState?: boolean;
9
+ theme?: 'light' | 'dark' | 'auto';
10
+ classNames?: Partial<ClassNameMap>;
11
+ onRowClick?: (row: T) => void;
12
+ onRowSelect?: (rows: T[]) => void;
13
+ onStateChange?: (state: TableState) => void;
14
+ }
15
+ export interface TableFeatures {
16
+ columnVisibility?: boolean;
17
+ columnResizing?: boolean;
18
+ columnReordering?: boolean;
19
+ filtering?: boolean;
20
+ sorting?: boolean;
21
+ pagination?: boolean;
22
+ rowSelection?: boolean;
23
+ grouping?: boolean;
24
+ columnPinning?: boolean;
25
+ }
26
+ export interface TableConfig {
27
+ id: string;
28
+ version: string;
29
+ defaultVisibleColumns?: string[];
30
+ defaultColumnOrder?: string[];
31
+ defaultColumnSizing?: Record<string, number>;
32
+ pinnedColumns?: {
33
+ left?: string[];
34
+ right?: string[];
35
+ };
36
+ defaultFilters?: FilterCondition[];
37
+ defaultSorting?: SortConfig[];
38
+ pagination?: {
39
+ pageSize: number;
40
+ pageSizeOptions?: number[];
41
+ };
42
+ presets?: ViewPreset[];
43
+ }
44
+ export interface ViewPreset {
45
+ id: string;
46
+ name: string;
47
+ description?: string;
48
+ config: Partial<TableConfig>;
49
+ }
50
+ export type FilterOperator = 'equals' | 'not_equals' | 'contains' | 'not_contains' | 'starts_with' | 'ends_with' | 'is_empty' | 'is_not_empty' | 'greater_than' | 'less_than' | 'greater_or_equal' | 'less_or_equal' | 'is_before' | 'is_after';
51
+ export type FilterLogic = 'and' | 'or';
52
+ export interface FilterCondition {
53
+ id: string;
54
+ field: string;
55
+ operator: FilterOperator;
56
+ value: any;
57
+ }
58
+ export interface SortConfig {
59
+ columnId: string;
60
+ direction: 'asc' | 'desc';
61
+ }
62
+ export interface ClassNameMap {
63
+ container: string;
64
+ table: string;
65
+ thead: string;
66
+ tbody: string;
67
+ tfoot: string;
68
+ tr: string;
69
+ th: string;
70
+ td: string;
71
+ pagination: string;
72
+ filterBar: string;
73
+ columnPicker: string;
74
+ }
75
+ export interface TableState {
76
+ columnVisibility: Record<string, boolean>;
77
+ columnOrder: string[];
78
+ columnSizing: Record<string, number>;
79
+ columnFilters: FilterCondition[];
80
+ sorting: SortConfig[];
81
+ pagination: {
82
+ pageIndex: number;
83
+ pageSize: number;
84
+ };
85
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ // TypeScript types for svelte-table-kit
2
+ export {};
@@ -0,0 +1,18 @@
1
+ import type { TableConfig } from '../types';
2
+ /**
3
+ * Generate table configuration from AI parameters
4
+ * This is a placeholder for AI integration
5
+ */
6
+ export declare function generateTableConfig(params: {
7
+ query: string;
8
+ availableColumns: any[];
9
+ userData?: Record<string, any>;
10
+ }): Partial<TableConfig>;
11
+ /**
12
+ * Validate table configuration against schema
13
+ */
14
+ export declare function validateTableConfig(config: Partial<TableConfig>): boolean;
15
+ /**
16
+ * Merge multiple table configurations
17
+ */
18
+ export declare function mergeConfigs(...configs: Partial<TableConfig>[]): TableConfig;
@@ -0,0 +1,32 @@
1
+ // Utilities for AI configuration generation and validation
2
+ /**
3
+ * Generate table configuration from AI parameters
4
+ * This is a placeholder for AI integration
5
+ */
6
+ export function generateTableConfig(params) {
7
+ // TODO: Implement AI configuration generation
8
+ // This will be connected to Issue #4 (NL query AI agent)
9
+ console.log('Generating config for query:', params.query);
10
+ return {
11
+ id: 'ai-generated',
12
+ version: '1.0.0'
13
+ };
14
+ }
15
+ /**
16
+ * Validate table configuration against schema
17
+ */
18
+ export function validateTableConfig(config) {
19
+ // TODO: Implement JSON schema validation
20
+ return !!config.id;
21
+ }
22
+ /**
23
+ * Merge multiple table configurations
24
+ */
25
+ export function mergeConfigs(...configs) {
26
+ // TODO: Implement deep merge logic
27
+ return {
28
+ id: 'merged',
29
+ version: '1.0.0',
30
+ ...Object.assign({}, ...configs)
31
+ };
32
+ }