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"
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"
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'
116127import {useBooleanish } from ' ../../composables'
117128import 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
149163const 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
166193const captionTopBoolean = useBooleanish (toRef (props , ' captionTop' ))
167194const borderlessBoolean = useBooleanish (toRef (props , ' borderless' ))
@@ -171,6 +198,8 @@ const footCloneBoolean = useBooleanish(toRef(props, 'footClone'))
171198const hoverBoolean = useBooleanish (toRef (props , ' hover' ))
172199const smallBoolean = useBooleanish (toRef (props , ' small' ))
173200const stripedBoolean = useBooleanish (toRef (props , ' striped' ))
201+ const sortInternalBoolean = useBooleanish (toRef (props , ' sortInternal' ))
202+ const selectableBoolean = useBooleanish (toRef (props , ' selectable' ))
174203
175204const 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
191223const itemHelper = useItemHelper ()
192224const computedFields = computed (() => itemHelper .normaliseFields (props .fields , props .items ))
193225const 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+
204240const 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
225300const 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 >
0 commit comments