@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
|
@@ -1,474 +1,382 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<section id="basicTable">
|
|
3
|
-
<h2 class="h4 mb-3">
|
|
4
|
-
<IconTable class="me-2" />
|
|
5
|
-
|
|
6
|
-
</h2>
|
|
7
|
-
<p class="text-muted mb-4">
|
|
8
|
-
A comprehensive example
|
|
9
|
-
|
|
10
|
-
</p>
|
|
11
|
-
|
|
12
|
-
<!-- Toolbar -->
|
|
13
|
-
<div class="card shadow-sm mb-3">
|
|
14
|
-
<div class="card-body py-2">
|
|
15
|
-
<div class="
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
onMounted(() => {
|
|
241
|
-
const element = tableRef.value
|
|
242
|
-
if (!element) return
|
|
243
|
-
|
|
244
|
-
table
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
table.
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
function clearSearch() {
|
|
385
|
-
searchQuery.value = ''
|
|
386
|
-
handleSearch()
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
function handleCategoryFilter() {
|
|
390
|
-
if (!table) return
|
|
391
|
-
if (categoryFilter.value) {
|
|
392
|
-
table.filterColumnByValue(2, categoryFilter.value)
|
|
393
|
-
} else {
|
|
394
|
-
table.clearFilters()
|
|
395
|
-
}
|
|
396
|
-
updateStats()
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function resetTable() {
|
|
400
|
-
if (!table) return
|
|
401
|
-
table.setData(sampleProducts)
|
|
402
|
-
table.clearSelection()
|
|
403
|
-
table.clearFilters()
|
|
404
|
-
searchQuery.value = ''
|
|
405
|
-
categoryFilter.value = ''
|
|
406
|
-
productCounter = 10
|
|
407
|
-
updateStats()
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Watch for filter changes to clear search when category changes
|
|
411
|
-
watch(categoryFilter, () => {
|
|
412
|
-
if (categoryFilter.value && searchQuery.value) {
|
|
413
|
-
searchQuery.value = ''
|
|
414
|
-
}
|
|
415
|
-
})
|
|
416
|
-
|
|
417
|
-
const codeExample = `import { ref, onMounted, onUnmounted } from 'vue'
|
|
418
|
-
import { useTable } from '@mikestools/usetable'
|
|
419
|
-
|
|
420
|
-
const tableRef = ref<HTMLTableElement>()
|
|
421
|
-
let table: ReturnType<typeof useTable>
|
|
422
|
-
|
|
423
|
-
onMounted(() => {
|
|
424
|
-
table = useTable(ref(tableRef.value!))
|
|
425
|
-
|
|
426
|
-
// Set up structure
|
|
427
|
-
table.setHeaders(['SKU', 'Product', 'Category', 'Price', 'Stock', 'Status'])
|
|
428
|
-
table.setData([
|
|
429
|
-
['PRD-001', 'MacBook Pro', 'Electronics', 1999.99, 25, 'Active'],
|
|
430
|
-
['PRD-002', 'Magic Keyboard', 'Accessories', 99.99, 150, 'Active'],
|
|
431
|
-
])
|
|
432
|
-
|
|
433
|
-
// Enable inline editing (double-click to edit)
|
|
434
|
-
const cleanupEdit = table.enableEditing()
|
|
435
|
-
|
|
436
|
-
// Enable keyboard navigation (arrow keys)
|
|
437
|
-
const cleanupNav = table.enableKeyboardNavigation()
|
|
438
|
-
|
|
439
|
-
// Set row key for identification
|
|
440
|
-
table.setRowKeyFunction((row) => String(row[0]))
|
|
441
|
-
|
|
442
|
-
// React to changes
|
|
443
|
-
table.onDataChange((data) => {
|
|
444
|
-
console.log('Data changed:', data)
|
|
445
|
-
// Save to database, localStorage, etc.
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
// Aggregations
|
|
449
|
-
const totalStock = table.sum(4) // Sum of Stock column
|
|
450
|
-
const avgPrice = table.average(3) // Average of Price column
|
|
451
|
-
const lowStock = table.count(5, (v) => v === 'Low Stock')
|
|
452
|
-
|
|
453
|
-
// Sorting
|
|
454
|
-
table.sortColumnAscending(1) // Sort by Product name
|
|
455
|
-
|
|
456
|
-
// Filtering
|
|
457
|
-
table.filterColumnByValue(2, 'Electronics') // Show only Electronics
|
|
458
|
-
|
|
459
|
-
// Selection
|
|
460
|
-
table.selectRow(0)
|
|
461
|
-
table.selectRowRange(0, 2) // Select rows 0-2
|
|
462
|
-
|
|
463
|
-
// Undo/Redo
|
|
464
|
-
table.setCell(0, 4, 30) // Change stock
|
|
465
|
-
table.undo() // Revert change
|
|
466
|
-
|
|
467
|
-
// Transactions (atomic operations)
|
|
468
|
-
table.transaction(() => {
|
|
469
|
-
table.addRow(['PRD-003', 'New', 'Audio', 199.99, 50, 'Active'])
|
|
470
|
-
table.setCell(0, 4, 20)
|
|
471
|
-
// If any fails, all changes roll back
|
|
472
|
-
})
|
|
473
|
-
})`
|
|
474
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<section id="basicTable">
|
|
3
|
+
<h2 class="h4 mb-3">
|
|
4
|
+
<IconTable class="me-2" />
|
|
5
|
+
Basic Table Operations
|
|
6
|
+
</h2>
|
|
7
|
+
<p class="text-muted mb-4">
|
|
8
|
+
A comprehensive product inventory example showing core table operations:
|
|
9
|
+
headers, rows, cells, and reactive data binding.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<!-- Toolbar -->
|
|
13
|
+
<div class="card shadow-sm mb-3">
|
|
14
|
+
<div class="card-body py-2">
|
|
15
|
+
<div class="d-flex gap-2 flex-wrap align-items-center">
|
|
16
|
+
<button
|
|
17
|
+
class="btn btn-success btn-sm"
|
|
18
|
+
@click="addProduct"
|
|
19
|
+
>
|
|
20
|
+
<IconPlus class="me-1" />
|
|
21
|
+
Add Product
|
|
22
|
+
</button>
|
|
23
|
+
<button
|
|
24
|
+
class="btn btn-outline-warning btn-sm"
|
|
25
|
+
:disabled="rowCount === 0"
|
|
26
|
+
@click="updateRandomPrice"
|
|
27
|
+
>
|
|
28
|
+
<IconPencil class="me-1" />
|
|
29
|
+
Update Random Price
|
|
30
|
+
</button>
|
|
31
|
+
<button
|
|
32
|
+
class="btn btn-outline-danger btn-sm"
|
|
33
|
+
:disabled="rowCount === 0"
|
|
34
|
+
@click="removeLastRow"
|
|
35
|
+
>
|
|
36
|
+
<IconTrash class="me-1" />
|
|
37
|
+
Remove Last
|
|
38
|
+
</button>
|
|
39
|
+
<button
|
|
40
|
+
class="btn btn-outline-secondary btn-sm"
|
|
41
|
+
@click="resetTable"
|
|
42
|
+
>
|
|
43
|
+
<IconArrowRepeat class="me-1" />
|
|
44
|
+
Reset
|
|
45
|
+
</button>
|
|
46
|
+
<div class="vr mx-2" />
|
|
47
|
+
<div class="form-check form-switch mb-0">
|
|
48
|
+
<input
|
|
49
|
+
id="showFooter"
|
|
50
|
+
v-model="showFooter"
|
|
51
|
+
class="form-check-input"
|
|
52
|
+
type="checkbox"
|
|
53
|
+
@change="toggleFooter"
|
|
54
|
+
>
|
|
55
|
+
<label
|
|
56
|
+
class="form-check-label small"
|
|
57
|
+
for="showFooter"
|
|
58
|
+
>
|
|
59
|
+
Show Totals
|
|
60
|
+
</label>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<!-- Table -->
|
|
67
|
+
<div class="card shadow-sm mb-3">
|
|
68
|
+
<div class="table-responsive">
|
|
69
|
+
<table
|
|
70
|
+
ref="tableRef"
|
|
71
|
+
class="table table-hover table-striped mb-0 align-middle"
|
|
72
|
+
>
|
|
73
|
+
<!-- Table structure built via useTable -->
|
|
74
|
+
</table>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<!-- Stats Panel -->
|
|
79
|
+
<div class="row g-3 mb-4">
|
|
80
|
+
<div class="col-md-3">
|
|
81
|
+
<div class="card bg-primary text-white">
|
|
82
|
+
<div class="card-body py-2 text-center">
|
|
83
|
+
<div class="small opacity-75">
|
|
84
|
+
Total Products
|
|
85
|
+
</div>
|
|
86
|
+
<div class="h4 mb-0">
|
|
87
|
+
{{ rowCount }}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="col-md-3">
|
|
93
|
+
<div class="card bg-success text-white">
|
|
94
|
+
<div class="card-body py-2 text-center">
|
|
95
|
+
<div class="small opacity-75">
|
|
96
|
+
Total Value
|
|
97
|
+
</div>
|
|
98
|
+
<div class="h4 mb-0">
|
|
99
|
+
${{ totalValue.toLocaleString('en-US', { minimumFractionDigits: 2 }) }}
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="col-md-3">
|
|
105
|
+
<div class="card bg-info text-white">
|
|
106
|
+
<div class="card-body py-2 text-center">
|
|
107
|
+
<div class="small opacity-75">
|
|
108
|
+
Total Stock
|
|
109
|
+
</div>
|
|
110
|
+
<div class="h4 mb-0">
|
|
111
|
+
{{ totalStock }} units
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="col-md-3">
|
|
117
|
+
<div class="card bg-warning text-dark">
|
|
118
|
+
<div class="card-body py-2 text-center">
|
|
119
|
+
<div class="small opacity-75">
|
|
120
|
+
Columns
|
|
121
|
+
</div>
|
|
122
|
+
<div class="h4 mb-0">
|
|
123
|
+
{{ columnCount }}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<!-- Instructions -->
|
|
131
|
+
<div class="alert alert-info mb-4">
|
|
132
|
+
<h6 class="alert-heading">
|
|
133
|
+
<IconInfoCircle class="me-1" />
|
|
134
|
+
What this demonstrates
|
|
135
|
+
</h6>
|
|
136
|
+
<ul class="mb-0 small">
|
|
137
|
+
<li><strong>Reactive Data:</strong> Stats automatically update when data changes</li>
|
|
138
|
+
<li><strong>Row Operations:</strong> Add, update, and remove rows dynamically</li>
|
|
139
|
+
<li><strong>Cell Operations:</strong> Update individual cell values</li>
|
|
140
|
+
<li><strong>Footer:</strong> Optional summary row with totals</li>
|
|
141
|
+
<li><strong>Computed Values:</strong> Calculate totals from table data</li>
|
|
142
|
+
</ul>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<!-- Code Example -->
|
|
146
|
+
<CodeBlock :code="codeExample" />
|
|
147
|
+
</section>
|
|
148
|
+
</template>
|
|
149
|
+
|
|
150
|
+
<script setup lang="ts">
|
|
151
|
+
import { ref, onMounted, watch } from 'vue'
|
|
152
|
+
import { useTable, type UseTableReturn } from '@mikestools/usetable'
|
|
153
|
+
|
|
154
|
+
import CodeBlock from '@mikestools/usetools/components/CodeBlock.vue'
|
|
155
|
+
import IconTable from 'bootstrap-icons/icons/table.svg?component'
|
|
156
|
+
import IconPlus from 'bootstrap-icons/icons/plus-lg.svg?component'
|
|
157
|
+
import IconPencil from 'bootstrap-icons/icons/pencil.svg?component'
|
|
158
|
+
import IconTrash from 'bootstrap-icons/icons/trash.svg?component'
|
|
159
|
+
import IconArrowRepeat from 'bootstrap-icons/icons/arrow-repeat.svg?component'
|
|
160
|
+
import IconInfoCircle from 'bootstrap-icons/icons/info-circle.svg?component'
|
|
161
|
+
|
|
162
|
+
const tableRef = ref<HTMLTableElement>()
|
|
163
|
+
const showFooter = ref(true)
|
|
164
|
+
|
|
165
|
+
// Tracked state - updated via onDataChange callback
|
|
166
|
+
const rowCount = ref(0)
|
|
167
|
+
const columnCount = ref(0)
|
|
168
|
+
const totalValue = ref(0)
|
|
169
|
+
const totalStock = ref(0)
|
|
170
|
+
|
|
171
|
+
// Table instance stored for method access
|
|
172
|
+
let table: UseTableReturn | undefined
|
|
173
|
+
|
|
174
|
+
// Sample product data
|
|
175
|
+
const sampleProducts: (string | number)[][] = [
|
|
176
|
+
['PRD-001', 'MacBook Pro 14"', 'Electronics', 1999.99, 25],
|
|
177
|
+
['PRD-002', 'Magic Keyboard', 'Accessories', 99.99, 150],
|
|
178
|
+
['PRD-003', 'AirPods Pro 2', 'Audio', 249.99, 75],
|
|
179
|
+
['PRD-004', 'USB-C Hub 7-in-1', 'Accessories', 79.99, 200],
|
|
180
|
+
['PRD-005', 'Studio Display 27"', 'Displays', 1599.99, 12],
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
const categories = ['Electronics', 'Accessories', 'Audio', 'Displays', 'Storage', 'Peripherals']
|
|
184
|
+
const productNames = ['Wireless Mouse', 'Webcam HD', 'External SSD', 'Monitor Stand', 'Desk Lamp', 'Cable Kit']
|
|
185
|
+
|
|
186
|
+
let productCounter = 5
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Update all tracked stats from the table
|
|
190
|
+
*/
|
|
191
|
+
function updateStats() {
|
|
192
|
+
if (!table) return
|
|
193
|
+
|
|
194
|
+
rowCount.value = table.rowCount.value
|
|
195
|
+
columnCount.value = table.columnCount.value
|
|
196
|
+
|
|
197
|
+
// Calculate totals from table data
|
|
198
|
+
let valueSum = 0
|
|
199
|
+
let stockSum = 0
|
|
200
|
+
|
|
201
|
+
for (const row of table.data.value) {
|
|
202
|
+
const price = Number(row[3]) || 0
|
|
203
|
+
const stock = Number(row[4]) || 0
|
|
204
|
+
valueSum += price * stock
|
|
205
|
+
stockSum += stock
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
totalValue.value = valueSum
|
|
209
|
+
totalStock.value = stockSum
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Update footer with current totals
|
|
214
|
+
*/
|
|
215
|
+
function updateFooter() {
|
|
216
|
+
if (!table) return
|
|
217
|
+
|
|
218
|
+
table.setFooter([
|
|
219
|
+
'',
|
|
220
|
+
`${rowCount.value} Products`,
|
|
221
|
+
'',
|
|
222
|
+
`$${totalValue.value.toLocaleString('en-US', { minimumFractionDigits: 2 })}`,
|
|
223
|
+
`${totalStock.value} units`
|
|
224
|
+
])
|
|
225
|
+
|
|
226
|
+
// Style the footer
|
|
227
|
+
const element = tableRef.value
|
|
228
|
+
if (element) {
|
|
229
|
+
const tfoot = element.tFoot
|
|
230
|
+
if (tfoot) {
|
|
231
|
+
tfoot.classList.add('table-dark')
|
|
232
|
+
const row = tfoot.rows[0]
|
|
233
|
+
if (row) {
|
|
234
|
+
row.classList.add('fw-bold')
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
onMounted(() => {
|
|
241
|
+
const element = tableRef.value
|
|
242
|
+
if (!element) return
|
|
243
|
+
|
|
244
|
+
// Create table instance
|
|
245
|
+
table = useTable(ref(element), sampleProducts, {
|
|
246
|
+
headers: ['SKU', 'Product Name', 'Category', 'Price ($)', 'Stock'],
|
|
247
|
+
caption: 'Product Inventory',
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
// Initial stats calculation
|
|
251
|
+
updateStats()
|
|
252
|
+
|
|
253
|
+
// Set initial footer if enabled
|
|
254
|
+
if (showFooter.value) {
|
|
255
|
+
updateFooter()
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Subscribe to data changes to keep stats in sync
|
|
259
|
+
table.onDataChange(() => {
|
|
260
|
+
updateStats()
|
|
261
|
+
if (showFooter.value) {
|
|
262
|
+
updateFooter()
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
// Watch for footer toggle
|
|
268
|
+
watch(showFooter, (show) => {
|
|
269
|
+
if (show) {
|
|
270
|
+
updateFooter()
|
|
271
|
+
} else {
|
|
272
|
+
table?.clearFooter()
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
function addProduct() {
|
|
277
|
+
if (!table) return
|
|
278
|
+
productCounter++
|
|
279
|
+
|
|
280
|
+
const category = categories[Math.floor(Math.random() * categories.length)]
|
|
281
|
+
const name = productNames[Math.floor(Math.random() * productNames.length)]
|
|
282
|
+
const price = (Math.random() * 300 + 29.99).toFixed(2)
|
|
283
|
+
const stock = Math.floor(Math.random() * 100 + 5)
|
|
284
|
+
|
|
285
|
+
table.addRow([
|
|
286
|
+
`PRD-${String(productCounter).padStart(3, '0')}`,
|
|
287
|
+
name,
|
|
288
|
+
category,
|
|
289
|
+
price,
|
|
290
|
+
stock
|
|
291
|
+
])
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function updateRandomPrice() {
|
|
295
|
+
if (!table || rowCount.value === 0) return
|
|
296
|
+
|
|
297
|
+
const randomRow = Math.floor(Math.random() * rowCount.value)
|
|
298
|
+
const currentPrice = Number(table.getCell(randomRow, 3)) || 0
|
|
299
|
+
const change = (Math.random() - 0.5) * 100 // -50 to +50
|
|
300
|
+
const newPrice = Math.max(9.99, currentPrice + change).toFixed(2)
|
|
301
|
+
|
|
302
|
+
table.setCell(randomRow, 3, newPrice)
|
|
303
|
+
|
|
304
|
+
// Flash the row to show the change
|
|
305
|
+
const element = tableRef.value
|
|
306
|
+
if (element) {
|
|
307
|
+
const tbody = element.tBodies[0]
|
|
308
|
+
if (tbody) {
|
|
309
|
+
const row = tbody.rows[randomRow]
|
|
310
|
+
if (row) {
|
|
311
|
+
row.classList.add('table-warning')
|
|
312
|
+
setTimeout(() => row.classList.remove('table-warning'), 500)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function removeLastRow() {
|
|
319
|
+
if (!table || rowCount.value === 0) return
|
|
320
|
+
table.removeRow(rowCount.value - 1)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function resetTable() {
|
|
324
|
+
if (!table) return
|
|
325
|
+
table.setData(sampleProducts)
|
|
326
|
+
productCounter = 5
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function toggleFooter() {
|
|
330
|
+
// Handled by watcher
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const codeExample = `import { ref, onMounted } from 'vue'
|
|
334
|
+
import { useTable, type CellValue } from '@mikestools/usetable'
|
|
335
|
+
|
|
336
|
+
const tableRef = ref<HTMLTableElement>()
|
|
337
|
+
|
|
338
|
+
// Tracked stats - updated via callback
|
|
339
|
+
const rowCount = ref(0)
|
|
340
|
+
const totalValue = ref(0)
|
|
341
|
+
|
|
342
|
+
// Table instance
|
|
343
|
+
let table
|
|
344
|
+
|
|
345
|
+
onMounted(() => {
|
|
346
|
+
// Initialize table with data and options
|
|
347
|
+
table = useTable(ref(tableRef.value!), [
|
|
348
|
+
['PRD-001', 'MacBook Pro', 'Electronics', 1999.99, 25],
|
|
349
|
+
['PRD-002', 'Magic Keyboard', 'Accessories', 99.99, 150],
|
|
350
|
+
], {
|
|
351
|
+
headers: ['SKU', 'Product', 'Category', 'Price', 'Stock'],
|
|
352
|
+
caption: 'Product Inventory'
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
// Calculate stats
|
|
356
|
+
function updateStats() {
|
|
357
|
+
rowCount.value = table.rowCount.value
|
|
358
|
+
totalValue.value = table.data.value.reduce((sum, row) => {
|
|
359
|
+
return sum + (Number(row[3]) * Number(row[4]))
|
|
360
|
+
}, 0)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Initial calculation
|
|
364
|
+
updateStats()
|
|
365
|
+
|
|
366
|
+
// Subscribe to data changes
|
|
367
|
+
table.onDataChange(() => updateStats())
|
|
368
|
+
|
|
369
|
+
// Add a new row
|
|
370
|
+
table.addRow(['PRD-003', 'AirPods', 'Audio', 249.99, 75])
|
|
371
|
+
|
|
372
|
+
// Update a cell value
|
|
373
|
+
table.setCell(0, 3, 1899.99)
|
|
374
|
+
|
|
375
|
+
// Remove a row
|
|
376
|
+
table.removeRow(1)
|
|
377
|
+
|
|
378
|
+
// Set footer with totals
|
|
379
|
+
table.setFooter(['', 'Total', '', '', totalStock.value])
|
|
380
|
+
})`
|
|
381
|
+
</script>
|
|
382
|
+
|