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
|
@@ -17,108 +17,75 @@
|
|
|
17
17
|
<Tabs
|
|
18
18
|
class="column-options-tabs"
|
|
19
19
|
:tabs="optionTabs"
|
|
20
|
-
default-tab="
|
|
20
|
+
default-tab="current"
|
|
21
21
|
@tab-change="onOptionTabChange"
|
|
22
22
|
>
|
|
23
|
-
<template #tab-
|
|
23
|
+
<template #tab-current>
|
|
24
24
|
<p class="subtle column-options-help">
|
|
25
|
-
Drag
|
|
25
|
+
Drag the handle to reorder columns. Toggle checkboxes to show or hide columns. Set responsive priority (1 hides first) when using column priorities layout.
|
|
26
26
|
</p>
|
|
27
27
|
|
|
28
|
-
<
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
:
|
|
40
|
-
@dragstart="onDragStart(key, $event)"
|
|
41
|
-
@dragover.prevent="onDragOver(key)"
|
|
42
|
-
@drop.prevent="onDrop(key)"
|
|
43
|
-
@dragend="onDragEnd"
|
|
28
|
+
<label v-if="groupableColumnOptions.length > 0" class="column-options-group-by">
|
|
29
|
+
<span class="field-label">Group by</span>
|
|
30
|
+
<select
|
|
31
|
+
:value="draftGroupBy ?? ''"
|
|
32
|
+
aria-label="Group table rows by column"
|
|
33
|
+
@change="setGroupBy($event.target.value)"
|
|
34
|
+
>
|
|
35
|
+
<option value="">None</option>
|
|
36
|
+
<option
|
|
37
|
+
v-for="header in groupableColumnOptions"
|
|
38
|
+
:key="header.key"
|
|
39
|
+
:value="header.key"
|
|
44
40
|
>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
:disabled="!isHideable(key) || (isVisible(key) && draftVisibleKeys.length <= 1)"
|
|
50
|
-
@change="toggleVisible(key)"
|
|
51
|
-
/>
|
|
52
|
-
<span>{{ columnDisplayLabel(key) }}</span>
|
|
53
|
-
</label>
|
|
54
|
-
|
|
55
|
-
<select
|
|
56
|
-
v-if="isPriorityConfigurable(key)"
|
|
57
|
-
class="column-options-priority"
|
|
58
|
-
:value="prioritySelectValue(key)"
|
|
59
|
-
:aria-label="`Priority for ${columnLabel(key)} column`"
|
|
60
|
-
@change="setPriority(key, $event.target.value)"
|
|
61
|
-
>
|
|
62
|
-
<option value="">Never hide</option>
|
|
63
|
-
<option
|
|
64
|
-
v-for="level in priorityLevels"
|
|
65
|
-
:key="level"
|
|
66
|
-
:value="level"
|
|
67
|
-
>
|
|
68
|
-
{{ priorityOptionLabel(level) }}
|
|
69
|
-
</option>
|
|
70
|
-
</select>
|
|
71
|
-
<span v-else class="column-options-priority-spacer" aria-hidden="true" />
|
|
41
|
+
{{ header.label || header.key }}
|
|
42
|
+
</option>
|
|
43
|
+
</select>
|
|
44
|
+
</label>
|
|
72
45
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class="neutral column-options-move-button"
|
|
93
|
-
:disabled="!canMove(key, 1)"
|
|
94
|
-
:aria-label="`Move ${columnLabel(key)} down`"
|
|
95
|
-
@click="moveColumn(key, 1)"
|
|
96
|
-
>
|
|
97
|
-
<HugeiconsIcon
|
|
98
|
-
:icon="ArrowDown01Icon"
|
|
99
|
-
width="0.9em"
|
|
100
|
-
height="0.9em"
|
|
101
|
-
:strokeWidth="2"
|
|
102
|
-
aria-hidden="true"
|
|
103
|
-
/>
|
|
104
|
-
</button>
|
|
105
|
-
</div>
|
|
106
|
-
|
|
107
|
-
<span
|
|
108
|
-
class="column-options-drag-handle"
|
|
109
|
-
:class="{ disabled: !isOrderable(key) }"
|
|
110
|
-
aria-hidden="true"
|
|
111
|
-
>
|
|
112
|
-
<HugeiconsIcon
|
|
113
|
-
:icon="DragDropVerticalIcon"
|
|
114
|
-
width="0.95em"
|
|
115
|
-
height="0.95em"
|
|
116
|
-
:strokeWidth="2"
|
|
46
|
+
<div ref="listScrollRef" class="column-options-scroll" :style="listScrollStyle">
|
|
47
|
+
<ReorderList
|
|
48
|
+
:model-value="draftOrder"
|
|
49
|
+
:item-key="columnKeyIdentity"
|
|
50
|
+
:item-label="columnLabel"
|
|
51
|
+
:is-item-reorder-disabled="(key) => !isOrderable(key)"
|
|
52
|
+
aria-label="Column order"
|
|
53
|
+
:show-move-buttons="false"
|
|
54
|
+
:show-drag-handles="true"
|
|
55
|
+
@update:model-value="onDraftOrderUpdate"
|
|
56
|
+
>
|
|
57
|
+
<template #item="{ item: key }">
|
|
58
|
+
<div class="column-options-row-body">
|
|
59
|
+
<label class="column-options-label">
|
|
60
|
+
<input
|
|
61
|
+
type="checkbox"
|
|
62
|
+
:checked="isVisible(key)"
|
|
63
|
+
:disabled="!isHideable(key) || (isVisible(key) && draftVisibleKeys.length <= 1)"
|
|
64
|
+
@change="toggleVisible(key)"
|
|
117
65
|
/>
|
|
118
|
-
|
|
66
|
+
<span>{{ columnDisplayLabel(key) }}</span>
|
|
67
|
+
</label>
|
|
68
|
+
|
|
69
|
+
<select
|
|
70
|
+
v-if="isPriorityConfigurable(key)"
|
|
71
|
+
class="column-options-priority"
|
|
72
|
+
:value="prioritySelectValue(key)"
|
|
73
|
+
:aria-label="`Priority for ${columnLabel(key)} column`"
|
|
74
|
+
@change="setPriority(key, $event.target.value)"
|
|
75
|
+
>
|
|
76
|
+
<option value="">Never hide</option>
|
|
77
|
+
<option
|
|
78
|
+
v-for="level in priorityLevels"
|
|
79
|
+
:key="level"
|
|
80
|
+
:value="level"
|
|
81
|
+
>
|
|
82
|
+
{{ priorityOptionLabel(level) }}
|
|
83
|
+
</option>
|
|
84
|
+
</select>
|
|
85
|
+
<span v-else class="column-options-priority-spacer" aria-hidden="true" />
|
|
119
86
|
</div>
|
|
120
|
-
</
|
|
121
|
-
</
|
|
87
|
+
</template>
|
|
88
|
+
</ReorderList>
|
|
122
89
|
</div>
|
|
123
90
|
</template>
|
|
124
91
|
|
|
@@ -173,13 +140,15 @@
|
|
|
173
140
|
<p v-else class="subtle column-options-tab-placeholder">No saved layouts yet.</p>
|
|
174
141
|
</template>
|
|
175
142
|
<p v-else class="subtle column-options-tab-placeholder">
|
|
176
|
-
|
|
143
|
+
Set <code>view-control-mode="localStorageControlled"</code> with a
|
|
144
|
+
<code>table-id</code>, or <code>view-control-mode="callbackControlled"</code>
|
|
145
|
+
with <code>view-settings</code> callbacks, to enable saved layouts.
|
|
177
146
|
</p>
|
|
178
147
|
|
|
179
148
|
<p v-if="loadError" class="column-options-tab-error">{{ loadError }}</p>
|
|
180
149
|
|
|
181
|
-
<div class="column-options-load-default">
|
|
182
|
-
<button type="button" class="neutral" @click="
|
|
150
|
+
<div v-if="defaultPresetId" class="column-options-load-default">
|
|
151
|
+
<button type="button" class="neutral" @click="submitLoadPreset(defaultPresetId)">
|
|
183
152
|
Load default
|
|
184
153
|
</button>
|
|
185
154
|
</div>
|
|
@@ -188,35 +157,76 @@
|
|
|
188
157
|
|
|
189
158
|
<template #tab-save>
|
|
190
159
|
<div v-if="!layoutPresetsEnabled" class="column-options-tab-placeholder">
|
|
191
|
-
<p class="subtle">
|
|
160
|
+
<p class="subtle">
|
|
161
|
+
Set <code>view-control-mode="localStorageControlled"</code> with a
|
|
162
|
+
<code>table-id</code>, or <code>view-control-mode="callbackControlled"</code>
|
|
163
|
+
with <code>view-settings</code> callbacks, to enable saved layouts.
|
|
164
|
+
</p>
|
|
192
165
|
</div>
|
|
193
166
|
<div v-else class="column-options-save-panel">
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
167
|
+
<div class="column-options-save-overwrite">
|
|
168
|
+
<p class="field-label">Saved views</p>
|
|
169
|
+
<ul v-if="presets.length > 0" class="column-options-preset-list">
|
|
170
|
+
<li
|
|
171
|
+
v-for="preset in presets"
|
|
172
|
+
:key="preset.id"
|
|
173
|
+
class="column-options-preset-item"
|
|
174
|
+
>
|
|
175
|
+
<div class="column-options-preset-meta">
|
|
176
|
+
<strong>{{ preset.name }}</strong>
|
|
177
|
+
<span v-if="preset.id === defaultPresetId" class="tag">Default</span>
|
|
178
|
+
<p class="subtle column-options-preset-date">{{ formatPresetDate(preset.savedAt) }}</p>
|
|
179
|
+
</div>
|
|
180
|
+
<div
|
|
181
|
+
class="column-options-preset-actions"
|
|
182
|
+
role="group"
|
|
183
|
+
:aria-label="`Overwrite ${preset.name}`"
|
|
184
|
+
>
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
class="neutral"
|
|
188
|
+
@click="submitOverwritePreset(preset.id, preset.name)"
|
|
189
|
+
>
|
|
190
|
+
Overwrite
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
193
|
+
</li>
|
|
194
|
+
</ul>
|
|
195
|
+
<p v-else class="subtle column-options-tab-placeholder">No saved layouts yet.</p>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<div class="column-options-save-as">
|
|
199
|
+
<p class="field-label">Save as</p>
|
|
200
|
+
<p class="subtle column-options-save-help">
|
|
201
|
+
Saves the current table view (filters, group by, page size, column order, visibility, and priorities).
|
|
202
|
+
</p>
|
|
203
|
+
<label class="column-options-save-field">
|
|
204
|
+
<span class="field-label">Layout name</span>
|
|
205
|
+
<input
|
|
206
|
+
v-model="savePresetName"
|
|
207
|
+
type="text"
|
|
208
|
+
placeholder="e.g. Managers view"
|
|
209
|
+
@keydown.enter.prevent="submitSavePreset"
|
|
210
|
+
/>
|
|
211
|
+
</label>
|
|
212
|
+
<label class="column-options-save-default">
|
|
213
|
+
<input v-model="saveAsDefault" type="checkbox" />
|
|
214
|
+
<span>Set as default</span>
|
|
215
|
+
</label>
|
|
216
|
+
<p v-if="saveError" class="column-options-tab-error">{{ saveError }}</p>
|
|
217
|
+
<button type="button" @click="submitSavePreset">Save as new layout</button>
|
|
218
|
+
</div>
|
|
213
219
|
</div>
|
|
214
220
|
</template>
|
|
215
221
|
</Tabs>
|
|
216
222
|
</div>
|
|
217
223
|
|
|
218
|
-
<div
|
|
219
|
-
|
|
224
|
+
<div
|
|
225
|
+
v-if="activeOptionTab === 'current'"
|
|
226
|
+
ref="actionsRef"
|
|
227
|
+
role="toolbar"
|
|
228
|
+
class="popover-actions"
|
|
229
|
+
>
|
|
220
230
|
<button type="button" class="neutral" @click="showAll">Show all</button>
|
|
221
231
|
</div>
|
|
222
232
|
</div>
|
|
@@ -225,24 +235,25 @@
|
|
|
225
235
|
|
|
226
236
|
<script setup>
|
|
227
237
|
import { computed, nextTick, onBeforeUnmount, ref, useId, watch } from 'vue'
|
|
228
|
-
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
229
|
-
import {
|
|
230
|
-
ArrowDown01Icon,
|
|
231
|
-
ArrowUp01Icon,
|
|
232
|
-
DragDropVerticalIcon,
|
|
233
|
-
} from '@hugeicons/core-free-icons'
|
|
234
238
|
import {
|
|
235
239
|
isFilterEntryActive,
|
|
236
240
|
normalizeColumnFilterEntries,
|
|
237
241
|
} from '../composables/tableFilters.js'
|
|
242
|
+
import { groupableHeaders } from '../composables/tableGroupBy.js'
|
|
243
|
+
import { useNotificationPopups } from '../composables/useNotificationPopups.js'
|
|
244
|
+
import ReorderList from './ReorderList.vue'
|
|
238
245
|
import Tabs from './Tabs.vue'
|
|
239
246
|
|
|
247
|
+
const { show: showNotificationPopup } = useNotificationPopups()
|
|
248
|
+
|
|
240
249
|
const optionTabs = [
|
|
241
|
-
{ id: '
|
|
250
|
+
{ id: 'current', label: 'Current' },
|
|
242
251
|
{ id: 'load', label: 'Load' },
|
|
243
252
|
{ id: 'save', label: 'Save' },
|
|
244
253
|
]
|
|
245
254
|
|
|
255
|
+
const activeOptionTab = ref('current')
|
|
256
|
+
|
|
246
257
|
const props = defineProps({
|
|
247
258
|
open: {
|
|
248
259
|
type: Boolean,
|
|
@@ -300,6 +311,10 @@ const props = defineProps({
|
|
|
300
311
|
type: Function,
|
|
301
312
|
default: null,
|
|
302
313
|
},
|
|
314
|
+
overwriteLayoutPreset: {
|
|
315
|
+
type: Function,
|
|
316
|
+
default: null,
|
|
317
|
+
},
|
|
303
318
|
loadLayoutPreset: {
|
|
304
319
|
type: Function,
|
|
305
320
|
default: null,
|
|
@@ -316,6 +331,14 @@ const props = defineProps({
|
|
|
316
331
|
type: Function,
|
|
317
332
|
default: null,
|
|
318
333
|
},
|
|
334
|
+
groupBy: {
|
|
335
|
+
type: String,
|
|
336
|
+
default: null,
|
|
337
|
+
},
|
|
338
|
+
groupable: {
|
|
339
|
+
type: Boolean,
|
|
340
|
+
default: true,
|
|
341
|
+
},
|
|
319
342
|
})
|
|
320
343
|
|
|
321
344
|
const emit = defineEmits(['update:open', 'apply', 'cancel'])
|
|
@@ -335,12 +358,10 @@ const priorityLevels = [1, 2, 3, 4, 5]
|
|
|
335
358
|
const draftOrder = ref([])
|
|
336
359
|
const draftVisibleKeys = ref([])
|
|
337
360
|
const draftPriorities = ref({})
|
|
338
|
-
const
|
|
339
|
-
const dragOverKey = ref(null)
|
|
361
|
+
const draftGroupBy = ref(null)
|
|
340
362
|
const savePresetName = ref('')
|
|
341
363
|
const saveAsDefault = ref(false)
|
|
342
364
|
const saveError = ref('')
|
|
343
|
-
const saveSuccess = ref('')
|
|
344
365
|
const loadError = ref('')
|
|
345
366
|
|
|
346
367
|
const headerByKey = computed(() => new Map(
|
|
@@ -349,10 +370,16 @@ const headerByKey = computed(() => new Map(
|
|
|
349
370
|
.map((header) => [header.key, header]),
|
|
350
371
|
))
|
|
351
372
|
|
|
373
|
+
const groupableColumnOptions = computed(() => groupableHeaders(props.headers, props.groupable))
|
|
374
|
+
|
|
352
375
|
function columnLabel(key) {
|
|
353
376
|
return headerByKey.value.get(key)?.label || key
|
|
354
377
|
}
|
|
355
378
|
|
|
379
|
+
function columnKeyIdentity(key) {
|
|
380
|
+
return key
|
|
381
|
+
}
|
|
382
|
+
|
|
356
383
|
function columnFilterCount(key) {
|
|
357
384
|
const header = headerByKey.value.get(key)
|
|
358
385
|
if (!header || header.filterable === false) {
|
|
@@ -474,7 +501,7 @@ function positionPopover() {
|
|
|
474
501
|
function updateListScrollLimit() {
|
|
475
502
|
listScrollStyle.value = {}
|
|
476
503
|
|
|
477
|
-
if (!popoverRef.value || !listScrollRef.value) {
|
|
504
|
+
if (activeOptionTab.value !== 'current' || !popoverRef.value || !listScrollRef.value) {
|
|
478
505
|
return
|
|
479
506
|
}
|
|
480
507
|
|
|
@@ -483,7 +510,9 @@ function updateListScrollLimit() {
|
|
|
483
510
|
const popoverEl = popoverRef.value
|
|
484
511
|
const titleHeight = titleRef.value?.offsetHeight ?? 0
|
|
485
512
|
const tabsHeaderHeight = popoverEl.querySelector('.column-options-tabs .tabs-header')?.offsetHeight ?? 0
|
|
486
|
-
const
|
|
513
|
+
const activePanel = popoverEl.querySelector('.column-options-tabs .tab-panel.active')
|
|
514
|
+
const helpHeight = activePanel?.querySelector('.column-options-help')?.offsetHeight ?? 0
|
|
515
|
+
const groupByHeight = activePanel?.querySelector('.column-options-group-by')?.offsetHeight ?? 0
|
|
487
516
|
const actionsHeight = actionsRef.value?.offsetHeight ?? 0
|
|
488
517
|
const popoverStyles = window.getComputedStyle(popoverEl)
|
|
489
518
|
const paddingTop = Number.parseFloat(popoverStyles.paddingTop) || 0
|
|
@@ -497,10 +526,10 @@ function updateListScrollLimit() {
|
|
|
497
526
|
availablePopoverHeight = Math.max(spaceBelow, spaceAbove)
|
|
498
527
|
}
|
|
499
528
|
|
|
500
|
-
const reservedHeight = paddingTop + paddingBottom + titleHeight + tabsHeaderHeight + helpHeight + actionsHeight + bodyGap
|
|
529
|
+
const reservedHeight = paddingTop + paddingBottom + titleHeight + tabsHeaderHeight + helpHeight + groupByHeight + actionsHeight + bodyGap
|
|
501
530
|
const optionsAreaBudget = Math.max(0, availablePopoverHeight - reservedHeight)
|
|
502
531
|
|
|
503
|
-
const firstItem = listScrollRef.value.querySelector('.
|
|
532
|
+
const firstItem = listScrollRef.value.querySelector('.reorder-list-row')
|
|
504
533
|
const rowHeight = firstItem?.offsetHeight || 40
|
|
505
534
|
const optionCount = draftOrder.value.length
|
|
506
535
|
|
|
@@ -529,7 +558,6 @@ function resetPresetForm() {
|
|
|
529
558
|
savePresetName.value = ''
|
|
530
559
|
saveAsDefault.value = false
|
|
531
560
|
saveError.value = ''
|
|
532
|
-
saveSuccess.value = ''
|
|
533
561
|
loadError.value = ''
|
|
534
562
|
}
|
|
535
563
|
|
|
@@ -546,7 +574,6 @@ function formatPresetDate(savedAt) {
|
|
|
546
574
|
|
|
547
575
|
async function submitSavePreset() {
|
|
548
576
|
saveError.value = ''
|
|
549
|
-
saveSuccess.value = ''
|
|
550
577
|
|
|
551
578
|
if (!props.saveLayoutPreset) {
|
|
552
579
|
saveError.value = 'Save is unavailable.'
|
|
@@ -563,11 +590,40 @@ async function submitSavePreset() {
|
|
|
563
590
|
return
|
|
564
591
|
}
|
|
565
592
|
|
|
566
|
-
|
|
593
|
+
showNotificationPopup({
|
|
594
|
+
label: 'Saved',
|
|
595
|
+
class: 'success',
|
|
596
|
+
message: `Saved “${result.preset.name}”.`,
|
|
597
|
+
})
|
|
567
598
|
savePresetName.value = ''
|
|
568
599
|
saveAsDefault.value = false
|
|
569
600
|
}
|
|
570
601
|
|
|
602
|
+
async function submitOverwritePreset(presetId, presetName) {
|
|
603
|
+
saveError.value = ''
|
|
604
|
+
|
|
605
|
+
if (!props.overwriteLayoutPreset) {
|
|
606
|
+
saveError.value = 'Overwrite is unavailable.'
|
|
607
|
+
return
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (!globalThis.confirm?.(`Overwrite saved layout “${presetName}” with the current table view?`)) {
|
|
611
|
+
return
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const result = await Promise.resolve(props.overwriteLayoutPreset({ presetId }))
|
|
615
|
+
if (!result?.ok) {
|
|
616
|
+
saveError.value = result?.error || 'Unable to overwrite layout.'
|
|
617
|
+
return
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
showNotificationPopup({
|
|
621
|
+
label: 'Saved',
|
|
622
|
+
class: 'success',
|
|
623
|
+
message: `Overwrote “${presetName}”.`,
|
|
624
|
+
})
|
|
625
|
+
}
|
|
626
|
+
|
|
571
627
|
async function submitLoadPreset(presetId) {
|
|
572
628
|
loadError.value = ''
|
|
573
629
|
const result = await Promise.resolve(props.loadLayoutPreset?.(presetId))
|
|
@@ -612,6 +668,12 @@ function resetDraft() {
|
|
|
612
668
|
draftOrder.value = [...props.columnKeys]
|
|
613
669
|
draftVisibleKeys.value = [...props.visibleKeys]
|
|
614
670
|
draftPriorities.value = { ...props.columnPriorities }
|
|
671
|
+
draftGroupBy.value = props.groupBy || null
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function setGroupBy(rawValue) {
|
|
675
|
+
draftGroupBy.value = rawValue || null
|
|
676
|
+
emitLiveChange()
|
|
615
677
|
}
|
|
616
678
|
|
|
617
679
|
function toggleVisible(key) {
|
|
@@ -632,90 +694,22 @@ function toggleVisible(key) {
|
|
|
632
694
|
emitLiveChange()
|
|
633
695
|
}
|
|
634
696
|
|
|
635
|
-
function
|
|
636
|
-
if (!
|
|
637
|
-
return false
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
let index = draftOrder.value.indexOf(key)
|
|
641
|
-
while (index >= 0 && index < draftOrder.value.length) {
|
|
642
|
-
index += direction
|
|
643
|
-
if (index < 0 || index >= draftOrder.value.length) {
|
|
644
|
-
return false
|
|
645
|
-
}
|
|
646
|
-
if (isOrderable(draftOrder.value[index])) {
|
|
647
|
-
return true
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
return false
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function moveColumn(key, direction) {
|
|
655
|
-
if (!canMove(key, direction)) {
|
|
697
|
+
function onDraftOrderUpdate(next) {
|
|
698
|
+
if (!Array.isArray(next) || next.length !== draftOrder.value.length) {
|
|
656
699
|
return
|
|
657
700
|
}
|
|
658
701
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
while (true) {
|
|
663
|
-
const targetIndex = index + direction
|
|
664
|
-
if (targetIndex < 0 || targetIndex >= order.length) {
|
|
665
|
-
return
|
|
666
|
-
}
|
|
667
|
-
if (isOrderable(order[targetIndex])) {
|
|
668
|
-
const swapKey = order[targetIndex]
|
|
669
|
-
order[index] = swapKey
|
|
670
|
-
order[targetIndex] = key
|
|
671
|
-
draftOrder.value = order
|
|
672
|
-
emitLiveChange()
|
|
702
|
+
for (let index = 0; index < next.length; index += 1) {
|
|
703
|
+
const previousKey = draftOrder.value[index]
|
|
704
|
+
if (!isOrderable(previousKey) && next[index] !== previousKey) {
|
|
673
705
|
return
|
|
674
706
|
}
|
|
675
|
-
index = targetIndex
|
|
676
707
|
}
|
|
677
|
-
}
|
|
678
708
|
|
|
679
|
-
|
|
680
|
-
if (!isOrderable(key)) {
|
|
681
|
-
event.preventDefault()
|
|
682
|
-
return
|
|
683
|
-
}
|
|
684
|
-
draggingKey.value = key
|
|
685
|
-
dragOverKey.value = key
|
|
686
|
-
event.dataTransfer.effectAllowed = 'move'
|
|
687
|
-
event.dataTransfer.setData('text/plain', key)
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function onDragOver(key) {
|
|
691
|
-
if (!draggingKey.value || draggingKey.value === key) {
|
|
692
|
-
return
|
|
693
|
-
}
|
|
694
|
-
dragOverKey.value = key
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
function onDrop(targetKey) {
|
|
698
|
-
const sourceKey = draggingKey.value
|
|
699
|
-
if (!sourceKey || sourceKey === targetKey) {
|
|
700
|
-
return
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
const order = draftOrder.value.filter((key) => key !== sourceKey)
|
|
704
|
-
const targetIndex = order.indexOf(targetKey)
|
|
705
|
-
if (targetIndex === -1) {
|
|
706
|
-
return
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
order.splice(targetIndex, 0, sourceKey)
|
|
710
|
-
draftOrder.value = order
|
|
709
|
+
draftOrder.value = next
|
|
711
710
|
emitLiveChange()
|
|
712
711
|
}
|
|
713
712
|
|
|
714
|
-
function onDragEnd() {
|
|
715
|
-
draggingKey.value = null
|
|
716
|
-
dragOverKey.value = null
|
|
717
|
-
}
|
|
718
|
-
|
|
719
713
|
function onDocumentPointerDown(event) {
|
|
720
714
|
if (!props.open || !popoverRef.value) {
|
|
721
715
|
return
|
|
@@ -772,6 +766,7 @@ function emitLiveChange() {
|
|
|
772
766
|
order: [...draftOrder.value],
|
|
773
767
|
visibleKeys,
|
|
774
768
|
priorities: buildPriorityOverrides(),
|
|
769
|
+
groupBy: draftGroupBy.value || null,
|
|
775
770
|
})
|
|
776
771
|
}
|
|
777
772
|
|
|
@@ -780,7 +775,8 @@ function showAll() {
|
|
|
780
775
|
emitLiveChange()
|
|
781
776
|
}
|
|
782
777
|
|
|
783
|
-
async function onOptionTabChange() {
|
|
778
|
+
async function onOptionTabChange(_tab, tabKey) {
|
|
779
|
+
activeOptionTab.value = tabKey ?? 'current'
|
|
784
780
|
await nextTick()
|
|
785
781
|
updateListScrollLimit()
|
|
786
782
|
await nextTick()
|
|
@@ -793,6 +789,7 @@ function close() {
|
|
|
793
789
|
}
|
|
794
790
|
|
|
795
791
|
async function initializePopover() {
|
|
792
|
+
activeOptionTab.value = 'current'
|
|
796
793
|
resetDraft()
|
|
797
794
|
resetPresetForm()
|
|
798
795
|
await nextTick()
|
|
@@ -807,8 +804,6 @@ watch(
|
|
|
807
804
|
async (isOpen, wasOpen) => {
|
|
808
805
|
if (!isOpen) {
|
|
809
806
|
listScrollStyle.value = {}
|
|
810
|
-
draggingKey.value = null
|
|
811
|
-
dragOverKey.value = null
|
|
812
807
|
document.removeEventListener('pointerdown', onDocumentPointerDown)
|
|
813
808
|
document.removeEventListener('keydown', onDocumentKeyDown)
|
|
814
809
|
return
|
|
@@ -875,6 +870,18 @@ onBeforeUnmount(() => {
|
|
|
875
870
|
flex-shrink: 0;
|
|
876
871
|
}
|
|
877
872
|
|
|
873
|
+
.column-options-group-by {
|
|
874
|
+
display: flex;
|
|
875
|
+
flex-direction: column;
|
|
876
|
+
gap: 0.35rem;
|
|
877
|
+
margin: 0 0 0.75rem;
|
|
878
|
+
flex-shrink: 0;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
.column-options-group-by select {
|
|
882
|
+
width: 100%;
|
|
883
|
+
}
|
|
884
|
+
|
|
878
885
|
.column-options-tabs {
|
|
879
886
|
flex: 1 1 auto;
|
|
880
887
|
min-height: 0;
|
|
@@ -889,7 +896,7 @@ onBeforeUnmount(() => {
|
|
|
889
896
|
min-height: 0;
|
|
890
897
|
}
|
|
891
898
|
|
|
892
|
-
.column-options-tabs :deep(.tab-panel) {
|
|
899
|
+
.column-options-tabs :deep(.tab-panel:not([hidden])) {
|
|
893
900
|
padding-block: 0.75rem;
|
|
894
901
|
padding-inline: 0;
|
|
895
902
|
min-height: 0;
|
|
@@ -897,6 +904,10 @@ onBeforeUnmount(() => {
|
|
|
897
904
|
flex-direction: column;
|
|
898
905
|
}
|
|
899
906
|
|
|
907
|
+
.column-options-tabs :deep(.tab-panel[hidden]) {
|
|
908
|
+
display: none;
|
|
909
|
+
}
|
|
910
|
+
|
|
900
911
|
.column-options-tabs :deep(.tab-button) {
|
|
901
912
|
padding: 0.45rem 0.85rem;
|
|
902
913
|
font-size: 0.9rem;
|
|
@@ -930,6 +941,21 @@ onBeforeUnmount(() => {
|
|
|
930
941
|
gap: 0.45rem;
|
|
931
942
|
}
|
|
932
943
|
|
|
944
|
+
.column-options-save-overwrite {
|
|
945
|
+
display: flex;
|
|
946
|
+
flex-direction: column;
|
|
947
|
+
gap: 0.5rem;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
.column-options-save-as {
|
|
951
|
+
display: flex;
|
|
952
|
+
flex-direction: column;
|
|
953
|
+
gap: 0.75rem;
|
|
954
|
+
margin-top: 1.25rem;
|
|
955
|
+
padding-top: 1rem;
|
|
956
|
+
border-top: 1px solid var(--border-color);
|
|
957
|
+
}
|
|
958
|
+
|
|
933
959
|
.field-label {
|
|
934
960
|
font-size: 0.85rem;
|
|
935
961
|
color: var(--table-popover-muted-fg);
|
|
@@ -941,12 +967,6 @@ onBeforeUnmount(() => {
|
|
|
941
967
|
color: var(--table-popover-error-fg);
|
|
942
968
|
}
|
|
943
969
|
|
|
944
|
-
.column-options-tab-success {
|
|
945
|
-
margin: 0;
|
|
946
|
-
font-size: 0.85rem;
|
|
947
|
-
color: var(--table-popover-success-fg);
|
|
948
|
-
}
|
|
949
|
-
|
|
950
970
|
.column-options-load-panel {
|
|
951
971
|
display: flex;
|
|
952
972
|
flex-direction: column;
|
|
@@ -1008,49 +1028,23 @@ onBeforeUnmount(() => {
|
|
|
1008
1028
|
overscroll-behavior: contain;
|
|
1009
1029
|
}
|
|
1010
1030
|
|
|
1011
|
-
.column-options-list {
|
|
1012
|
-
list-style: none;
|
|
1013
|
-
margin: 0;
|
|
1014
|
-
padding: 0;
|
|
1015
|
-
display: flex;
|
|
1016
|
-
flex-direction: column;
|
|
1031
|
+
.column-options-scroll :deep(.reorder-list) {
|
|
1017
1032
|
gap: 0.35rem;
|
|
1018
1033
|
}
|
|
1019
1034
|
|
|
1020
|
-
.column-options-
|
|
1021
|
-
display: grid;
|
|
1022
|
-
grid-template-columns: minmax(0, 1fr) 5.5rem auto;
|
|
1023
|
-
align-items: center;
|
|
1024
|
-
gap: 0.5rem;
|
|
1035
|
+
.column-options-scroll :deep(.reorder-list-row) {
|
|
1025
1036
|
padding: 0.25rem 0.45rem;
|
|
1026
|
-
|
|
1027
|
-
border-
|
|
1037
|
+
gap: 0.5rem;
|
|
1038
|
+
border-color: var(--table-popover-border);
|
|
1028
1039
|
background: var(--table-popover-item-bg);
|
|
1029
1040
|
}
|
|
1030
1041
|
|
|
1031
|
-
.column-options-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
.column-options-item.is-drag-over {
|
|
1036
|
-
border-color: var(--table-popover-item-drag-border);
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
.column-options-item.is-fixed {
|
|
1040
|
-
opacity: 0.85;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
.column-options-drag-handle {
|
|
1044
|
-
display: inline-flex;
|
|
1042
|
+
.column-options-row-body {
|
|
1043
|
+
display: grid;
|
|
1044
|
+
grid-template-columns: minmax(0, 1fr) 5.5rem;
|
|
1045
1045
|
align-items: center;
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
cursor: grab;
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
.column-options-item.is-fixed .column-options-drag-handle.disabled {
|
|
1052
|
-
opacity: 0.35;
|
|
1053
|
-
cursor: default;
|
|
1046
|
+
gap: 0.5rem;
|
|
1047
|
+
min-width: 0;
|
|
1054
1048
|
}
|
|
1055
1049
|
|
|
1056
1050
|
.column-options-label {
|
|
@@ -1079,25 +1073,6 @@ onBeforeUnmount(() => {
|
|
|
1079
1073
|
display: block;
|
|
1080
1074
|
}
|
|
1081
1075
|
|
|
1082
|
-
.column-options-reorder {
|
|
1083
|
-
display: inline-flex;
|
|
1084
|
-
align-items: center;
|
|
1085
|
-
gap: 0.25rem;
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
.column-options-move {
|
|
1089
|
-
display: inline-flex;
|
|
1090
|
-
gap: 0.2rem;
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
.column-options-move-button {
|
|
1094
|
-
display: inline-flex;
|
|
1095
|
-
align-items: center;
|
|
1096
|
-
justify-content: center;
|
|
1097
|
-
min-width: 1.75rem;
|
|
1098
|
-
padding: 0.2rem 0.35rem;
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
1076
|
.popover-actions {
|
|
1102
1077
|
display: flex;
|
|
1103
1078
|
flex-shrink: 0;
|