picocrank 1.31.0 → 1.32.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/package.json +1 -1
- package/router.js +11 -0
- package/styles/picocrank-extensions.css +96 -6
- package/vue/components/Calendar.vue +41 -63
- package/vue/components/Header.vue +5 -1
- package/vue/components/LoadingArea.spec.js +72 -0
- package/vue/components/LoadingArea.vue +176 -0
- package/vue/components/Sidebar.vue +28 -0
- package/vue/components/StatusCard.vue +211 -0
- package/vue/components/Table.spec.js +253 -0
- package/vue/components/Table.vue +379 -2
- package/vue/components/TableColumnFilterPopover.vue +56 -8
- package/vue/components/TableRowContextMenu.vue +267 -0
- package/vue/data/navSectionHubs.js +5 -0
- package/vue/examples/Admin.vue +148 -53
- package/vue/examples/LoadingAreaExample.vue +175 -0
- package/vue/examples/TableExample.vue +156 -4
package/vue/components/Table.vue
CHANGED
|
@@ -7,6 +7,21 @@
|
|
|
7
7
|
<table class="row-hover" :class="{ loading: isLoading }">
|
|
8
8
|
<thead>
|
|
9
9
|
<tr>
|
|
10
|
+
<th
|
|
11
|
+
v-if="selectable"
|
|
12
|
+
class="table-select-col"
|
|
13
|
+
@click.stop
|
|
14
|
+
>
|
|
15
|
+
<label class="table-select-label">
|
|
16
|
+
<input
|
|
17
|
+
type="checkbox"
|
|
18
|
+
:checked="allDisplayRowsSelected"
|
|
19
|
+
:indeterminate="someDisplayRowsSelected"
|
|
20
|
+
:aria-label="allDisplayRowsSelected ? 'Deselect all rows on this page' : 'Select all rows on this page'"
|
|
21
|
+
@change="toggleSelectAllDisplayRows"
|
|
22
|
+
>
|
|
23
|
+
</label>
|
|
24
|
+
</th>
|
|
10
25
|
<th
|
|
11
26
|
v-for="(header, index) in visibleHeaders"
|
|
12
27
|
:key="header.key || index"
|
|
@@ -62,9 +77,29 @@
|
|
|
62
77
|
<tr
|
|
63
78
|
v-for="(row, rowIndex) in displayRows"
|
|
64
79
|
:key="resolveRowKey(row, rowIndex)"
|
|
65
|
-
:class="{
|
|
80
|
+
:class="{
|
|
81
|
+
'row-clickable': rowClickable,
|
|
82
|
+
'row-selected': selectable && isRowSelected(row, rowIndex),
|
|
83
|
+
'row-context-menu-active': isRowContextMenuTarget(row, rowIndex),
|
|
84
|
+
}"
|
|
66
85
|
@click="onRowClick(row, rowIndex, $event)"
|
|
86
|
+
@contextmenu="onRowContextMenu(row, rowIndex, $event)"
|
|
67
87
|
>
|
|
88
|
+
<td
|
|
89
|
+
v-if="selectable"
|
|
90
|
+
class="table-select-col"
|
|
91
|
+
data-row-click-ignore
|
|
92
|
+
@click.stop
|
|
93
|
+
>
|
|
94
|
+
<label class="table-select-label">
|
|
95
|
+
<input
|
|
96
|
+
type="checkbox"
|
|
97
|
+
:checked="isRowSelected(row, rowIndex)"
|
|
98
|
+
:aria-label="`Select row ${resolveRowKey(row, rowIndex)}`"
|
|
99
|
+
@change="toggleRowSelection(row, rowIndex)"
|
|
100
|
+
>
|
|
101
|
+
</label>
|
|
102
|
+
</td>
|
|
68
103
|
<td
|
|
69
104
|
v-for="(header, cellIndex) in visibleHeaders"
|
|
70
105
|
:key="header.key || cellIndex"
|
|
@@ -120,9 +155,11 @@
|
|
|
120
155
|
:value="activeFilterValue"
|
|
121
156
|
:rows="props.data"
|
|
122
157
|
:select-options="activeSelectOptions"
|
|
158
|
+
:can-hide="canHideActiveFilterColumn"
|
|
123
159
|
@apply="onFilterApply"
|
|
124
160
|
@clear="onFilterClear"
|
|
125
161
|
@cancel="onFilterCancel"
|
|
162
|
+
@hide="onFilterHideColumn"
|
|
126
163
|
/>
|
|
127
164
|
|
|
128
165
|
<TableColumnOptionsPopover
|
|
@@ -148,6 +185,15 @@
|
|
|
148
185
|
@apply="onColumnOptionsApply"
|
|
149
186
|
@cancel="onColumnOptionsCancel"
|
|
150
187
|
/>
|
|
188
|
+
|
|
189
|
+
<TableRowContextMenu
|
|
190
|
+
v-model:open="rowContextMenuOpen"
|
|
191
|
+
:items="rowContextMenuItems"
|
|
192
|
+
:client-x="rowContextMenuX"
|
|
193
|
+
:client-y="rowContextMenuY"
|
|
194
|
+
:target-count="rowContextMenuTargetKeys.length"
|
|
195
|
+
@select="onRowContextMenuSelect"
|
|
196
|
+
/>
|
|
151
197
|
</template>
|
|
152
198
|
|
|
153
199
|
<script setup>
|
|
@@ -157,6 +203,7 @@ import { LayoutGridIcon } from '@hugeicons/core-free-icons'
|
|
|
157
203
|
import Pagination from './Pagination.vue'
|
|
158
204
|
import TableColumnFilterPopover from './TableColumnFilterPopover.vue'
|
|
159
205
|
import TableColumnOptionsPopover from './TableColumnOptionsPopover.vue'
|
|
206
|
+
import TableRowContextMenu from './TableRowContextMenu.vue'
|
|
160
207
|
import { useLongPress } from '../composables/useLongPress.js'
|
|
161
208
|
import {
|
|
162
209
|
buildSelectOptions,
|
|
@@ -276,6 +323,24 @@ const props = defineProps({
|
|
|
276
323
|
type: Object,
|
|
277
324
|
default: null,
|
|
278
325
|
},
|
|
326
|
+
selectable: {
|
|
327
|
+
type: Boolean,
|
|
328
|
+
default: false,
|
|
329
|
+
},
|
|
330
|
+
selectedKeys: {
|
|
331
|
+
type: Array,
|
|
332
|
+
default: undefined,
|
|
333
|
+
},
|
|
334
|
+
/**
|
|
335
|
+
* Row right-click menu. Array of items, or a function that returns items
|
|
336
|
+
* for the click context. Each item may include `action(ctx)` where
|
|
337
|
+
* `ctx.keys` / `ctx.rows` are the target selection (clicked row, or all
|
|
338
|
+
* selected rows when the click is inside the current multi-selection).
|
|
339
|
+
*/
|
|
340
|
+
rowContextMenu: {
|
|
341
|
+
type: [Array, Function],
|
|
342
|
+
default: null,
|
|
343
|
+
},
|
|
279
344
|
})
|
|
280
345
|
|
|
281
346
|
const emit = defineEmits([
|
|
@@ -284,6 +349,10 @@ const emit = defineEmits([
|
|
|
284
349
|
'query-change',
|
|
285
350
|
'fetch-error',
|
|
286
351
|
'row-click',
|
|
352
|
+
'update:selectedKeys',
|
|
353
|
+
'selection-change',
|
|
354
|
+
'row-context-menu',
|
|
355
|
+
'row-context-menu-action',
|
|
287
356
|
'update:columnVisibility',
|
|
288
357
|
'column-visibility-change',
|
|
289
358
|
'update:columnOrder',
|
|
@@ -297,8 +366,9 @@ const emit = defineEmits([
|
|
|
297
366
|
const attrs = useAttrs()
|
|
298
367
|
const slots = useSlots()
|
|
299
368
|
|
|
300
|
-
const rowClickable = computed(() => Boolean(attrs.onRowClick))
|
|
369
|
+
const rowClickable = computed(() => Boolean(attrs.onRowClick) || props.selectable)
|
|
301
370
|
const internalFilters = ref({})
|
|
371
|
+
const internalSelectedKeys = ref([])
|
|
302
372
|
const remoteRows = ref([])
|
|
303
373
|
const remoteTotal = ref(0)
|
|
304
374
|
const internalLoading = ref(false)
|
|
@@ -632,6 +702,72 @@ function resolveRowKey(row, index) {
|
|
|
632
702
|
return index
|
|
633
703
|
}
|
|
634
704
|
|
|
705
|
+
const activeSelectedKeys = computed(() => (
|
|
706
|
+
props.selectedKeys !== undefined ? props.selectedKeys : internalSelectedKeys.value
|
|
707
|
+
))
|
|
708
|
+
|
|
709
|
+
const selectedKeySet = computed(() => new Set(activeSelectedKeys.value))
|
|
710
|
+
|
|
711
|
+
function isRowSelected(row, index) {
|
|
712
|
+
return selectedKeySet.value.has(resolveRowKey(row, index))
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function setSelectedKeys(nextKeys) {
|
|
716
|
+
const unique = [...new Set(nextKeys)]
|
|
717
|
+
if (props.selectedKeys === undefined) {
|
|
718
|
+
internalSelectedKeys.value = unique
|
|
719
|
+
}
|
|
720
|
+
emit('update:selectedKeys', unique)
|
|
721
|
+
emit('selection-change', unique)
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function toggleRowSelection(row, index) {
|
|
725
|
+
const key = resolveRowKey(row, index)
|
|
726
|
+
const current = activeSelectedKeys.value
|
|
727
|
+
if (selectedKeySet.value.has(key)) {
|
|
728
|
+
setSelectedKeys(current.filter((item) => item !== key))
|
|
729
|
+
return
|
|
730
|
+
}
|
|
731
|
+
setSelectedKeys([...current, key])
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const displayRowKeys = computed(() =>
|
|
735
|
+
displayRows.value.map((row, index) => resolveRowKey(row, index)),
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
const allDisplayRowsSelected = computed(() => {
|
|
739
|
+
const keys = displayRowKeys.value
|
|
740
|
+
return keys.length > 0 && keys.every((key) => selectedKeySet.value.has(key))
|
|
741
|
+
})
|
|
742
|
+
|
|
743
|
+
const someDisplayRowsSelected = computed(() => {
|
|
744
|
+
if (allDisplayRowsSelected.value) {
|
|
745
|
+
return false
|
|
746
|
+
}
|
|
747
|
+
return displayRowKeys.value.some((key) => selectedKeySet.value.has(key))
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
function toggleSelectAllDisplayRows() {
|
|
751
|
+
const pageKeys = displayRowKeys.value
|
|
752
|
+
if (pageKeys.length === 0) {
|
|
753
|
+
return
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (allDisplayRowsSelected.value) {
|
|
757
|
+
const pageKeySet = new Set(pageKeys)
|
|
758
|
+
setSelectedKeys(activeSelectedKeys.value.filter((key) => !pageKeySet.has(key)))
|
|
759
|
+
return
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
setSelectedKeys([...activeSelectedKeys.value, ...pageKeys])
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
watch(() => props.selectable, (enabled) => {
|
|
766
|
+
if (!enabled) {
|
|
767
|
+
setSelectedKeys([])
|
|
768
|
+
}
|
|
769
|
+
})
|
|
770
|
+
|
|
635
771
|
function setFilters(nextFilters) {
|
|
636
772
|
const cloned = cloneFilters(nextFilters)
|
|
637
773
|
if (props.filters === undefined) {
|
|
@@ -686,9 +822,152 @@ function onRowClick(row, displayIndex, event) {
|
|
|
686
822
|
if (event.target.closest('a, button, input, select, textarea, [data-row-click-ignore]')) {
|
|
687
823
|
return
|
|
688
824
|
}
|
|
825
|
+
if (props.selectable) {
|
|
826
|
+
toggleRowSelection(row, displayIndex)
|
|
827
|
+
return
|
|
828
|
+
}
|
|
689
829
|
emit('row-click', { row, index: filteredRowIndex(displayIndex) })
|
|
690
830
|
}
|
|
691
831
|
|
|
832
|
+
const rowContextMenuOpen = ref(false)
|
|
833
|
+
const rowContextMenuX = ref(0)
|
|
834
|
+
const rowContextMenuY = ref(0)
|
|
835
|
+
const rowContextMenuItems = ref([])
|
|
836
|
+
const rowContextMenuTargetKeys = ref([])
|
|
837
|
+
const rowContextMenuAnchor = ref(null)
|
|
838
|
+
|
|
839
|
+
const rowContextMenuTargetKeySet = computed(() => new Set(rowContextMenuTargetKeys.value))
|
|
840
|
+
|
|
841
|
+
function isRowContextMenuTarget(row, index) {
|
|
842
|
+
return rowContextMenuOpen.value && rowContextMenuTargetKeySet.value.has(resolveRowKey(row, index))
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const hasRowContextMenu = computed(() => {
|
|
846
|
+
if (typeof props.rowContextMenu === 'function') {
|
|
847
|
+
return true
|
|
848
|
+
}
|
|
849
|
+
return Array.isArray(props.rowContextMenu) && props.rowContextMenu.length > 0
|
|
850
|
+
})
|
|
851
|
+
|
|
852
|
+
function buildRowLookup() {
|
|
853
|
+
const map = new Map()
|
|
854
|
+
const addRows = (rows) => {
|
|
855
|
+
rows.forEach((row, index) => {
|
|
856
|
+
const key = resolveRowKey(row, index)
|
|
857
|
+
if (!map.has(key)) {
|
|
858
|
+
map.set(key, row)
|
|
859
|
+
}
|
|
860
|
+
})
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (isRemote.value) {
|
|
864
|
+
addRows(remoteRows.value)
|
|
865
|
+
} else {
|
|
866
|
+
addRows(props.data)
|
|
867
|
+
}
|
|
868
|
+
addRows(displayRows.value)
|
|
869
|
+
return map
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function resolveTargetRows(keys) {
|
|
873
|
+
const lookup = buildRowLookup()
|
|
874
|
+
return keys.map((key) => lookup.get(key)).filter((row) => row != null)
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function normalizeContextMenuItems(rawItems) {
|
|
878
|
+
if (!Array.isArray(rawItems)) {
|
|
879
|
+
return []
|
|
880
|
+
}
|
|
881
|
+
return rawItems.filter((item) => item && (item.divider || item.label || item.id))
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function closeRowContextMenu() {
|
|
885
|
+
rowContextMenuOpen.value = false
|
|
886
|
+
rowContextMenuItems.value = []
|
|
887
|
+
rowContextMenuTargetKeys.value = []
|
|
888
|
+
rowContextMenuAnchor.value = null
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function onRowContextMenu(row, displayIndex, event) {
|
|
892
|
+
if (!hasRowContextMenu.value) {
|
|
893
|
+
return
|
|
894
|
+
}
|
|
895
|
+
if (event.target.closest('a, button, input, select, textarea, [data-row-click-ignore]')) {
|
|
896
|
+
return
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
event.preventDefault()
|
|
900
|
+
event.stopPropagation()
|
|
901
|
+
|
|
902
|
+
const key = resolveRowKey(row, displayIndex)
|
|
903
|
+
const index = filteredRowIndex(displayIndex)
|
|
904
|
+
let targetKeys
|
|
905
|
+
|
|
906
|
+
if (props.selectable && selectedKeySet.value.has(key) && activeSelectedKeys.value.length > 0) {
|
|
907
|
+
targetKeys = [...activeSelectedKeys.value]
|
|
908
|
+
} else {
|
|
909
|
+
targetKeys = [key]
|
|
910
|
+
if (props.selectable) {
|
|
911
|
+
setSelectedKeys([key])
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
const rows = resolveTargetRows(targetKeys)
|
|
916
|
+
const ctx = {
|
|
917
|
+
row,
|
|
918
|
+
key,
|
|
919
|
+
index,
|
|
920
|
+
rows,
|
|
921
|
+
keys: targetKeys,
|
|
922
|
+
selectedKeys: [...activeSelectedKeys.value],
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const rawItems = typeof props.rowContextMenu === 'function'
|
|
926
|
+
? props.rowContextMenu(ctx)
|
|
927
|
+
: props.rowContextMenu
|
|
928
|
+
const items = normalizeContextMenuItems(rawItems)
|
|
929
|
+
if (items.length === 0) {
|
|
930
|
+
closeRowContextMenu()
|
|
931
|
+
return
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
rowContextMenuAnchor.value = ctx
|
|
935
|
+
rowContextMenuTargetKeys.value = targetKeys
|
|
936
|
+
rowContextMenuItems.value = items
|
|
937
|
+
rowContextMenuX.value = event.clientX
|
|
938
|
+
rowContextMenuY.value = event.clientY
|
|
939
|
+
rowContextMenuOpen.value = true
|
|
940
|
+
|
|
941
|
+
emit('row-context-menu', {
|
|
942
|
+
...ctx,
|
|
943
|
+
event,
|
|
944
|
+
})
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function onRowContextMenuSelect(item) {
|
|
948
|
+
const anchor = rowContextMenuAnchor.value
|
|
949
|
+
const keys = [...rowContextMenuTargetKeys.value]
|
|
950
|
+
const lookup = buildRowLookup()
|
|
951
|
+
const rows = keys.map((key) => lookup.get(key)).filter((row) => row != null)
|
|
952
|
+
const selectedKeys = [...activeSelectedKeys.value]
|
|
953
|
+
const payload = {
|
|
954
|
+
item,
|
|
955
|
+
row: anchor?.row,
|
|
956
|
+
key: anchor?.key,
|
|
957
|
+
index: anchor?.index,
|
|
958
|
+
rows,
|
|
959
|
+
keys,
|
|
960
|
+
selectedKeys,
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (typeof item.action === 'function') {
|
|
964
|
+
item.action(payload)
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
emit('row-context-menu-action', payload)
|
|
968
|
+
closeRowContextMenu()
|
|
969
|
+
}
|
|
970
|
+
|
|
692
971
|
function openFilter(header, event) {
|
|
693
972
|
if (!isHeaderFilterable(header)) {
|
|
694
973
|
return
|
|
@@ -756,6 +1035,39 @@ function onFilterCancel() {
|
|
|
756
1035
|
activeFilterValue.value = []
|
|
757
1036
|
}
|
|
758
1037
|
|
|
1038
|
+
const canHideActiveFilterColumn = computed(() => {
|
|
1039
|
+
if (!showColumnOptionsButton.value || !activeFilterHeader.value) {
|
|
1040
|
+
return false
|
|
1041
|
+
}
|
|
1042
|
+
const header = activeFilterHeader.value
|
|
1043
|
+
if (!header.key || header.hideable === false || header.hidden) {
|
|
1044
|
+
return false
|
|
1045
|
+
}
|
|
1046
|
+
if (!isHeaderHidden(header) && visibleColumnKeys.value.length <= 1) {
|
|
1047
|
+
return false
|
|
1048
|
+
}
|
|
1049
|
+
return configurableHeaders.value.some((item) => item.key === header.key)
|
|
1050
|
+
})
|
|
1051
|
+
|
|
1052
|
+
function onFilterHideColumn() {
|
|
1053
|
+
const header = activeFilterHeader.value
|
|
1054
|
+
if (!header?.key || !canHideActiveFilterColumn.value) {
|
|
1055
|
+
onFilterCancel()
|
|
1056
|
+
return
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
const nextVisibleKeys = visibleColumnKeys.value.filter((key) => key !== header.key)
|
|
1060
|
+
if (nextVisibleKeys.length === 0) {
|
|
1061
|
+
onFilterCancel()
|
|
1062
|
+
return
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
setColumnVisibility(nextVisibleKeys)
|
|
1066
|
+
updateActiveLayoutLabel()
|
|
1067
|
+
filterPopoverOpen.value = false
|
|
1068
|
+
onFilterCancel()
|
|
1069
|
+
}
|
|
1070
|
+
|
|
759
1071
|
function buildColumnVisibility(visibleKeys) {
|
|
760
1072
|
const visibleKeySet = new Set(visibleKeys)
|
|
761
1073
|
return Object.fromEntries(
|
|
@@ -1457,10 +1769,75 @@ th:first-child {
|
|
|
1457
1769
|
padding-left: 1rem;
|
|
1458
1770
|
}
|
|
1459
1771
|
|
|
1772
|
+
th.table-select-col,
|
|
1773
|
+
td.table-select-col {
|
|
1774
|
+
width: 2.5rem;
|
|
1775
|
+
min-width: 2.5rem;
|
|
1776
|
+
max-width: 2.5rem;
|
|
1777
|
+
text-align: center;
|
|
1778
|
+
vertical-align: middle;
|
|
1779
|
+
padding-left: 1rem;
|
|
1780
|
+
padding-right: 0.25rem;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
.table-select-label {
|
|
1784
|
+
display: inline-flex;
|
|
1785
|
+
align-items: center;
|
|
1786
|
+
justify-content: center;
|
|
1787
|
+
margin: 0;
|
|
1788
|
+
cursor: pointer;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1460
1791
|
tbody tr.row-clickable {
|
|
1461
1792
|
cursor: pointer;
|
|
1462
1793
|
}
|
|
1463
1794
|
|
|
1795
|
+
tbody tr.row-selected > td {
|
|
1796
|
+
background-color: var(--control-checked-bg);
|
|
1797
|
+
color: var(--control-checked-fg);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
table.row-hover tbody tr.row-selected:hover > td,
|
|
1801
|
+
table.row-hover tbody tr.row-selected:focus-within > td,
|
|
1802
|
+
table.row-hover tbody tr.row-selected.row-context-menu-active > td {
|
|
1803
|
+
background-color: var(--control-checked-hover-bg);
|
|
1804
|
+
color: var(--control-checked-fg);
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
/* Keep hover styling while the row context menu is open (pointer has left the row). */
|
|
1808
|
+
table.row-hover tbody tr.row-context-menu-active > td {
|
|
1809
|
+
background-color: var(--table-row-hover-bg);
|
|
1810
|
+
color: var(--table-row-hover-fg);
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
/* Beat Femtocrank sticky cell backgrounds so selection remains visible while scrolling. */
|
|
1814
|
+
.table-scroll.sticky-cols-1 table tbody tr.row-selected > td,
|
|
1815
|
+
.table-scroll.sticky-cols-2 table tbody tr.row-selected > td,
|
|
1816
|
+
.table-scroll.sticky-cols-3 table tbody tr.row-selected > td {
|
|
1817
|
+
background-color: var(--control-checked-bg);
|
|
1818
|
+
color: var(--control-checked-fg);
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
.table-scroll.sticky-cols-1 table.row-hover tbody tr.row-selected:hover > td,
|
|
1822
|
+
.table-scroll.sticky-cols-1 table.row-hover tbody tr.row-selected:focus-within > td,
|
|
1823
|
+
.table-scroll.sticky-cols-1 table.row-hover tbody tr.row-selected.row-context-menu-active > td,
|
|
1824
|
+
.table-scroll.sticky-cols-2 table.row-hover tbody tr.row-selected:hover > td,
|
|
1825
|
+
.table-scroll.sticky-cols-2 table.row-hover tbody tr.row-selected:focus-within > td,
|
|
1826
|
+
.table-scroll.sticky-cols-2 table.row-hover tbody tr.row-selected.row-context-menu-active > td,
|
|
1827
|
+
.table-scroll.sticky-cols-3 table.row-hover tbody tr.row-selected:hover > td,
|
|
1828
|
+
.table-scroll.sticky-cols-3 table.row-hover tbody tr.row-selected:focus-within > td,
|
|
1829
|
+
.table-scroll.sticky-cols-3 table.row-hover tbody tr.row-selected.row-context-menu-active > td {
|
|
1830
|
+
background-color: var(--control-checked-hover-bg);
|
|
1831
|
+
color: var(--control-checked-fg);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
.table-scroll.sticky-cols-1 table.row-hover tbody tr.row-context-menu-active > td,
|
|
1835
|
+
.table-scroll.sticky-cols-2 table.row-hover tbody tr.row-context-menu-active > td,
|
|
1836
|
+
.table-scroll.sticky-cols-3 table.row-hover tbody tr.row-context-menu-active > td {
|
|
1837
|
+
background-color: var(--table-row-hover-bg);
|
|
1838
|
+
color: var(--table-row-hover-fg);
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1464
1841
|
.table-empty-state {
|
|
1465
1842
|
display: flex;
|
|
1466
1843
|
align-items: center;
|
|
@@ -10,9 +10,27 @@
|
|
|
10
10
|
@click.stop
|
|
11
11
|
>
|
|
12
12
|
<div class="popover-body">
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
<div class="popover-title-row">
|
|
14
|
+
<h4 :id="titleId" ref="titleRef" class="popover-title">
|
|
15
|
+
{{ popoverTitle }}
|
|
16
|
+
</h4>
|
|
17
|
+
<button
|
|
18
|
+
v-if="canHide"
|
|
19
|
+
type="button"
|
|
20
|
+
class="neutral inline-icon popover-hide-column"
|
|
21
|
+
:aria-label="`Hide ${columnLabel || 'column'} column`"
|
|
22
|
+
title="Hide column"
|
|
23
|
+
@click="hideColumn"
|
|
24
|
+
>
|
|
25
|
+
<HugeiconsIcon
|
|
26
|
+
:icon="ViewOffIcon"
|
|
27
|
+
width="1em"
|
|
28
|
+
height="1em"
|
|
29
|
+
:strokeWidth="2.5"
|
|
30
|
+
aria-hidden="true"
|
|
31
|
+
/>
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
16
34
|
|
|
17
35
|
<div v-if="filterType === 'text'" class="filter-fields">
|
|
18
36
|
<label>
|
|
@@ -148,7 +166,7 @@
|
|
|
148
166
|
<script setup>
|
|
149
167
|
import { computed, nextTick, onBeforeUnmount, ref, useId, watch } from 'vue'
|
|
150
168
|
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
151
|
-
import { Delete02Icon } from '@hugeicons/core-free-icons'
|
|
169
|
+
import { Delete02Icon, ViewOffIcon } from '@hugeicons/core-free-icons'
|
|
152
170
|
import CheckGroup from './CheckGroup.vue'
|
|
153
171
|
import RadioGroup from './RadioGroup.vue'
|
|
154
172
|
import {
|
|
@@ -186,9 +204,13 @@ const props = defineProps({
|
|
|
186
204
|
type: Array,
|
|
187
205
|
default: () => [],
|
|
188
206
|
},
|
|
207
|
+
canHide: {
|
|
208
|
+
type: Boolean,
|
|
209
|
+
default: false,
|
|
210
|
+
},
|
|
189
211
|
})
|
|
190
212
|
|
|
191
|
-
const emit = defineEmits(['update:open', 'apply', 'clear', 'cancel'])
|
|
213
|
+
const emit = defineEmits(['update:open', 'apply', 'clear', 'cancel', 'hide'])
|
|
192
214
|
|
|
193
215
|
const MIN_VISIBLE_OPTIONS = 3
|
|
194
216
|
const MAX_VISIBLE_OPTIONS = 20
|
|
@@ -441,6 +463,14 @@ function cancel() {
|
|
|
441
463
|
emit('cancel')
|
|
442
464
|
}
|
|
443
465
|
|
|
466
|
+
function hideColumn() {
|
|
467
|
+
if (!props.canHide) {
|
|
468
|
+
return
|
|
469
|
+
}
|
|
470
|
+
emit('hide')
|
|
471
|
+
emit('update:open', false)
|
|
472
|
+
}
|
|
473
|
+
|
|
444
474
|
function detachDocumentListeners() {
|
|
445
475
|
document.removeEventListener('pointerdown', onDocumentPointerDown)
|
|
446
476
|
document.removeEventListener('keydown', onDocumentKeyDown)
|
|
@@ -459,7 +489,9 @@ async function initializePopover() {
|
|
|
459
489
|
updateCheckGroupScrollLimit()
|
|
460
490
|
await nextTick()
|
|
461
491
|
positionPopover()
|
|
462
|
-
popoverRef.value?.querySelector(
|
|
492
|
+
popoverRef.value?.querySelector(
|
|
493
|
+
'.filter-fields input, .filter-fields select, .filter-fields button, .popover-actions button',
|
|
494
|
+
)?.focus()
|
|
463
495
|
attachDocumentListeners()
|
|
464
496
|
}
|
|
465
497
|
|
|
@@ -511,10 +543,26 @@ onBeforeUnmount(() => {
|
|
|
511
543
|
flex-direction: column;
|
|
512
544
|
}
|
|
513
545
|
|
|
546
|
+
.popover-title-row {
|
|
547
|
+
display: flex;
|
|
548
|
+
align-items: flex-start;
|
|
549
|
+
justify-content: space-between;
|
|
550
|
+
gap: 0.5rem;
|
|
551
|
+
margin-bottom: 0.75rem;
|
|
552
|
+
flex-shrink: 0;
|
|
553
|
+
}
|
|
554
|
+
|
|
514
555
|
.popover-title {
|
|
515
|
-
margin: 0
|
|
556
|
+
margin: 0;
|
|
516
557
|
font-size: 0.95rem;
|
|
517
|
-
|
|
558
|
+
min-width: 0;
|
|
559
|
+
flex: 1 1 auto;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.popover-hide-column {
|
|
563
|
+
flex: 0 0 auto;
|
|
564
|
+
padding: 0.2em 0.45em;
|
|
565
|
+
line-height: 1;
|
|
518
566
|
}
|
|
519
567
|
|
|
520
568
|
.filter-fields {
|