Skip to content

Commit 9ecc735

Browse files
committed
fix: added few missing props to the BTable.d.ts file
fix: replaced BTable emits definition array with a typescript interface for better type support BREAKING CHANGE: The event output value was changed to an object with 'sort[string]' and 'desc[boolean]' for better ts type support. feat: Added BTable selectable prop that adds the selection functionality to the table, Added select-mode prop with value of [multi, single, range] to expend the table selection can be used with. feat: Added Btable select-head prop to show an extra Head & Cell to the table, Added selectHead & selectCell slots to customize the selection extra head & cells.
1 parent ee40c14 commit 9ecc735

File tree

3 files changed

+96
-5
lines changed

3 files changed

+96
-5
lines changed

packages/bootstrap-vue-3/src/components/BTable/BTable.vue

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
<thead>
55
<slot v-if="$slots['thead-top']" name="thead-top" />
66
<tr>
7+
<th v-if="addSelectableCell">
8+
<slot name="selectHead">
9+
{{ typeof selectHead === 'boolean' ? 'Selected' : selectHead }}
10+
</slot>
11+
</th>
712
<th
813
v-for="field in computedFields"
914
:key="field.key"
@@ -55,8 +60,14 @@
5560
<tr
5661
v-for="(tr, ind) in computedItems"
5762
:key="ind"
58-
:class="tr._rowVariant ? `table-${tr._rowVariant}` : null"
63+
:class="getRowClasses(tr)"
64+
@click.prevent="onRowClick(tr, ind, $event)"
5965
>
66+
<td v-if="addSelectableCell">
67+
<slot name="selectCell">
68+
<span :class="selectedItems.has(tr) ? 'text-primary' : ''">🗹</span>
69+
</slot>
70+
</td>
6071
<td
6172
v-for="(field, index) in computedFields"
6273
:key="field.key"
@@ -112,7 +123,7 @@
112123

