@mikestools/usetable 0.0.3 → 0.0.5

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,380 @@
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
+ | 📐 **Sections** | Section-specific row ops (thead/tfoot/tbody) |
21
+ | 📊 **Columns** | Add, remove, move, swap columns |
22
+ | 🔲 **Cells** | Get, set individual cell values |
23
+ | â˜‘ī¸ **Selection** | Row and cell selection with range support |
24
+ | đŸŽ¯ **Focus** | Cell focus with keyboard navigation |
25
+ | 📝 **Footer** | Footer row for totals/summaries |
26
+ | đŸ—„ī¸ **Records** | Work with typed records by ID |
27
+ | 🔔 **Events** | Granular change callbacks |
28
+ | âŒ¨ī¸ **Keyboard** | Arrow key cell navigation |
29
+ | ⚡ **Reactive** | Vue computed properties |
30
+ | 🔄 **Sync** | Sync with pre-existing DOM tables |
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ npm install @mikestools/usetable vue
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```typescript
41
+ import { ref, onMounted } from 'vue'
42
+ import { useTable, type UseTableReturn } from '@mikestools/usetable'
43
+
44
+ const tableRef = ref<HTMLTableElement>()
45
+
46
+ // Template-bound state (updated via callbacks)
47
+ const rowCount = ref(0)
48
+ const totalSalary = ref(0)
49
+
50
+ // Table instance
51
+ let table: UseTableReturn | undefined
52
+
53
+ // Update tracked stats from table
54
+ function updateStats() {
55
+ if (!table) return
56
+ rowCount.value = table.rowCount.value
57
+ totalSalary.value = table.data.value.reduce((sum, row) => {
58
+ return sum + (Number(row[2]) || 0)
59
+ }, 0)
60
+ }
61
+
62
+ onMounted(() => {
63
+ // Create table with initial data
64
+ table = useTable(ref(tableRef.value!), [
65
+ ['Alice Johnson', 'Engineering', 85000],
66
+ ['Bob Smith', 'Marketing', 72000],
67
+ ], {
68
+ headers: ['Name', 'Department', 'Salary'],
69
+ caption: 'Employee Directory'
70
+ })
71
+
72
+ // Initial calculation
73
+ updateStats()
74
+
75
+ // Subscribe to data changes
76
+ table.onDataChange(() => updateStats())
77
+ })
78
+
79
+ // Functions can access table directly
80
+ function addEmployee() {
81
+ table?.addRow(['Carol Williams', 'Sales', 68000])
82
+ }
83
+ ```
84
+
85
+ ```vue
86
+ <template>
87
+ <table ref="tableRef" class="table"></table>
88
+ <p>Employees: {{ rowCount }}</p>
89
+ <p>Total Salary: ${{ totalSalary }}</p>
90
+ <button @click="addEmployee">Add</button>
91
+ </template>
92
+ ```
93
+
94
+ ## Architecture
95
+
96
+ ### Two-Layer Design
97
+
98
+ ```
99
+ ┌─────────────────────────────────────────────────────────────┐
100
+ │ useTable (Vue) │
101
+ │ Adds Vue reactivity: ref(), computed(), onUnmounted() │
102
+ ├─────────────────────────────────────────────────────────────┤
103
+ │ createTable (Vanilla) │
104
+ │ Pure DOM manipulation using native table APIs │
105
+ │ element.tHead, element.tBodies, row.cells, etc. │
106
+ └─────────────────────────────────────────────────────────────┘
107
+ ```
108
+
109
+ **Use `createTable`** when:
110
+ - Building non-Vue applications
111
+ - Need vanilla JavaScript table manipulation
112
+ - Want smallest possible bundle size
113
+
114
+ **Use `useTable`** when:
115
+ - Building Vue 3 applications
116
+ - Need reactive state (computed refs)
117
+ - Want automatic cleanup on component unmount
118
+
119
+ ## API Reference
120
+
121
+ ### useTable
122
+
123
+ ```typescript
124
+ function useTable(
125
+ elementRef: Ref<HTMLTableElement | null>,
126
+ initialData?: readonly (readonly CellValue[])[],
127
+ options?: UseTableOptions
128
+ ): UseTableReturn
129
+ ```
130
+
131
+ #### Options
132
+
133
+ | Option | Type | Description |
134
+ |----------------|-------------------------|--------------------------------|
135
+ | `headers` | `readonly CellValue[]` | Initial header values |
136
+ | `caption` | `string` | Table caption text |
137
+ | `footer` | `readonly CellValue[]` | Initial footer values |
138
+ | `idGenerator` | `() => string` | Custom row ID generator |
139
+
140
+ #### Reactive State
141
+
142
+ | Property | Type | Description |
143
+ |---------------|-----------------------------------------|--------------------------------|
144
+ | `element` | `Readonly<Ref<HTMLTableElement>>` | The table element |
145
+ | `headers` | `ComputedRef<readonly string[]>` | Header values |
146
+ | `data` | `ComputedRef<readonly CellValue[][]>` | Body data as 2D array |
147
+ | `rowCount` | `ComputedRef<number>` | Number of body rows |
148
+ | `columnCount` | `ComputedRef<number>` | Number of columns |
149
+ | `rowIds` | `ComputedRef<readonly string[]>` | Array of row IDs |
150
+ | `footerData` | `ComputedRef<CellValue[] \| undefined>` | Footer row values |
151
+ | `captionText` | `ComputedRef<string \| undefined>` | Caption text |
152
+ | `version` | `Ref<number>` | Version counter for reactivity |
153
+
154
+ #### Selection State
155
+
156
+ | Property | Type | Description |
157
+ |-------------------|--------------------|------------------------------|
158
+ | `selection.rows` | `Ref<Set<number>>` | Selected row indices |
159
+ | `selection.cells` | `Ref<Set<string>>` | Selected cell keys (row,col) |
160
+
161
+ #### Focus State
162
+
163
+ | Property | Type | Description |
164
+ |--------------|----------------------------------------------|-----------------------|
165
+ | `focus.cell` | `Ref<{row: number, column: number} \| null>` | Focused cell position |
166
+
167
+ ### Methods
168
+
169
+ #### Header Methods
170
+
171
+ ```typescript
172
+ setHeaders(headers: readonly CellValue[]): void
173
+ setHeader(index: number, value: CellValue): void
174
+ addHeader(value: CellValue, index?: number): void
175
+ removeHeader(index: number): CellValue | undefined
176
+ ```
177
+
178
+ #### Row Methods
179
+
180
+ ```typescript
181
+ addRow(data: readonly CellValue[], index?: number, id?: string): HTMLTableRowElement
182
+ removeRow(index: number): CellValue[] | undefined
183
+ removeRowById(id: string): CellValue[] | undefined
184
+ updateRow(index: number, data: readonly CellValue[]): void
185
+ updateRowById(id: string, data: readonly CellValue[]): boolean
186
+ moveRow(fromIndex: number, toIndex: number): void
187
+ swapRows(index1: number, index2: number): void
188
+ clearRows(): void
189
+ getRowData(index: number): readonly CellValue[] | undefined
190
+ getRowId(index: number): string | undefined
191
+ getRowIndex(id: string): number
192
+ ```
193
+
194
+ #### Section-Specific Row Methods
195
+
196
+ Methods using native `HTMLTableSectionElement` APIs for direct section manipulation:
197
+
198
+ ```typescript
199
+ // Generic section methods
200
+ getSectionRows(section: HTMLTableSectionElement): HTMLCollectionOf<HTMLTableRowElement>
201
+ insertSectionRow(section: HTMLTableSectionElement, data: RowData, index?: number, id?: string): HTMLTableRowElement
202
+ deleteSectionRow(section: HTMLTableSectionElement, index: number): CellValue[] | undefined
203
+ getSectionRowCount(section: HTMLTableSectionElement): number
204
+
205
+ // thead-specific
206
+ addHeadRow(data: RowData, index?: number): HTMLTableRowElement
207
+ removeHeadRow(index: number): CellValue[] | undefined
208
+ getHeadRowCount(): number
209
+
210
+ // tfoot-specific
211
+ addFootRow(data: RowData, index?: number): HTMLTableRowElement
212
+ removeFootRow(index: number): CellValue[] | undefined
213
+ getFootRowCount(): number
214
+
215
+ // tbody-specific (supports multiple tbodies)
216
+ addBodyRow(data: RowData, tbodyIndex?: number, rowIndex?: number, id?: string): HTMLTableRowElement
217
+ removeBodyRow(rowIndex: number, tbodyIndex?: number): CellValue[] | undefined
218
+ getBodyRowCount(tbodyIndex?: number): number
219
+ ```
220
+
221
+ #### Column Methods
222
+
223
+ ```typescript
224
+ addColumn(header: CellValue, defaultValue?: CellValue, index?: number): void
225
+ removeColumn(index: number): void
226
+ moveColumn(fromIndex: number, toIndex: number): void
227
+ swapColumns(index1: number, index2: number): void
228
+ getColumnData(columnIndex: number): readonly CellValue[]
229
+ setColumnData(columnIndex: number, data: readonly CellValue[]): void
230
+ ```
231
+
232
+ #### Cell Methods
233
+
234
+ ```typescript
235
+ getCell(rowIndex: number, columnIndex: number): CellValue | undefined
236
+ setCell(rowIndex: number, columnIndex: number, value: CellValue): void
237
+ setCellByRowId(rowId: string, columnIndex: number, value: CellValue): boolean
238
+ setCellRange(startRow: number, startCol: number, data: readonly (readonly CellValue[])[]): void
239
+ getCellElement(rowIndex: number, columnIndex: number): HTMLTableCellElement | null
240
+ ```
241
+
242
+ #### Selection Methods
243
+
244
+ ```typescript
245
+ selectRow(index: number): void
246
+ deselectRow(index: number): void
247
+ toggleRowSelection(index: number): void
248
+ selectAllRows(): void
249
+ deselectAllRows(): void
250
+ selectRowRange(startIndex: number, endIndex: number): void
251
+ isRowSelected(index: number): boolean
252
+ getSelectedRowIndices(): number[]
253
+ getSelectedRowData(): readonly CellValue[][]
254
+
255
+ selectCell(rowIndex: number, columnIndex: number): void
256
+ deselectCell(rowIndex: number, columnIndex: number): void
257
+ toggleCellSelection(rowIndex: number, columnIndex: number): void
258
+ isCellSelected(rowIndex: number, columnIndex: number): boolean
259
+ clearSelection(): void
260
+ ```
261
+
262
+ #### Focus Methods
263
+
264
+ ```typescript
265
+ focusCell(rowIndex: number, columnIndex: number): void
266
+ clearFocus(): void
267
+ getFocusedCell(): { row: number; column: number } | null
268
+ isCellFocused(rowIndex: number, columnIndex: number): boolean
269
+ moveFocusUp(): boolean
270
+ moveFocusDown(): boolean
271
+ moveFocusLeft(): boolean
272
+ moveFocusRight(): boolean
273
+ moveFocusToFirst(): void
274
+ moveFocusToLast(): void
275
+ ```
276
+
277
+ #### Footer Methods
278
+
279
+ ```typescript
280
+ setFooter(data: readonly CellValue[]): void
281
+ setFooterCell(index: number, value: CellValue): void
282
+ getFooterCell(index: number): string | undefined
283
+ clearFooter(): void
284
+ ```
285
+
286
+ #### Record Methods
287
+
288
+ ```typescript
289
+ setRecords<T extends BaseRecord>(
290
+ records: readonly T[],
291
+ fields: readonly (keyof T | ((record: T) => CellValue))[]
292
+ ): void
293
+
294
+ addRecord<T extends BaseRecord>(
295
+ record: T,
296
+ fields: readonly (keyof T | ((record: T) => CellValue))[],
297
+ index?: number
298
+ ): string
299
+
300
+ getRecordData(id: string): readonly CellValue[] | undefined
301
+ updateRecordRow(id: string, data: readonly CellValue[]): boolean
302
+ removeRecord(id: string): boolean
303
+ hasRecord(id: string): boolean
304
+ selectRecords(ids: readonly string[]): void
305
+ getSelectedRecordIds(): string[]
306
+ ```
307
+
308
+ #### Event Subscriptions
309
+
310
+ ```typescript
311
+ onRowAdd(callback: RowAddCallback): () => void
312
+ onRowRemove(callback: RowRemoveCallback): () => void
313
+ onRowUpdate(callback: RowUpdateCallback): () => void
314
+ onCellChange(callback: CellChangeCallback): () => void
315
+ onHeaderChange(callback: HeaderChangeCallback): () => void
316
+ onDataChange(callback: DataChangeCallback): () => void
317
+ onSelectionChange(callback: SelectionChangeCallback): () => void
318
+ onFocusChange(callback: FocusChangeCallback): () => void
319
+ ```
320
+
321
+ #### Lifecycle Methods
322
+
323
+ ```typescript
324
+ enableKeyboardNavigation(): () => void // Returns cleanup function
325
+ sync(): void // Re-sync state from DOM
326
+ destroy(): void // Cleanup all resources
327
+ reset(): void // Clear all table content
328
+ ```
329
+
330
+ ### createTable
331
+
332
+ For vanilla JavaScript usage without Vue:
333
+
334
+ ```typescript
335
+ import { createTable } from '@mikestools/usetable'
336
+
337
+ const tableElement = document.querySelector('table')
338
+ const table = createTable(tableElement, [
339
+ ['Alice', 'Engineering', 85000],
340
+ ['Bob', 'Marketing', 72000],
341
+ ], {
342
+ headers: ['Name', 'Department', 'Salary']
343
+ })
344
+
345
+ // Same API as useTable, but without Vue reactivity
346
+ table.addRow(['Carol', 'Sales', 68000])
347
+ console.log(table.getRowCount()) // 3
348
+ ```
349
+
350
+ ## Types
351
+
352
+ ```typescript
353
+ // Cell value can be string, number, boolean, null, or DOM element
354
+ type CellValue = string | number | boolean | null | undefined | Node
355
+
356
+ // Base record interface for record-based operations
357
+ interface BaseRecord {
358
+ id: string
359
+ [key: string]: unknown
360
+ }
361
+
362
+ // Callback types
363
+ type RowAddCallback = (data: readonly CellValue[], index: number, id: string | undefined) => void
364
+ type RowRemoveCallback = (data: readonly CellValue[], index: number, id: string | undefined) => void
365
+ type RowUpdateCallback = (data: readonly CellValue[], index: number) => void
366
+ type CellChangeCallback = (rowIndex: number, columnIndex: number, oldValue: CellValue, newValue: CellValue) => void
367
+ type HeaderChangeCallback = (headers: readonly string[]) => void
368
+ type DataChangeCallback = (data: readonly (readonly CellValue[])[]) => void
369
+ type SelectionChangeCallback = (rows: ReadonlySet<number>, cells: ReadonlySet<string>) => void
370
+ type FocusChangeCallback = (cell: { row: number; column: number } | null) => void
371
+ ```
372
+
373
+ ## Examples
374
+
375
+ See the [showcase](./showcase.html) for interactive examples.
376
+
377
+ ## License
378
+
379
+ MIT
380
+