picocrank 1.32.0 → 1.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/filter-query-v1.md +5 -1
- package/docs/table.md +99 -0
- package/package.json +1 -1
- package/router.js +33 -0
- package/styles/picocrank-extensions.css +23 -2
- package/vue/components/Accordion.spec.js +48 -0
- package/vue/components/Accordion.vue +46 -0
- package/vue/components/AccordionItem.vue +103 -0
- package/vue/components/FieldValue.spec.js +88 -0
- package/vue/components/FieldValue.vue +222 -0
- package/vue/components/FormField.spec.js +1 -0
- package/vue/components/FormField.vue +38 -10
- package/vue/components/Header.vue +24 -2
- package/vue/components/NotificationBlock.vue +25 -0
- package/vue/components/ReorderList.spec.js +318 -0
- package/vue/components/ReorderList.vue +614 -0
- package/vue/components/Table.spec.js +490 -1
- package/vue/components/Table.vue +1038 -111
- package/vue/components/TableColumnFilterPopover.vue +63 -19
- package/vue/components/TableColumnOptionsPopover.vue +248 -273
- package/vue/components/TopBar.vue +6 -2
- package/vue/components/accordionContext.js +1 -0
- package/vue/composables/tableColumnPresets.js +33 -0
- package/vue/composables/tableFilters.js +6 -1
- package/vue/composables/tableGroupBy.js +173 -0
- package/vue/composables/tableGroupBy.spec.js +69 -0
- package/vue/composables/tableViewSettings.js +34 -0
- package/vue/composables/tableViewSettings.spec.js +21 -0
- package/vue/composables/useHeaderTopBarFit.js +91 -0
- package/vue/composables/useResponsiveNav.js +5 -3
- package/vue/data/navSectionHubs.js +16 -1
- package/vue/examples/AccordionExample.vue +33 -0
- package/vue/examples/FieldValueExample.vue +143 -0
- package/vue/examples/FormExample.vue +11 -6
- package/vue/examples/ReorderListExample.vue +236 -0
- package/vue/examples/TableExample.vue +5 -1
- package/vue/examples/TableRemoteExample.vue +28 -4
package/vue/components/Table.vue
CHANGED
|
@@ -41,11 +41,38 @@
|
|
|
41
41
|
|
|
42
42
|
<span v-if="filterable && isHeaderFiltered(header)" class="filter-indicator" title="Filtered" aria-hidden="true">⧩</span>
|
|
43
43
|
|
|
44
|
+
<span
|
|
45
|
+
v-if="isHeaderGrouped(header)"
|
|
46
|
+
class="group-indicator"
|
|
47
|
+
title="Grouped"
|
|
48
|
+
aria-hidden="true"
|
|
49
|
+
>
|
|
50
|
+
<HugeiconsIcon
|
|
51
|
+
:icon="Group01Icon"
|
|
52
|
+
width="0.95em"
|
|
53
|
+
height="0.95em"
|
|
54
|
+
:strokeWidth="2"
|
|
55
|
+
aria-hidden="true"
|
|
56
|
+
/>
|
|
57
|
+
</span>
|
|
58
|
+
|
|
44
59
|
<span v-if="header.sortable" class="sort-indicator">
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
<HugeiconsIcon
|
|
61
|
+
v-if="sortBy === header.key && sortDir === 'asc'"
|
|
62
|
+
:icon="SortByUp02Icon"
|
|
63
|
+
width="0.95em"
|
|
64
|
+
height="0.95em"
|
|
65
|
+
:strokeWidth="2"
|
|
66
|
+
aria-hidden="true"
|
|
67
|
+
/>
|
|
68
|
+
<HugeiconsIcon
|
|
69
|
+
v-else-if="sortBy === header.key && sortDir === 'desc'"
|
|
70
|
+
:icon="SortByDown02Icon"
|
|
71
|
+
width="0.95em"
|
|
72
|
+
height="0.95em"
|
|
73
|
+
:strokeWidth="2"
|
|
74
|
+
aria-hidden="true"
|
|
75
|
+
/>
|
|
49
76
|
</span>
|
|
50
77
|
</th>
|
|
51
78
|
<th
|
|
@@ -74,59 +101,112 @@
|
|
|
74
101
|
</tr>
|
|
75
102
|
</thead>
|
|
76
103
|
<tbody v-if="hasRows">
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
'row-
|
|
82
|
-
'row-selected': selectable && isRowSelected(row, rowIndex),
|
|
83
|
-
'row-context-menu-active': isRowContextMenuTarget(row, rowIndex),
|
|
84
|
-
}"
|
|
85
|
-
@click="onRowClick(row, rowIndex, $event)"
|
|
86
|
-
@contextmenu="onRowContextMenu(row, rowIndex, $event)"
|
|
87
|
-
>
|
|
88
|
-
<td
|
|
89
|
-
v-if="selectable"
|
|
90
|
-
class="table-select-col"
|
|
91
|
-
data-row-click-ignore
|
|
92
|
-
@click.stop
|
|
104
|
+
<template v-for="(item, itemIndex) in displayBodyItems" :key="bodyItemKey(item, itemIndex)">
|
|
105
|
+
<tr
|
|
106
|
+
v-if="item.kind === 'group'"
|
|
107
|
+
class="table-group-row"
|
|
108
|
+
:class="{ 'table-group-row-collapsed': item.collapsed }"
|
|
93
109
|
>
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
<td
|
|
111
|
+
v-if="selectable"
|
|
112
|
+
class="table-select-col table-group-cell"
|
|
113
|
+
data-row-click-ignore
|
|
114
|
+
/>
|
|
115
|
+
<td
|
|
116
|
+
:colspan="bodyGroupColspan"
|
|
117
|
+
class="table-group-cell"
|
|
118
|
+
>
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
class="table-group-toggle neutral"
|
|
122
|
+
:aria-expanded="item.collapsed ? 'false' : 'true'"
|
|
123
|
+
:aria-label="item.collapsed ? 'Expand group' : 'Collapse group'"
|
|
124
|
+
data-row-click-ignore
|
|
125
|
+
@click.stop="toggleGroupCollapsed(item.groupId)"
|
|
100
126
|
>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
127
|
+
<HugeiconsIcon
|
|
128
|
+
class="table-group-chevron"
|
|
129
|
+
:icon="ArrowDown01Icon"
|
|
130
|
+
width="1em"
|
|
131
|
+
height="1em"
|
|
132
|
+
aria-hidden="true"
|
|
133
|
+
/>
|
|
134
|
+
</button>
|
|
135
|
+
<span class="table-group-label">{{ formatGroupValue(item.value) }}</span>
|
|
136
|
+
<span class="table-group-count subtle">({{ item.count }})</span>
|
|
137
|
+
</td>
|
|
138
|
+
</tr>
|
|
139
|
+
<tr
|
|
140
|
+
v-else
|
|
107
141
|
:class="[
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
142
|
+
{
|
|
143
|
+
'row-clickable': rowClickable,
|
|
144
|
+
'row-selected': selectable && isRowSelected(item.row, item.displayIndex),
|
|
145
|
+
'row-context-menu-active': isRowContextMenuTarget(item.row, item.displayIndex),
|
|
146
|
+
},
|
|
147
|
+
resolveRowClass(item.row, item.displayIndex),
|
|
111
148
|
]"
|
|
149
|
+
:style="resolveRowStyle(item.row, item.displayIndex)"
|
|
150
|
+
@click="onRowClick(item.row, item.displayIndex, $event)"
|
|
151
|
+
@contextmenu="onRowContextMenu(item.row, item.displayIndex, $event)"
|
|
112
152
|
>
|
|
113
|
-
<
|
|
114
|
-
v-if="
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
153
|
+
<td
|
|
154
|
+
v-if="selectable"
|
|
155
|
+
class="table-select-col"
|
|
156
|
+
data-row-click-ignore
|
|
157
|
+
@click.stop
|
|
158
|
+
>
|
|
159
|
+
<label class="table-select-label">
|
|
160
|
+
<input
|
|
161
|
+
type="checkbox"
|
|
162
|
+
:checked="isRowSelected(item.row, item.displayIndex)"
|
|
163
|
+
:aria-label="`Select row ${resolveRowKey(item.row, item.displayIndex)}`"
|
|
164
|
+
@click.stop="rememberSelectionCheckboxClick(item.row, item.displayIndex, $event)"
|
|
165
|
+
@change="onSelectionCheckboxChange(item.row, item.displayIndex)"
|
|
166
|
+
>
|
|
167
|
+
</label>
|
|
168
|
+
</td>
|
|
169
|
+
<td
|
|
170
|
+
v-for="(header, cellIndex) in visibleHeaders"
|
|
171
|
+
:key="header.key || cellIndex"
|
|
172
|
+
:ref="(element) => registerCellElement(element, item.row, header, item.displayIndex)"
|
|
173
|
+
v-bind="resolveCellAttrs(item.row, header, item.displayIndex)"
|
|
174
|
+
:colspan="bodyColumnColspan(cellIndex) || undefined"
|
|
175
|
+
:class="[
|
|
176
|
+
cellClasses(header),
|
|
177
|
+
header.class,
|
|
178
|
+
resolveCellClass(item.row, header, item.displayIndex),
|
|
179
|
+
{ 'table-active-cell': isActiveCell(item.row, header, item.displayIndex) },
|
|
180
|
+
isLastColumnWithOptions(cellIndex) ? 'table-last-column-with-options' : null,
|
|
181
|
+
]"
|
|
182
|
+
:style="resolveCellStyle(item.row, header, item.displayIndex)"
|
|
183
|
+
:tabindex="isActiveCell(item.row, header, item.displayIndex) ? 0 : -1"
|
|
184
|
+
@click="onCellClick(item.row, header, item.displayIndex, $event)"
|
|
185
|
+
>
|
|
186
|
+
<slot
|
|
187
|
+
v-if="slots[`cell-${header.key}`]"
|
|
188
|
+
:name="`cell-${header.key}`"
|
|
189
|
+
:row="item.row"
|
|
190
|
+
:value="item.row[header.key]"
|
|
191
|
+
:header="header"
|
|
192
|
+
:row-index="item.displayIndex"
|
|
193
|
+
:source-index="sourceRowIndex(item.row, item.displayIndex)"
|
|
194
|
+
/>
|
|
195
|
+
<slot
|
|
196
|
+
v-else-if="slots.cell"
|
|
197
|
+
name="cell"
|
|
198
|
+
:row="item.row"
|
|
199
|
+
:value="item.row[header.key]"
|
|
200
|
+
:header="header"
|
|
201
|
+
:row-index="item.displayIndex"
|
|
202
|
+
:source-index="sourceRowIndex(item.row, item.displayIndex)"
|
|
203
|
+
/>
|
|
204
|
+
<span v-else>
|
|
205
|
+
{{ item.row[header.key] }}
|
|
206
|
+
</span>
|
|
207
|
+
</td>
|
|
208
|
+
</tr>
|
|
209
|
+
</template>
|
|
130
210
|
</tbody>
|
|
131
211
|
</table>
|
|
132
212
|
|
|
@@ -156,10 +236,13 @@
|
|
|
156
236
|
:rows="props.data"
|
|
157
237
|
:select-options="activeSelectOptions"
|
|
158
238
|
:can-hide="canHideActiveFilterColumn"
|
|
239
|
+
:can-group-by="canGroupByActiveFilterColumn"
|
|
240
|
+
:is-grouped-column="isActiveFilterHeaderGrouped"
|
|
159
241
|
@apply="onFilterApply"
|
|
160
242
|
@clear="onFilterClear"
|
|
161
243
|
@cancel="onFilterCancel"
|
|
162
244
|
@hide="onFilterHideColumn"
|
|
245
|
+
@group-by="onFilterGroupBy"
|
|
163
246
|
/>
|
|
164
247
|
|
|
165
248
|
<TableColumnOptionsPopover
|
|
@@ -177,10 +260,13 @@
|
|
|
177
260
|
:presets="savedPresets"
|
|
178
261
|
:default-preset-id="defaultPresetId"
|
|
179
262
|
:save-layout-preset="onSavePreset"
|
|
263
|
+
:overwrite-layout-preset="onOverwritePreset"
|
|
180
264
|
:load-layout-preset="onLoadPreset"
|
|
181
265
|
:delete-layout-preset="onDeletePreset"
|
|
182
266
|
:set-default-layout-preset="onSetDefaultPreset"
|
|
183
267
|
:load-developer-defaults="loadDeveloperDefaults"
|
|
268
|
+
:group-by="activeGroupBy"
|
|
269
|
+
:groupable="props.groupable"
|
|
184
270
|
:anchor-el="columnOptionsButtonRef"
|
|
185
271
|
@apply="onColumnOptionsApply"
|
|
186
272
|
@cancel="onColumnOptionsCancel"
|
|
@@ -197,9 +283,15 @@
|
|
|
197
283
|
</template>
|
|
198
284
|
|
|
199
285
|
<script setup>
|
|
200
|
-
import { ref, computed, watch, onMounted, onBeforeUnmount, useSlots, useAttrs } from 'vue'
|
|
286
|
+
import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount, useSlots, useAttrs } from 'vue'
|
|
201
287
|
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
202
|
-
import {
|
|
288
|
+
import {
|
|
289
|
+
ArrowDown01Icon,
|
|
290
|
+
Group01Icon,
|
|
291
|
+
LayoutGridIcon,
|
|
292
|
+
SortByDown02Icon,
|
|
293
|
+
SortByUp02Icon,
|
|
294
|
+
} from '@hugeicons/core-free-icons'
|
|
203
295
|
import Pagination from './Pagination.vue'
|
|
204
296
|
import TableColumnFilterPopover from './TableColumnFilterPopover.vue'
|
|
205
297
|
import TableColumnOptionsPopover from './TableColumnOptionsPopover.vue'
|
|
@@ -224,23 +316,29 @@ import {
|
|
|
224
316
|
getPresetById,
|
|
225
317
|
listPresets,
|
|
226
318
|
savePreset,
|
|
319
|
+
overwritePreset,
|
|
227
320
|
deletePreset,
|
|
228
321
|
setDefaultPreset,
|
|
229
322
|
sanitizePresetState,
|
|
230
323
|
layoutStatesEqual,
|
|
231
324
|
} from '../composables/tableColumnPresets.js'
|
|
325
|
+
import {
|
|
326
|
+
getViewSettingsHandlers,
|
|
327
|
+
isViewSettingsControlEnabled,
|
|
328
|
+
VIEW_CONTROL_CALLBACK,
|
|
329
|
+
} from '../composables/tableViewSettings.js'
|
|
232
330
|
import {
|
|
233
331
|
defaultColumnPriorityForIndex,
|
|
234
332
|
isValidColPriority,
|
|
235
333
|
} from '../composables/tableColumnPriorities.js'
|
|
334
|
+
import {
|
|
335
|
+
buildDisplayBodyItems,
|
|
336
|
+
formatGroupValue,
|
|
337
|
+
isHeaderGroupable,
|
|
338
|
+
} from '../composables/tableGroupBy.js'
|
|
236
339
|
|
|
237
340
|
const DEFAULT_PAGE_SIZE = 10
|
|
238
341
|
|
|
239
|
-
const sortBy = ref(null)
|
|
240
|
-
const sortDir = ref('asc')
|
|
241
|
-
const page = ref(1)
|
|
242
|
-
const pageSize = ref(DEFAULT_PAGE_SIZE)
|
|
243
|
-
|
|
244
342
|
const props = defineProps({
|
|
245
343
|
headers: {
|
|
246
344
|
type: Array,
|
|
@@ -250,6 +348,29 @@ const props = defineProps({
|
|
|
250
348
|
type: Array,
|
|
251
349
|
default: () => [],
|
|
252
350
|
},
|
|
351
|
+
sortBy: {
|
|
352
|
+
type: [String, Number],
|
|
353
|
+
default: undefined,
|
|
354
|
+
},
|
|
355
|
+
sortDir: {
|
|
356
|
+
type: String,
|
|
357
|
+
default: undefined,
|
|
358
|
+
validator: (value) => value === undefined || value === 'asc' || value === 'desc',
|
|
359
|
+
},
|
|
360
|
+
page: {
|
|
361
|
+
type: Number,
|
|
362
|
+
default: undefined,
|
|
363
|
+
validator: (value) => value === undefined || (Number.isFinite(value) && value >= 1),
|
|
364
|
+
},
|
|
365
|
+
pageSize: {
|
|
366
|
+
type: Number,
|
|
367
|
+
default: undefined,
|
|
368
|
+
validator: (value) => value === undefined || (Number.isFinite(value) && value >= 1),
|
|
369
|
+
},
|
|
370
|
+
resetPageOnSort: {
|
|
371
|
+
type: Boolean,
|
|
372
|
+
default: true,
|
|
373
|
+
},
|
|
253
374
|
showPagination: {
|
|
254
375
|
type: Boolean,
|
|
255
376
|
default: true,
|
|
@@ -307,6 +428,21 @@ const props = defineProps({
|
|
|
307
428
|
type: String,
|
|
308
429
|
default: '',
|
|
309
430
|
},
|
|
431
|
+
/**
|
|
432
|
+
* How saved table views (column order, visibility, filters in presets, etc.) are stored.
|
|
433
|
+
* - `localStorageControlled`: requires `tableId`; uses browser localStorage (default).
|
|
434
|
+
* - `callbackControlled`: requires `viewSettings` callbacks (server or app-owned persistence).
|
|
435
|
+
*/
|
|
436
|
+
viewControlMode: {
|
|
437
|
+
type: String,
|
|
438
|
+
default: 'localStorageControlled',
|
|
439
|
+
validator: (value) => ['localStorageControlled', 'callbackControlled'].includes(value),
|
|
440
|
+
},
|
|
441
|
+
/** Callback handlers when `viewControlMode` is `callbackControlled`. */
|
|
442
|
+
viewSettings: {
|
|
443
|
+
type: Object,
|
|
444
|
+
default: null,
|
|
445
|
+
},
|
|
310
446
|
loadSavedLayout: {
|
|
311
447
|
type: Boolean,
|
|
312
448
|
default: true,
|
|
@@ -319,6 +455,7 @@ const props = defineProps({
|
|
|
319
455
|
type: [String, Function],
|
|
320
456
|
default: 'id',
|
|
321
457
|
},
|
|
458
|
+
/** @deprecated Use `viewSettings` with `viewControlMode="callbackControlled"`. */
|
|
322
459
|
layoutPresets: {
|
|
323
460
|
type: Object,
|
|
324
461
|
default: null,
|
|
@@ -331,6 +468,35 @@ const props = defineProps({
|
|
|
331
468
|
type: Array,
|
|
332
469
|
default: undefined,
|
|
333
470
|
},
|
|
471
|
+
selectionClickMode: {
|
|
472
|
+
type: String,
|
|
473
|
+
default: 'row',
|
|
474
|
+
validator: (value) => ['row', 'checkbox', 'modifier', 'extended'].includes(value),
|
|
475
|
+
},
|
|
476
|
+
activeCell: {
|
|
477
|
+
type: Object,
|
|
478
|
+
default: undefined,
|
|
479
|
+
},
|
|
480
|
+
rowClass: {
|
|
481
|
+
type: Function,
|
|
482
|
+
default: null,
|
|
483
|
+
},
|
|
484
|
+
rowStyle: {
|
|
485
|
+
type: Function,
|
|
486
|
+
default: null,
|
|
487
|
+
},
|
|
488
|
+
cellClass: {
|
|
489
|
+
type: Function,
|
|
490
|
+
default: null,
|
|
491
|
+
},
|
|
492
|
+
cellStyle: {
|
|
493
|
+
type: Function,
|
|
494
|
+
default: null,
|
|
495
|
+
},
|
|
496
|
+
cellAttrs: {
|
|
497
|
+
type: Function,
|
|
498
|
+
default: null,
|
|
499
|
+
},
|
|
334
500
|
/**
|
|
335
501
|
* Row right-click menu. Array of items, or a function that returns items
|
|
336
502
|
* for the click context. Each item may include `action(ctx)` where
|
|
@@ -341,6 +507,14 @@ const props = defineProps({
|
|
|
341
507
|
type: [Array, Function],
|
|
342
508
|
default: null,
|
|
343
509
|
},
|
|
510
|
+
groupable: {
|
|
511
|
+
type: Boolean,
|
|
512
|
+
default: true,
|
|
513
|
+
},
|
|
514
|
+
groupBy: {
|
|
515
|
+
type: String,
|
|
516
|
+
default: undefined,
|
|
517
|
+
},
|
|
344
518
|
})
|
|
345
519
|
|
|
346
520
|
const emit = defineEmits([
|
|
@@ -349,8 +523,14 @@ const emit = defineEmits([
|
|
|
349
523
|
'query-change',
|
|
350
524
|
'fetch-error',
|
|
351
525
|
'row-click',
|
|
526
|
+
'cell-click',
|
|
527
|
+
'update:sortBy',
|
|
528
|
+
'update:sortDir',
|
|
529
|
+
'update:page',
|
|
530
|
+
'update:pageSize',
|
|
352
531
|
'update:selectedKeys',
|
|
353
532
|
'selection-change',
|
|
533
|
+
'update:activeCell',
|
|
354
534
|
'row-context-menu',
|
|
355
535
|
'row-context-menu-action',
|
|
356
536
|
'update:columnVisibility',
|
|
@@ -361,14 +541,118 @@ const emit = defineEmits([
|
|
|
361
541
|
'column-priorities-change',
|
|
362
542
|
'update:layoutLabel',
|
|
363
543
|
'layout-label-change',
|
|
544
|
+
'update:groupBy',
|
|
545
|
+
'group-by-change',
|
|
364
546
|
])
|
|
365
547
|
|
|
366
548
|
const attrs = useAttrs()
|
|
367
549
|
const slots = useSlots()
|
|
368
550
|
|
|
369
|
-
|
|
551
|
+
function normalizePage(value) {
|
|
552
|
+
const numeric = Number(value)
|
|
553
|
+
return Number.isFinite(numeric) && numeric >= 1 ? Math.floor(numeric) : 1
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function normalizePageSize(value) {
|
|
557
|
+
const numeric = Number(value)
|
|
558
|
+
return Number.isFinite(numeric) && numeric >= 1 ? Math.floor(numeric) : DEFAULT_PAGE_SIZE
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function normalizeSortDir(value) {
|
|
562
|
+
return value === 'desc' ? 'desc' : 'asc'
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function normalizeActiveCell(value) {
|
|
566
|
+
if (
|
|
567
|
+
!value
|
|
568
|
+
|| typeof value !== 'object'
|
|
569
|
+
|| value.rowKey === null
|
|
570
|
+
|| value.rowKey === undefined
|
|
571
|
+
|| value.columnKey === null
|
|
572
|
+
|| value.columnKey === undefined
|
|
573
|
+
) {
|
|
574
|
+
return null
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
rowKey: value.rowKey,
|
|
578
|
+
columnKey: value.columnKey,
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const internalSortBy = ref(props.sortBy !== undefined ? props.sortBy : null)
|
|
583
|
+
const internalSortDir = ref(normalizeSortDir(props.sortDir))
|
|
584
|
+
const internalPage = ref(normalizePage(props.page))
|
|
585
|
+
const internalPageSize = ref(normalizePageSize(props.pageSize))
|
|
586
|
+
const internalActiveCell = ref(normalizeActiveCell(props.activeCell))
|
|
587
|
+
|
|
588
|
+
const sortBy = computed({
|
|
589
|
+
get: () => props.sortBy !== undefined ? props.sortBy : internalSortBy.value,
|
|
590
|
+
set: (value) => {
|
|
591
|
+
const nextValue = value ?? null
|
|
592
|
+
if (nextValue === sortBy.value) {
|
|
593
|
+
return
|
|
594
|
+
}
|
|
595
|
+
if (props.sortBy === undefined) {
|
|
596
|
+
internalSortBy.value = nextValue
|
|
597
|
+
}
|
|
598
|
+
emit('update:sortBy', nextValue)
|
|
599
|
+
},
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
const sortDir = computed({
|
|
603
|
+
get: () => props.sortDir !== undefined ? normalizeSortDir(props.sortDir) : internalSortDir.value,
|
|
604
|
+
set: (value) => {
|
|
605
|
+
const nextValue = normalizeSortDir(value)
|
|
606
|
+
if (nextValue === sortDir.value) {
|
|
607
|
+
return
|
|
608
|
+
}
|
|
609
|
+
if (props.sortDir === undefined) {
|
|
610
|
+
internalSortDir.value = nextValue
|
|
611
|
+
}
|
|
612
|
+
emit('update:sortDir', nextValue)
|
|
613
|
+
},
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
const page = computed({
|
|
617
|
+
get: () => props.page !== undefined ? normalizePage(props.page) : internalPage.value,
|
|
618
|
+
set: (value) => {
|
|
619
|
+
const nextValue = normalizePage(value)
|
|
620
|
+
if (nextValue === page.value) {
|
|
621
|
+
return
|
|
622
|
+
}
|
|
623
|
+
if (props.page === undefined) {
|
|
624
|
+
internalPage.value = nextValue
|
|
625
|
+
}
|
|
626
|
+
emit('update:page', nextValue)
|
|
627
|
+
},
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
const pageSize = computed({
|
|
631
|
+
get: () => props.pageSize !== undefined ? normalizePageSize(props.pageSize) : internalPageSize.value,
|
|
632
|
+
set: (value) => {
|
|
633
|
+
const nextValue = normalizePageSize(value)
|
|
634
|
+
if (nextValue === pageSize.value) {
|
|
635
|
+
return
|
|
636
|
+
}
|
|
637
|
+
if (props.pageSize === undefined) {
|
|
638
|
+
internalPageSize.value = nextValue
|
|
639
|
+
}
|
|
640
|
+
emit('update:pageSize', nextValue)
|
|
641
|
+
},
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
const activeCell = computed(() => (
|
|
645
|
+
props.activeCell !== undefined ? normalizeActiveCell(props.activeCell) : internalActiveCell.value
|
|
646
|
+
))
|
|
647
|
+
|
|
648
|
+
const rowClickable = computed(() =>
|
|
649
|
+
Boolean(attrs.onRowClick)
|
|
650
|
+
|| (props.selectable && props.selectionClickMode !== 'checkbox'),
|
|
651
|
+
)
|
|
370
652
|
const internalFilters = ref({})
|
|
371
653
|
const internalSelectedKeys = ref([])
|
|
654
|
+
const selectionAnchorKey = ref(null)
|
|
655
|
+
const pendingSelectionCheckboxClick = ref(null)
|
|
372
656
|
const remoteRows = ref([])
|
|
373
657
|
const remoteTotal = ref(0)
|
|
374
658
|
const internalLoading = ref(false)
|
|
@@ -388,15 +672,24 @@ const internalColumnPriorities = ref({})
|
|
|
388
672
|
const savedPresets = ref([])
|
|
389
673
|
const defaultPresetId = ref(null)
|
|
390
674
|
const activeLayoutPresetId = ref(null)
|
|
675
|
+
const internalGroupBy = ref(null)
|
|
676
|
+
const collapsedGroupIds = ref([])
|
|
391
677
|
|
|
392
678
|
const isRemote = computed(() => typeof props.fetchRows === 'function')
|
|
393
679
|
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
680
|
+
const viewSettingsHandlers = computed(() =>
|
|
681
|
+
getViewSettingsHandlers(props.viewSettings, props.layoutPresets))
|
|
682
|
+
|
|
683
|
+
const isCallbackViewControl = computed(
|
|
684
|
+
() => props.viewControlMode === VIEW_CONTROL_CALLBACK,
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
const layoutPresetsAvailable = computed(() =>
|
|
688
|
+
isViewSettingsControlEnabled(
|
|
689
|
+
props.viewControlMode,
|
|
690
|
+
props.tableId,
|
|
691
|
+
viewSettingsHandlers.value,
|
|
692
|
+
))
|
|
400
693
|
|
|
401
694
|
const activeFilters = computed(() => {
|
|
402
695
|
if (!props.filterable) {
|
|
@@ -456,6 +749,53 @@ function bodyColumnColspan(index) {
|
|
|
456
749
|
return isLastColumnWithOptions(index) ? 2 : null
|
|
457
750
|
}
|
|
458
751
|
|
|
752
|
+
const bodyGroupColspan = computed(() => {
|
|
753
|
+
let colspan = visibleHeaders.value.length
|
|
754
|
+
if (showColumnOptionsButton.value) {
|
|
755
|
+
colspan += 1
|
|
756
|
+
}
|
|
757
|
+
return colspan
|
|
758
|
+
})
|
|
759
|
+
|
|
760
|
+
function bodyItemKey(item, index) {
|
|
761
|
+
if (item.kind === 'group') {
|
|
762
|
+
return `group-${item.groupId}`
|
|
763
|
+
}
|
|
764
|
+
return resolveRowKey(item.row, item.sourceIndex ?? index)
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function isHeaderGrouped(header) {
|
|
768
|
+
return Boolean(activeGroupBy.value && header?.key === activeGroupBy.value)
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function toggleGroupCollapsed(groupId) {
|
|
772
|
+
const next = new Set(collapsedGroupIdSet.value)
|
|
773
|
+
if (next.has(groupId)) {
|
|
774
|
+
next.delete(groupId)
|
|
775
|
+
} else {
|
|
776
|
+
next.add(groupId)
|
|
777
|
+
}
|
|
778
|
+
collapsedGroupIds.value = [...next]
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function setGroupBy(nextGroupBy) {
|
|
782
|
+
const header = props.headers.find((item) => item.key === nextGroupBy)
|
|
783
|
+
const normalized = isHeaderGroupable(header, props.groupable) ? nextGroupBy : null
|
|
784
|
+
|
|
785
|
+
if (props.groupBy === undefined) {
|
|
786
|
+
internalGroupBy.value = normalized
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
collapsedGroupIds.value = []
|
|
790
|
+
page.value = 1
|
|
791
|
+
emit('update:groupBy', normalized)
|
|
792
|
+
emit('group-by-change', normalized)
|
|
793
|
+
if (isRemote.value) {
|
|
794
|
+
scheduleFetchRows()
|
|
795
|
+
}
|
|
796
|
+
emitQueryChange()
|
|
797
|
+
}
|
|
798
|
+
|
|
459
799
|
function isHeaderHidden(header) {
|
|
460
800
|
if (header.hidden) {
|
|
461
801
|
return true
|
|
@@ -561,23 +901,61 @@ const filteredItems = computed(() => {
|
|
|
561
901
|
return applyFilterQuery(props.data, props.headers, activeFilterQuery.value)
|
|
562
902
|
})
|
|
563
903
|
|
|
904
|
+
const activeSortHeader = computed(() =>
|
|
905
|
+
props.headers.find((header) => header?.key === sortBy.value),
|
|
906
|
+
)
|
|
907
|
+
|
|
564
908
|
const sortedItems = computed(() => {
|
|
565
909
|
if (isRemote.value) {
|
|
566
910
|
return filteredItems.value
|
|
567
911
|
}
|
|
568
|
-
return sortRows(
|
|
912
|
+
return sortRows(
|
|
913
|
+
filteredItems.value,
|
|
914
|
+
sortBy.value,
|
|
915
|
+
sortDir.value,
|
|
916
|
+
activeSortHeader.value?.comparator,
|
|
917
|
+
)
|
|
569
918
|
})
|
|
570
919
|
|
|
571
|
-
const
|
|
572
|
-
if (
|
|
573
|
-
return
|
|
920
|
+
const activeGroupBy = computed(() => {
|
|
921
|
+
if (!props.groupable) {
|
|
922
|
+
return null
|
|
574
923
|
}
|
|
924
|
+
if (props.groupBy !== undefined) {
|
|
925
|
+
return props.groupBy || null
|
|
926
|
+
}
|
|
927
|
+
return internalGroupBy.value
|
|
928
|
+
})
|
|
575
929
|
|
|
576
|
-
|
|
577
|
-
|
|
930
|
+
const collapsedGroupIdSet = computed(() => new Set(collapsedGroupIds.value))
|
|
931
|
+
|
|
932
|
+
const displayBodyItems = computed(() => {
|
|
933
|
+
const paginate = !isRemote.value && props.showPagination
|
|
934
|
+
const items = buildDisplayBodyItems(sortedItems.value, {
|
|
935
|
+
groupBy: activeGroupBy.value,
|
|
936
|
+
page: page.value,
|
|
937
|
+
pageSize: pageSize.value,
|
|
938
|
+
paginate,
|
|
939
|
+
collapsedGroupIds: collapsedGroupIdSet.value,
|
|
940
|
+
})
|
|
941
|
+
let displayIndex = 0
|
|
942
|
+
return items.map((item) => {
|
|
943
|
+
if (item.kind !== 'row') {
|
|
944
|
+
return item
|
|
945
|
+
}
|
|
946
|
+
const indexed = { ...item, displayIndex }
|
|
947
|
+
displayIndex += 1
|
|
948
|
+
return indexed
|
|
949
|
+
})
|
|
578
950
|
})
|
|
579
951
|
|
|
580
|
-
const
|
|
952
|
+
const displayRows = computed(() =>
|
|
953
|
+
displayBodyItems.value
|
|
954
|
+
.filter((item) => item.kind === 'row')
|
|
955
|
+
.map((item) => item.row),
|
|
956
|
+
)
|
|
957
|
+
|
|
958
|
+
const hasRows = computed(() => displayBodyItems.value.length > 0)
|
|
581
959
|
|
|
582
960
|
const hasActiveFilters = computed(() =>
|
|
583
961
|
isFilterQueryActive(activeFilterQuery.value, props.headers, props.data),
|
|
@@ -631,6 +1009,7 @@ const queryParams = computed(() => {
|
|
|
631
1009
|
pageSize: pageSize.value,
|
|
632
1010
|
sortBy: sortBy.value,
|
|
633
1011
|
sortDir: sortDir.value,
|
|
1012
|
+
groupBy: activeGroupBy.value,
|
|
634
1013
|
filterQuery: activeFilterQuery.value,
|
|
635
1014
|
}
|
|
636
1015
|
|
|
@@ -670,6 +1049,7 @@ function headerClasses(header) {
|
|
|
670
1049
|
sortable: header.sortable,
|
|
671
1050
|
filterable: isHeaderFilterable(header),
|
|
672
1051
|
filtered: isHeaderFiltered(header),
|
|
1052
|
+
grouped: isHeaderGrouped(header),
|
|
673
1053
|
'menu-open': filterPopoverOpen.value && activeFilterHeader.value?.key === header.key,
|
|
674
1054
|
[colPriorityClass(priority)]: isValidColPriority(priority),
|
|
675
1055
|
}
|
|
@@ -683,6 +1063,60 @@ function cellClasses(header) {
|
|
|
683
1063
|
}
|
|
684
1064
|
}
|
|
685
1065
|
|
|
1066
|
+
function sourceRowIndex(row, displayIndex) {
|
|
1067
|
+
const sourceRows = isRemote.value ? remoteRows.value : props.data
|
|
1068
|
+
const index = sourceRows.indexOf(row)
|
|
1069
|
+
return index >= 0 ? index : filteredRowIndex(displayIndex)
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function rowContext(row, rowIndex) {
|
|
1073
|
+
return {
|
|
1074
|
+
row,
|
|
1075
|
+
rowIndex,
|
|
1076
|
+
sourceIndex: sourceRowIndex(row, rowIndex),
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function cellContext(row, header, rowIndex) {
|
|
1081
|
+
return {
|
|
1082
|
+
...rowContext(row, rowIndex),
|
|
1083
|
+
value: row?.[header.key],
|
|
1084
|
+
header,
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
function resolveRowClass(row, rowIndex) {
|
|
1089
|
+
return props.rowClass?.(rowContext(row, rowIndex))
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
function resolveRowStyle(row, rowIndex) {
|
|
1093
|
+
return props.rowStyle?.(rowContext(row, rowIndex))
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function resolveCellClass(row, header, rowIndex) {
|
|
1097
|
+
return props.cellClass?.(cellContext(row, header, rowIndex))
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function resolveCellStyle(row, header, rowIndex) {
|
|
1101
|
+
return props.cellStyle?.(cellContext(row, header, rowIndex))
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function resolveCellAttrs(row, header, rowIndex) {
|
|
1105
|
+
const value = props.cellAttrs?.(cellContext(row, header, rowIndex))
|
|
1106
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
1107
|
+
return {}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
const {
|
|
1111
|
+
class: _class,
|
|
1112
|
+
style: _style,
|
|
1113
|
+
key: _key,
|
|
1114
|
+
ref: _ref,
|
|
1115
|
+
...safeAttrs
|
|
1116
|
+
} = value
|
|
1117
|
+
return safeAttrs
|
|
1118
|
+
}
|
|
1119
|
+
|
|
686
1120
|
function resolveRowKey(row, index) {
|
|
687
1121
|
if (typeof props.rowKey === 'function') {
|
|
688
1122
|
const key = props.rowKey(row, index)
|
|
@@ -702,6 +1136,192 @@ function resolveRowKey(row, index) {
|
|
|
702
1136
|
return index
|
|
703
1137
|
}
|
|
704
1138
|
|
|
1139
|
+
function activeCellsEqual(left, right) {
|
|
1140
|
+
return left?.rowKey === right?.rowKey && left?.columnKey === right?.columnKey
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
function setActiveCell(nextCell) {
|
|
1144
|
+
const normalized = normalizeActiveCell(nextCell)
|
|
1145
|
+
if (activeCellsEqual(normalized, activeCell.value)) {
|
|
1146
|
+
return
|
|
1147
|
+
}
|
|
1148
|
+
if (props.activeCell === undefined) {
|
|
1149
|
+
internalActiveCell.value = normalized
|
|
1150
|
+
}
|
|
1151
|
+
emit('update:activeCell', normalized)
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function clearActiveCell() {
|
|
1155
|
+
setActiveCell(null)
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function isActiveCell(row, header, rowIndex) {
|
|
1159
|
+
const current = activeCell.value
|
|
1160
|
+
return Boolean(
|
|
1161
|
+
current
|
|
1162
|
+
&& current.rowKey === resolveRowKey(row, rowIndex)
|
|
1163
|
+
&& current.columnKey === header.key,
|
|
1164
|
+
)
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
const activeCellElements = new Map()
|
|
1168
|
+
|
|
1169
|
+
function registerCellElement(element, row, header, rowIndex) {
|
|
1170
|
+
const rowKey = resolveRowKey(row, rowIndex)
|
|
1171
|
+
let rowCells = activeCellElements.get(rowKey)
|
|
1172
|
+
|
|
1173
|
+
if (!element) {
|
|
1174
|
+
rowCells?.delete(header.key)
|
|
1175
|
+
if (rowCells?.size === 0) {
|
|
1176
|
+
activeCellElements.delete(rowKey)
|
|
1177
|
+
}
|
|
1178
|
+
return
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
if (!rowCells) {
|
|
1182
|
+
rowCells = new Map()
|
|
1183
|
+
activeCellElements.set(rowKey, rowCells)
|
|
1184
|
+
}
|
|
1185
|
+
rowCells.set(header.key, element)
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function focusCell(cell, options) {
|
|
1189
|
+
if (!cell) {
|
|
1190
|
+
return false
|
|
1191
|
+
}
|
|
1192
|
+
const element = activeCellElements.get(cell.rowKey)?.get(cell.columnKey)
|
|
1193
|
+
if (!element) {
|
|
1194
|
+
return false
|
|
1195
|
+
}
|
|
1196
|
+
element.focus(options)
|
|
1197
|
+
return true
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
function focusActiveCell(options) {
|
|
1201
|
+
return focusCell(activeCell.value, options)
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
function cellContextAt(rowIndex, columnIndex) {
|
|
1205
|
+
const row = displayRows.value[rowIndex]
|
|
1206
|
+
const header = visibleHeaders.value[columnIndex]
|
|
1207
|
+
if (!row || !header) {
|
|
1208
|
+
return null
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
return {
|
|
1212
|
+
...cellContext(row, header, rowIndex),
|
|
1213
|
+
rowKey: resolveRowKey(row, rowIndex),
|
|
1214
|
+
columnKey: header.key,
|
|
1215
|
+
columnIndex,
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function getActiveCellContext() {
|
|
1220
|
+
const current = activeCell.value
|
|
1221
|
+
if (!current) {
|
|
1222
|
+
return null
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
const rowIndex = displayRows.value.findIndex(
|
|
1226
|
+
(row, index) => resolveRowKey(row, index) === current.rowKey,
|
|
1227
|
+
)
|
|
1228
|
+
const columnIndex = visibleHeaders.value.findIndex(
|
|
1229
|
+
(header) => header.key === current.columnKey,
|
|
1230
|
+
)
|
|
1231
|
+
if (rowIndex < 0 || columnIndex < 0) {
|
|
1232
|
+
return null
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
return cellContextAt(rowIndex, columnIndex)
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function clampGridIndex(value, maximum) {
|
|
1239
|
+
const numeric = Number(value)
|
|
1240
|
+
const normalized = Number.isFinite(numeric) ? Math.trunc(numeric) : 0
|
|
1241
|
+
return Math.min(Math.max(normalized, 0), maximum)
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function scheduleCellFocus(cell, options) {
|
|
1245
|
+
if (!options?.focus) {
|
|
1246
|
+
return
|
|
1247
|
+
}
|
|
1248
|
+
void nextTick(() => focusCell(cell, options.focusOptions))
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function activateCellAt(rowIndex, columnIndex, options = {}) {
|
|
1252
|
+
if (displayRows.value.length === 0 || visibleHeaders.value.length === 0) {
|
|
1253
|
+
return null
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
const nextRowIndex = clampGridIndex(rowIndex, displayRows.value.length - 1)
|
|
1257
|
+
const nextColumnIndex = clampGridIndex(columnIndex, visibleHeaders.value.length - 1)
|
|
1258
|
+
const context = cellContextAt(nextRowIndex, nextColumnIndex)
|
|
1259
|
+
const nextActiveCell = {
|
|
1260
|
+
rowKey: context.rowKey,
|
|
1261
|
+
columnKey: context.columnKey,
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
setActiveCell(nextActiveCell)
|
|
1265
|
+
scheduleCellFocus(nextActiveCell, options)
|
|
1266
|
+
return context
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function moveActiveCell(rowDelta, columnDelta, options = {}) {
|
|
1270
|
+
if (displayRows.value.length === 0 || visibleHeaders.value.length === 0) {
|
|
1271
|
+
return null
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
const current = getActiveCellContext()
|
|
1275
|
+
if (!current) {
|
|
1276
|
+
return activateCellAt(0, 0, options)
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
const normalizedRowDelta = Number.isFinite(Number(rowDelta)) ? Math.trunc(Number(rowDelta)) : 0
|
|
1280
|
+
const normalizedColumnDelta = Number.isFinite(Number(columnDelta))
|
|
1281
|
+
? Math.trunc(Number(columnDelta))
|
|
1282
|
+
: 0
|
|
1283
|
+
const rowIndex = clampGridIndex(
|
|
1284
|
+
current.rowIndex + normalizedRowDelta,
|
|
1285
|
+
displayRows.value.length - 1,
|
|
1286
|
+
)
|
|
1287
|
+
let columnIndex
|
|
1288
|
+
|
|
1289
|
+
if (options.wrapColumns) {
|
|
1290
|
+
const linearIndex = (rowIndex * visibleHeaders.value.length)
|
|
1291
|
+
+ current.columnIndex
|
|
1292
|
+
+ normalizedColumnDelta
|
|
1293
|
+
const clampedLinearIndex = clampGridIndex(
|
|
1294
|
+
linearIndex,
|
|
1295
|
+
(displayRows.value.length * visibleHeaders.value.length) - 1,
|
|
1296
|
+
)
|
|
1297
|
+
return activateCellAt(
|
|
1298
|
+
Math.floor(clampedLinearIndex / visibleHeaders.value.length),
|
|
1299
|
+
clampedLinearIndex % visibleHeaders.value.length,
|
|
1300
|
+
options,
|
|
1301
|
+
)
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
columnIndex = clampGridIndex(
|
|
1305
|
+
current.columnIndex + normalizedColumnDelta,
|
|
1306
|
+
visibleHeaders.value.length - 1,
|
|
1307
|
+
)
|
|
1308
|
+
return activateCellAt(rowIndex, columnIndex, options)
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function onCellClick(row, header, rowIndex, event) {
|
|
1312
|
+
const context = cellContext(row, header, rowIndex)
|
|
1313
|
+
const nextActiveCell = {
|
|
1314
|
+
rowKey: resolveRowKey(row, rowIndex),
|
|
1315
|
+
columnKey: header.key,
|
|
1316
|
+
}
|
|
1317
|
+
setActiveCell(nextActiveCell)
|
|
1318
|
+
emit('cell-click', {
|
|
1319
|
+
...context,
|
|
1320
|
+
...nextActiveCell,
|
|
1321
|
+
event,
|
|
1322
|
+
})
|
|
1323
|
+
}
|
|
1324
|
+
|
|
705
1325
|
const activeSelectedKeys = computed(() => (
|
|
706
1326
|
props.selectedKeys !== undefined ? props.selectedKeys : internalSelectedKeys.value
|
|
707
1327
|
))
|
|
@@ -724,6 +1344,7 @@ function setSelectedKeys(nextKeys) {
|
|
|
724
1344
|
function toggleRowSelection(row, index) {
|
|
725
1345
|
const key = resolveRowKey(row, index)
|
|
726
1346
|
const current = activeSelectedKeys.value
|
|
1347
|
+
selectionAnchorKey.value = key
|
|
727
1348
|
if (selectedKeySet.value.has(key)) {
|
|
728
1349
|
setSelectedKeys(current.filter((item) => item !== key))
|
|
729
1350
|
return
|
|
@@ -731,10 +1352,60 @@ function toggleRowSelection(row, index) {
|
|
|
731
1352
|
setSelectedKeys([...current, key])
|
|
732
1353
|
}
|
|
733
1354
|
|
|
1355
|
+
function replaceRowSelection(row, index) {
|
|
1356
|
+
const key = resolveRowKey(row, index)
|
|
1357
|
+
selectionAnchorKey.value = key
|
|
1358
|
+
setSelectedKeys([key])
|
|
1359
|
+
}
|
|
1360
|
+
|
|
734
1361
|
const displayRowKeys = computed(() =>
|
|
735
1362
|
displayRows.value.map((row, index) => resolveRowKey(row, index)),
|
|
736
1363
|
)
|
|
737
1364
|
|
|
1365
|
+
function selectRowRange(row, index, { updateAnchor = true } = {}) {
|
|
1366
|
+
const key = resolveRowKey(row, index)
|
|
1367
|
+
const anchorIndex = displayRowKeys.value.findIndex((item) => item === selectionAnchorKey.value)
|
|
1368
|
+
const targetIndex = displayRowKeys.value.findIndex((item) => item === key)
|
|
1369
|
+
|
|
1370
|
+
if (anchorIndex < 0 || targetIndex < 0) {
|
|
1371
|
+
setSelectedKeys([...activeSelectedKeys.value, key])
|
|
1372
|
+
selectionAnchorKey.value = key
|
|
1373
|
+
return
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
const from = Math.min(anchorIndex, targetIndex)
|
|
1377
|
+
const to = Math.max(anchorIndex, targetIndex)
|
|
1378
|
+
setSelectedKeys([
|
|
1379
|
+
...activeSelectedKeys.value,
|
|
1380
|
+
...displayRowKeys.value.slice(from, to + 1),
|
|
1381
|
+
])
|
|
1382
|
+
if (updateAnchor) {
|
|
1383
|
+
selectionAnchorKey.value = key
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
function rememberSelectionCheckboxClick(row, index, event) {
|
|
1388
|
+
pendingSelectionCheckboxClick.value = {
|
|
1389
|
+
key: resolveRowKey(row, index),
|
|
1390
|
+
shiftKey: event.shiftKey,
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
function onSelectionCheckboxChange(row, index) {
|
|
1395
|
+
const key = resolveRowKey(row, index)
|
|
1396
|
+
const shiftKey = (
|
|
1397
|
+
pendingSelectionCheckboxClick.value?.key === key
|
|
1398
|
+
&& pendingSelectionCheckboxClick.value.shiftKey
|
|
1399
|
+
)
|
|
1400
|
+
pendingSelectionCheckboxClick.value = null
|
|
1401
|
+
|
|
1402
|
+
if (shiftKey) {
|
|
1403
|
+
selectRowRange(row, index)
|
|
1404
|
+
return
|
|
1405
|
+
}
|
|
1406
|
+
toggleRowSelection(row, index)
|
|
1407
|
+
}
|
|
1408
|
+
|
|
738
1409
|
const allDisplayRowsSelected = computed(() => {
|
|
739
1410
|
const keys = displayRowKeys.value
|
|
740
1411
|
return keys.length > 0 && keys.every((key) => selectedKeySet.value.has(key))
|
|
@@ -756,15 +1427,18 @@ function toggleSelectAllDisplayRows() {
|
|
|
756
1427
|
if (allDisplayRowsSelected.value) {
|
|
757
1428
|
const pageKeySet = new Set(pageKeys)
|
|
758
1429
|
setSelectedKeys(activeSelectedKeys.value.filter((key) => !pageKeySet.has(key)))
|
|
1430
|
+
selectionAnchorKey.value = null
|
|
759
1431
|
return
|
|
760
1432
|
}
|
|
761
1433
|
|
|
762
1434
|
setSelectedKeys([...activeSelectedKeys.value, ...pageKeys])
|
|
1435
|
+
selectionAnchorKey.value = pageKeys.at(-1) ?? null
|
|
763
1436
|
}
|
|
764
1437
|
|
|
765
1438
|
watch(() => props.selectable, (enabled) => {
|
|
766
1439
|
if (!enabled) {
|
|
767
1440
|
setSelectedKeys([])
|
|
1441
|
+
selectionAnchorKey.value = null
|
|
768
1442
|
}
|
|
769
1443
|
})
|
|
770
1444
|
|
|
@@ -822,11 +1496,49 @@ function onRowClick(row, displayIndex, event) {
|
|
|
822
1496
|
if (event.target.closest('a, button, input, select, textarea, [data-row-click-ignore]')) {
|
|
823
1497
|
return
|
|
824
1498
|
}
|
|
1499
|
+
|
|
825
1500
|
if (props.selectable) {
|
|
826
|
-
|
|
827
|
-
|
|
1501
|
+
if (props.selectionClickMode === 'extended') {
|
|
1502
|
+
if (event.ctrlKey || event.metaKey) {
|
|
1503
|
+
toggleRowSelection(row, displayIndex)
|
|
1504
|
+
return
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
const hasRangeAnchor = displayRowKeys.value.includes(selectionAnchorKey.value)
|
|
1508
|
+
if (event.shiftKey && hasRangeAnchor) {
|
|
1509
|
+
selectRowRange(row, displayIndex, { updateAnchor: false })
|
|
1510
|
+
return
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
replaceRowSelection(row, displayIndex)
|
|
1514
|
+
return
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
if (
|
|
1518
|
+
props.selectionClickMode !== 'checkbox'
|
|
1519
|
+
&& event.shiftKey
|
|
1520
|
+
) {
|
|
1521
|
+
selectRowRange(row, displayIndex)
|
|
1522
|
+
return
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
if (
|
|
1526
|
+
props.selectionClickMode === 'row'
|
|
1527
|
+
|| (
|
|
1528
|
+
props.selectionClickMode === 'modifier'
|
|
1529
|
+
&& (event.ctrlKey || event.metaKey)
|
|
1530
|
+
)
|
|
1531
|
+
) {
|
|
1532
|
+
toggleRowSelection(row, displayIndex)
|
|
1533
|
+
return
|
|
1534
|
+
}
|
|
828
1535
|
}
|
|
829
|
-
|
|
1536
|
+
|
|
1537
|
+
emit('row-click', {
|
|
1538
|
+
row,
|
|
1539
|
+
index: filteredRowIndex(displayIndex),
|
|
1540
|
+
event,
|
|
1541
|
+
})
|
|
830
1542
|
}
|
|
831
1543
|
|
|
832
1544
|
const rowContextMenuOpen = ref(false)
|
|
@@ -888,7 +1600,7 @@ function closeRowContextMenu() {
|
|
|
888
1600
|
rowContextMenuAnchor.value = null
|
|
889
1601
|
}
|
|
890
1602
|
|
|
891
|
-
function onRowContextMenu(row,
|
|
1603
|
+
function onRowContextMenu(row, sourceIndex, event) {
|
|
892
1604
|
if (!hasRowContextMenu.value) {
|
|
893
1605
|
return
|
|
894
1606
|
}
|
|
@@ -899,8 +1611,8 @@ function onRowContextMenu(row, displayIndex, event) {
|
|
|
899
1611
|
event.preventDefault()
|
|
900
1612
|
event.stopPropagation()
|
|
901
1613
|
|
|
902
|
-
const key = resolveRowKey(row,
|
|
903
|
-
const index = filteredRowIndex(
|
|
1614
|
+
const key = resolveRowKey(row, sourceIndex)
|
|
1615
|
+
const index = filteredRowIndex(sourceIndex)
|
|
904
1616
|
let targetKeys
|
|
905
1617
|
|
|
906
1618
|
if (props.selectable && selectedKeySet.value.has(key) && activeSelectedKeys.value.length > 0) {
|
|
@@ -1035,6 +1747,17 @@ function onFilterCancel() {
|
|
|
1035
1747
|
activeFilterValue.value = []
|
|
1036
1748
|
}
|
|
1037
1749
|
|
|
1750
|
+
const canGroupByActiveFilterColumn = computed(() => {
|
|
1751
|
+
if (!props.groupable || !activeFilterHeader.value) {
|
|
1752
|
+
return false
|
|
1753
|
+
}
|
|
1754
|
+
return isHeaderGroupable(activeFilterHeader.value, props.groupable)
|
|
1755
|
+
})
|
|
1756
|
+
|
|
1757
|
+
const isActiveFilterHeaderGrouped = computed(() =>
|
|
1758
|
+
Boolean(activeFilterHeader.value?.key && activeFilterHeader.value.key === activeGroupBy.value),
|
|
1759
|
+
)
|
|
1760
|
+
|
|
1038
1761
|
const canHideActiveFilterColumn = computed(() => {
|
|
1039
1762
|
if (!showColumnOptionsButton.value || !activeFilterHeader.value) {
|
|
1040
1763
|
return false
|
|
@@ -1049,6 +1772,12 @@ const canHideActiveFilterColumn = computed(() => {
|
|
|
1049
1772
|
return configurableHeaders.value.some((item) => item.key === header.key)
|
|
1050
1773
|
})
|
|
1051
1774
|
|
|
1775
|
+
function onFilterGroupBy(columnKey) {
|
|
1776
|
+
setGroupBy(columnKey || null)
|
|
1777
|
+
filterPopoverOpen.value = false
|
|
1778
|
+
onFilterCancel()
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1052
1781
|
function onFilterHideColumn() {
|
|
1053
1782
|
const header = activeFilterHeader.value
|
|
1054
1783
|
if (!header?.key || !canHideActiveFilterColumn.value) {
|
|
@@ -1134,10 +1863,13 @@ function openColumnOptions() {
|
|
|
1134
1863
|
void refreshSavedPresets()
|
|
1135
1864
|
}
|
|
1136
1865
|
|
|
1137
|
-
function onColumnOptionsApply({ order, visibleKeys, priorities }) {
|
|
1866
|
+
function onColumnOptionsApply({ order, visibleKeys, priorities, groupBy }) {
|
|
1138
1867
|
setColumnOrder(order)
|
|
1139
1868
|
setColumnVisibility(visibleKeys)
|
|
1140
1869
|
setColumnPriorities(priorities)
|
|
1870
|
+
if (groupBy !== undefined && groupBy !== activeGroupBy.value) {
|
|
1871
|
+
setGroupBy(groupBy)
|
|
1872
|
+
}
|
|
1141
1873
|
updateActiveLayoutLabel()
|
|
1142
1874
|
}
|
|
1143
1875
|
|
|
@@ -1146,15 +1878,16 @@ function onColumnOptionsCancel() {
|
|
|
1146
1878
|
}
|
|
1147
1879
|
|
|
1148
1880
|
async function refreshSavedPresets() {
|
|
1149
|
-
if (
|
|
1150
|
-
|
|
1881
|
+
if (isCallbackViewControl.value) {
|
|
1882
|
+
const handlers = viewSettingsHandlers.value
|
|
1883
|
+
if (!handlers || typeof handlers.list !== 'function') {
|
|
1151
1884
|
savedPresets.value = []
|
|
1152
1885
|
defaultPresetId.value = null
|
|
1153
1886
|
return
|
|
1154
1887
|
}
|
|
1155
1888
|
|
|
1156
1889
|
try {
|
|
1157
|
-
const result = await
|
|
1890
|
+
const result = await handlers.list()
|
|
1158
1891
|
savedPresets.value = Array.isArray(result?.presets) ? result.presets : []
|
|
1159
1892
|
defaultPresetId.value = result?.defaultPresetId ?? null
|
|
1160
1893
|
} catch {
|
|
@@ -1192,6 +1925,7 @@ function getCurrentLayoutState() {
|
|
|
1192
1925
|
filters: cloneFilters(activeFilters.value),
|
|
1193
1926
|
sortBy: sortBy.value,
|
|
1194
1927
|
sortDir: sortDir.value,
|
|
1928
|
+
groupBy: activeGroupBy.value,
|
|
1195
1929
|
pageSize: pageSize.value,
|
|
1196
1930
|
}
|
|
1197
1931
|
}
|
|
@@ -1231,6 +1965,12 @@ function applyPresetState(state) {
|
|
|
1231
1965
|
sortDir.value = 'asc'
|
|
1232
1966
|
}
|
|
1233
1967
|
|
|
1968
|
+
if (sanitized.groupBy) {
|
|
1969
|
+
setGroupBy(sanitized.groupBy)
|
|
1970
|
+
} else {
|
|
1971
|
+
setGroupBy(null)
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1234
1974
|
if (sanitized.pageSize) {
|
|
1235
1975
|
pageSize.value = sanitized.pageSize
|
|
1236
1976
|
}
|
|
@@ -1246,13 +1986,14 @@ function applyPresetState(state) {
|
|
|
1246
1986
|
async function onSavePreset({ name, setAsDefault }) {
|
|
1247
1987
|
const state = getCurrentLayoutState()
|
|
1248
1988
|
|
|
1249
|
-
if (
|
|
1250
|
-
|
|
1251
|
-
|
|
1989
|
+
if (isCallbackViewControl.value) {
|
|
1990
|
+
const handlers = viewSettingsHandlers.value
|
|
1991
|
+
if (!handlers || typeof handlers.save !== 'function') {
|
|
1992
|
+
return { ok: false, error: 'View save callback is unavailable.' }
|
|
1252
1993
|
}
|
|
1253
1994
|
|
|
1254
1995
|
try {
|
|
1255
|
-
const result = await
|
|
1996
|
+
const result = await handlers.save({ name, state, setAsDefault })
|
|
1256
1997
|
if (result?.ok) {
|
|
1257
1998
|
await refreshSavedPresets()
|
|
1258
1999
|
if (result.preset?.id) {
|
|
@@ -1285,14 +2026,50 @@ async function onSavePreset({ name, setAsDefault }) {
|
|
|
1285
2026
|
return result
|
|
1286
2027
|
}
|
|
1287
2028
|
|
|
2029
|
+
async function onOverwritePreset({ presetId }) {
|
|
2030
|
+
const state = getCurrentLayoutState()
|
|
2031
|
+
|
|
2032
|
+
if (isCallbackViewControl.value) {
|
|
2033
|
+
const handlers = viewSettingsHandlers.value
|
|
2034
|
+
if (!handlers || typeof handlers.overwrite !== 'function') {
|
|
2035
|
+
return { ok: false, error: 'View overwrite callback is unavailable.' }
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
try {
|
|
2039
|
+
const result = await handlers.overwrite({ presetId, state })
|
|
2040
|
+
if (result?.ok) {
|
|
2041
|
+
await refreshSavedPresets()
|
|
2042
|
+
activeLayoutPresetId.value = presetId
|
|
2043
|
+
updateActiveLayoutLabel()
|
|
2044
|
+
}
|
|
2045
|
+
return result ?? { ok: false, error: 'Unable to overwrite layout.' }
|
|
2046
|
+
} catch {
|
|
2047
|
+
return { ok: false, error: 'Unable to overwrite layout.' }
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
if (!props.tableId) {
|
|
2052
|
+
return { ok: false, error: 'Table identity is required.' }
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
const result = overwritePreset(props.tableId, presetId, { state })
|
|
2056
|
+
if (result.ok) {
|
|
2057
|
+
refreshSavedPresets()
|
|
2058
|
+
activeLayoutPresetId.value = presetId
|
|
2059
|
+
updateActiveLayoutLabel()
|
|
2060
|
+
}
|
|
2061
|
+
return result
|
|
2062
|
+
}
|
|
2063
|
+
|
|
1288
2064
|
async function onLoadPreset(presetId) {
|
|
1289
|
-
if (
|
|
1290
|
-
|
|
1291
|
-
|
|
2065
|
+
if (isCallbackViewControl.value) {
|
|
2066
|
+
const handlers = viewSettingsHandlers.value
|
|
2067
|
+
if (!handlers || typeof handlers.load !== 'function') {
|
|
2068
|
+
return { ok: false, error: 'View load callback is unavailable.' }
|
|
1292
2069
|
}
|
|
1293
2070
|
|
|
1294
2071
|
try {
|
|
1295
|
-
const result = await
|
|
2072
|
+
const result = await handlers.load(presetId)
|
|
1296
2073
|
if (!result?.ok || !result.preset?.state) {
|
|
1297
2074
|
return result ?? { ok: false, error: 'Preset not found.' }
|
|
1298
2075
|
}
|
|
@@ -1322,13 +2099,14 @@ async function onLoadPreset(presetId) {
|
|
|
1322
2099
|
}
|
|
1323
2100
|
|
|
1324
2101
|
async function onDeletePreset(presetId) {
|
|
1325
|
-
if (
|
|
1326
|
-
|
|
1327
|
-
|
|
2102
|
+
if (isCallbackViewControl.value) {
|
|
2103
|
+
const handlers = viewSettingsHandlers.value
|
|
2104
|
+
if (!handlers || typeof handlers.delete !== 'function') {
|
|
2105
|
+
return { ok: false, error: 'View delete callback is unavailable.' }
|
|
1328
2106
|
}
|
|
1329
2107
|
|
|
1330
2108
|
try {
|
|
1331
|
-
const result = await
|
|
2109
|
+
const result = await handlers.delete(presetId)
|
|
1332
2110
|
if (result?.ok) {
|
|
1333
2111
|
await refreshSavedPresets()
|
|
1334
2112
|
if (activeLayoutPresetId.value === presetId) {
|
|
@@ -1358,13 +2136,14 @@ async function onDeletePreset(presetId) {
|
|
|
1358
2136
|
}
|
|
1359
2137
|
|
|
1360
2138
|
async function onSetDefaultPreset(presetId) {
|
|
1361
|
-
if (
|
|
1362
|
-
|
|
2139
|
+
if (isCallbackViewControl.value) {
|
|
2140
|
+
const handlers = viewSettingsHandlers.value
|
|
2141
|
+
if (!handlers || typeof handlers.setDefault !== 'function') {
|
|
1363
2142
|
return { ok: false, error: 'Unable to update default layout.' }
|
|
1364
2143
|
}
|
|
1365
2144
|
|
|
1366
2145
|
try {
|
|
1367
|
-
const result = await
|
|
2146
|
+
const result = await handlers.setDefault(presetId)
|
|
1368
2147
|
if (result?.ok) {
|
|
1369
2148
|
await refreshSavedPresets()
|
|
1370
2149
|
}
|
|
@@ -1407,6 +2186,7 @@ function getDeveloperDefaultLayoutState() {
|
|
|
1407
2186
|
filters: {},
|
|
1408
2187
|
sortBy: null,
|
|
1409
2188
|
sortDir: 'asc',
|
|
2189
|
+
groupBy: null,
|
|
1410
2190
|
pageSize: DEFAULT_PAGE_SIZE,
|
|
1411
2191
|
}
|
|
1412
2192
|
}
|
|
@@ -1466,6 +2246,7 @@ function loadDeveloperDefaults() {
|
|
|
1466
2246
|
setFilters({})
|
|
1467
2247
|
sortBy.value = null
|
|
1468
2248
|
sortDir.value = 'asc'
|
|
2249
|
+
setGroupBy(null)
|
|
1469
2250
|
setColumnOrder(getDeveloperDefaultColumnOrder())
|
|
1470
2251
|
setColumnPriorities({})
|
|
1471
2252
|
|
|
@@ -1550,7 +2331,18 @@ function scheduleFetchRows() {
|
|
|
1550
2331
|
}
|
|
1551
2332
|
|
|
1552
2333
|
watch(
|
|
1553
|
-
|
|
2334
|
+
pageSize,
|
|
2335
|
+
() => {
|
|
2336
|
+
page.value = 1
|
|
2337
|
+
if (isRemote.value) {
|
|
2338
|
+
scheduleFetchRows()
|
|
2339
|
+
}
|
|
2340
|
+
emitQueryChange()
|
|
2341
|
+
},
|
|
2342
|
+
)
|
|
2343
|
+
|
|
2344
|
+
watch(
|
|
2345
|
+
activeGroupBy,
|
|
1554
2346
|
() => {
|
|
1555
2347
|
page.value = 1
|
|
1556
2348
|
if (isRemote.value) {
|
|
@@ -1560,6 +2352,19 @@ watch(
|
|
|
1560
2352
|
},
|
|
1561
2353
|
)
|
|
1562
2354
|
|
|
2355
|
+
watch(
|
|
2356
|
+
() => [sortBy.value, sortDir.value],
|
|
2357
|
+
() => {
|
|
2358
|
+
if (props.resetPageOnSort) {
|
|
2359
|
+
page.value = 1
|
|
2360
|
+
}
|
|
2361
|
+
if (isRemote.value) {
|
|
2362
|
+
scheduleFetchRows()
|
|
2363
|
+
}
|
|
2364
|
+
emitQueryChange()
|
|
2365
|
+
},
|
|
2366
|
+
)
|
|
2367
|
+
|
|
1563
2368
|
watch(page, () => {
|
|
1564
2369
|
if (isRemote.value) {
|
|
1565
2370
|
scheduleFetchRows()
|
|
@@ -1567,6 +2372,40 @@ watch(page, () => {
|
|
|
1567
2372
|
emitQueryChange()
|
|
1568
2373
|
})
|
|
1569
2374
|
|
|
2375
|
+
watch(() => props.sortBy, (value) => {
|
|
2376
|
+
if (value !== undefined) {
|
|
2377
|
+
internalSortBy.value = value ?? null
|
|
2378
|
+
}
|
|
2379
|
+
})
|
|
2380
|
+
|
|
2381
|
+
watch(() => props.sortDir, (value) => {
|
|
2382
|
+
if (value !== undefined) {
|
|
2383
|
+
internalSortDir.value = normalizeSortDir(value)
|
|
2384
|
+
}
|
|
2385
|
+
})
|
|
2386
|
+
|
|
2387
|
+
watch(() => props.page, (value) => {
|
|
2388
|
+
if (value !== undefined) {
|
|
2389
|
+
internalPage.value = normalizePage(value)
|
|
2390
|
+
}
|
|
2391
|
+
})
|
|
2392
|
+
|
|
2393
|
+
watch(() => props.pageSize, (value) => {
|
|
2394
|
+
if (value !== undefined) {
|
|
2395
|
+
internalPageSize.value = normalizePageSize(value)
|
|
2396
|
+
}
|
|
2397
|
+
})
|
|
2398
|
+
|
|
2399
|
+
watch(
|
|
2400
|
+
() => props.activeCell,
|
|
2401
|
+
(value) => {
|
|
2402
|
+
if (value !== undefined) {
|
|
2403
|
+
internalActiveCell.value = normalizeActiveCell(value)
|
|
2404
|
+
}
|
|
2405
|
+
},
|
|
2406
|
+
{ deep: true },
|
|
2407
|
+
)
|
|
2408
|
+
|
|
1570
2409
|
watch(
|
|
1571
2410
|
activeFilters,
|
|
1572
2411
|
() => {
|
|
@@ -1623,6 +2462,24 @@ watch(
|
|
|
1623
2462
|
{ deep: true },
|
|
1624
2463
|
)
|
|
1625
2464
|
|
|
2465
|
+
watch(
|
|
2466
|
+
() => props.groupBy,
|
|
2467
|
+
(nextGroupBy) => {
|
|
2468
|
+
if (nextGroupBy !== undefined) {
|
|
2469
|
+
collapsedGroupIds.value = []
|
|
2470
|
+
}
|
|
2471
|
+
},
|
|
2472
|
+
)
|
|
2473
|
+
|
|
2474
|
+
watch(
|
|
2475
|
+
() => props.groupable,
|
|
2476
|
+
(enabled) => {
|
|
2477
|
+
if (!enabled) {
|
|
2478
|
+
setGroupBy(null)
|
|
2479
|
+
}
|
|
2480
|
+
},
|
|
2481
|
+
)
|
|
2482
|
+
|
|
1626
2483
|
watch(
|
|
1627
2484
|
() => props.columnPriorities,
|
|
1628
2485
|
(nextPriorities) => {
|
|
@@ -1643,7 +2500,7 @@ watch(
|
|
|
1643
2500
|
)
|
|
1644
2501
|
|
|
1645
2502
|
watch(
|
|
1646
|
-
() => [props.
|
|
2503
|
+
() => [props.viewControlMode, props.tableId, viewSettingsHandlers.value],
|
|
1647
2504
|
() => {
|
|
1648
2505
|
void refreshSavedPresets()
|
|
1649
2506
|
updateActiveLayoutLabel()
|
|
@@ -1660,6 +2517,7 @@ watch(
|
|
|
1660
2517
|
activeFilters,
|
|
1661
2518
|
sortBy,
|
|
1662
2519
|
sortDir,
|
|
2520
|
+
activeGroupBy,
|
|
1663
2521
|
pageSize,
|
|
1664
2522
|
() => props.headers,
|
|
1665
2523
|
savedPresets,
|
|
@@ -1674,25 +2532,30 @@ watch(activeLayoutLabel, (label) => {
|
|
|
1674
2532
|
emitLayoutLabelChange(label)
|
|
1675
2533
|
}, { immediate: true })
|
|
1676
2534
|
|
|
1677
|
-
watch(
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
2535
|
+
watch(
|
|
2536
|
+
() => props.data,
|
|
2537
|
+
() => {
|
|
2538
|
+
if (!isRemote.value) {
|
|
2539
|
+
page.value = 1
|
|
2540
|
+
}
|
|
2541
|
+
},
|
|
2542
|
+
{ deep: true },
|
|
2543
|
+
)
|
|
1682
2544
|
|
|
1683
2545
|
onMounted(async () => {
|
|
1684
2546
|
if (props.loadSavedLayout) {
|
|
1685
|
-
|
|
2547
|
+
const handlers = viewSettingsHandlers.value
|
|
2548
|
+
if (isCallbackViewControl.value && handlers && typeof handlers.getDefault === 'function') {
|
|
1686
2549
|
try {
|
|
1687
|
-
const result = await
|
|
2550
|
+
const result = await handlers.getDefault()
|
|
1688
2551
|
if (result?.preset?.state) {
|
|
1689
2552
|
applyPresetState(result.preset.state)
|
|
1690
2553
|
activeLayoutPresetId.value = result.preset.id
|
|
1691
2554
|
}
|
|
1692
2555
|
} catch {
|
|
1693
|
-
// Ignore default layout load failures in
|
|
2556
|
+
// Ignore default layout load failures in callback mode.
|
|
1694
2557
|
}
|
|
1695
|
-
} else if (!
|
|
2558
|
+
} else if (!isCallbackViewControl.value && props.tableId) {
|
|
1696
2559
|
const preset = getDefaultPreset(props.tableId)
|
|
1697
2560
|
if (preset?.state) {
|
|
1698
2561
|
applyPresetState(preset.state)
|
|
@@ -1721,6 +2584,12 @@ onBeforeUnmount(() => {
|
|
|
1721
2584
|
defineExpose({
|
|
1722
2585
|
layoutLabel: activeLayoutLabel,
|
|
1723
2586
|
loadDeveloperDefaults,
|
|
2587
|
+
clearFilters: clearAllFilters,
|
|
2588
|
+
focusActiveCell,
|
|
2589
|
+
clearActiveCell,
|
|
2590
|
+
activateCellAt,
|
|
2591
|
+
moveActiveCell,
|
|
2592
|
+
getActiveCellContext,
|
|
1724
2593
|
})
|
|
1725
2594
|
</script>
|
|
1726
2595
|
|
|
@@ -1759,9 +2628,57 @@ table.loading tbody {
|
|
|
1759
2628
|
}
|
|
1760
2629
|
|
|
1761
2630
|
.sort-indicator {
|
|
2631
|
+
display: inline-flex;
|
|
2632
|
+
align-items: center;
|
|
2633
|
+
justify-content: center;
|
|
1762
2634
|
width: 1.5em;
|
|
1763
|
-
|
|
1764
|
-
|
|
2635
|
+
vertical-align: middle;
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
.group-indicator {
|
|
2639
|
+
display: inline-flex;
|
|
2640
|
+
vertical-align: middle;
|
|
2641
|
+
margin-right: 0.25rem;
|
|
2642
|
+
opacity: 0.85;
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
table thead th.grouped {
|
|
2646
|
+
color: var(--table-header-active-fg);
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
tbody tr.table-group-row > td.table-group-cell {
|
|
2650
|
+
background-color: var(--table-header-active-bg);
|
|
2651
|
+
color: var(--table-header-fg);
|
|
2652
|
+
font-weight: 600;
|
|
2653
|
+
padding-top: 0.65rem;
|
|
2654
|
+
padding-bottom: 0.65rem;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
.table-group-toggle {
|
|
2658
|
+
display: inline-flex;
|
|
2659
|
+
align-items: center;
|
|
2660
|
+
justify-content: center;
|
|
2661
|
+
min-width: 1.5rem;
|
|
2662
|
+
margin-right: 0.35rem;
|
|
2663
|
+
padding: 0.1rem 0.35rem;
|
|
2664
|
+
vertical-align: middle;
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
.table-group-chevron {
|
|
2668
|
+
flex-shrink: 0;
|
|
2669
|
+
transition: transform 0.15s ease;
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
.table-group-row:not(.table-group-row-collapsed) .table-group-chevron {
|
|
2673
|
+
transform: rotate(180deg);
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
.table-group-label {
|
|
2677
|
+
margin-right: 0.35rem;
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
.table-group-count {
|
|
2681
|
+
font-weight: normal;
|
|
1765
2682
|
}
|
|
1766
2683
|
|
|
1767
2684
|
td:first-child,
|
|
@@ -1792,6 +2709,16 @@ tbody tr.row-clickable {
|
|
|
1792
2709
|
cursor: pointer;
|
|
1793
2710
|
}
|
|
1794
2711
|
|
|
2712
|
+
tbody td.table-active-cell {
|
|
2713
|
+
outline: 2px solid currentColor;
|
|
2714
|
+
outline-offset: -2px;
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
tbody td.table-active-cell:focus,
|
|
2718
|
+
tbody td.table-active-cell:focus-visible {
|
|
2719
|
+
outline-width: 3px;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
1795
2722
|
tbody tr.row-selected > td {
|
|
1796
2723
|
background-color: var(--control-checked-bg);
|
|
1797
2724
|
color: var(--control-checked-fg);
|