@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 +380 -586
- package/dist/index.d.ts +463 -1002
- package/dist/usetable.js +968 -1753
- package/dist/usetable.umd.cjs +1 -7
- package/package.json +10 -10
- package/showcase/examples/BasicExample.vue +382 -474
package/README.md
CHANGED
|
@@ -1,586 +1,380 @@
|
|
|
1
|
-
# @mikestools/usetable
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- đ¯ **
|
|
8
|
-
- đĻ **TypeScript First** - Full type safety with IntelliSense
|
|
9
|
-
- đ **
|
|
10
|
-
- ⨠**
|
|
11
|
-
|
|
12
|
-
> **Note:** Data is read from DOM `textContent`, so values are strings. Use `Number()` when working with numeric data.
|
|
13
|
-
|
|
14
|
-
###
|
|
15
|
-
|
|
16
|
-
| Feature
|
|
17
|
-
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
['
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
```typescript
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
```typescript
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
//
|
|
357
|
-
interface
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
+
|