buefy 0.9.13 → 0.9.17

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.
Files changed (265) hide show
  1. package/CHANGELOG.md +1706 -1639
  2. package/README.md +1 -1
  3. package/dist/buefy.css +313 -1
  4. package/dist/buefy.esm.js +4431 -2331
  5. package/dist/buefy.esm.min.js +2 -2
  6. package/dist/buefy.js +4467 -2364
  7. package/dist/buefy.min.css +1 -1
  8. package/dist/buefy.min.js +2 -2
  9. package/dist/cjs/autocomplete.js +5 -5
  10. package/dist/cjs/button.js +3 -3
  11. package/dist/cjs/carousel.js +4 -4
  12. package/dist/cjs/{chunk-34949503.js → chunk-0d6f213f.js} +2 -2
  13. package/dist/cjs/{chunk-2c7de785.js → chunk-0e3108f5.js} +2 -2
  14. package/dist/cjs/{chunk-2911aa4b.js → chunk-1438658c.js} +14 -11
  15. package/dist/cjs/{chunk-114191ae.js → chunk-1f7e4ed3.js} +3 -3
  16. package/dist/cjs/{chunk-a11294f9.js → chunk-334bc809.js} +1 -1
  17. package/dist/cjs/chunk-45739695.js +332 -0
  18. package/dist/cjs/{chunk-c7b2aa4b.js → chunk-4bcfaf1c.js} +0 -0
  19. package/dist/cjs/{chunk-f5106717.js → chunk-5058e659.js} +3 -3
  20. package/dist/cjs/{chunk-61023b09.js → chunk-545e1c7f.js} +102 -16
  21. package/dist/cjs/{chunk-30670fac.js → chunk-54b0042d.js} +19 -3
  22. package/dist/cjs/{chunk-fe2f57ee.js → chunk-5ed8a75a.js} +2 -2
  23. package/dist/cjs/{chunk-d120e215.js → chunk-73f8eef8.js} +8 -2
  24. package/dist/cjs/{chunk-2062216d.js → chunk-7c11fdde.js} +5 -4
  25. package/dist/cjs/{chunk-3cc5d9a6.js → chunk-841c0e0f.js} +1 -1
  26. package/dist/cjs/{chunk-9e4cf4c5.js → chunk-87a116d9.js} +0 -0
  27. package/dist/cjs/{chunk-fefd7b77.js → chunk-a53b7aff.js} +0 -0
  28. package/dist/cjs/{chunk-d0f8ea39.js → chunk-c0a093d7.js} +7 -7
  29. package/dist/cjs/{chunk-7da0c017.js → chunk-c8abb3ed.js} +21 -21
  30. package/dist/cjs/{chunk-c6fbc7b4.js → chunk-d0df905a.js} +15 -2
  31. package/dist/cjs/{chunk-d54e40f6.js → chunk-e86d3eeb.js} +1 -8
  32. package/dist/cjs/{chunk-6cb902f8.js → chunk-e872f5e2.js} +1 -1
  33. package/dist/cjs/{chunk-2ae50815.js → chunk-f5285f14.js} +4 -4
  34. package/dist/cjs/clockpicker.js +8 -8
  35. package/dist/cjs/colorpicker.js +1752 -0
  36. package/dist/cjs/config.js +1 -1
  37. package/dist/cjs/datepicker.js +11 -11
  38. package/dist/cjs/datetimepicker.js +30 -14
  39. package/dist/cjs/dialog.js +5 -5
  40. package/dist/cjs/dropdown.js +3 -3
  41. package/dist/cjs/field.js +2 -2
  42. package/dist/cjs/helpers.js +21 -1
  43. package/dist/cjs/icon.js +2 -2
  44. package/dist/cjs/image.js +2 -2
  45. package/dist/cjs/index.js +27 -22
  46. package/dist/cjs/input.js +4 -4
  47. package/dist/cjs/loading.js +5 -5
  48. package/dist/cjs/menu.js +2 -2
  49. package/dist/cjs/message.js +4 -9
  50. package/dist/cjs/modal.js +5 -5
  51. package/dist/cjs/navbar.js +4 -1
  52. package/dist/cjs/notification.js +7 -7
  53. package/dist/cjs/numberinput.js +30 -21
  54. package/dist/cjs/pagination.js +7 -7
  55. package/dist/cjs/progress.js +3 -3
  56. package/dist/cjs/rate.js +2 -2
  57. package/dist/cjs/select.js +4 -4
  58. package/dist/cjs/sidebar.js +1 -1
  59. package/dist/cjs/slider.js +3 -3
  60. package/dist/cjs/snackbar.js +5 -4
  61. package/dist/cjs/steps.js +5 -5
  62. package/dist/cjs/table.js +157 -146
  63. package/dist/cjs/tabs.js +7 -7
  64. package/dist/cjs/taginput.js +5 -5
  65. package/dist/cjs/timepicker.js +12 -12
  66. package/dist/cjs/toast.js +3 -3
  67. package/dist/cjs/tooltip.js +4 -4
  68. package/dist/cjs/upload.js +23 -9
  69. package/dist/components/autocomplete/index.js +24 -2
  70. package/dist/components/autocomplete/index.min.js +2 -2
  71. package/dist/components/breadcrumb/index.js +1 -1
  72. package/dist/components/breadcrumb/index.min.js +1 -1
  73. package/dist/components/button/index.js +8 -2
  74. package/dist/components/button/index.min.js +2 -2
  75. package/dist/components/carousel/index.js +8 -2
  76. package/dist/components/carousel/index.min.js +2 -2
  77. package/dist/components/checkbox/index.js +1 -1
  78. package/dist/components/checkbox/index.min.js +1 -1
  79. package/dist/components/clockpicker/index.js +41 -22
  80. package/dist/components/clockpicker/index.min.js +2 -2
  81. package/dist/components/collapse/index.js +1 -1
  82. package/dist/components/collapse/index.min.js +1 -1
  83. package/dist/components/colorpicker/index.js +4318 -0
  84. package/dist/components/colorpicker/index.min.js +2 -0
  85. package/dist/components/datepicker/index.js +48 -26
  86. package/dist/components/datepicker/index.min.js +2 -2
  87. package/dist/components/datetimepicker/index.js +66 -28
  88. package/dist/components/datetimepicker/index.min.js +2 -2
  89. package/dist/components/dialog/index.js +8 -2
  90. package/dist/components/dialog/index.min.js +2 -2
  91. package/dist/components/dropdown/index.js +14 -1
  92. package/dist/components/dropdown/index.min.js +2 -2
  93. package/dist/components/field/index.js +21 -21
  94. package/dist/components/field/index.min.js +1 -1
  95. package/dist/components/icon/index.js +8 -2
  96. package/dist/components/icon/index.min.js +2 -2
  97. package/dist/components/image/index.js +1 -1
  98. package/dist/components/image/index.min.js +1 -1
  99. package/dist/components/input/index.js +8 -2
  100. package/dist/components/input/index.min.js +2 -2
  101. package/dist/components/loading/index.js +1 -1
  102. package/dist/components/loading/index.min.js +1 -1
  103. package/dist/components/menu/index.js +8 -2
  104. package/dist/components/menu/index.min.js +2 -2
  105. package/dist/components/message/index.js +11 -9
  106. package/dist/components/message/index.min.js +2 -2
  107. package/dist/components/modal/index.js +1 -1
  108. package/dist/components/modal/index.min.js +1 -1
  109. package/dist/components/navbar/index.js +4 -1
  110. package/dist/components/navbar/index.min.js +2 -2
  111. package/dist/components/notification/index.js +13 -13
  112. package/dist/components/notification/index.min.js +2 -2
  113. package/dist/components/numberinput/index.js +34 -19
  114. package/dist/components/numberinput/index.min.js +2 -2
  115. package/dist/components/pagination/index.js +107 -16
  116. package/dist/components/pagination/index.min.js +2 -2
  117. package/dist/components/progress/index.js +2 -2
  118. package/dist/components/progress/index.min.js +2 -2
  119. package/dist/components/radio/index.js +1 -1
  120. package/dist/components/radio/index.min.js +1 -1
  121. package/dist/components/rate/index.js +8 -2
  122. package/dist/components/rate/index.min.js +2 -2
  123. package/dist/components/select/index.js +8 -2
  124. package/dist/components/select/index.min.js +2 -2
  125. package/dist/components/sidebar/index.js +1 -1
  126. package/dist/components/sidebar/index.min.js +1 -1
  127. package/dist/components/skeleton/index.js +1 -1
  128. package/dist/components/skeleton/index.min.js +1 -1
  129. package/dist/components/slider/index.js +2 -2
  130. package/dist/components/slider/index.min.js +2 -2
  131. package/dist/components/snackbar/index.js +4 -10
  132. package/dist/components/snackbar/index.min.js +2 -2
  133. package/dist/components/steps/index.js +8 -2
  134. package/dist/components/steps/index.min.js +2 -2
  135. package/dist/components/switch/index.js +1 -1
  136. package/dist/components/switch/index.min.js +1 -1
  137. package/dist/components/table/index.js +253 -133
  138. package/dist/components/table/index.min.js +2 -2
  139. package/dist/components/tabs/index.js +10 -4
  140. package/dist/components/tabs/index.min.js +2 -2
  141. package/dist/components/tag/index.js +1 -1
  142. package/dist/components/tag/index.min.js +1 -1
  143. package/dist/components/taginput/index.js +24 -2
  144. package/dist/components/taginput/index.min.js +2 -2
  145. package/dist/components/timepicker/index.js +41 -22
  146. package/dist/components/timepicker/index.min.js +2 -2
  147. package/dist/components/toast/index.js +2 -9
  148. package/dist/components/toast/index.min.js +2 -2
  149. package/dist/components/tooltip/index.js +2 -2
  150. package/dist/components/tooltip/index.min.js +2 -2
  151. package/dist/components/upload/index.js +20 -6
  152. package/dist/components/upload/index.min.js +2 -2
  153. package/dist/esm/autocomplete.js +6 -6
  154. package/dist/esm/button.js +4 -4
  155. package/dist/esm/carousel.js +4 -4
  156. package/dist/esm/{chunk-18e8b067.js → chunk-1a4fde6d.js} +102 -17
  157. package/dist/esm/{chunk-3773c62d.js → chunk-22cf6667.js} +2 -2
  158. package/dist/esm/{chunk-ece062a7.js → chunk-262b3f82.js} +1 -1
  159. package/dist/esm/chunk-455cdeae.js +317 -0
  160. package/dist/esm/{chunk-d7f92d97.js → chunk-5435bd9a.js} +5 -4
  161. package/dist/esm/{chunk-e7c9b2cb.js → chunk-58cdbf2b.js} +2 -2
  162. package/dist/esm/{chunk-29ca0df8.js → chunk-60a03517.js} +1 -1
  163. package/dist/esm/{chunk-21fc0948.js → chunk-66cef090.js} +7 -7
  164. package/dist/esm/{chunk-71a547bc.js → chunk-690d5be4.js} +1 -1
  165. package/dist/esm/{chunk-83eb0d37.js → chunk-6adc5c5d.js} +3 -3
  166. package/dist/esm/{chunk-c9c58d0c.js → chunk-6d0f2352.js} +0 -0
  167. package/dist/esm/{chunk-8d0f95b8.js → chunk-6d96579e.js} +4 -4
  168. package/dist/esm/{chunk-22e9f916.js → chunk-6fb4a069.js} +14 -11
  169. package/dist/esm/{chunk-75a5af93.js → chunk-84c6dfd6.js} +0 -0
  170. package/dist/esm/{chunk-ae8ab23a.js → chunk-a628d44d.js} +3 -3
  171. package/dist/esm/{chunk-b0c0c6b0.js → chunk-c9c18b2f.js} +0 -0
  172. package/dist/esm/{chunk-9f7f7441.js → chunk-d35985c7.js} +0 -0
  173. package/dist/esm/{chunk-799e084d.js → chunk-d9232770.js} +1 -8
  174. package/dist/esm/{chunk-4b67a181.js → chunk-dbd43ef1.js} +15 -2
  175. package/dist/esm/{chunk-d92f0cd9.js → chunk-e044aa02.js} +8 -2
  176. package/dist/esm/{chunk-b07e3182.js → chunk-effa4d25.js} +21 -21
  177. package/dist/esm/{chunk-6019fd7a.js → chunk-f9eaeac4.js} +19 -3
  178. package/dist/esm/clockpicker.js +8 -8
  179. package/dist/esm/colorpicker.js +1748 -0
  180. package/dist/esm/config.js +1 -1
  181. package/dist/esm/datepicker.js +10 -10
  182. package/dist/esm/datetimepicker.js +29 -13
  183. package/dist/esm/dialog.js +4 -4
  184. package/dist/esm/dropdown.js +4 -4
  185. package/dist/esm/field.js +3 -3
  186. package/dist/esm/helpers.js +20 -2
  187. package/dist/esm/icon.js +3 -3
  188. package/dist/esm/image.js +3 -3
  189. package/dist/esm/index.js +94 -91
  190. package/dist/esm/input.js +5 -5
  191. package/dist/esm/loading.js +3 -3
  192. package/dist/esm/menu.js +2 -2
  193. package/dist/esm/message.js +3 -8
  194. package/dist/esm/modal.js +3 -3
  195. package/dist/esm/navbar.js +4 -1
  196. package/dist/esm/notification.js +5 -5
  197. package/dist/esm/numberinput.js +30 -21
  198. package/dist/esm/pagination.js +4 -4
  199. package/dist/esm/progress.js +3 -3
  200. package/dist/esm/rate.js +2 -2
  201. package/dist/esm/select.js +5 -5
  202. package/dist/esm/sidebar.js +1 -1
  203. package/dist/esm/slider.js +2 -2
  204. package/dist/esm/snackbar.js +4 -3
  205. package/dist/esm/steps.js +5 -5
  206. package/dist/esm/table.js +155 -144
  207. package/dist/esm/tabs.js +7 -7
  208. package/dist/esm/taginput.js +5 -5
  209. package/dist/esm/timepicker.js +11 -11
  210. package/dist/esm/toast.js +2 -2
  211. package/dist/esm/tooltip.js +3 -3
  212. package/dist/esm/upload.js +21 -7
  213. package/dist/vetur/attributes.json +96 -0
  214. package/dist/vetur/tags.json +30 -1
  215. package/package.json +1 -1
  216. package/src/components/autocomplete/Autocomplete.vue +10 -0
  217. package/src/components/breadcrumb/__snapshots__/BreadcrumbItem.spec.js.snap +1 -5
  218. package/src/components/carousel/__snapshots__/CarouselList.spec.js.snap +48 -48
  219. package/src/components/colorpicker/Colorpicker.spec.js +10 -0
  220. package/src/components/colorpicker/Colorpicker.vue +354 -0
  221. package/src/components/colorpicker/ColorpickerAlphaSlider.spec.js +14 -0
  222. package/src/components/colorpicker/ColorpickerAlphaSlider.vue +194 -0
  223. package/src/components/colorpicker/ColorpickerHSLRepresentationSquare.spec.js +22 -0
  224. package/src/components/colorpicker/ColorpickerHSLRepresentationSquare.vue +366 -0
  225. package/src/components/colorpicker/ColorpickerHSLRepresentationTriangle.spec.js +22 -0
  226. package/src/components/colorpicker/ColorpickerHSLRepresentationTriangle.vue +442 -0
  227. package/src/components/colorpicker/__snapshots__/ColorPickerHSLRepresentationSquare.spec.js.snap +12 -0
  228. package/src/components/colorpicker/__snapshots__/Colorpicker.spec.js.snap +32 -0
  229. package/src/components/colorpicker/__snapshots__/ColorpickerAlphaSlider.spec.js.snap +11 -0
  230. package/src/components/colorpicker/__snapshots__/ColorpickerHSLRepresentationTriangle.spec.js.snap +36 -0
  231. package/src/components/colorpicker/index.js +17 -0
  232. package/src/components/datepicker/DatepickerTableRow.spec.js +26 -0
  233. package/src/components/datepicker/DatepickerTableRow.vue +4 -5
  234. package/src/components/datetimepicker/Datetimepicker.vue +17 -1
  235. package/src/components/dropdown/Dropdown.vue +13 -0
  236. package/src/components/field/Field.vue +271 -271
  237. package/src/components/index.js +2 -0
  238. package/src/components/message/Message.vue +0 -5
  239. package/src/components/navbar/NavbarDropdown.vue +4 -0
  240. package/src/components/notification/Notification.vue +1 -1
  241. package/src/components/numberinput/Numberinput.spec.js +62 -0
  242. package/src/components/numberinput/Numberinput.vue +22 -15
  243. package/src/components/pagination/Pagination.vue +141 -51
  244. package/src/components/progress/Progress.vue +1 -1
  245. package/src/components/snackbar/index.js +2 -1
  246. package/src/components/table/Table.spec.js +17 -3
  247. package/src/components/table/Table.vue +1436 -1409
  248. package/src/components/table/TablePagination.vue +10 -2
  249. package/src/components/tabs/Tabs.vue +2 -0
  250. package/src/components/timepicker/__snapshots__/Timepicker.spec.js.snap +18 -47
  251. package/src/components/tooltip/Tooltip.vue +2 -2
  252. package/src/components/upload/Upload.vue +19 -7
  253. package/src/scss/buefy.scss +1 -0
  254. package/src/scss/components/_colorpicker.scss +283 -0
  255. package/src/scss/components/_pagination.scss +38 -0
  256. package/src/utils/MessageMixin.js +2 -1
  257. package/src/utils/NoticeMixin.js +1 -5
  258. package/src/utils/color.js +441 -0
  259. package/src/utils/color.spec.js +52 -0
  260. package/src/utils/helpers.js +16 -0
  261. package/src/utils/icons.js +7 -1
  262. package/types/components.d.ts +7 -2
  263. package/types/helpers.d.ts +2 -1
  264. package/dist/cjs/chunk-92621ff7.js +0 -141
  265. package/dist/esm/chunk-2452e3d3.js +0 -134
