@mikestools/usetable 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,586 +1,352 @@
1
- # @mikestools/usetable
2
-
3
- The most comprehensive Vue 3 table composable with 20+ advanced features. Pure DOM wrapper with reactive data layer for complete table manipulation.
4
-
5
- ## Features
6
-
7
- - đŸŽ¯ **Composable-First** - Pure Vue 3 Composition API, no components
8
- - đŸ“Ļ **TypeScript First** - Full type safety with IntelliSense
9
- - 🔗 **DOM-First Architecture** - HTML table is source of truth
10
- - ✨ **20+ Advanced Features** - All-in-one solution
11
-
12
- > **Note:** Data is read from DOM `textContent`, so values are strings. Use `Number()` when working with numeric data.
13
-
14
- ### Complete Feature Set
15
-
16
- | Feature | Description |
17
- |---------------------------|-------------------------------------------|
18
- | đŸ—„ī¸ **Record-Based Data** | Work with typed records by ID |
19
- | 🔀 **Sorting** | Column sorting with state tracking |
20
- | 🔍 **Filtering** | Row filtering with predicates |
21
- | â†Šī¸ **Undo/Redo** | Full history tracking with rollback |
22
- | đŸŽ¯ **Cell Focus** | Keyboard navigation between cells |
23
- | đŸ‘ī¸ **Column Visibility** | Show/hide columns dynamically |
24
- | â†•ī¸ **Reordering** | Move rows and columns programmatically |
25
- | 📋 **Clipboard** | Copy/paste with Excel-compatible format |
26
- | 📌 **Row State** | Expand/collapse and pin rows |
27
- | đŸˇī¸ **Metadata** | Custom data on cells, rows, columns |
28
- | â˜‘ī¸ **Selection** | Row and cell selection with range support |
29
- | 🔔 **Events** | Granular change callbacks |
30
- | ⚡ **Batch Updates** | Efficient bulk operations |
31
- | âŗ **Async Data** | Loading states and async fetching |
32
- | 🧊 **Frozen Columns** | Pin columns to left/right |
33
- | 🔄 **Transactions** | Batch operations with automatic rollback |
34
- | ✅ **Validation** | Cell, row, column validation |
35
- | 📊 **Aggregation** | Sum, average, min, max, count |
36
- | 📄 **Pagination** | Client-side pagination |
37
- | đŸ“Ĩ **Import/Export** | CSV and JSON format support |
38
- | 🧩 **HTML Elements** | Rich cell content support |
39
-
40
- ## Installation
41
-
42
- ```bash
43
- npm install @mikestools/usetable vue
44
- ```
45
-
46
- ## Quick Start
47
-
48
- ```typescript
49
- import { ref, onMounted } from 'vue'
50
- import { useTable } from '@mikestools/usetable'
51
-
52
- const tableRef = ref<HTMLTableElement>()
53
-
54
- onMounted(() => {
55
- const element = tableRef.value
56
- if (!element) return
57
-
58
- const table = useTable(ref(element))
59
-
60
- // Set up table structure
61
- table.setHeaders(['Name', 'Department', 'Salary'])
62
- table.setData([
63
- ['Alice Johnson', 'Engineering', 85000],
64
- ['Bob Smith', 'Marketing', 72000],
65
- ['Carol Williams', 'Sales', 68000]
66
- ])
67
-
68
- // Add a row
69
- table.addRow(['David Brown', 'Engineering', 92000])
70
-
71
- // Update a cell
72
- table.setCell(0, 2, 90000)
73
-
74
- // Use aggregation
75
- const totalSalary = table.sum(2)
76
- console.log('Total:', totalSalary)
77
-
78
- // Enable inline editing
79
- const cleanup = table.enableEditing()
80
- })
81
- ```
82
-
83
- ```vue
84
- <template>
85
- <table ref="tableRef" class="table"></table>
86
- </template>
87
- ```
88
-
89
- ## API Reference
90
-
91
- ### Core Properties
92
-
93
- | Property | Type | Description |
94
- |----------------------|-----------------------------------|-----------------------------------|
95
- | `element` | `Readonly<Ref<HTMLTableElement>>` | The underlying table element |
96
- | `data` | `Ref<unknown[][]>` | Reactive 2D array of body data |
97
- | `headers` | `Ref<string[]>` | Reactive array of headers |
98
- | `rowCount` | `Readonly<Ref<number>>` | Number of data rows |
99
- | `columnCount` | `Readonly<Ref<number>>` | Number of columns |
100
- | `selectedRows` | `Ref<Set<number>>` | Selected row indices |
101
- | `selectedCells` | `Ref<Set<string>>` | Selected cell keys |
102
- | `sortState` | `SortState` | Current sort column and direction |
103
- | `filterState` | `FilterState` | Current filter state |
104
- | `focusedCell` | `Ref<{row, column} \| null>` | Currently focused cell |
105
- | `visibleColumnCount` | `Readonly<Ref<number>>` | Count of visible columns |
106
- | `dirtyState` | `Ref<boolean>` | Whether table has unsaved changes |
107
- | `expandedRows` | `Ref<Set<number>>` | Expanded row indices |
108
- | `pinnedTopRows` | `Ref<Set<number>>` | Rows pinned to top |
109
- | `pinnedBottomRows` | `Ref<Set<number>>` | Rows pinned to bottom |
110
- | `tableLoading` | `Ref<boolean>` | Loading state |
111
-
112
- ### Data Methods
113
-
114
- #### Setup
115
- ```typescript
116
- table.setHeaders(['Name', 'Age', 'Email'])
117
- table.setData([['Alice', 30, 'alice@email.com'], ...])
118
- table.setFooter(['Total', '', count])
119
- table.setCaption('User List')
120
- ```
121
-
122
- #### Row Operations
123
- ```typescript
124
- table.addRow(['value1', 'value2', ...], index?)
125
- table.addRowWithAttributes(['value1', ...], { class: 'highlight' })
126
- table.removeRow(index) // -1 for last row
127
- table.updateRow(index, ['newValue1', ...])
128
- table.getRowData(index) // Returns copy
129
- table.setRowData(index, ['value1', ...])
130
- ```
131
-
132
- #### Cell Operations
133
- ```typescript
134
- table.getCell(rowIndex, columnIndex)
135
- table.setCell(rowIndex, columnIndex, value)
136
- table.setCellWithAttributes(rowIndex, columnIndex, value, { class: 'active' })
137
- table.updateCell(rowIndex, columnIndex, { value, attributes })
138
- table.getCellRange(rowStart, rowEnd, columnStart, columnEnd)
139
- table.setCellRange(rowStart, columnStart, [[data]])
140
- ```
141
-
142
- #### Column Operations
143
- ```typescript
144
- table.addColumn('Header', ['data1', 'data2', ...], index?)
145
- table.removeColumn(index)
146
- table.getColumnData(index)
147
- table.setColumnData(index, ['value1', 'value2', ...])
148
- ```
149
-
150
- ### Advanced Features
151
-
152
- #### Transactions
153
- ```typescript
154
- // Batch operations with automatic rollback on error
155
- table.transaction(() => {
156
- table.addRow(['New', 'Row', 'Data'])
157
- table.setCell(0, 0, 'Updated')
158
- // If any operation fails, all changes are reverted
159
- })
160
-
161
- // Async transactions
162
- await table.transaction(async () => {
163
- const data = await fetchData()
164
- table.setData(data)
165
- })
166
- ```
167
-
168
- #### Validation
169
- ```typescript
170
- const result = table.validateAll((value, rowIndex, columnIndex) => {
171
- if (columnIndex === 0 && !value) return 'Name required'
172
- if (columnIndex === 2 && isNaN(Number(value))) return 'Must be number'
173
- return null // Valid
174
- })
175
-
176
- if (!result.valid) {
177
- result.errors.forEach(error => {
178
- console.log(`Row ${error.row}, Column ${error.column}: ${error.message}`)
179
- })
180
- }
181
- ```
182
-
183
- #### Aggregation
184
- ```typescript
185
- const total = table.sum(colIndex) // Returns number (auto-converts)
186
- const avg = table.average(colIndex) // Returns number
187
- const min = table.min(colIndex) // Returns string from DOM
188
- const max = table.max(colIndex) // Returns string from DOM
189
- const count = table.count(colIndex, predicate?)
190
- const custom = table.aggregate(colIndex, { initial, reducer })
191
-
192
- // Convert min/max when needed
193
- const maxSalary = Number(table.max(2))
194
- ```
195
-
196
- #### Transformation
197
- ```typescript
198
- table.transformColumn(columnIndex, (value, rowIndex) => {
199
- return Number(value) * 1.1 // 10% increase
200
- })
201
-
202
- table.transformRow(rowIndex, (value, columnIndex) => {
203
- return String(value).toUpperCase()
204
- })
205
-
206
- table.transformCells((value, rowIndex, columnIndex) => {
207
- return columnIndex === 2 ? Number(value).toFixed(2) : value
208
- })
209
- ```
210
-
211
- #### Computed Columns
212
- ```typescript
213
- table.addComputedColumn({
214
- label: 'Total',
215
- computeFunction: (row) => Number(row[1]) * Number(row[2]),
216
- index: 3 // Optional, defaults to end
217
- })
218
-
219
- table.removeComputedColumn(3)
220
- ```
221
-
222
- #### Grouping
223
- ```typescript
224
- const grouped = table.groupBy(row => row[1]) // Group by column 1
225
- // Returns: Map<string, number[]> - key to row indices
226
- ```
227
-
228
- #### Pagination
229
- ```typescript
230
- table.paginate({ pageSize: 10, currentPage: 1 })
231
-
232
- // Navigation
233
- table.nextPage()
234
- table.previousPage()
235
- table.goToPage(5)
236
- ```
237
-
238
- #### Virtual Scrolling
239
- ```typescript
240
- table.enableVirtualScrolling({
241
- rowHeight: 40,
242
- containerHeight: 400
243
- })
244
-
245
- table.disableVirtualScrolling()
246
- ```
247
-
248
- #### Import/Export
249
- ```typescript
250
- // Export
251
- const csv = table.exportToCSV()
252
- const json = table.exportToJSON()
253
-
254
- // Import
255
- table.importFromCSV(csvString)
256
- table.importFromJSON(jsonString)
257
- table.importFromArray([['row1col1', ...], ['row2col1', ...]])
258
- ```
259
-
260
- #### Search
261
- ```typescript
262
- const results = table.search('query', { caseSensitive: false })
263
- // Returns: [{ row, column, value }, ...]
264
-
265
- const columnResults = table.searchColumn(0, 'query')
266
- ```
267
-
268
- #### Selection
269
- ```typescript
270
- table.selectRow(rowIndex)
271
- table.deselectRow(rowIndex)
272
- table.toggleRowSelection(rowIndex)
273
- table.selectCell(rowIndex, columnIndex)
274
- table.clearSelection()
275
-
276
- // Check state
277
- table.isRowSelected(rowIndex)
278
- table.selectedRows.value // Set<number>
279
- table.selectedCells.value // Set<string>
280
- ```
281
-
282
- #### Inline Editing
283
- ```typescript
284
- const cleanup = table.enableEditing()
285
- // Double-click to edit, Enter to save, Escape to cancel
286
-
287
- // Disable when done
288
- cleanup()
289
- ```
290
-
291
- #### DOM Sync
292
- ```typescript
293
- // Sync pre-existing HTML table to data layer
294
- table.sync()
295
-
296
- // Now table.headers and table.data contain DOM content
297
- ```
298
-
299
- #### Data Change Subscription
300
- ```typescript
301
- const unsubscribe = table.onDataChange((newData) => {
302
- console.log('Data changed:', newData)
303
- // Save to server, localStorage, etc.
304
- })
305
-
306
- // Stop watching
307
- unsubscribe()
308
- ```
309
-
310
- ### Types
311
-
312
- ```typescript
313
- // Cell configuration
314
- interface CellConfig {
315
- value: unknown
316
- attributes?: ElementAttributes
317
- columnSpan?: number
318
- rowSpan?: number
319
- }
320
-
321
- // Element attributes
322
- interface ElementAttributes {
323
- class?: string | string[] | Record<string, boolean>
324
- style?: string | Record<string, string | number>
325
- id?: string
326
- title?: string
327
- // ... all HTML attributes, ARIA, data-*, events
328
- }
329
-
330
- // Computed column configuration
331
- interface ComputedColumnConfig {
332
- computeFunction: (row: unknown[]) => unknown
333
- label: string
334
- index?: number
335
- }
336
-
337
- // Validation
338
- interface ValidationResult {
339
- valid: boolean
340
- errors: ValidationError[]
341
- }
342
-
343
- interface ValidationError {
344
- row: number
345
- column: number
346
- message: string
347
- }
348
-
349
- // Search
350
- interface SearchResult {
351
- row: number
352
- column: number
353
- value: unknown
354
- }
355
-
356
- // Sort state
357
- interface SortState {
358
- columnIndex: Readonly<Ref<number | null>>
359
- ascending: Readonly<Ref<boolean>>
360
- descending: Readonly<Ref<boolean>>
361
- }
362
-
363
- // Filter state
364
- interface FilterState {
365
- isFiltered: Readonly<Ref<boolean>>
366
- filteredRowCount: Readonly<Ref<number>>
367
- filteredIndices: Readonly<Ref<number[]>>
368
- }
369
-
370
- // Base record type
371
- interface BaseRecord {
372
- readonly id: string
373
- }
374
-
375
- // Column definition for records
376
- interface RecordColumnDef<T extends BaseRecord> {
377
- field: string | ((record: T) => unknown)
378
- header: string
379
- format?: (value: unknown, record: T) => string
380
- parse?: (value: string, record: T) => unknown
381
- editable?: boolean
382
- width?: string | number
383
- defaultValue?: unknown
384
- }
385
- ```
386
-
387
- ### Record-Based Data API
388
-
389
- Work with typed records instead of raw arrays:
390
-
391
- ```typescript
392
- import type { RecordColumnDef } from '@mikestools/usetable'
393
-
394
- interface User {
395
- id: string
396
- name: string
397
- email: string
398
- age: number
399
- }
400
-
401
- const columns: RecordColumnDef<User>[] = [
402
- { field: 'id', header: 'ID' },
403
- { field: 'name', header: 'Name' },
404
- { field: 'email', header: 'Email' },
405
- { field: 'age', header: 'Age' },
406
- ]
407
-
408
- // Configure table for records
409
- table.setRecordColumns(columns)
410
- table.setRecords([
411
- { id: 'user-1', name: 'Alice', email: 'alice@example.com', age: 30 },
412
- { id: 'user-2', name: 'Bob', email: 'bob@example.com', age: 25 },
413
- ])
414
-
415
- // CRUD operations
416
- const newId = table.addRecord({ name: 'Carol', email: 'carol@example.com', age: 28 })
417
- table.updateRecord<User>('user-1', { age: 31 })
418
- table.removeRecord('user-2')
419
- const user = table.getRecordById<User>('user-1')
420
-
421
- // Selection by ID
422
- table.selectRecords(['user-1', 'user-3'])
423
- const selectedIds = table.getSelectedRecordIds()
424
- const selectedRecords = table.getSelectedRecords<User>()
425
-
426
- // Change notifications
427
- table.onRecordChange<User>((id, record, type) => {
428
- console.log(`${type}: ${id}`) // 'add' | 'update' | 'remove'
429
- })
430
-
431
- // Export/Import
432
- const json = table.exportRecordsToJSON()
433
- table.importRecordsFromJSON(json)
434
- ```
435
-
436
- ### Sorting API
437
-
438
- ```typescript
439
- table.sortColumnAscending(columnIndex)
440
- table.sortColumnDescending(columnIndex)
441
- table.sortColumnAscendingWith(columnIndex, comparator)
442
- table.sortColumnDescendingWith(columnIndex, comparator)
443
- table.clearColumnSort()
444
-
445
- // Check state
446
- table.isSorted()
447
- table.isSortedAscending()
448
- table.isSortedDescending()
449
- table.getSortedColumnIndex()
450
-
451
- // Reactive state
452
- table.sortState.columnIndex.value
453
- table.sortState.ascending.value
454
- ```
455
-
456
- ### Filtering API
457
-
458
- ```typescript
459
- table.filterRows(predicate)
460
- table.filterColumn(columnIndex, predicate)
461
- table.filterColumnByValue(columnIndex, value)
462
- table.filterColumnByValues(columnIndex, values)
463
- table.clearFilters()
464
-
465
- // Check state
466
- table.getFilteredRowIndices()
467
- table.filterState.isFiltered.value
468
- ```
469
-
470
- ### Undo/Redo API
471
-
472
- ```typescript
473
- table.undo()
474
- table.redo()
475
- table.canUndo()
476
- table.canRedo()
477
- table.clearHistory()
478
- table.setHistoryLimit(100)
479
- ```
480
-
481
- ### Cell Focus API
482
-
483
- ```typescript
484
- table.focusCell(rowIndex, columnIndex)
485
- table.clearCellFocus()
486
- table.getFocusedCell()
487
- table.moveFocusUp()
488
- table.moveFocusDown()
489
- table.moveFocusLeft()
490
- table.moveFocusRight()
491
- table.enableKeyboardNavigation()
492
- ```
493
-
494
- ### Column Visibility API
495
-
496
- ```typescript
497
- table.hideColumn(columnIndex)
498
- table.showColumn(columnIndex)
499
- table.toggleColumnVisibility(columnIndex)
500
- table.isColumnVisible(columnIndex)
501
- table.getVisibleColumnIndices()
502
- table.getHiddenColumnIndices()
503
- ```
504
-
505
- ### Reordering API
506
-
507
- ```typescript
508
- table.moveRow(fromIndex, toIndex)
509
- table.moveRowUp(index)
510
- table.moveRowDown(index)
511
- table.moveRowToTop(index)
512
- table.moveRowToBottom(index)
513
- table.swapRows(index1, index2)
514
- table.moveColumn(fromIndex, toIndex)
515
- table.swapColumns(index1, index2)
516
- ```
517
-
518
- ### Clipboard API
519
-
520
- ```typescript
521
- table.copyCell(rowIndex, columnIndex)
522
- table.copyRow(rowIndex)
523
- table.copyColumn(columnIndex)
524
- table.copyCellRange(startRow, startCol, endRow, endCol)
525
- table.copySelectedCells()
526
- table.copySelectedRows()
527
- table.pasteAtCell(rowIndex, columnIndex, data)
528
- ```
529
-
530
- ### Row State API
531
-
532
- ```typescript
533
- table.expandRow(index)
534
- table.collapseRow(index)
535
- table.toggleRowExpansion(index)
536
- table.pinRowTop(index)
537
- table.pinRowBottom(index)
538
- table.unpinRow(index)
539
- table.unpinAllRows()
540
- ```
541
-
542
- ### Metadata API
543
-
544
- ```typescript
545
- table.setCellMeta(row, col, key, value)
546
- table.getCellMeta(row, col, key)
547
- table.setRowMeta(index, key, value)
548
- table.getRowMeta(index, key)
549
- table.setColumnMeta(index, key, value)
550
- table.getColumnMeta(index, key)
551
- ```
552
-
553
- ## Examples
554
-
555
- The showcase includes 22 interactive examples:
556
-
557
- 1. **Basic Setup** - Headers, data, caption, row manipulation
558
- 2. **Record-Based Data** - Work with typed records by ID
559
- 3. **Inline Editing** - Double-click to edit with data sync
560
- 4. **Sorting** - Column sorting with state tracking
561
- 5. **Search & Filter** - Search and filter with predicates
562
- 6. **Selection** - Row/cell selection with range support
563
- 7. **Cell Navigation** - Keyboard navigation between cells
564
- 8. **Column Visibility** - Show/hide columns
565
- 9. **Reordering** - Move rows and columns
566
- 10. **Undo/Redo** - History tracking with rollback
567
- 11. **Clipboard** - Copy/paste operations
568
- 12. **Row State** - Expand/collapse and pin rows
569
- 13. **Aggregation** - Sum, average, computed columns
570
- 14. **Validation** - Per-cell validation rules
571
- 15. **Pagination** - Page through large datasets
572
- 16. **Import/Export** - CSV and JSON support
573
- 17. **Styling** - Dynamic classes and attributes
574
- 18. **Transactions** - Batch operations with rollback
575
- 19. **Grouping** - Group rows by values
576
- 20. **Footer & Totals** - Summary rows
577
- 21. **HTML Elements** - Rich content in cells
578
- 22. **Pre-existing Sync** - Connect to existing tables
579
-
580
- ## Browser Support
581
-
582
- Works in all modern browsers that support ES2020+.
583
-
584
- ## License
585
-
586
- MIT Š Mike Garcia
1
+ # @mikestools/usetable
2
+
3
+ A Vue 3 composable for HTML table manipulation with native DOM APIs. Two-layer architecture with `createTable` (vanilla JS) and `useTable` (Vue composable).
4
+
5
+ ## Features
6
+
7
+ - đŸŽ¯ **DOM-First Architecture** - HTML table element is source of truth
8
+ - đŸ“Ļ **TypeScript First** - Full type safety with IntelliSense
9
+ - 🔗 **Two-Layer Design** - `createTable` (vanilla) + `useTable` (Vue)
10
+ - ✨ **Vue Reactivity** - Computed state that reacts to DOM changes
11
+
12
+ > **Note:** Data is read from DOM `textContent`, so values are strings. Use `Number()` when working with numeric data.
13
+
14
+ ### Core Features
15
+
16
+ | Feature | Description |
17
+ |------------------|-------------------------------------------|
18
+ | 📋 **Headers** | Set, update, add, remove header cells |
19
+ | ➕ **Rows** | Add, remove, update, move, swap rows |
20
+ | 📊 **Columns** | Add, remove, move, swap columns |
21
+ | 🔲 **Cells** | Get, set individual cell values |
22
+ | â˜‘ī¸ **Selection** | Row and cell selection with range support |
23
+ | đŸŽ¯ **Focus** | Cell focus with keyboard navigation |
24
+ | 📝 **Footer** | Footer row for totals/summaries |
25
+ | đŸ—„ī¸ **Records** | Work with typed records by ID |
26
+ | 🔔 **Events** | Granular change callbacks |
27
+ | âŒ¨ī¸ **Keyboard** | Arrow key cell navigation |
28
+ | ⚡ **Reactive** | Vue computed properties |
29
+ | 🔄 **Sync** | Sync with pre-existing DOM tables |
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ npm install @mikestools/usetable vue
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```typescript
40
+ import { ref, onMounted } from 'vue'
41
+ import { useTable, type UseTableReturn } from '@mikestools/usetable'
42
+
43
+ const tableRef = ref<HTMLTableElement>()
44
+
45
+ // Template-bound state (updated via callbacks)
46
+ const rowCount = ref(0)
47
+ const totalSalary = ref(0)
48
+
49
+ // Table instance
50
+ let table: UseTableReturn | undefined
51
+
52
+ // Update tracked stats from table
53
+ function updateStats() {
54
+ if (!table) return
55
+ rowCount.value = table.rowCount.value
56
+ totalSalary.value = table.data.value.reduce((sum, row) => {
57
+ return sum + (Number(row[2]) || 0)
58
+ }, 0)
59
+ }
60
+
61
+ onMounted(() => {
62
+ // Create table with initial data
63
+ table = useTable(ref(tableRef.value!), [
64
+ ['Alice Johnson', 'Engineering', 85000],
65
+ ['Bob Smith', 'Marketing', 72000],
66
+ ], {
67
+ headers: ['Name', 'Department', 'Salary'],
68
+ caption: 'Employee Directory'
69
+ })
70
+
71
+ // Initial calculation
72
+ updateStats()
73
+
74
+ // Subscribe to data changes
75
+ table.onDataChange(() => updateStats())
76
+ })
77
+
78
+ // Functions can access table directly
79
+ function addEmployee() {
80
+ table?.addRow(['Carol Williams', 'Sales', 68000])
81
+ }
82
+ ```
83
+
84
+ ```vue
85
+ <template>
86
+ <table ref="tableRef" class="table"></table>
87
+ <p>Employees: {{ rowCount }}</p>
88
+ <p>Total Salary: ${{ totalSalary }}</p>
89
+ <button @click="addEmployee">Add</button>
90
+ </template>
91
+ ```
92
+
93
+ ## Architecture
94
+
95
+ ### Two-Layer Design
96
+
97
+ ```
98
+ ┌─────────────────────────────────────────────────────────────┐
99
+ │ useTable (Vue) │
100
+ │ Adds Vue reactivity: ref(), computed(), onUnmounted() │
101
+ ├─────────────────────────────────────────────────────────────┤
102
+ │ createTable (Vanilla) │
103
+ │ Pure DOM manipulation using native table APIs │
104
+ │ element.tHead, element.tBodies, row.cells, etc. │
105
+ └─────────────────────────────────────────────────────────────┘
106
+ ```
107
+
108
+ **Use `createTable`** when:
109
+ - Building non-Vue applications
110
+ - Need vanilla JavaScript table manipulation
111
+ - Want smallest possible bundle size
112
+
113
+ **Use `useTable`** when:
114
+ - Building Vue 3 applications
115
+ - Need reactive state (computed refs)
116
+ - Want automatic cleanup on component unmount
117
+
118
+ ## API Reference
119
+
120
+ ### useTable
121
+
122
+ ```typescript
123
+ function useTable(
124
+ elementRef: Ref<HTMLTableElement | null>,
125
+ initialData?: readonly (readonly CellValue[])[],
126
+ options?: UseTableOptions
127
+ ): UseTableReturn
128
+ ```
129
+
130
+ #### Options
131
+
132
+ | Option | Type | Description |
133
+ |----------------|-------------------------|--------------------------------|
134
+ | `headers` | `readonly CellValue[]` | Initial header values |
135
+ | `caption` | `string` | Table caption text |
136
+ | `footer` | `readonly CellValue[]` | Initial footer values |
137
+ | `idGenerator` | `() => string` | Custom row ID generator |
138
+
139
+ #### Reactive State
140
+
141
+ | Property | Type | Description |
142
+ |---------------|-----------------------------------------|--------------------------------|
143
+ | `element` | `Readonly<Ref<HTMLTableElement>>` | The table element |
144
+ | `headers` | `ComputedRef<readonly string[]>` | Header values |
145
+ | `data` | `ComputedRef<readonly CellValue[][]>` | Body data as 2D array |
146
+ | `rowCount` | `ComputedRef<number>` | Number of body rows |
147
+ | `columnCount` | `ComputedRef<number>` | Number of columns |
148
+ | `rowIds` | `ComputedRef<readonly string[]>` | Array of row IDs |
149
+ | `footerData` | `ComputedRef<CellValue[] \| undefined>` | Footer row values |
150
+ | `captionText` | `ComputedRef<string \| undefined>` | Caption text |
151
+ | `version` | `Ref<number>` | Version counter for reactivity |
152
+
153
+ #### Selection State
154
+
155
+ | Property | Type | Description |
156
+ |-------------------|--------------------|------------------------------|
157
+ | `selection.rows` | `Ref<Set<number>>` | Selected row indices |
158
+ | `selection.cells` | `Ref<Set<string>>` | Selected cell keys (row,col) |
159
+
160
+ #### Focus State
161
+
162
+ | Property | Type | Description |
163
+ |--------------|----------------------------------------------|-----------------------|
164
+ | `focus.cell` | `Ref<{row: number, column: number} \| null>` | Focused cell position |
165
+
166
+ ### Methods
167
+
168
+ #### Header Methods
169
+
170
+ ```typescript
171
+ setHeaders(headers: readonly CellValue[]): void
172
+ setHeader(index: number, value: CellValue): void
173
+ addHeader(value: CellValue, index?: number): void
174
+ removeHeader(index: number): CellValue | undefined
175
+ ```
176
+
177
+ #### Row Methods
178
+
179
+ ```typescript
180
+ addRow(data: readonly CellValue[], index?: number, id?: string): HTMLTableRowElement
181
+ removeRow(index: number): CellValue[] | undefined
182
+ removeRowById(id: string): CellValue[] | undefined
183
+ updateRow(index: number, data: readonly CellValue[]): void
184
+ updateRowById(id: string, data: readonly CellValue[]): boolean
185
+ moveRow(fromIndex: number, toIndex: number): void
186
+ swapRows(index1: number, index2: number): void
187
+ clearRows(): void
188
+ getRowData(index: number): readonly CellValue[] | undefined
189
+ getRowId(index: number): string | undefined
190
+ getRowIndex(id: string): number
191
+ ```
192
+
193
+ #### Column Methods
194
+
195
+ ```typescript
196
+ addColumn(header: CellValue, defaultValue?: CellValue, index?: number): void
197
+ removeColumn(index: number): void
198
+ moveColumn(fromIndex: number, toIndex: number): void
199
+ swapColumns(index1: number, index2: number): void
200
+ getColumnData(columnIndex: number): readonly CellValue[]
201
+ setColumnData(columnIndex: number, data: readonly CellValue[]): void
202
+ ```
203
+
204
+ #### Cell Methods
205
+
206
+ ```typescript
207
+ getCell(rowIndex: number, columnIndex: number): CellValue | undefined
208
+ setCell(rowIndex: number, columnIndex: number, value: CellValue): void
209
+ setCellByRowId(rowId: string, columnIndex: number, value: CellValue): boolean
210
+ setCellRange(startRow: number, startCol: number, data: readonly (readonly CellValue[])[]): void
211
+ getCellElement(rowIndex: number, columnIndex: number): HTMLTableCellElement | null
212
+ ```
213
+
214
+ #### Selection Methods
215
+
216
+ ```typescript
217
+ selectRow(index: number): void
218
+ deselectRow(index: number): void
219
+ toggleRowSelection(index: number): void
220
+ selectAllRows(): void
221
+ deselectAllRows(): void
222
+ selectRowRange(startIndex: number, endIndex: number): void
223
+ isRowSelected(index: number): boolean
224
+ getSelectedRowIndices(): number[]
225
+ getSelectedRowData(): readonly CellValue[][]
226
+
227
+ selectCell(rowIndex: number, columnIndex: number): void
228
+ deselectCell(rowIndex: number, columnIndex: number): void
229
+ toggleCellSelection(rowIndex: number, columnIndex: number): void
230
+ isCellSelected(rowIndex: number, columnIndex: number): boolean
231
+ clearSelection(): void
232
+ ```
233
+
234
+ #### Focus Methods
235
+
236
+ ```typescript
237
+ focusCell(rowIndex: number, columnIndex: number): void
238
+ clearFocus(): void
239
+ getFocusedCell(): { row: number; column: number } | null
240
+ isCellFocused(rowIndex: number, columnIndex: number): boolean
241
+ moveFocusUp(): boolean
242
+ moveFocusDown(): boolean
243
+ moveFocusLeft(): boolean
244
+ moveFocusRight(): boolean
245
+ moveFocusToFirst(): void
246
+ moveFocusToLast(): void
247
+ ```
248
+
249
+ #### Footer Methods
250
+
251
+ ```typescript
252
+ setFooter(data: readonly CellValue[]): void
253
+ setFooterCell(index: number, value: CellValue): void
254
+ getFooterCell(index: number): string | undefined
255
+ clearFooter(): void
256
+ ```
257
+
258
+ #### Record Methods
259
+
260
+ ```typescript
261
+ setRecords<T extends BaseRecord>(
262
+ records: readonly T[],
263
+ fields: readonly (keyof T | ((record: T) => CellValue))[]
264
+ ): void
265
+
266
+ addRecord<T extends BaseRecord>(
267
+ record: T,
268
+ fields: readonly (keyof T | ((record: T) => CellValue))[],
269
+ index?: number
270
+ ): string
271
+
272
+ getRecordData(id: string): readonly CellValue[] | undefined
273
+ updateRecordRow(id: string, data: readonly CellValue[]): boolean
274
+ removeRecord(id: string): boolean
275
+ hasRecord(id: string): boolean
276
+ selectRecords(ids: readonly string[]): void
277
+ getSelectedRecordIds(): string[]
278
+ ```
279
+
280
+ #### Event Subscriptions
281
+
282
+ ```typescript
283
+ onRowAdd(callback: RowAddCallback): () => void
284
+ onRowRemove(callback: RowRemoveCallback): () => void
285
+ onRowUpdate(callback: RowUpdateCallback): () => void
286
+ onCellChange(callback: CellChangeCallback): () => void
287
+ onHeaderChange(callback: HeaderChangeCallback): () => void
288
+ onDataChange(callback: DataChangeCallback): () => void
289
+ onSelectionChange(callback: SelectionChangeCallback): () => void
290
+ onFocusChange(callback: FocusChangeCallback): () => void
291
+ ```
292
+
293
+ #### Lifecycle Methods
294
+
295
+ ```typescript
296
+ enableKeyboardNavigation(): () => void // Returns cleanup function
297
+ sync(): void // Re-sync state from DOM
298
+ destroy(): void // Cleanup all resources
299
+ reset(): void // Clear all table content
300
+ ```
301
+
302
+ ### createTable
303
+
304
+ For vanilla JavaScript usage without Vue:
305
+
306
+ ```typescript
307
+ import { createTable } from '@mikestools/usetable'
308
+
309
+ const tableElement = document.querySelector('table')
310
+ const table = createTable(tableElement, [
311
+ ['Alice', 'Engineering', 85000],
312
+ ['Bob', 'Marketing', 72000],
313
+ ], {
314
+ headers: ['Name', 'Department', 'Salary']
315
+ })
316
+
317
+ // Same API as useTable, but without Vue reactivity
318
+ table.addRow(['Carol', 'Sales', 68000])
319
+ console.log(table.getRowCount()) // 3
320
+ ```
321
+
322
+ ## Types
323
+
324
+ ```typescript
325
+ // Cell value can be string, number, boolean, null, or DOM element
326
+ type CellValue = string | number | boolean | null | undefined | Node
327
+
328
+ // Base record interface for record-based operations
329
+ interface BaseRecord {
330
+ id: string
331
+ [key: string]: unknown
332
+ }
333
+
334
+ // Callback types
335
+ type RowAddCallback = (data: readonly CellValue[], index: number, id: string | undefined) => void
336
+ type RowRemoveCallback = (data: readonly CellValue[], index: number, id: string | undefined) => void
337
+ type RowUpdateCallback = (data: readonly CellValue[], index: number) => void
338
+ type CellChangeCallback = (rowIndex: number, columnIndex: number, oldValue: CellValue, newValue: CellValue) => void
339
+ type HeaderChangeCallback = (headers: readonly string[]) => void
340
+ type DataChangeCallback = (data: readonly (readonly CellValue[])[]) => void
341
+ type SelectionChangeCallback = (rows: ReadonlySet<number>, cells: ReadonlySet<string>) => void
342
+ type FocusChangeCallback = (cell: { row: number; column: number } | null) => void
343
+ ```
344
+
345
+ ## Examples
346
+
347
+ See the [showcase](./showcase.html) for interactive examples.
348
+
349
+ ## License
350
+
351
+ MIT
352
+