113124
<script setup lang="ts">
114125
// import type {Breakpoint} from '../../types'
115-
import {computed, toRef} from 'vue'
126+
import {computed, ref, toRef, useSlots} from 'vue'
116127
import {useBooleanish} from '../../composables'
117128
import type {
118129
Booleanish,
@@ -144,6 +155,9 @@ interface BTableProps {
144155
sortBy?: string
145156
sortDesc?: boolean
146157
sortInternal?: boolean
158+
selectable?: boolean
159+
selectHead?: boolean | string
160+
selectMode?: 'multi' | 'single' | 'range'
147161
}
148162
149163
const props = withDefaults(defineProps<BTableProps>(), {
@@ -159,9 +173,22 @@ const props = withDefaults(defineProps<BTableProps>(), {
159173
small: false,
160174
striped: false,
161175
sortInternal: true,
176+
selectable: false,
177+
selectHead: true,
178+
selectMode: 'single',
162179
})
163180
164-
const emits = defineEmits(['update:sortBy', 'update:sortDesc', 'sorted'])
181+
interface BTableEmits {
182+
(e: 'rowSelected', value: TableItem): void
183+
(e: 'rowUnselected', value: TableItem): void
184+
(e: 'selection', value: TableItem[]): void
185+
(e: 'update:sortBy', value: string): void
186+
(e: 'update:sortDesc', value: boolean): void
187+
(e: 'sorted', ...value: Parameters<(sort?: {by?: string; desc?: boolean}) => any>): void
188+
}
189+
190+
const emits = defineEmits<BTableEmits>()
191+
const slots = useSlots()
165192
166193
const captionTopBoolean = useBooleanish(toRef(props, 'captionTop'))
167194
const borderlessBoolean = useBooleanish(toRef(props, 'borderless'))
@@ -171,6 +198,8 @@ const footCloneBoolean = useBooleanish(toRef(props, 'footClone'))
171198
const hoverBoolean = useBooleanish(toRef(props, 'hover'))
172199
const smallBoolean = useBooleanish(toRef(props, 'small'))
173200
const stripedBoolean = useBooleanish(toRef(props, 'striped'))
201+
const sortInternalBoolean = useBooleanish(toRef(props, 'sortInternal'))
202+
const selectableBoolean = useBooleanish(toRef(props, 'selectable'))
174203
175204
const classes = computed(() => [
176205
'table',
@@ -185,13 +214,16 @@ const classes = computed(() => [
185214
'table-borderless': borderlessBoolean.value,
186215
'table-sm': smallBoolean.value,
187216
'caption-top': captionTopBoolean.value,
217+
'b-table-selectable': selectableBoolean.value,
218+
[`b-table-select-${props.selectMode}`]: selectableBoolean.value,
219+
'b-table-selecting user-select-none': selectableBoolean.value && isSelecting.value,
188220
},
189221
])
190222
191223
const itemHelper = useItemHelper()
192224
const computedFields = computed(() => itemHelper.normaliseFields(props.fields, props.items))
193225
const computedItems = computed(() =>
194-
props.sortInternal
226+
sortInternalBoolean.value === true
195227
? itemHelper.sortItems(props.fields, props.items, {key: props.sortBy, desc: props.sortDesc})
196228
: props.items
197229
)
@@ -201,6 +233,10 @@ const responsiveClasses = computed(() => ({
201233
[`table-responsive-${props.responsive}`]: typeof props.responsive === 'string',
202234
}))
203235
236+
const addSelectableCell = computed(
237+
() => selectableBoolean && (props.selectHead || slots.selectHead !== undefined)
238+
)
239+
204240
const isSortable = computed(
205241
() =>
206242
props.fields.filter((field) => (typeof field === 'string' ? false : field.sortable)).length > 0
@@ -218,8 +254,47 @@ const columnClicked = (field: TableField<Record<string, unknown>>) => {
218254
emits('update:sortBy', typeof field === 'string' ? field : field.key)
219255
emits('update:sortDesc', false)
220256
}
221-
emits('sorted', props.sortBy, props.sortDesc)
257+
emits('sorted', {by: props.sortBy, desc: props.sortDesc})
258+
}
259+
}
260+
261+
const selectedItems = ref<Set<TableItem>>(new Set([]))
262+
const isSelecting = computed(() => selectedItems.value.size > 0)
263+
264+
const onRowClick = (row: TableItem, index: number, e: MouseEvent) => {
265+
handleRowSelection(row, index, e.shiftKey)
266+
}
267+
268+
const handleRowSelection = (row: TableItem, index: number, shiftClicked = false) => {
269+
if (!selectableBoolean.value) return
270+
271+
if (selectedItems.value.has(row)) {
272+
selectedItems.value.delete(row)
273+
emits('rowUnselected', row)
274+
} else {
275+
if (props.selectMode === 'single' && selectedItems.value.size > 0) {
276+
selectedItems.value.forEach((item) => emits('rowUnselected', item))
277+
selectedItems.value.clear()
278+
}
279+
280+
if (props.selectMode === 'range' && selectedItems.value.size > 0 && shiftClicked) {
281+
const lastSelectedItem = Array.from(selectedItems.value).pop()
282+
const lastSelectedIndex = computedItems.value.findIndex((i) => i === lastSelectedItem)
283+
const selectStartIndex = Math.min(lastSelectedIndex, index)
284+
const selectEndIndex = Math.max(lastSelectedIndex, index)
285+
computedItems.value.slice(selectStartIndex, selectEndIndex + 1).forEach((item) => {
286+
if (!selectedItems.value.has(item)) {
287+
selectedItems.value.add(item)
288+
emits('rowSelected', item)
289+
}
290+
})
291+
} else {
292+
selectedItems.value.add(row)
293+
emits('rowSelected', row)
294+
}
222295
}
296+
297+
emits('selection', Array.from(selectedItems.value))
223298
}
224299
225300
const getFieldColumnClasses = (field: TableFieldObject) => [
@@ -228,4 +303,9 @@ const getFieldColumnClasses = (field: TableFieldObject) => [
228303
field.variant ? `table-${field.variant}` : undefined,
229304
{'b-table-sortable-column': isSortable.value && field.sortable},
230305
]
306+
307+
const getRowClasses = (item: TableItem) => [
308+
item._rowVariant ? `table-${item._rowVariant}` : null,
309+
selectableBoolean.value && selectedItems.value.has(item) ? `selected table-primary` : null,
310+
]
231311
</script>

packages/bootstrap-vue-3/src/components/BTable/_table.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,11 @@
8686
cursor: pointer;
8787
}
8888
}
89+
90+
.table {
91+
&.b-table-selectable {
92+
tr {
93+
cursor: pointer;
94+
}
95+
}
96+
}

packages/bootstrap-vue-3/src/types/components/BTable/BTable.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ export interface Props {
1616
small?: boolean
1717
striped?: boolean
1818
variant?: ColorVariant
19+
sortInternal?: boolean
20+
selectable?: boolean
21+
selectMode?: 'multi' | 'single' | 'range'
1922
}
2023
// Emits
2124

0 commit comments

Comments
 (0)