@@ -1,1409 +1,1436 @@
1
- <template>
2
- <div class="b-table">
3
-
4
- <slot />
5
-
6
- <b-table-mobile-sort
7
- v-if="mobileCards && hasSortablenewColumns"
8
- :current-sort-column="currentSortColumn"
9
- :sort-multiple="sortMultiple"
10
- :sort-multiple-data="sortMultipleDataComputed"
11
- :is-asc="isAsc"
12
- :columns="newColumns"
13
- :placeholder="mobileSortPlaceholder"
14
- :icon-pack="iconPack"
15
- :sort-icon="sortIcon"
16
- :sort-icon-size="sortIconSize"
17
- @sort="(column, event) => sort(column, null, event)"
18
- @removePriority="(column) => removeSortingPriority(column)"
19
- />
20
-
21
- <template
22
- v-if="paginated && (paginationPosition === 'top' || paginationPosition === 'both')">
23
- <slot name="pagination">
24
- <b-table-pagination
25
- v-bind="$attrs"
26
- :per-page="perPage"
27
- :paginated="paginated"
28
- :rounded="paginationRounded"
29
- :icon-pack="iconPack"
30
- :total="newDataTotal"
31
- :current-page.sync="newCurrentPage"
32
- :aria-next-label="ariaNextLabel"
33
- :aria-previous-label="ariaPreviousLabel"
34
- :aria-page-label="ariaPageLabel"
35
- :aria-current-label="ariaCurrentLabel"
36
- @page-change="(event) => $emit('page-change', event)"
37
- >
38
- <slot name="top-left"/>
39
- </b-table-pagination>
40
- </slot>
41
- </template>
42
-
43
- <div
44
- class="table-wrapper"
45
- :class="tableWrapperClasses"
46
- :style="tableStyle"
47
- >
48
- <table
49
- class="table"
50
- :class="tableClasses"
51
- :tabindex="!focusable ? false : 0"
52
- @keydown.self.prevent.up="pressedArrow(-1)"
53
- @keydown.self.prevent.down="pressedArrow(1)">
54
- <caption v-show="showCaption" v-if="caption">{{ caption }}</caption>
55
- <thead v-if="newColumns.length && showHeader">
56
- <tr>
57
- <th v-if="showDetailRowIcon" width="40px"/>
58
- <th
59
- :class="['checkbox-cell', { 'is-sticky': stickyCheckbox } ]"
60
- v-if="checkable && checkboxPosition === 'left'">
61
- <template v-if="headerCheckable">
62
- <b-checkbox
63
- autocomplete="off"
64
- :value="isAllChecked"
65
- :disabled="isAllUncheckable"
66
- @change.native="checkAll"/>
67
- </template>
68
- </th>
69
- <th
70
- v-for="(column, index) in visibleColumns"
71
- :key="column.newKey + ':' + index + 'header'"
72
- v-bind="column.thAttrs(column)"
73
- :class="[column.thClasses, {
74
- 'is-current-sort': !sortMultiple && currentSortColumn === column,
75
- }]"
76
- :style="column.thStyle"
77
- @click.stop="sort(column, null, $event)"
78
- :draggable="canDragColumn"
79
- @dragstart="handleColumnDragStart($event, column, index)"
80
- @dragend="handleColumnDragEnd($event, column, index)"
81
- @drop="handleColumnDrop($event, column, index)"
82
- @dragover="handleColumnDragOver($event, column, index)"
83
- @dragleave="handleColumnDragLeave($event, column, index)">
84
- <div
85
- class="th-wrap"
86
- :class="{
87
- 'is-numeric': column.numeric,
88
- 'is-centered': column.centered
89
- }">
90
- <template v-if="column.$scopedSlots && column.$scopedSlots.header">
91
- <b-slot-component
92
- :component="column"
93
- scoped
94
- name="header"
95
- tag="span"
96
- :props="{ column, index }"
97
- />
98
- </template>
99
- <template v-else>
100
- <span class="is-relative">
101
- {{ column.label }}
102
- <template
103
- v-if="sortMultiple &&
104
- sortMultipleDataComputed &&
105
- sortMultipleDataComputed.length > 0 &&
106
- sortMultipleDataComputed.filter(i =>
107
- i.field === column.field).length > 0">
108
- <b-icon
109
- :icon="sortIcon"
110
- :pack="iconPack"
111
- both
112
- :size="sortIconSize"
113
- :class="{
114
- 'is-desc': sortMultipleDataComputed.filter(i =>
115
- i.field === column.field)[0].order === 'desc'}"
116
- />
117
- {{ findIndexOfSortData(column) }}
118
- <button
119
- class="delete is-small multi-sort-cancel-icon"
120
- type="button"
121
- @click.stop="removeSortingPriority(column)"/>
122
- </template>
123
-
124
- <b-icon
125
- v-else
126
- :icon="sortIcon"
127
- :pack="iconPack"
128
- both
129
- :size="sortIconSize"
130
- class="sort-icon"
131
- :class="{
132
- 'is-desc': !isAsc,
133
- 'is-invisible': currentSortColumn !== column
134
- }"
135
- />
136
- </span>
137
- </template>
138
- </div>
139
- </th>
140
- <th
141
- :class="['checkbox-cell', { 'is-sticky': stickyCheckbox } ]"
142
- v-if="checkable && checkboxPosition === 'right'">
143
- <template v-if="headerCheckable">
144
- <b-checkbox
145
- autocomplete="off"
146
- :value="isAllChecked"
147
- :disabled="isAllUncheckable"
148
- @change.native="checkAll"/>
149
- </template>
150
- </th>
151
- </tr>
152
- <tr v-if="hasCustomSubheadings" class="is-subheading">
153
- <th v-if="showDetailRowIcon" width="40px"/>
154
- <th v-if="checkable && checkboxPosition === 'left'" />
155
- <th
156
- v-for="(column, index) in visibleColumns"
157
- :key="column.newKey + ':' + index + 'subheading'"
158
- :style="column.style">
159
- <div
160
- class="th-wrap"
161
- :class="{
162
- 'is-numeric': column.numeric,
163
- 'is-centered': column.centered
164
- }">
165
- <template
166
- v-if="column.$scopedSlots && column.$scopedSlots.subheading"
167
- >
168
- <b-slot-component
169
- :component="column"
170
- scoped
171
- name="subheading"
172
- tag="span"
173
- :props="{ column, index }"
174
- />
175
- </template>
176
- <template v-else>{{ column.subheading }}</template>
177
- </div>
178
- </th>
179
- <th v-if="checkable && checkboxPosition === 'right'" />
180
- </tr>
181
- <tr v-if="hasSearchablenewColumns">
182
- <th v-if="showDetailRowIcon" width="40px"/>
183
- <th v-if="checkable && checkboxPosition === 'left'" />
184
- <th
185
- v-for="(column, index) in visibleColumns"
186
- :key="column.newKey + ':' + index + 'searchable'"
187
- v-bind="column.thAttrs(column)"
188
- :style="column.thStyle"
189
- :class="{'is-sticky': column.sticky}">
190
- <div class="th-wrap">
191
- <template v-if="column.searchable">
192
- <template
193
- v-if="column.$scopedSlots
194
- && column.$scopedSlots.searchable">
195
- <b-slot-component
196
- :component="column"
197
- :scoped="true"
198
- name="searchable"
199
- tag="span"
200
- :props="{ column, filters }"
201
- />
202
- </template>
203
- <b-input
204
- v-else
205
- @[filtersEvent].native="onFiltersEvent"
206
- v-model="filters[column.field]"
207
- :type="column.numeric ? 'number' : 'text'" />
208
- </template>
209
- </div>
210
- </th>
211
- <th v-if="checkable && checkboxPosition === 'right'" />
212
- </tr>
213
- </thead>
214
- <tbody>
215
- <template v-for="(row, index) in visibleData">
216
- <tr
217
- :key="customRowKey ? row[customRowKey] : index"
218
- :class="[rowClass(row, index), {
219
- 'is-selected': isRowSelected(row, selected),
220
- 'is-checked': isRowChecked(row),
221
- }]"
222
- @click="selectRow(row)"
223
- @dblclick="$emit('dblclick', row)"
224
- @mouseenter="emitEventForRow('mouseenter', $event, row)"
225
- @mouseleave="emitEventForRow('mouseleave', $event, row)"
226
- @contextmenu="$emit('contextmenu', row, $event)"
227
- :draggable="canDragRow"
228
- @dragstart="handleDragStart($event, row, index)"
229
- @dragend="handleDragEnd($event, row, index)"
230
- @drop="handleDrop($event, row, index)"
231
- @dragover="handleDragOver($event, row, index)"
232
- @dragleave="handleDragLeave($event, row, index)">
233
-
234
- <td
235
- v-if="showDetailRowIcon"
236
- class="chevron-cell"
237
- >
238
- <a
239
- v-if="hasDetailedVisible(row)"
240
- role="button"
241
- @click.stop="toggleDetails(row)">
242
- <b-icon
243
- :icon="detailIcon"
244
- :pack="iconPack"
245
- both
246
- :class="{'is-expanded': isVisibleDetailRow(row)}"/>
247
- </a>
248
- </td>
249
-
250
- <td
251
- :class="['checkbox-cell', { 'is-sticky': stickyCheckbox } ]"
252
- v-if="checkable && checkboxPosition === 'left'">
253
- <b-checkbox
254
- autocomplete="off"
255
- :disabled="!isRowCheckable(row)"
256
- :value="isRowChecked(row)"
257
- @click.native.prevent.stop="checkRow(row, index, $event)"
258
- />
259
- </td>
260
-
261
- <template v-for="(column, colindex) in visibleColumns">
262
-
263
- <template v-if="column.$scopedSlots && column.$scopedSlots.default">
264
- <b-slot-component
265
- :key="column.newKey + ':' + index + ':' + colindex"
266
- :component="column"
267
- v-bind="column.tdAttrs(row, column)"
268
- scoped
269
- name="default"
270
- tag="td"
271
- :class="column.getRootClasses(row)"
272
- :style="column.getRootStyle(row)"
273
- :data-label="column.label"
274
- :props="{ row, column, index, colindex, toggleDetails }"
275
- @click.native="$emit('cellclick',row,column,index,colindex)"
276
- />
277
- </template>
278
-
279
- </template>
280
-
281
- <td
282
- :class="['checkbox-cell', { 'is-sticky': stickyCheckbox } ]"
283
- v-if="checkable && checkboxPosition === 'right'">
284
- <b-checkbox
285
- autocomplete="off"
286
- :disabled="!isRowCheckable(row)"
287
- :value="isRowChecked(row)"
288
- @click.native.prevent.stop="checkRow(row, index, $event)"
289
- />
290
- </td>
291
- </tr>
292
-
293
- <transition
294
- :key="(customRowKey ? row[customRowKey] : index) + 'detail'"
295
- :name="detailTransition"
296
- >
297
- <tr
298
- v-if="isActiveDetailRow(row)"
299
- class="detail">
300
- <td :colspan="columnCount">
301
- <div class="detail-container">
302
- <slot
303
- name="detail"
304
- :row="row"
305
- :index="index"
306
- />
307
- </div>
308
- </td>
309
- </tr>
310
- </transition>
311
- <slot
312
- v-if="isActiveCustomDetailRow(row)"
313
- name="detail"
314
- :row="row"
315
- :index="index"
316
- />
317
- </template>
318
-
319
- <tr
320
- v-if="!visibleData.length"
321
- class="is-empty">
322
- <td :colspan="columnCount">
323
- <slot name="empty"/>
324
- </td>
325
- </tr>
326
-
327
- </tbody>
328
-
329
- <tfoot v-if="$slots.footer !== undefined">
330
- <tr class="table-footer">
331
- <slot name="footer" v-if="hasCustomFooterSlot()"/>
332
- <th :colspan="columnCount" v-else>
333
- <slot name="footer"/>
334
- </th>
335
- </tr>
336
- </tfoot>
337
- </table>
338
-
339
- <template v-if="loading">
340
- <slot name="loading">
341
- <b-loading :is-full-page="false" :active.sync="loading" />
342
- </slot>
343
- </template>
344
-
345
- </div>
346
-
347
- <template
348
- v-if="(checkable && hasBottomLeftSlot()) ||
349
- (paginated && (paginationPosition === 'bottom' || paginationPosition === 'both'))"
350
- >
351
- <slot name="pagination">
352
- <b-table-pagination
353
- v-bind="$attrs"
354
- :per-page="perPage"
355
- :paginated="paginated"
356
- :rounded="paginationRounded"
357
- :icon-pack="iconPack"
358
- :total="newDataTotal"
359
- :current-page.sync="newCurrentPage"
360
- :aria-next-label="ariaNextLabel"
361
- :aria-previous-label="ariaPreviousLabel"
362
- :aria-page-label="ariaPageLabel"
363
- :aria-current-label="ariaCurrentLabel"
364
- @page-change="(event) => $emit('page-change', event)"
365
- >
366
- <slot name="bottom-left"/>
367
- </b-table-pagination>
368
- </slot>
369
- </template>
370
-
371
- </div>
372
- </template>
373
-
374
- <script>
375
- import { getValueByPath, indexOf, multiColumnSort, escapeRegExpChars, toCssWidth } from '../../utils/helpers'
376
- import debounce from '../../utils/debounce'
377
- import { VueInstance } from '../../utils/config'
378
- import Checkbox from '../checkbox/Checkbox'
379
- import Icon from '../icon/Icon'
380
- import Input from '../input/Input'
381
- import Loading from '../loading/Loading'
382
- import SlotComponent from '../../utils/SlotComponent'
383
- import TableMobileSort from './TableMobileSort'
384
- import TableColumn from './TableColumn'
385
- import TablePagination from './TablePagination'
386
-
387
- export default {
388
- name: 'BTable',
389
- components: {
390
- [Checkbox.name]: Checkbox,
391
- [Icon.name]: Icon,
392
- [Input.name]: Input,
393
- [Loading.name]: Loading,
394
- [SlotComponent.name]: SlotComponent,
395
- [TableMobileSort.name]: TableMobileSort,
396
- [TableColumn.name]: TableColumn,
397
- [TablePagination.name]: TablePagination
398
- },
399
- inheritAttrs: false,
400
- provide() {
401
- return {
402
- $table: this
403
- }
404
- },
405
- props: {
406
- data: {
407
- type: Array,
408
- default: () => []
409
- },
410
- columns: {
411
- type: Array,
412
- default: () => []
413
- },
414
- bordered: Boolean,
415
- striped: Boolean,
416
- narrowed: Boolean,
417
- hoverable: Boolean,
418
- loading: Boolean,
419
- detailed: Boolean,
420
- checkable: Boolean,
421
- headerCheckable: {
422
- type: Boolean,
423
- default: true
424
- },
425
- checkboxPosition: {
426
- type: String,
427
- default: 'left',
428
- validator: (value) => {
429
- return [
430
- 'left',
431
- 'right'
432
- ].indexOf(value) >= 0
433
- }
434
- },
435
- stickyCheckbox: {
436
- type: Boolean,
437
- default: false
438
- },
439
- selected: Object,
440
- isRowSelectable: {
441
- type: Function,
442
- default: () => true
443
- },
444
- focusable: Boolean,
445
- customIsChecked: Function,
446
- isRowCheckable: {
447
- type: Function,
448
- default: () => true
449
- },
450
- checkedRows: {
451
- type: Array,
452
- default: () => []
453
- },
454
- mobileCards: {
455
- type: Boolean,
456
- default: true
457
- },
458
- defaultSort: [String, Array],
459
- defaultSortDirection: {
460
- type: String,
461
- default: 'asc'
462
- },
463
- sortIcon: {
464
- type: String,
465
- default: 'arrow-up'
466
- },
467
- sortIconSize: {
468
- type: String,
469
- default: 'is-small'
470
- },
471
- sortMultiple: {
472
- type: Boolean,
473
- default: false
474
- },
475
- sortMultipleData: {
476
- type: Array,
477
- default: () => []
478
- },
479
- sortMultipleKey: {
480
- type: String,
481
- default: null
482
- },
483
- paginated: Boolean,
484
- currentPage: {
485
- type: Number,
486
- default: 1
487
- },
488
- perPage: {
489
- type: [Number, String],
490
- default: 20
491
- },
492
- showDetailIcon: {
493
- type: Boolean,
494
- default: true
495
- },
496
- detailIcon: {
497
- type: String,
498
- default: 'chevron-right'
499
- },
500
- paginationPosition: {
501
- type: String,
502
- default: 'bottom',
503
- validator: (value) => {
504
- return [
505
- 'bottom',
506
- 'top',
507
- 'both'
508
- ].indexOf(value) >= 0
509
- }
510
- },
511
- paginationRounded: Boolean,
512
- backendSorting: Boolean,
513
- backendFiltering: Boolean,
514
- rowClass: {
515
- type: Function,
516
- default: () => ''
517
- },
518
- openedDetailed: {
519
- type: Array,
520
- default: () => []
521
- },
522
- hasDetailedVisible: {
523
- type: Function,
524
- default: () => true
525
- },
526
- detailKey: {
527
- type: String,
528
- default: ''
529
- },
530
- detailTransition: {
531
- type: String,
532
- default: ''
533
- },
534
- customDetailRow: {
535
- type: Boolean,
536
- default: false
537
- },
538
- backendPagination: Boolean,
539
- total: {
540
- type: [Number, String],
541
- default: 0
542
- },
543
- iconPack: String,
544
- mobileSortPlaceholder: String,
545
- customRowKey: String,
546
- draggable: {
547
- type: Boolean,
548
- default: false
549
- },
550
- draggableColumn: {
551
- type: Boolean,
552
- default: false
553
- },
554
- scrollable: Boolean,
555
- ariaNextLabel: String,
556
- ariaPreviousLabel: String,
557
- ariaPageLabel: String,
558
- ariaCurrentLabel: String,
559
- stickyHeader: Boolean,
560
- height: [Number, String],
561
- filtersEvent: {
562
- type: String,
563
- default: ''
564
- },
565
- cardLayout: Boolean,
566
- showHeader: {
567
- type: Boolean,
568
- default: true
569
- },
570
- debounceSearch: Number,
571
- caption: String,
572
- showCaption: {
573
- type: Boolean,
574
- default: true
575
- }
576
- },
577
- data() {
578
- return {
579
- sortMultipleDataLocal: [],
580
- getValueByPath,
581
- visibleDetailRows: this.openedDetailed,
582
- newData: this.data,
583
- newDataTotal: this.backendPagination ? this.total : this.data.length,
584
- newCheckedRows: [...this.checkedRows],
585
- lastCheckedRowIndex: null,
586
- newCurrentPage: this.currentPage,
587
- currentSortColumn: {},
588
- isAsc: true,
589
- filters: {},
590
- defaultSlots: [],
591
- firstTimeSort: true, // Used by first time initSort
592
- _isTable: true, // Used by TableColumn
593
- isDraggingRow: false,
594
- isDraggingColumn: false
595
- }
596
- },
597
- computed: {
598
- sortMultipleDataComputed() {
599
- return this.backendSorting ? this.sortMultipleData : this.sortMultipleDataLocal
600
- },
601
- tableClasses() {
602
- return {
603
- 'is-bordered': this.bordered,
604
- 'is-striped': this.striped,
605
- 'is-narrow': this.narrowed,
606
- 'is-hoverable': (
607
- (this.hoverable || this.focusable) &&
608
- this.visibleData.length
609
- )
610
- }
611
- },
612
- tableWrapperClasses() {
613
- return {
614
- 'has-mobile-cards': this.mobileCards,
615
- 'has-sticky-header': this.stickyHeader,
616
- 'is-card-list': this.cardLayout,
617
- 'table-container': this.isScrollable
618
- }
619
- },
620
- tableStyle() {
621
- return {
622
- height: toCssWidth(this.height)
623
- }
624
- },
625
-
626
- /**
627
- * Splitted data based on the pagination.
628
- */
629
- visibleData() {
630
- if (!this.paginated) return this.newData
631
-
632
- const currentPage = this.newCurrentPage
633
- const perPage = this.perPage
634
-
635
- if (this.newData.length <= perPage) {
636
- return this.newData
637
- } else {
638
- const start = (currentPage - 1) * perPage
639
- const end = parseInt(start, 10) + parseInt(perPage, 10)
640
- return this.newData.slice(start, end)
641
- }
642
- },
643
-
644
- visibleColumns() {
645
- if (!this.newColumns) return this.newColumns
646
- return this.newColumns.filter((column) => {
647
- return column.visible || column.visible === undefined
648
- })
649
- },
650
-
651
- /**
652
- * Check if all rows in the page are checked.
653
- */
654
- isAllChecked() {
655
- const validVisibleData = this.visibleData.filter(
656
- (row) => this.isRowCheckable(row))
657
- if (validVisibleData.length === 0) return false
658
- const isAllChecked = validVisibleData.some((currentVisibleRow) => {
659
- return indexOf(this.newCheckedRows, currentVisibleRow, this.customIsChecked) < 0
660
- })
661
- return !isAllChecked
662
- },
663
-
664
- /**
665
- * Check if all rows in the page are checkable.
666
- */
667
- isAllUncheckable() {
668
- const validVisibleData = this.visibleData.filter(
669
- (row) => this.isRowCheckable(row))
670
- return validVisibleData.length === 0
671
- },
672
-
673
- /**
674
- * Check if has any sortable column.
675
- */
676
- hasSortablenewColumns() {
677
- return this.newColumns.some((column) => {
678
- return column.sortable
679
- })
680
- },
681
-
682
- /**
683
- * Check if has any searchable column.
684
- */
685
- hasSearchablenewColumns() {
686
- return this.newColumns.some((column) => {
687
- return column.searchable
688
- })
689
- },
690
-
691
- /**
692
- * Check if has any column using subheading.
693
- */
694
- hasCustomSubheadings() {
695
- if (this.$scopedSlots && this.$scopedSlots.subheading) return true
696
- return this.newColumns.some((column) => {
697
- return column.subheading || (column.$scopedSlots && column.$scopedSlots.subheading)
698
- })
699
- },
700
-
701
- /**
702
- * Return total column count based if it's checkable or expanded
703
- */
704
- columnCount() {
705
- let count = this.visibleColumns.length
706
- count += this.checkable ? 1 : 0
707
- count += (this.detailed && this.showDetailIcon) ? 1 : 0
708
-
709
- return count
710
- },
711
-
712
- /**
713
- * return if detailed row tabled
714
- * will be with chevron column & icon or not
715
- */
716
- showDetailRowIcon() {
717
- return this.detailed && this.showDetailIcon
718
- },
719
-
720
- /**
721
- * return if scrollable table
722
- */
723
- isScrollable() {
724
- if (this.scrollable) return true
725
- if (!this.newColumns) return false
726
- return this.newColumns.some((column) => {
727
- return column.sticky
728
- })
729
- },
730
-
731
- newColumns() {
732
- if (this.columns && this.columns.length) {
733
- return this.columns.map((column) => {
734
- const TableColumnComponent = VueInstance.extend(TableColumn)
735
- const component = new TableColumnComponent(
736
- { parent: this, propsData: column }
737
- )
738
- component.$scopedSlots = {
739
- default: (props) => {
740
- const vnode = component.$createElement('span', {
741
- domProps: {
742
- innerHTML: getValueByPath(props.row, column.field)
743
- }
744
- })
745
- return [vnode]
746
- }
747
- }
748
- return component
749
- })
750
- }
751
- return this.defaultSlots
752
- .filter((vnode) =>
753
- vnode.componentInstance &&
754
- vnode.componentInstance.$data &&
755
- vnode.componentInstance.$data._isTableColumn)
756
- .map((vnode) => vnode.componentInstance)
757
- },
758
- canDragRow() {
759
- return this.draggable && !this.isDraggingColumn
760
- },
761
- canDragColumn() {
762
- return this.draggableColumn && !this.isDraggingRow
763
- }
764
- },
765
- watch: {
766
- /**
767
- * When data prop change:
768
- * 1. Update internal value.
769
- * 2. Filter data if it's not backend-filtered.
770
- * 3. Sort again if it's not backend-sorted.
771
- * 4. Set new total if it's not backend-paginated.
772
- */
773
- data(value) {
774
- this.newData = value
775
- if (!this.backendFiltering) {
776
- this.newData = value.filter(
777
- (row) => this.isRowFiltered(row))
778
- }
779
- if (!this.backendSorting) {
780
- this.sort(this.currentSortColumn, true)
781
- }
782
- if (!this.backendPagination) {
783
- this.newDataTotal = this.newData.length
784
- }
785
- },
786
-
787
- /**
788
- * When Pagination total change, update internal total
789
- * only if it's backend-paginated.
790
- */
791
- total(newTotal) {
792
- if (!this.backendPagination) return
793
-
794
- this.newDataTotal = newTotal
795
- },
796
-
797
- currentPage(newVal) {
798
- this.newCurrentPage = newVal
799
- },
800
-
801
- newCurrentPage(newVal) {
802
- this.$emit('update:currentPage', newVal)
803
- },
804
-
805
- /**
806
- * When checkedRows prop change, update internal value without
807
- * mutating original data.
808
- */
809
- checkedRows(rows) {
810
- this.newCheckedRows = [...rows]
811
- },
812
-
813
- /*
814
- newColumns(value) {
815
- this.checkSort()
816
- },
817
- */
818
-
819
- debounceSearch: {
820
- handler(value) {
821
- this.debouncedHandleFiltersChange = debounce(this.handleFiltersChange, value)
822
- },
823
- immediate: true
824
- },
825
-
826
- filters: {
827
- handler(value) {
828
- if (this.debounceSearch) {
829
- this.debouncedHandleFiltersChange(value)
830
- } else {
831
- this.handleFiltersChange(value)
832
- }
833
- },
834
- deep: true
835
- },
836
-
837
- /**
838
- * When the user wants to control the detailed rows via props.
839
- * Or wants to open the details of certain row with the router for example.
840
- */
841
- openedDetailed(expandedRows) {
842
- this.visibleDetailRows = expandedRows
843
- }
844
- },
845
- methods: {
846
- onFiltersEvent(event) {
847
- this.$emit(`filters-event-${this.filtersEvent}`, { event, filters: this.filters })
848
- },
849
- handleFiltersChange(value) {
850
- if (this.backendFiltering) {
851
- this.$emit('filters-change', value)
852
- } else {
853
- this.newData = this.data.filter(
854
- (row) => this.isRowFiltered(row))
855
- if (!this.backendPagination) {
856
- this.newDataTotal = this.newData.length
857
- }
858
- if (!this.backendSorting) {
859
- if (this.sortMultiple &&
860
- this.sortMultipleDataLocal && this.sortMultipleDataLocal.length > 0) {
861
- this.doSortMultiColumn()
862
- } else if (Object.keys(this.currentSortColumn).length > 0) {
863
- this.doSortSingleColumn(this.currentSortColumn)
864
- }
865
- }
866
- }
867
- },
868
- findIndexOfSortData(column) {
869
- let sortObj = this.sortMultipleDataComputed.filter((i) =>
870
- i.field === column.field)[0]
871
- return this.sortMultipleDataComputed.indexOf(sortObj) + 1
872
- },
873
- removeSortingPriority(column) {
874
- if (this.backendSorting) {
875
- this.$emit('sorting-priority-removed', column.field)
876
- } else {
877
- this.sortMultipleDataLocal = this.sortMultipleDataLocal.filter(
878
- (priority) => priority.field !== column.field)
879
-
880
- let formattedSortingPriority = this.sortMultipleDataLocal.map((i) => {
881
- return (i.order && i.order === 'desc' ? '-' : '') + i.field
882
- })
883
- this.newData = multiColumnSort(this.newData, formattedSortingPriority)
884
- }
885
- },
886
- resetMultiSorting() {
887
- this.sortMultipleDataLocal = []
888
- this.currentSortColumn = {}
889
- this.newData = this.data
890
- },
891
- /**
892
- * Sort an array by key without mutating original data.
893
- * Call the user sort function if it was passed.
894
- */
895
- sortBy(array, key, fn, isAsc) {
896
- let sorted = []
897
- // Sorting without mutating original data
898
- if (fn && typeof fn === 'function') {
899
- sorted = [...array].sort((a, b) => fn(a, b, isAsc))
900
- } else {
901
- sorted = [...array].sort((a, b) => {
902
- // Get nested values from objects
903
- let newA = getValueByPath(a, key)
904
- let newB = getValueByPath(b, key)
905
-
906
- // sort boolean type
907
- if (typeof newA === 'boolean' && typeof newB === 'boolean') {
908
- return isAsc ? newA - newB : newB - newA
909
- }
910
-
911
- if (!newA && newA !== 0) return 1
912
- if (!newB && newB !== 0) return -1
913
- if (newA === newB) return 0
914
-
915
- newA = (typeof newA === 'string')
916
- ? newA.toUpperCase()
917
- : newA
918
- newB = (typeof newB === 'string')
919
- ? newB.toUpperCase()
920
- : newB
921
-
922
- return isAsc
923
- ? newA > newB ? 1 : -1
924
- : newA > newB ? -1 : 1
925
- })
926
- }
927
-
928
- return sorted
929
- },
930
-
931
- sortMultiColumn(column) {
932
- this.currentSortColumn = {}
933
- if (!this.backendSorting) {
934
- let existingPriority = this.sortMultipleDataLocal.filter((i) =>
935
- i.field === column.field)[0]
936
- if (existingPriority) {
937
- existingPriority.order = existingPriority.order === 'desc' ? 'asc' : 'desc'
938
- } else {
939
- this.sortMultipleDataLocal.push(
940
- {field: column.field, order: column.isAsc}
941
- )
942
- }
943
- this.doSortMultiColumn()
944
- }
945
- },
946
-
947
- doSortMultiColumn() {
948
- let formattedSortingPriority = this.sortMultipleDataLocal.map((i) => {
949
- return (i.order && i.order === 'desc' ? '-' : '') + i.field
950
- })
951
- this.newData = multiColumnSort(this.newData, formattedSortingPriority)
952
- },
953
-
954
- /**
955
- * Sort the column.
956
- * Toggle current direction on column if it's sortable
957
- * and not just updating the prop.
958
- */
959
- sort(column, updatingData = false, event = null) {
960
- if (!column || !column.sortable) return
961
- if (
962
- // if backend sorting is enabled, just emit the sort press like usual
963
- // if the correct key combination isnt pressed, sort like usual
964
- !this.backendSorting &&
965
- this.sortMultiple &&
966
- ((this.sortMultipleKey && event[this.sortMultipleKey]) || !this.sortMultipleKey)
967
- ) {
968
- if (updatingData) {
969
- this.doSortMultiColumn()
970
- } else {
971
- this.sortMultiColumn(column)
972
- }
973
- } else {
974
- // sort multiple is enabled but the correct key combination isnt pressed so reset
975
- if (this.sortMultiple) {
976
- this.sortMultipleDataLocal = []
977
- }
978
-
979
- if (!updatingData) {
980
- this.isAsc = column === this.currentSortColumn
981
- ? !this.isAsc
982
- : (this.defaultSortDirection.toLowerCase() !== 'desc')
983
- }
984
- if (!this.firstTimeSort) {
985
- this.$emit('sort', column.field, this.isAsc ? 'asc' : 'desc', event)
986
- }
987
- if (!this.backendSorting) {
988
- this.doSortSingleColumn(column)
989
- }
990
- this.currentSortColumn = column
991
- }
992
- },
993
-
994
- doSortSingleColumn(column) {
995
- this.newData = this.sortBy(
996
- this.newData,
997
- column.field,
998
- column.customSort,
999
- this.isAsc
1000
- )
1001
- },
1002
-
1003
- isRowSelected(row, selected) {
1004
- if (!selected) {
1005
- return false
1006
- }
1007
- if (this.customRowKey) {
1008
- return row[this.customRowKey] === selected[this.customRowKey]
1009
- }
1010
- return row === selected
1011
- },
1012
-
1013
- /**
1014
- * Check if the row is checked (is added to the array).
1015
- */
1016
- isRowChecked(row) {
1017
- return indexOf(this.newCheckedRows, row, this.customIsChecked) >= 0
1018
- },
1019
-
1020
- /**
1021
- * Remove a checked row from the array.
1022
- */
1023
- removeCheckedRow(row) {
1024
- const index = indexOf(this.newCheckedRows, row, this.customIsChecked)
1025
- if (index >= 0) {
1026
- this.newCheckedRows.splice(index, 1)
1027
- }
1028
- },
1029
-
1030
- /**
1031
- * Header checkbox click listener.
1032
- * Add or remove all rows in current page.
1033
- */
1034
- checkAll() {
1035
- const isAllChecked = this.isAllChecked
1036
- this.visibleData.forEach((currentRow) => {
1037
- if (this.isRowCheckable(currentRow)) {
1038
- this.removeCheckedRow(currentRow)
1039
- }
1040
- if (!isAllChecked) {
1041
- if (this.isRowCheckable(currentRow)) {
1042
- this.newCheckedRows.push(currentRow)
1043
- }
1044
- }
1045
- })
1046
-
1047
- this.$emit('check', this.newCheckedRows)
1048
- this.$emit('check-all', this.newCheckedRows)
1049
-
1050
- // Emit checked rows to update user variable
1051
- this.$emit('update:checkedRows', this.newCheckedRows)
1052
- },
1053
-
1054
- /**
1055
- * Row checkbox click listener.
1056
- */
1057
- checkRow(row, index, event) {
1058
- if (!this.isRowCheckable(row)) return
1059
- const lastIndex = this.lastCheckedRowIndex
1060
- this.lastCheckedRowIndex = index
1061
-
1062
- if (event.shiftKey && lastIndex !== null && index !== lastIndex) {
1063
- this.shiftCheckRow(row, index, lastIndex)
1064
- } else if (!this.isRowChecked(row)) {
1065
- this.newCheckedRows.push(row)
1066
- } else {
1067
- this.removeCheckedRow(row)
1068
- }
1069
-
1070
- this.$emit('check', this.newCheckedRows, row)
1071
-
1072
- // Emit checked rows to update user variable
1073
- this.$emit('update:checkedRows', this.newCheckedRows)
1074
- },
1075
-
1076
- /**
1077
- * Check row when shift is pressed.
1078
- */
1079
- shiftCheckRow(row, index, lastCheckedRowIndex) {
1080
- // Get the subset of the list between the two indicies
1081
- const subset = this.visibleData.slice(
1082
- Math.min(index, lastCheckedRowIndex),
1083
- Math.max(index, lastCheckedRowIndex) + 1
1084
- )
1085
-
1086
- // Determine the operation based on the state of the clicked checkbox
1087
- const shouldCheck = !this.isRowChecked(row)
1088
-
1089
- subset.forEach((item) => {
1090
- this.removeCheckedRow(item)
1091
- if (shouldCheck && this.isRowCheckable(item)) {
1092
- this.newCheckedRows.push(item)
1093
- }
1094
- })
1095
- },
1096
-
1097
- /**
1098
- * Row click listener.
1099
- * Emit all necessary events.
1100
- */
1101
- selectRow(row, index) {
1102
- this.$emit('click', row)
1103
-
1104
- if (this.selected === row) return
1105
- if (!this.isRowSelectable(row)) return
1106
-
1107
- // Emit new and old row
1108
- this.$emit('select', row, this.selected)
1109
-
1110
- // Emit new row to update user variable
1111
- this.$emit('update:selected', row)
1112
- },
1113
-
1114
- /**
1115
- * Toggle to show/hide details slot
1116
- */
1117
- toggleDetails(obj) {
1118
- const found = this.isVisibleDetailRow(obj)
1119
-
1120
- if (found) {
1121
- this.closeDetailRow(obj)
1122
- this.$emit('details-close', obj)
1123
- } else {
1124
- this.openDetailRow(obj)
1125
- this.$emit('details-open', obj)
1126
- }
1127
-
1128
- // Syncs the detailed rows with the parent component
1129
- this.$emit('update:openedDetailed', this.visibleDetailRows)
1130
- },
1131
-
1132
- openDetailRow(obj) {
1133
- const index = this.handleDetailKey(obj)
1134
- this.visibleDetailRows.push(index)
1135
- },
1136
-
1137
- closeDetailRow(obj) {
1138
- const index = this.handleDetailKey(obj)
1139
- const i = this.visibleDetailRows.indexOf(index)
1140
- if (i >= 0) {
1141
- this.visibleDetailRows.splice(i, 1)
1142
- }
1143
- },
1144
-
1145
- isVisibleDetailRow(obj) {
1146
- const index = this.handleDetailKey(obj)
1147
- return this.visibleDetailRows.indexOf(index) >= 0
1148
- },
1149
-
1150
- isActiveDetailRow(row) {
1151
- return this.detailed && !this.customDetailRow && this.isVisibleDetailRow(row)
1152
- },
1153
-
1154
- isActiveCustomDetailRow(row) {
1155
- return this.detailed && this.customDetailRow && this.isVisibleDetailRow(row)
1156
- },
1157
-
1158
- isRowFiltered(row) {
1159
- for (const key in this.filters) {
1160
- // remove key if empty
1161
- if (!this.filters[key]) {
1162
- delete this.filters[key]
1163
- return true
1164
- }
1165
- const input = this.filters[key]
1166
- const column = this.newColumns.filter((c) => c.field === key)[0]
1167
- if (column && column.customSearch && typeof column.customSearch === 'function') {
1168
- if (!column.customSearch(row, input)) return false
1169
- } else {
1170
- let value = this.getValueByPath(row, key)
1171
- if (value == null) return false
1172
- if (Number.isInteger(value)) {
1173
- if (value !== Number(input)) return false
1174
- } else {
1175
- const re = new RegExp(escapeRegExpChars(input), 'i')
1176
- if (!re.test(value)) return false
1177
- }
1178
- }
1179
- }
1180
- return true
1181
- },
1182
-
1183
- /**
1184
- * When the detailKey is defined we use the object[detailKey] as index.
1185
- * If not, use the object reference by default.
1186
- */
1187
- handleDetailKey(index) {
1188
- const key = this.detailKey
1189
- return !key.length || !index
1190
- ? index
1191
- : index[key]
1192
- },
1193
-
1194
- checkPredefinedDetailedRows() {
1195
- const defaultExpandedRowsDefined = this.openedDetailed.length > 0
1196
- if (defaultExpandedRowsDefined && !this.detailKey.length) {
1197
- throw new Error('If you set a predefined opened-detailed, you must provide a unique key using the prop "detail-key"')
1198
- }
1199
- },
1200
-
1201
- /**
1202
- * Call initSort only first time (For example async data).
1203
- */
1204
- checkSort() {
1205
- if (this.newColumns.length && this.firstTimeSort) {
1206
- this.initSort()
1207
- this.firstTimeSort = false
1208
- } else if (this.newColumns.length) {
1209
- if (Object.keys(this.currentSortColumn).length > 0) {
1210
- for (let i = 0; i < this.newColumns.length; i++) {
1211
- if (this.newColumns[i].field === this.currentSortColumn.field) {
1212
- this.currentSortColumn = this.newColumns[i]
1213
- break
1214
- }
1215
- }
1216
- }
1217
- }
1218
- },
1219
-
1220
- /**
1221
- * Check if footer slot has custom content.
1222
- */
1223
- hasCustomFooterSlot() {
1224
- if (this.$slots.footer.length > 1) return true
1225
-
1226
- const tag = this.$slots.footer[0].tag
1227
- if (tag !== 'th' && tag !== 'td') return false
1228
-
1229
- return true
1230
- },
1231
-
1232
- /**
1233
- * Check if bottom-left slot exists.
1234
- */
1235
- hasBottomLeftSlot() {
1236
- return typeof this.$slots['bottom-left'] !== 'undefined'
1237
- },
1238
-
1239
- /**
1240
- * Table arrow keys listener, change selection.
1241
- */
1242
- pressedArrow(pos) {
1243
- if (!this.visibleData.length) return
1244
-
1245
- let index = this.visibleData.indexOf(this.selected) + pos
1246
-
1247
- // Prevent from going up from first and down from last
1248
- index = index < 0
1249
- ? 0
1250
- : index > this.visibleData.length - 1
1251
- ? this.visibleData.length - 1
1252
- : index
1253
-
1254
- const row = this.visibleData[index]
1255
-
1256
- if (!this.isRowSelectable(row)) {
1257
- let newIndex = null
1258
- if (pos > 0) {
1259
- for (let i = index; i < this.visibleData.length && newIndex === null; i++) {
1260
- if (this.isRowSelectable(this.visibleData[i])) newIndex = i
1261
- }
1262
- } else {
1263
- for (let i = index; i >= 0 && newIndex === null; i--) {
1264
- if (this.isRowSelectable(this.visibleData[i])) newIndex = i
1265
- }
1266
- }
1267
- if (newIndex >= 0) {
1268
- this.selectRow(this.visibleData[newIndex])
1269
- }
1270
- } else {
1271
- this.selectRow(row)
1272
- }
1273
- },
1274
-
1275
- /**
1276
- * Focus table element if has selected prop.
1277
- */
1278
- focus() {
1279
- if (!this.focusable) return
1280
-
1281
- this.$el.querySelector('table').focus()
1282
- },
1283
-
1284
- /**
1285
- * Initial sorted column based on the default-sort prop.
1286
- */
1287
- initSort() {
1288
- if (this.sortMultiple && this.sortMultipleData) {
1289
- this.sortMultipleData.forEach((column) => {
1290
- this.sortMultiColumn(column)
1291
- })
1292
- } else {
1293
- if (!this.defaultSort) return
1294
-
1295
- let sortField = ''
1296
- let sortDirection = this.defaultSortDirection
1297
-
1298
- if (Array.isArray(this.defaultSort)) {
1299
- sortField = this.defaultSort[0]
1300
- if (this.defaultSort[1]) {
1301
- sortDirection = this.defaultSort[1]
1302
- }
1303
- } else {
1304
- sortField = this.defaultSort
1305
- }
1306
-
1307
- const sortColumn = this.newColumns.filter(
1308
- (column) => (column.field === sortField))[0]
1309
- if (sortColumn) {
1310
- this.isAsc = sortDirection.toLowerCase() !== 'desc'
1311
- this.sort(sortColumn, true)
1312
- }
1313
- }
1314
- },
1315
- /**
1316
- * Emits drag start event (row)
1317
- */
1318
- handleDragStart(event, row, index) {
1319
- if (!this.canDragRow) return
1320
- this.isDraggingRow = true
1321
- this.$emit('dragstart', {event, row, index})
1322
- },
1323
- /**
1324
- * Emits drag leave event (row)
1325
- */
1326
- handleDragEnd(event, row, index) {
1327
- if (!this.canDragRow) return
1328
- this.isDraggingRow = false
1329
- this.$emit('dragend', {event, row, index})
1330
- },
1331
- /**
1332
- * Emits drop event (row)
1333
- */
1334
- handleDrop(event, row, index) {
1335
- if (!this.canDragRow) return
1336
- this.$emit('drop', {event, row, index})
1337
- },
1338
- /**
1339
- * Emits drag over event (row)
1340
- */
1341
- handleDragOver(event, row, index) {
1342
- if (!this.canDragRow) return
1343
- this.$emit('dragover', {event, row, index})
1344
- },
1345
- /**
1346
- * Emits drag leave event (row)
1347
- */
1348
- handleDragLeave(event, row, index) {
1349
- if (!this.canDragRow) return
1350
- this.$emit('dragleave', {event, row, index})
1351
- },
1352
-
1353
- emitEventForRow(eventName, event, row) {
1354
- return this.$listeners[eventName] ? this.$emit(eventName, row, event) : null
1355
- },
1356
-
1357
- /**
1358
- * Emits drag start event (column)
1359
- */
1360
- handleColumnDragStart(event, column, index) {
1361
- if (!this.canDragColumn) return
1362
- this.isDraggingColumn = true
1363
- this.$emit('columndragstart', {event, column, index})
1364
- },
1365
-
1366
- /**
1367
- * Emits drag leave event (column)
1368
- */
1369
- handleColumnDragEnd(event, column, index) {
1370
- if (!this.canDragColumn) return
1371
- this.isDraggingColumn = false
1372
- this.$emit('columndragend', {event, column, index})
1373
- },
1374
-
1375
- /**
1376
- * Emits drop event (column)
1377
- */
1378
- handleColumnDrop(event, column, index) {
1379
- if (!this.canDragColumn) return
1380
- this.$emit('columndrop', {event, column, index})
1381
- },
1382
-
1383
- /**
1384
- * Emits drag over event (column)
1385
- */
1386
- handleColumnDragOver(event, column, index) {
1387
- if (!this.canDragColumn) return
1388
- this.$emit('columndragover', {event, column, index})
1389
- },
1390
-
1391
- /**
1392
- * Emits drag leave event (column)
1393
- */
1394
- handleColumnDragLeave(event, column, index) {
1395
- if (!this.canDragColumn) return
1396
- this.$emit('columndragleave', {event, column, index})
1397
- },
1398
-
1399
- refreshSlots() {
1400
- this.defaultSlots = this.$slots.default || []
1401
- }
1402
- },
1403
- mounted() {
1404
- this.refreshSlots()
1405
- this.checkPredefinedDetailedRows()
1406
- this.checkSort()
1407
- }
1408
- }
1409
- </script>
1
+ <template>
2
+ <div class="b-table">
3
+
4
+ <slot />
5
+
6
+ <b-table-mobile-sort
7
+ v-if="mobileCards && hasSortablenewColumns"
8
+ :current-sort-column="currentSortColumn"
9
+ :sort-multiple="sortMultiple"
10
+ :sort-multiple-data="sortMultipleDataComputed"
11
+ :is-asc="isAsc"
12
+ :columns="newColumns"
13
+ :placeholder="mobileSortPlaceholder"
14
+ :icon-pack="iconPack"
15
+ :sort-icon="sortIcon"
16
+ :sort-icon-size="sortIconSize"
17
+ @sort="(column, event) => sort(column, null, event)"
18
+ @removePriority="(column) => removeSortingPriority(column)"
19
+ />
20
+
21
+ <template
22
+ v-if="paginated && (paginationPosition === 'top' || paginationPosition === 'both')">
23
+ <slot name="pagination">
24
+ <b-table-pagination
25
+ v-bind="$attrs"
26
+ :per-page="perPage"
27
+ :paginated="paginated"
28
+ :rounded="paginationRounded"
29
+ :icon-pack="iconPack"
30
+ :total="newDataTotal"
31
+ :current-page.sync="newCurrentPage"
32
+ :aria-next-label="ariaNextLabel"
33
+ :aria-previous-label="ariaPreviousLabel"
34
+ :aria-page-label="ariaPageLabel"
35
+ :aria-current-label="ariaCurrentLabel"
36
+ @page-change="(event) => $emit('page-change', event)"
37
+ :page-input="pageInput"
38
+ :pagination-order="paginationOrder"
39
+ :page-input-position="pageInputPosition"
40
+ :debounce-page-input="debouncePageInput"
41
+ >
42
+ <slot name="top-left"/>
43
+ </b-table-pagination>
44
+ </slot>
45
+ </template>
46
+
47
+ <div
48
+ class="table-wrapper"
49
+ :class="tableWrapperClasses"
50
+ :style="tableStyle"
51
+ >
52
+ <table
53
+ class="table"
54
+ :class="tableClasses"
55
+ :tabindex="!focusable ? false : 0"
56
+ @keydown.self.prevent.up="pressedArrow(-1)"
57
+ @keydown.self.prevent.down="pressedArrow(1)">
58
+ <caption v-show="showCaption" v-if="caption">{{ caption }}</caption>
59
+ <thead v-if="newColumns.length && showHeader">
60
+ <tr>
61
+ <th v-if="showDetailRowIcon" width="40px"/>
62
+ <th
63
+ :class="['checkbox-cell', { 'is-sticky': stickyCheckbox } ]"
64
+ v-if="checkable && checkboxPosition === 'left'">
65
+ <template v-if="headerCheckable">
66
+ <b-checkbox
67
+ autocomplete="off"
68
+ :value="isAllChecked"
69
+ :disabled="isAllUncheckable"
70
+ @change.native="checkAll"/>
71
+ </template>
72
+ </th>
73
+ <th
74
+ v-for="(column, index) in visibleColumns"
75
+ :key="column.newKey + ':' + index + 'header'"
76
+ v-bind="column.thAttrs(column)"
77
+ :class="[column.thClasses, {
78
+ 'is-current-sort': !sortMultiple && currentSortColumn === column,
79
+ }]"
80
+ :style="column.thStyle"
81
+ @click.stop="sort(column, null, $event)"
82
+ :draggable="canDragColumn"
83
+ @dragstart="handleColumnDragStart($event, column, index)"
84
+ @dragend="handleColumnDragEnd($event, column, index)"
85
+ @drop="handleColumnDrop($event, column, index)"
86
+ @dragover="handleColumnDragOver($event, column, index)"
87
+ @dragleave="handleColumnDragLeave($event, column, index)">
88
+ <div
89
+ class="th-wrap"
90
+ :class="{
91
+ 'is-numeric': column.numeric,
92
+ 'is-centered': column.centered
93
+ }">
94
+ <template v-if="column.$scopedSlots && column.$scopedSlots.header">
95
+ <b-slot-component
96
+ :component="column"
97
+ scoped
98
+ name="header"
99
+ tag="span"
100
+ :props="{ column, index }"
101
+ />
102
+ </template>
103
+ <template v-else>
104
+ <span class="is-relative">
105
+ {{ column.label }}
106
+ <template
107
+ v-if="sortMultiple &&
108
+ sortMultipleDataComputed &&
109
+ sortMultipleDataComputed.length > 0 &&
110
+ sortMultipleDataComputed.filter(i =>
111
+ i.field === column.field).length > 0">
112
+ <b-icon
113
+ :icon="sortIcon"
114
+ :pack="iconPack"
115
+ both
116
+ :size="sortIconSize"
117
+ :class="{
118
+ 'is-desc': sortMultipleDataComputed.filter(i =>
119
+ i.field === column.field)[0].order === 'desc'}"
120
+ />
121
+ {{ findIndexOfSortData(column) }}
122
+ <button
123
+ class="delete is-small multi-sort-cancel-icon"
124
+ type="button"
125
+ @click.stop="removeSortingPriority(column)"/>
126
+ </template>
127
+
128
+ <b-icon
129
+ v-else
130
+ :icon="sortIcon"
131
+ :pack="iconPack"
132
+ both
133
+ :size="sortIconSize"
134
+ class="sort-icon"
135
+ :class="{
136
+ 'is-desc': !isAsc,
137
+ 'is-invisible': currentSortColumn !== column
138
+ }"
139
+ />
140
+ </span>
141
+ </template>
142
+ </div>
143
+ </th>
144
+ <th
145
+ :class="['checkbox-cell', { 'is-sticky': stickyCheckbox } ]"
146
+ v-if="checkable && checkboxPosition === 'right'">
147
+ <template v-if="headerCheckable">
148
+ <b-checkbox
149
+ autocomplete="off"
150
+ :value="isAllChecked"
151
+ :disabled="isAllUncheckable"
152
+ @change.native="checkAll"/>
153
+ </template>
154
+ </th>
155
+ </tr>
156
+ <tr v-if="hasCustomSubheadings" class="is-subheading">
157
+ <th v-if="showDetailRowIcon" width="40px"/>
158
+ <th v-if="checkable && checkboxPosition === 'left'" />
159
+ <th
160
+ v-for="(column, index) in visibleColumns"
161
+ :key="column.newKey + ':' + index + 'subheading'"
162
+ :style="column.style">
163
+ <div
164
+ class="th-wrap"
165
+ :class="{
166
+ 'is-numeric': column.numeric,
167
+ 'is-centered': column.centered
168
+ }">
169
+ <template
170
+ v-if="column.$scopedSlots && column.$scopedSlots.subheading"
171
+ >
172
+ <b-slot-component
173
+ :component="column"
174
+ scoped
175
+ name="subheading"
176
+ tag="span"
177
+ :props="{ column, index }"
178
+ />
179
+ </template>
180
+ <template v-else>{{ column.subheading }}</template>
181
+ </div>
182
+ </th>
183
+ <th v-if="checkable && checkboxPosition === 'right'" />
184
+ </tr>
185
+ <tr v-if="hasSearchablenewColumns">
186
+ <th v-if="showDetailRowIcon" width="40px"/>
187
+ <th v-if="checkable && checkboxPosition === 'left'" />
188
+ <th
189
+ v-for="(column, index) in visibleColumns"
190
+ :key="column.newKey + ':' + index + 'searchable'"
191
+ v-bind="column.thAttrs(column)"
192
+ :style="column.thStyle"
193
+ :class="{'is-sticky': column.sticky}">
194
+ <div class="th-wrap">
195
+ <template v-if="column.searchable">
196
+ <template
197
+ v-if="column.$scopedSlots
198
+ && column.$scopedSlots.searchable">
199
+ <b-slot-component
200
+ :component="column"
201
+ :scoped="true"
202
+ name="searchable"
203
+ tag="span"
204
+ :props="{ column, filters }"
205
+ />
206
+ </template>
207
+ <b-input
208
+ v-else
209
+ @[filtersEvent].native="onFiltersEvent"
210
+ v-model="filters[column.field]"
211
+ :type="column.numeric ? 'number' : 'text'" />
212
+ </template>
213
+ </div>
214
+ </th>
215
+ <th v-if="checkable && checkboxPosition === 'right'" />
216
+ </tr>
217
+ </thead>
218
+ <tbody>
219
+ <template v-for="(row, index) in visibleData">
220
+ <tr
221
+ :key="customRowKey ? row[customRowKey] : index"
222
+ :class="[rowClass(row, index), {
223
+ 'is-selected': isRowSelected(row, selected),
224
+ 'is-checked': isRowChecked(row),
225
+ }]"
226
+ @click="selectRow(row)"
227
+ @dblclick="$emit('dblclick', row)"
228
+ @mouseenter="emitEventForRow('mouseenter', $event, row)"
229
+ @mouseleave="emitEventForRow('mouseleave', $event, row)"
230
+ @contextmenu="$emit('contextmenu', row, $event)"
231
+ :draggable="canDragRow"
232
+ @dragstart="handleDragStart($event, row, index)"
233
+ @dragend="handleDragEnd($event, row, index)"
234
+ @drop="handleDrop($event, row, index)"
235
+ @dragover="handleDragOver($event, row, index)"
236
+ @dragleave="handleDragLeave($event, row, index)">
237
+
238
+ <td
239
+ v-if="showDetailRowIcon"
240
+ class="chevron-cell"
241
+ >
242
+ <a
243
+ v-if="hasDetailedVisible(row)"
244
+ role="button"
245
+ @click.stop="toggleDetails(row)">
246
+ <b-icon
247
+ :icon="detailIcon"
248
+ :pack="iconPack"
249
+ both
250
+ :class="{'is-expanded': isVisibleDetailRow(row)}"/>
251
+ </a>
252
+ </td>
253
+
254
+ <td
255
+ :class="['checkbox-cell', { 'is-sticky': stickyCheckbox } ]"
256
+ v-if="checkable && checkboxPosition === 'left'">
257
+ <b-checkbox
258
+ autocomplete="off"
259
+ :disabled="!isRowCheckable(row)"
260
+ :value="isRowChecked(row)"
261
+ @click.native.prevent.stop="checkRow(row, index, $event)"
262
+ />
263
+ </td>
264
+
265
+ <template v-for="(column, colindex) in visibleColumns">
266
+
267
+ <template v-if="column.$scopedSlots && column.$scopedSlots.default">
268
+ <b-slot-component
269
+ :key="column.newKey + ':' + index + ':' + colindex"
270
+ :component="column"
271
+ v-bind="column.tdAttrs(row, column)"
272
+ scoped
273
+ name="default"
274
+ tag="td"
275
+ :class="column.getRootClasses(row)"
276
+ :style="column.getRootStyle(row)"
277
+ :data-label="column.label"
278
+ :props="{ row, column, index, colindex, toggleDetails }"
279
+ @click.native="$emit('cellclick',row,column,index,colindex)"
280
+ />
281
+ </template>
282
+
283
+ </template>
284
+
285
+ <td
286
+ :class="['checkbox-cell', { 'is-sticky': stickyCheckbox } ]"
287
+ v-if="checkable && checkboxPosition === 'right'">
288
+ <b-checkbox
289
+ autocomplete="off"
290
+ :disabled="!isRowCheckable(row)"
291
+ :value="isRowChecked(row)"
292
+ @click.native.prevent.stop="checkRow(row, index, $event)"
293
+ />
294
+ </td>
295
+ </tr>
296
+
297
+ <transition
298
+ :key="(customRowKey ? row[customRowKey] : index) + 'detail'"
299
+ :name="detailTransition"
300
+ >
301
+ <tr
302
+ v-if="isActiveDetailRow(row)"
303
+ class="detail">
304
+ <td :colspan="columnCount">
305
+ <div class="detail-container">
306
+ <slot
307
+ name="detail"
308
+ :row="row"
309
+ :index="index"
310
+ />
311
+ </div>
312
+ </td>
313
+ </tr>
314
+ </transition>
315
+ <slot
316
+ v-if="isActiveCustomDetailRow(row)"
317
+ name="detail"
318
+ :row="row"
319
+ :index="index"
320
+ />
321
+ </template>
322
+
323
+ <tr
324
+ v-if="!visibleData.length"
325
+ class="is-empty">
326
+ <td :colspan="columnCount">
327
+ <slot name="empty"/>
328
+ </td>
329
+ </tr>
330
+
331
+ </tbody>
332
+
333
+ <tfoot v-if="$slots.footer !== undefined">
334
+ <tr class="table-footer">
335
+ <slot name="footer" v-if="hasCustomFooterSlot()"/>
336
+ <th :colspan="columnCount" v-else>
337
+ <slot name="footer"/>
338
+ </th>
339
+ </tr>
340
+ </tfoot>
341
+ </table>
342
+
343
+ <template v-if="loading">
344
+ <slot name="loading">
345
+ <b-loading :is-full-page="false" :active.sync="loading" />
346
+ </slot>
347
+ </template>
348
+
349
+ </div>
350
+
351
+ <template
352
+ v-if="(checkable && hasBottomLeftSlot()) ||
353
+ (paginated && (paginationPosition === 'bottom' || paginationPosition === 'both'))"
354
+ >
355
+ <slot name="pagination">
356
+ <b-table-pagination
357
+ v-bind="$attrs"
358
+ :per-page="perPage"
359
+ :paginated="paginated"
360
+ :rounded="paginationRounded"
361
+ :icon-pack="iconPack"
362
+ :total="newDataTotal"
363
+ :current-page.sync="newCurrentPage"
364
+ :aria-next-label="ariaNextLabel"
365
+ :aria-previous-label="ariaPreviousLabel"
366
+ :aria-page-label="ariaPageLabel"
367
+ :aria-current-label="ariaCurrentLabel"
368
+ @page-change="(event) => $emit('page-change', event)"
369
+ :page-input="pageInput"
370
+ :pagination-order="paginationOrder"
371
+ :page-input-position="pageInputPosition"
372
+ :debounce-page-input="debouncePageInput"
373
+ >
374
+ <slot name="bottom-left"/>
375
+ </b-table-pagination>
376
+ </slot>
377
+ </template>
378
+
379
+ </div>
380
+ </template>
381
+
382
+ <script>
383
+ import { getValueByPath, indexOf, multiColumnSort, escapeRegExpChars, toCssWidth, removeDiacriticsFromString, isNil } from '../../utils/helpers'
384
+ import debounce from '../../utils/debounce'
385
+ import { VueInstance } from '../../utils/config'
386
+ import Checkbox from '../checkbox/Checkbox'
387
+ import Icon from '../icon/Icon'
388
+ import Input from '../input/Input'
389
+ import Loading from '../loading/Loading'
390
+ import SlotComponent from '../../utils/SlotComponent'
391
+ import TableMobileSort from './TableMobileSort'
392
+ import TableColumn from './TableColumn'
393
+ import TablePagination from './TablePagination'
394
+
395
+ export default {
396
+ name: 'BTable',
397
+ components: {
398
+ [Checkbox.name]: Checkbox,
399
+ [Icon.name]: Icon,
400
+ [Input.name]: Input,
401
+ [Loading.name]: Loading,
402
+ [SlotComponent.name]: SlotComponent,
403
+ [TableMobileSort.name]: TableMobileSort,
404
+ [TableColumn.name]: TableColumn,
405
+ [TablePagination.name]: TablePagination
406
+ },
407
+ inheritAttrs: false,
408
+ provide() {
409
+ return {
410
+ $table: this
411
+ }
412
+ },
413
+ props: {
414
+ data: {
415
+ type: Array,
416
+ default: () => []
417
+ },
418
+ columns: {
419
+ type: Array,
420
+ default: () => []
421
+ },
422
+ bordered: Boolean,
423
+ striped: Boolean,
424
+ narrowed: Boolean,
425
+ hoverable: Boolean,
426
+ loading: Boolean,
427
+ detailed: Boolean,
428
+ checkable: Boolean,
429
+ headerCheckable: {
430
+ type: Boolean,
431
+ default: true
432
+ },
433
+ checkboxPosition: {
434
+ type: String,
435
+ default: 'left',
436
+ validator: (value) => {
437
+ return [
438
+ 'left',
439
+ 'right'
440
+ ].indexOf(value) >= 0
441
+ }
442
+ },
443
+ stickyCheckbox: {
444
+ type: Boolean,
445
+ default: false
446
+ },
447
+ selected: Object,
448
+ isRowSelectable: {
449
+ type: Function,
450
+ default: () => true
451
+ },
452
+ focusable: Boolean,
453
+ customIsChecked: Function,
454
+ isRowCheckable: {
455
+ type: Function,
456
+ default: () => true
457
+ },
458
+ checkedRows: {
459
+ type: Array,
460
+ default: () => []
461
+ },
462
+ mobileCards: {
463
+ type: Boolean,
464
+ default: true
465
+ },
466
+ defaultSort: [String, Array],
467
+ defaultSortDirection: {
468
+ type: String,
469
+ default: 'asc'
470
+ },
471
+ sortIcon: {
472
+ type: String,
473
+ default: 'arrow-up'
474
+ },
475
+ sortIconSize: {
476
+ type: String,
477
+ default: 'is-small'
478
+ },
479
+ sortMultiple: {
480
+ type: Boolean,
481
+ default: false
482
+ },
483
+ sortMultipleData: {
484
+ type: Array,
485
+ default: () => []
486
+ },
487
+ sortMultipleKey: {
488
+ type: String,
489
+ default: null
490
+ },
491
+ paginated: Boolean,
492
+ currentPage: {
493
+ type: Number,
494
+ default: 1
495
+ },
496
+ perPage: {
497
+ type: [Number, String],
498
+ default: 20
499
+ },
500
+ showDetailIcon: {
501
+ type: Boolean,
502
+ default: true
503
+ },
504
+ detailIcon: {
505
+ type: String,
506
+ default: 'chevron-right'
507
+ },
508
+ paginationPosition: {
509
+ type: String,
510
+ default: 'bottom',
511
+ validator: (value) => {
512
+ return [
513
+ 'bottom',
514
+ 'top',
515
+ 'both'
516
+ ].indexOf(value) >= 0
517
+ }
518
+ },
519
+ paginationRounded: Boolean,
520
+ backendSorting: Boolean,
521
+ backendFiltering: Boolean,
522
+ rowClass: {
523
+ type: Function,
524
+ default: () => ''
525
+ },
526
+ openedDetailed: {
527
+ type: Array,
528
+ default: () => []
529
+ },
530
+ hasDetailedVisible: {
531
+ type: Function,
532
+ default: () => true
533
+ },
534
+ detailKey: {
535
+ type: String,
536
+ default: ''
537
+ },
538
+ detailTransition: {
539
+ type: String,
540
+ default: ''
541
+ },
542
+ customDetailRow: {
543
+ type: Boolean,
544
+ default: false
545
+ },
546
+ backendPagination: Boolean,
547
+ total: {
548
+ type: [Number, String],
549
+ default: 0
550
+ },
551
+ iconPack: String,
552
+ mobileSortPlaceholder: String,
553
+ customRowKey: String,
554
+ draggable: {
555
+ type: Boolean,
556
+ default: false
557
+ },
558
+ draggableColumn: {
559
+ type: Boolean,
560
+ default: false
561
+ },
562
+ scrollable: Boolean,
563
+ ariaNextLabel: String,
564
+ ariaPreviousLabel: String,
565
+ ariaPageLabel: String,
566
+ ariaCurrentLabel: String,
567
+ stickyHeader: Boolean,
568
+ height: [Number, String],
569
+ filtersEvent: {
570
+ type: String,
571
+ default: ''
572
+ },
573
+ cardLayout: Boolean,
574
+ showHeader: {
575
+ type: Boolean,
576
+ default: true
577
+ },
578
+ debounceSearch: Number,
579
+ caption: String,
580
+ showCaption: {
581
+ type: Boolean,
582
+ default: true
583
+ },
584
+ pageInput: {
585
+ type: Boolean,
586
+ default: false
587
+ },
588
+ paginationOrder: String,
589
+ pageInputPosition: String,
590
+ debouncePageInput: [Number, String]
591
+ },
592
+ data() {
593
+ return {
594
+ sortMultipleDataLocal: [],
595
+ getValueByPath,
596
+ visibleDetailRows: this.openedDetailed,
597
+ newData: this.data,
598
+ newDataTotal: this.backendPagination ? this.total : this.data.length,
599
+ newCheckedRows: [...this.checkedRows],
600
+ lastCheckedRowIndex: null,
601
+ newCurrentPage: this.currentPage,
602
+ currentSortColumn: {},
603
+ isAsc: true,
604
+ filters: {},
605
+ defaultSlots: [],
606
+ firstTimeSort: true, // Used by first time initSort
607
+ _isTable: true, // Used by TableColumn
608
+ isDraggingRow: false,
609
+ isDraggingColumn: false
610
+ }
611
+ },
612
+ computed: {
613
+ sortMultipleDataComputed() {
614
+ return this.backendSorting ? this.sortMultipleData : this.sortMultipleDataLocal
615
+ },
616
+ tableClasses() {
617
+ return {
618
+ 'is-bordered': this.bordered,
619
+ 'is-striped': this.striped,
620
+ 'is-narrow': this.narrowed,
621
+ 'is-hoverable': (
622
+ (this.hoverable || this.focusable) &&
623
+ this.visibleData.length
624
+ )
625
+ }
626
+ },
627
+ tableWrapperClasses() {
628
+ return {
629
+ 'has-mobile-cards': this.mobileCards,
630
+ 'has-sticky-header': this.stickyHeader,
631
+ 'is-card-list': this.cardLayout,
632
+ 'table-container': this.isScrollable
633
+ }
634
+ },
635
+ tableStyle() {
636
+ return {
637
+ height: toCssWidth(this.height)
638
+ }
639
+ },
640
+
641
+ /**
642
+ * Splitted data based on the pagination.
643
+ */
644
+ visibleData() {
645
+ if (!this.paginated) return this.newData
646
+
647
+ const currentPage = this.newCurrentPage
648
+ const perPage = this.perPage
649
+
650
+ if (this.newData.length <= perPage) {
651
+ return this.newData
652
+ } else {
653
+ const start = (currentPage - 1) * perPage
654
+ const end = parseInt(start, 10) + parseInt(perPage, 10)
655
+ return this.newData.slice(start, end)
656
+ }
657
+ },
658
+
659
+ visibleColumns() {
660
+ if (!this.newColumns) return this.newColumns
661
+ return this.newColumns.filter((column) => {
662
+ return column.visible || column.visible === undefined
663
+ })
664
+ },
665
+
666
+ /**
667
+ * Check if all rows in the page are checked.
668
+ */
669
+ isAllChecked() {
670
+ const validVisibleData = this.visibleData.filter(
671
+ (row) => this.isRowCheckable(row))
672
+ if (validVisibleData.length === 0) return false
673
+ const isAllChecked = validVisibleData.some((currentVisibleRow) => {
674
+ return indexOf(this.newCheckedRows, currentVisibleRow, this.customIsChecked) < 0
675
+ })
676
+ return !isAllChecked
677
+ },
678
+
679
+ /**
680
+ * Check if all rows in the page are checkable.
681
+ */
682
+ isAllUncheckable() {
683
+ const validVisibleData = this.visibleData.filter(
684
+ (row) => this.isRowCheckable(row))
685
+ return validVisibleData.length === 0
686
+ },
687
+
688
+ /**
689
+ * Check if has any sortable column.
690
+ */
691
+ hasSortablenewColumns() {
692
+ return this.newColumns.some((column) => {
693
+ return column.sortable
694
+ })
695
+ },
696
+
697
+ /**
698
+ * Check if has any searchable column.
699
+ */
700
+ hasSearchablenewColumns() {
701
+ return this.newColumns.some((column) => {
702
+ return column.searchable
703
+ })
704
+ },
705
+
706
+ /**
707
+ * Check if has any column using subheading.
708
+ */
709
+ hasCustomSubheadings() {
710
+ if (this.$scopedSlots && this.$scopedSlots.subheading) return true
711
+ return this.newColumns.some((column) => {
712
+ return column.subheading || (column.$scopedSlots && column.$scopedSlots.subheading)
713
+ })
714
+ },
715
+
716
+ /**
717
+ * Return total column count based if it's checkable or expanded
718
+ */
719
+ columnCount() {
720
+ let count = this.visibleColumns.length
721
+ count += this.checkable ? 1 : 0
722
+ count += (this.detailed && this.showDetailIcon) ? 1 : 0
723
+
724
+ return count
725
+ },
726
+
727
+ /**
728
+ * return if detailed row tabled
729
+ * will be with chevron column & icon or not
730
+ */
731
+ showDetailRowIcon() {
732
+ return this.detailed && this.showDetailIcon
733
+ },
734
+
735
+ /**
736
+ * return if scrollable table
737
+ */
738
+ isScrollable() {
739
+ if (this.scrollable) return true
740
+ if (!this.newColumns) return false
741
+ return this.newColumns.some((column) => {
742
+ return column.sticky
743
+ })
744
+ },
745
+
746
+ newColumns() {
747
+ if (this.columns && this.columns.length) {
748
+ return this.columns.map((column) => {
749
+ const TableColumnComponent = VueInstance.extend(TableColumn)
750
+ const component = new TableColumnComponent(
751
+ { parent: this, propsData: column }
752
+ )
753
+ component.$scopedSlots = {
754
+ default: (props) => {
755
+ const vnode = component.$createElement('span', {
756
+ domProps: {
757
+ innerHTML: getValueByPath(props.row, column.field)
758
+ }
759
+ })
760
+ return [vnode]
761
+ }
762
+ }
763
+ return component
764
+ })
765
+ }
766
+ return this.defaultSlots
767
+ .filter((vnode) =>
768
+ vnode.componentInstance &&
769
+ vnode.componentInstance.$data &&
770
+ vnode.componentInstance.$data._isTableColumn)
771
+ .map((vnode) => vnode.componentInstance)
772
+ },
773
+ canDragRow() {
774
+ return this.draggable && !this.isDraggingColumn
775
+ },
776
+ canDragColumn() {
777
+ return this.draggableColumn && !this.isDraggingRow
778
+ }
779
+ },
780
+ watch: {
781
+ /**
782
+ * When data prop change:
783
+ * 1. Update internal value.
784
+ * 2. Filter data if it's not backend-filtered.
785
+ * 3. Sort again if it's not backend-sorted.
786
+ * 4. Set new total if it's not backend-paginated.
787
+ */
788
+ data(value) {
789
+ this.newData = value
790
+ if (!this.backendFiltering) {
791
+ this.newData = value.filter(
792
+ (row) => this.isRowFiltered(row))
793
+ }
794
+ if (!this.backendSorting) {
795
+ this.sort(this.currentSortColumn, true)
796
+ }
797
+ if (!this.backendPagination) {
798
+ this.newDataTotal = this.newData.length
799
+ }
800
+ },
801
+
802
+ /**
803
+ * When Pagination total change, update internal total
804
+ * only if it's backend-paginated.
805
+ */
806
+ total(newTotal) {
807
+ if (!this.backendPagination) return
808
+
809
+ this.newDataTotal = newTotal
810
+ },
811
+
812
+ currentPage(newVal) {
813
+ this.newCurrentPage = newVal
814
+ },
815
+
816
+ newCurrentPage(newVal) {
817
+ this.$emit('update:currentPage', newVal)
818
+ },
819
+
820
+ /**
821
+ * When checkedRows prop change, update internal value without
822
+ * mutating original data.
823
+ */
824
+ checkedRows(rows) {
825
+ this.newCheckedRows = [...rows]
826
+ },
827
+
828
+ /*
829
+ newColumns(value) {
830
+ this.checkSort()
831
+ },
832
+ */
833
+
834
+ debounceSearch: {
835
+ handler(value) {
836
+ this.debouncedHandleFiltersChange = debounce(this.handleFiltersChange, value)
837
+ },
838
+ immediate: true
839
+ },
840
+
841
+ filters: {
842
+ handler(value) {
843
+ if (this.debounceSearch) {
844
+ this.debouncedHandleFiltersChange(value)
845
+ } else {
846
+ this.handleFiltersChange(value)
847
+ }
848
+ },
849
+ deep: true
850
+ },
851
+
852
+ /**
853
+ * When the user wants to control the detailed rows via props.
854
+ * Or wants to open the details of certain row with the router for example.
855
+ */
856
+ openedDetailed(expandedRows) {
857
+ this.visibleDetailRows = expandedRows
858
+ }
859
+ },
860
+ methods: {
861
+ onFiltersEvent(event) {
862
+ this.$emit(`filters-event-${this.filtersEvent}`, { event, filters: this.filters })
863
+ },
864
+ handleFiltersChange(value) {
865
+ if (this.backendFiltering) {
866
+ this.$emit('filters-change', value)
867
+ } else {
868
+ this.newData = this.data.filter(
869
+ (row) => this.isRowFiltered(row))
870
+ if (!this.backendPagination) {
871
+ this.newDataTotal = this.newData.length
872
+ }
873
+ if (!this.backendSorting) {
874
+ if (this.sortMultiple &&
875
+ this.sortMultipleDataLocal && this.sortMultipleDataLocal.length > 0) {
876
+ this.doSortMultiColumn()
877
+ } else if (Object.keys(this.currentSortColumn).length > 0) {
878
+ this.doSortSingleColumn(this.currentSortColumn)
879
+ }
880
+ }
881
+ }
882
+ },
883
+ findIndexOfSortData(column) {
884
+ let sortObj = this.sortMultipleDataComputed.filter((i) =>
885
+ i.field === column.field)[0]
886
+ return this.sortMultipleDataComputed.indexOf(sortObj) + 1
887
+ },
888
+ removeSortingPriority(column) {
889
+ if (this.backendSorting) {
890
+ this.$emit('sorting-priority-removed', column.field)
891
+ } else {
892
+ this.sortMultipleDataLocal = this.sortMultipleDataLocal.filter(
893
+ (priority) => priority.field !== column.field)
894
+
895
+ let formattedSortingPriority = this.sortMultipleDataLocal.map((i) => {
896
+ return (i.order && i.order === 'desc' ? '-' : '') + i.field
897
+ })
898
+
899
+ if (formattedSortingPriority.length === 0) {
900
+ this.resetMultiSorting()
901
+ } else {
902
+ this.newData = multiColumnSort(this.newData, formattedSortingPriority)
903
+ }
904
+ }
905
+ },
906
+ resetMultiSorting() {
907
+ this.sortMultipleDataLocal = []
908
+ this.currentSortColumn = {}
909
+ this.newData = this.data
910
+ },
911
+ /**
912
+ * Sort an array by key without mutating original data.
913
+ * Call the user sort function if it was passed.
914
+ */
915
+ sortBy(array, key, fn, isAsc) {
916
+ let sorted = []
917
+ // Sorting without mutating original data
918
+ if (fn && typeof fn === 'function') {
919
+ sorted = [...array].sort((a, b) => fn(a, b, isAsc))
920
+ } else {
921
+ sorted = [...array].sort((a, b) => {
922
+ // Get nested values from objects
923
+ let newA = getValueByPath(a, key)
924
+ let newB = getValueByPath(b, key)
925
+
926
+ // sort boolean type
927
+ if (typeof newA === 'boolean' && typeof newB === 'boolean') {
928
+ return isAsc ? newA - newB : newB - newA
929
+ }
930
+
931
+ // sort null values to the bottom when in asc order
932
+ // and to the top when in desc order
933
+ if (!isNil(newB) && isNil(newA)) return isAsc ? 1 : -1
934
+ if (!isNil(newA) && isNil(newB)) return isAsc ? -1 : 1
935
+ if (newA === newB) return 0
936
+
937
+ newA = (typeof newA === 'string')
938
+ ? newA.toUpperCase()
939
+ : newA
940
+ newB = (typeof newB === 'string')
941
+ ? newB.toUpperCase()
942
+ : newB
943
+
944
+ return isAsc
945
+ ? newA > newB ? 1 : -1
946
+ : newA > newB ? -1 : 1
947
+ })
948
+ }
949
+
950
+ return sorted
951
+ },
952
+
953
+ sortMultiColumn(column) {
954
+ this.currentSortColumn = {}
955
+ if (!this.backendSorting) {
956
+ let existingPriority = this.sortMultipleDataLocal.filter((i) =>
957
+ i.field === column.field)[0]
958
+ if (existingPriority) {
959
+ existingPriority.order = existingPriority.order === 'desc' ? 'asc' : 'desc'
960
+ } else {
961
+ this.sortMultipleDataLocal.push(
962
+ {field: column.field, order: column.isAsc}
963
+ )
964
+ }
965
+ this.doSortMultiColumn()
966
+ }
967
+ },
968
+
969
+ doSortMultiColumn() {
970
+ let formattedSortingPriority = this.sortMultipleDataLocal.map((i) => {
971
+ return (i.order && i.order === 'desc' ? '-' : '') + i.field
972
+ })
973
+ this.newData = multiColumnSort(this.newData, formattedSortingPriority)
974
+ },
975
+
976
+ /**
977
+ * Sort the column.
978
+ * Toggle current direction on column if it's sortable
979
+ * and not just updating the prop.
980
+ */
981
+ sort(column, updatingData = false, event = null) {
982
+ if (!column || !column.sortable) return
983
+ if (
984
+ // if backend sorting is enabled, just emit the sort press like usual
985
+ // if the correct key combination isnt pressed, sort like usual
986
+ !this.backendSorting &&
987
+ this.sortMultiple &&
988
+ ((this.sortMultipleKey && event[this.sortMultipleKey]) || !this.sortMultipleKey)
989
+ ) {
990
+ if (updatingData) {
991
+ this.doSortMultiColumn()
992
+ } else {
993
+ this.sortMultiColumn(column)
994
+ }
995
+ } else {
996
+ // sort multiple is enabled but the correct key combination isnt pressed so reset
997
+ if (this.sortMultiple) {
998
+ this.sortMultipleDataLocal = []
999
+ }
1000
+
1001
+ if (!updatingData) {
1002
+ this.isAsc = column === this.currentSortColumn
1003
+ ? !this.isAsc
1004
+ : (this.defaultSortDirection.toLowerCase() !== 'desc')
1005
+ }
1006
+ if (!this.firstTimeSort) {
1007
+ this.$emit('sort', column.field, this.isAsc ? 'asc' : 'desc', event)
1008
+ }
1009
+ if (!this.backendSorting) {
1010
+ this.doSortSingleColumn(column)
1011
+ }
1012
+ this.currentSortColumn = column
1013
+ }
1014
+ },
1015
+
1016
+ doSortSingleColumn(column) {
1017
+ this.newData = this.sortBy(
1018
+ this.newData,
1019
+ column.field,
1020
+ column.customSort,
1021
+ this.isAsc
1022
+ )
1023
+ },
1024
+
1025
+ isRowSelected(row, selected) {
1026
+ if (!selected) {
1027
+ return false
1028
+ }
1029
+ if (this.customRowKey) {
1030
+ return row[this.customRowKey] === selected[this.customRowKey]
1031
+ }
1032
+ return row === selected
1033
+ },
1034
+
1035
+ /**
1036
+ * Check if the row is checked (is added to the array).
1037
+ */
1038
+ isRowChecked(row) {
1039
+ return indexOf(this.newCheckedRows, row, this.customIsChecked) >= 0
1040
+ },
1041
+
1042
+ /**
1043
+ * Remove a checked row from the array.
1044
+ */
1045
+ removeCheckedRow(row) {
1046
+ const index = indexOf(this.newCheckedRows, row, this.customIsChecked)
1047
+ if (index >= 0) {
1048
+ this.newCheckedRows.splice(index, 1)
1049
+ }
1050
+ },
1051
+
1052
+ /**
1053
+ * Header checkbox click listener.
1054
+ * Add or remove all rows in current page.
1055
+ */
1056
+ checkAll() {
1057
+ const isAllChecked = this.isAllChecked
1058
+ this.visibleData.forEach((currentRow) => {
1059
+ if (this.isRowCheckable(currentRow)) {
1060
+ this.removeCheckedRow(currentRow)
1061
+ }
1062
+ if (!isAllChecked) {
1063
+ if (this.isRowCheckable(currentRow)) {
1064
+ this.newCheckedRows.push(currentRow)
1065
+ }
1066
+ }
1067
+ })
1068
+
1069
+ this.$emit('check', this.newCheckedRows)
1070
+ this.$emit('check-all', this.newCheckedRows)
1071
+
1072
+ // Emit checked rows to update user variable
1073
+ this.$emit('update:checkedRows', this.newCheckedRows)
1074
+ },
1075
+
1076
+ /**
1077
+ * Row checkbox click listener.
1078
+ */
1079
+ checkRow(row, index, event) {
1080
+ if (!this.isRowCheckable(row)) return
1081
+ const lastIndex = this.lastCheckedRowIndex
1082
+ this.lastCheckedRowIndex = index
1083
+
1084
+ if (event.shiftKey && lastIndex !== null && index !== lastIndex) {
1085
+ this.shiftCheckRow(row, index, lastIndex)
1086
+ } else if (!this.isRowChecked(row)) {
1087
+ this.newCheckedRows.push(row)
1088
+ } else {
1089
+ this.removeCheckedRow(row)
1090
+ }
1091
+
1092
+ this.$emit('check', this.newCheckedRows, row)
1093
+
1094
+ // Emit checked rows to update user variable
1095
+ this.$emit('update:checkedRows', this.newCheckedRows)
1096
+ },
1097
+
1098
+ /**
1099
+ * Check row when shift is pressed.
1100
+ */
1101
+ shiftCheckRow(row, index, lastCheckedRowIndex) {
1102
+ // Get the subset of the list between the two indicies
1103
+ const subset = this.visibleData.slice(
1104
+ Math.min(index, lastCheckedRowIndex),
1105
+ Math.max(index, lastCheckedRowIndex) + 1
1106
+ )
1107
+
1108
+ // Determine the operation based on the state of the clicked checkbox
1109
+ const shouldCheck = !this.isRowChecked(row)
1110
+
1111
+ subset.forEach((item) => {
1112
+ this.removeCheckedRow(item)
1113
+ if (shouldCheck && this.isRowCheckable(item)) {
1114
+ this.newCheckedRows.push(item)
1115
+ }
1116
+ })
1117
+ },
1118
+
1119
+ /**
1120
+ * Row click listener.
1121
+ * Emit all necessary events.
1122
+ */
1123
+ selectRow(row, index) {
1124
+ this.$emit('click', row)
1125
+
1126
+ if (this.selected === row) return
1127
+ if (!this.isRowSelectable(row)) return
1128
+
1129
+ // Emit new and old row
1130
+ this.$emit('select', row, this.selected)
1131
+
1132
+ // Emit new row to update user variable
1133
+ this.$emit('update:selected', row)
1134
+ },
1135
+
1136
+ /**
1137
+ * Toggle to show/hide details slot
1138
+ */
1139
+ toggleDetails(obj) {
1140
+ const found = this.isVisibleDetailRow(obj)
1141
+
1142
+ if (found) {
1143
+ this.closeDetailRow(obj)
1144
+ this.$emit('details-close', obj)
1145
+ } else {
1146
+ this.openDetailRow(obj)
1147
+ this.$emit('details-open', obj)
1148
+ }
1149
+
1150
+ // Syncs the detailed rows with the parent component
1151
+ this.$emit('update:openedDetailed', this.visibleDetailRows)
1152
+ },
1153
+
1154
+ openDetailRow(obj) {
1155
+ const index = this.handleDetailKey(obj)
1156
+ this.visibleDetailRows.push(index)
1157
+ },
1158
+
1159
+ closeDetailRow(obj) {
1160
+ const index = this.handleDetailKey(obj)
1161
+ const i = this.visibleDetailRows.indexOf(index)
1162
+ if (i >= 0) {
1163
+ this.visibleDetailRows.splice(i, 1)
1164
+ }
1165
+ },
1166
+
1167
+ isVisibleDetailRow(obj) {
1168
+ const index = this.handleDetailKey(obj)
1169
+ return this.visibleDetailRows.indexOf(index) >= 0
1170
+ },
1171
+
1172
+ isActiveDetailRow(row) {
1173
+ return this.detailed && !this.customDetailRow && this.isVisibleDetailRow(row)
1174
+ },
1175
+
1176
+ isActiveCustomDetailRow(row) {
1177
+ return this.detailed && this.customDetailRow && this.isVisibleDetailRow(row)
1178
+ },
1179
+
1180
+ isRowFiltered(row) {
1181
+ for (const key in this.filters) {
1182
+ if (!this.filters[key]) continue
1183
+ const input = this.filters[key]
1184
+ const column = this.newColumns.filter((c) => c.field === key)[0]
1185
+ if (column && column.customSearch && typeof column.customSearch === 'function') {
1186
+ if (!column.customSearch(row, input)) return false
1187
+ } else {
1188
+ const value = this.getValueByPath(row, key)
1189
+ if (value == null) return false
1190
+ if (Number.isInteger(value)) {
1191
+ if (value !== Number(input)) return false
1192
+ } else {
1193
+ const re = new RegExp(escapeRegExpChars(input), 'i')
1194
+ if (Array.isArray(value)) {
1195
+ const valid = value.some((val) =>
1196
+ re.test(removeDiacriticsFromString(val)) || re.test(val)
1197
+ )
1198
+ if (!valid) return false
1199
+ } else {
1200
+ if (!re.test(removeDiacriticsFromString(value)) && !re.test(value)) {
1201
+ return false
1202
+ }
1203
+ }
1204
+ }
1205
+ }
1206
+ }
1207
+ return true
1208
+ },
1209
+
1210
+ /**
1211
+ * When the detailKey is defined we use the object[detailKey] as index.
1212
+ * If not, use the object reference by default.
1213
+ */
1214
+ handleDetailKey(index) {
1215
+ const key = this.detailKey
1216
+ return !key.length || !index
1217
+ ? index
1218
+ : index[key]
1219
+ },
1220
+
1221
+ checkPredefinedDetailedRows() {
1222
+ const defaultExpandedRowsDefined = this.openedDetailed.length > 0
1223
+ if (defaultExpandedRowsDefined && !this.detailKey.length) {
1224
+ throw new Error('If you set a predefined opened-detailed, you must provide a unique key using the prop "detail-key"')
1225
+ }
1226
+ },
1227
+
1228
+ /**
1229
+ * Call initSort only first time (For example async data).
1230
+ */
1231
+ checkSort() {
1232
+ if (this.newColumns.length && this.firstTimeSort) {
1233
+ this.initSort()
1234
+ this.firstTimeSort = false
1235
+ } else if (this.newColumns.length) {
1236
+ if (Object.keys(this.currentSortColumn).length > 0) {
1237
+ for (let i = 0; i < this.newColumns.length; i++) {
1238
+ if (this.newColumns[i].field === this.currentSortColumn.field) {
1239
+ this.currentSortColumn = this.newColumns[i]
1240
+ break
1241
+ }
1242
+ }
1243
+ }
1244
+ }
1245
+ },
1246
+
1247
+ /**
1248
+ * Check if footer slot has custom content.
1249
+ */
1250
+ hasCustomFooterSlot() {
1251
+ if (this.$slots.footer.length > 1) return true
1252
+
1253
+ const tag = this.$slots.footer[0].tag
1254
+ if (tag !== 'th' && tag !== 'td') return false
1255
+
1256
+ return true
1257
+ },
1258
+
1259
+ /**
1260
+ * Check if bottom-left slot exists.
1261
+ */
1262
+ hasBottomLeftSlot() {
1263
+ return typeof this.$slots['bottom-left'] !== 'undefined'
1264
+ },
1265
+
1266
+ /**
1267
+ * Table arrow keys listener, change selection.
1268
+ */
1269
+ pressedArrow(pos) {
1270
+ if (!this.visibleData.length) return
1271
+
1272
+ let index = this.visibleData.indexOf(this.selected) + pos
1273
+
1274
+ // Prevent from going up from first and down from last
1275
+ index = index < 0
1276
+ ? 0
1277
+ : index > this.visibleData.length - 1
1278
+ ? this.visibleData.length - 1
1279
+ : index
1280
+
1281
+ const row = this.visibleData[index]
1282
+
1283
+ if (!this.isRowSelectable(row)) {
1284
+ let newIndex = null
1285
+ if (pos > 0) {
1286
+ for (let i = index; i < this.visibleData.length && newIndex === null; i++) {
1287
+ if (this.isRowSelectable(this.visibleData[i])) newIndex = i
1288
+ }
1289
+ } else {
1290
+ for (let i = index; i >= 0 && newIndex === null; i--) {
1291
+ if (this.isRowSelectable(this.visibleData[i])) newIndex = i
1292
+ }
1293
+ }
1294
+ if (newIndex >= 0) {
1295
+ this.selectRow(this.visibleData[newIndex])
1296
+ }
1297
+ } else {
1298
+ this.selectRow(row)
1299
+ }
1300
+ },
1301
+
1302
+ /**
1303
+ * Focus table element if has selected prop.
1304
+ */
1305
+ focus() {
1306
+ if (!this.focusable) return
1307
+
1308
+ this.$el.querySelector('table').focus()
1309
+ },
1310
+
1311
+ /**
1312
+ * Initial sorted column based on the default-sort prop.
1313
+ */
1314
+ initSort() {
1315
+ if (this.sortMultiple && this.sortMultipleData) {
1316
+ this.sortMultipleData.forEach((column) => {
1317
+ this.sortMultiColumn(column)
1318
+ })
1319
+ } else {
1320
+ if (!this.defaultSort) return
1321
+
1322
+ let sortField = ''
1323
+ let sortDirection = this.defaultSortDirection
1324
+
1325
+ if (Array.isArray(this.defaultSort)) {
1326
+ sortField = this.defaultSort[0]
1327
+ if (this.defaultSort[1]) {
1328
+ sortDirection = this.defaultSort[1]
1329
+ }
1330
+ } else {
1331
+ sortField = this.defaultSort
1332
+ }
1333
+
1334
+ const sortColumn = this.newColumns.filter(
1335
+ (column) => (column.field === sortField))[0]
1336
+ if (sortColumn) {
1337
+ this.isAsc = sortDirection.toLowerCase() !== 'desc'
1338
+ this.sort(sortColumn, true)
1339
+ }
1340
+ }
1341
+ },
1342
+ /**
1343
+ * Emits drag start event (row)
1344
+ */
1345
+ handleDragStart(event, row, index) {
1346
+ if (!this.canDragRow) return
1347
+ this.isDraggingRow = true
1348
+ this.$emit('dragstart', {event, row, index})
1349
+ },
1350
+ /**
1351
+ * Emits drag leave event (row)
1352
+ */
1353
+ handleDragEnd(event, row, index) {
1354
+ if (!this.canDragRow) return
1355
+ this.isDraggingRow = false
1356
+ this.$emit('dragend', {event, row, index})
1357
+ },
1358
+ /**
1359
+ * Emits drop event (row)
1360
+ */
1361
+ handleDrop(event, row, index) {
1362
+ if (!this.canDragRow) return
1363
+ this.$emit('drop', {event, row, index})
1364
+ },
1365
+ /**
1366
+ * Emits drag over event (row)
1367
+ */
1368
+ handleDragOver(event, row, index) {
1369
+ if (!this.canDragRow) return
1370
+ this.$emit('dragover', {event, row, index})
1371
+ },
1372
+ /**
1373
+ * Emits drag leave event (row)
1374
+ */
1375
+ handleDragLeave(event, row, index) {
1376
+ if (!this.canDragRow) return
1377
+ this.$emit('dragleave', {event, row, index})
1378
+ },
1379
+
1380
+ emitEventForRow(eventName, event, row) {
1381
+ return this.$listeners[eventName] ? this.$emit(eventName, row, event) : null
1382
+ },
1383
+
1384
+ /**
1385
+ * Emits drag start event (column)
1386
+ */
1387
+ handleColumnDragStart(event, column, index) {
1388
+ if (!this.canDragColumn) return
1389
+ this.isDraggingColumn = true
1390
+ this.$emit('columndragstart', {event, column, index})
1391
+ },
1392
+
1393
+ /**
1394
+ * Emits drag leave event (column)
1395
+ */
1396
+ handleColumnDragEnd(event, column, index) {
1397
+ if (!this.canDragColumn) return
1398
+ this.isDraggingColumn = false
1399
+ this.$emit('columndragend', {event, column, index})
1400
+ },
1401
+
1402
+ /**
1403
+ * Emits drop event (column)
1404
+ */
1405
+ handleColumnDrop(event, column, index) {
1406
+ if (!this.canDragColumn) return
1407
+ this.$emit('columndrop', {event, column, index})
1408
+ },
1409
+
1410
+ /**
1411
+ * Emits drag over event (column)
1412
+ */
1413
+ handleColumnDragOver(event, column, index) {
1414
+ if (!this.canDragColumn) return
1415
+ this.$emit('columndragover', {event, column, index})
1416
+ },
1417
+
1418
+ /**
1419
+ * Emits drag leave event (column)
1420
+ */
1421
+ handleColumnDragLeave(event, column, index) {
1422
+ if (!this.canDragColumn) return
1423
+ this.$emit('columndragleave', {event, column, index})
1424
+ },
1425
+
1426
+ refreshSlots() {
1427
+ this.defaultSlots = this.$slots.default || []
1428
+ }
1429
+ },
1430
+ mounted() {
1431
+ this.refreshSlots()
1432
+ this.checkPredefinedDetailedRows()
1433
+ this.checkSort()
1434
+ }
1435
+ }
1436
+ </script>