@gitlab/ui 126.3.3 → 126.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/components/base/alert/alert.js +9 -0
  2. package/dist/components/base/breadcrumb/breadcrumb.js +3 -0
  3. package/dist/components/base/breadcrumb/breadcrumb_item.js +15 -0
  4. package/dist/components/base/datepicker/datepicker.js +54 -0
  5. package/dist/components/base/daterange_picker/daterange_picker.js +70 -1
  6. package/dist/components/base/filtered_search/filtered_search.js +1 -1
  7. package/dist/components/base/form/form_input_group/form_input_group.js +9 -1
  8. package/dist/components/base/path/data.js +1 -1
  9. package/dist/components/base/search_box_by_click/search_box_by_click.js +9 -1
  10. package/dist/components/charts/shared/tooltip/tooltip.js +2 -2
  11. package/dist/components/charts/stacked_column/stacked_column.js +16 -5
  12. package/dist/components/dashboards/dashboard_panel/dashboard_panel.js +10 -2
  13. package/dist/directives/resize_observer/resize_observer.js +10 -3
  14. package/dist/index.css +1 -1
  15. package/dist/index.css.map +1 -1
  16. package/dist/tailwind.css +1 -1
  17. package/dist/tailwind.css.map +1 -1
  18. package/dist/utils/charts/config.js +36 -1
  19. package/dist/utils/charts/mock_data.js +18 -1
  20. package/dist/vendor/bootstrap-vue/src/components/modal/modal.js +7 -2
  21. package/dist/vendor/bootstrap-vue/src/components/table/helpers/mixin-filtering.js +8 -0
  22. package/dist/vendor/bootstrap-vue/src/components/table/helpers/mixin-items.js +4 -0
  23. package/dist/vendor/bootstrap-vue/src/components/table/helpers/mixin-provider.js +8 -4
  24. package/dist/vendor/bootstrap-vue/src/components/table/helpers/mixin-selectable.js +3 -19
  25. package/dist/vendor/bootstrap-vue/src/components/table/helpers/mixin-sorting.js +1 -16
  26. package/dist/vendor/bootstrap-vue/src/components/table/helpers/mixin-tbody.js +8 -2
  27. package/dist/vendor/bootstrap-vue/src/components/table/helpers/mixin-thead.js +4 -0
  28. package/dist/vendor/bootstrap-vue/src/components/toast/helpers/bv-toast.js +15 -7
  29. package/dist/vendor/bootstrap-vue/src/components/tooltip/helpers/bv-popper.js +15 -13
  30. package/dist/vendor/bootstrap-vue/src/components/tooltip/helpers/bv-tooltip.js +24 -21
  31. package/dist/vendor/bootstrap-vue/src/components/tooltip/tooltip.js +10 -9
  32. package/dist/vendor/bootstrap-vue/src/components/transition/bv-transition.js +3 -0
  33. package/dist/vendor/bootstrap-vue/src/components/transporter/transporter.js +29 -21
  34. package/dist/vendor/bootstrap-vue/src/constants/events.js +1 -5
  35. package/dist/vendor/bootstrap-vue/src/directives/tooltip/tooltip.js +15 -13
  36. package/dist/vendor/bootstrap-vue/src/directives/visible/visible.js +15 -16
  37. package/dist/vendor/bootstrap-vue/src/mixins/attrs.js +1 -18
  38. package/dist/vendor/bootstrap-vue/src/mixins/dropdown.js +8 -3
  39. package/dist/vendor/bootstrap-vue/src/mixins/has-listener.js +1 -1
  40. package/dist/vendor/bootstrap-vue/src/mixins/listeners.js +13 -12
  41. package/dist/vendor/bootstrap-vue/src/utils/cache.js +35 -13
  42. package/dist/vendor/bootstrap-vue/src/utils/config-set.js +16 -11
  43. package/dist/vendor/bootstrap-vue/src/utils/config.js +4 -10
  44. package/dist/vendor/bootstrap-vue/src/utils/create-new-child-component.js +102 -3
  45. package/dist/vendor/bootstrap-vue/src/utils/element-to-vue-instance-registry.js +3 -9
  46. package/dist/vendor/bootstrap-vue/src/utils/get-instance-from-directive.js +1 -3
  47. package/dist/vendor/bootstrap-vue/src/utils/on-instance-destroy.js +22 -0
  48. package/dist/vendor/bootstrap-vue/src/utils/plugins.js +2 -21
  49. package/dist/vendor/bootstrap-vue/src/utils/router.js +15 -1
  50. package/dist/vendor/bootstrap-vue/src/utils/safe-vue-instance.js +1 -1
  51. package/dist/vendor/bootstrap-vue/src/vue.js +16 -81
  52. package/package.json +6 -6
  53. package/src/components/base/alert/alert.vue +9 -0
  54. package/src/components/base/breadcrumb/breadcrumb.vue +3 -0
  55. package/src/components/base/breadcrumb/breadcrumb_item.vue +15 -0
  56. package/src/components/base/datepicker/datepicker.vue +54 -0
  57. package/src/components/base/daterange_picker/daterange_picker.vue +70 -1
  58. package/src/components/base/filtered_search/filtered_search.vue +1 -1
  59. package/src/components/base/form/form_input_group/form_input_group.vue +9 -1
  60. package/src/components/base/new_dropdowns/dropdown_item.scss +1 -1
  61. package/src/components/base/path/data.js +1 -1
  62. package/src/components/base/search_box_by_click/search_box_by_click.vue +9 -0
  63. package/src/components/charts/shared/tooltip/tooltip.vue +2 -1
  64. package/src/components/charts/stacked_column/stacked_column.vue +16 -5
  65. package/src/components/dashboards/dashboard_panel/dashboard_panel.vue +27 -9
  66. package/src/directives/resize_observer/resize_observer.js +6 -0
  67. package/src/utils/charts/config.js +28 -0
  68. package/src/utils/charts/mock_data.js +7 -0
  69. package/src/vendor/bootstrap-vue/src/components/modal/modal.js +6 -1
  70. package/src/vendor/bootstrap-vue/src/components/table/helpers/mixin-filtering.js +8 -0
  71. package/src/vendor/bootstrap-vue/src/components/table/helpers/mixin-items.js +4 -0
  72. package/src/vendor/bootstrap-vue/src/components/table/helpers/mixin-provider.js +9 -4
  73. package/src/vendor/bootstrap-vue/src/components/table/helpers/mixin-selectable.js +3 -24
  74. package/src/vendor/bootstrap-vue/src/components/table/helpers/mixin-sorting.js +1 -20
  75. package/src/vendor/bootstrap-vue/src/components/table/helpers/mixin-tbody.js +8 -2
  76. package/src/vendor/bootstrap-vue/src/components/table/helpers/mixin-thead.js +4 -0
  77. package/src/vendor/bootstrap-vue/src/components/toast/helpers/bv-toast.js +15 -13
  78. package/src/vendor/bootstrap-vue/src/components/tooltip/helpers/bv-popper.js +14 -13
  79. package/src/vendor/bootstrap-vue/src/components/tooltip/helpers/bv-tooltip.js +16 -23
  80. package/src/vendor/bootstrap-vue/src/components/tooltip/tooltip.js +10 -9
  81. package/src/vendor/bootstrap-vue/src/components/transition/bv-transition.js +4 -0
  82. package/src/vendor/bootstrap-vue/src/components/transporter/transporter.js +36 -26
  83. package/src/vendor/bootstrap-vue/src/constants/events.js +0 -5
  84. package/src/vendor/bootstrap-vue/src/directives/tooltip/tooltip.js +17 -13
  85. package/src/vendor/bootstrap-vue/src/directives/visible/visible.js +13 -9
  86. package/src/vendor/bootstrap-vue/src/mixins/attrs.js +1 -17
  87. package/src/vendor/bootstrap-vue/src/mixins/dropdown.js +8 -3
  88. package/src/vendor/bootstrap-vue/src/mixins/has-listener.js +1 -1
  89. package/src/vendor/bootstrap-vue/src/mixins/listeners.js +14 -14
  90. package/src/vendor/bootstrap-vue/src/utils/cache.js +27 -7
  91. package/src/vendor/bootstrap-vue/src/utils/config-set.js +16 -11
  92. package/src/vendor/bootstrap-vue/src/utils/config.js +4 -10
  93. package/src/vendor/bootstrap-vue/src/utils/create-new-child-component.js +85 -2
  94. package/src/vendor/bootstrap-vue/src/utils/element-to-vue-instance-registry.js +3 -10
  95. package/src/vendor/bootstrap-vue/src/utils/get-instance-from-directive.js +1 -4
  96. package/src/vendor/bootstrap-vue/src/utils/on-instance-destroy.js +21 -0
  97. package/src/vendor/bootstrap-vue/src/utils/plugins.js +1 -26
  98. package/src/vendor/bootstrap-vue/src/utils/router.js +13 -1
  99. package/src/vendor/bootstrap-vue/src/utils/safe-vue-instance.js +1 -1
  100. package/src/vendor/bootstrap-vue/src/vue.js +11 -98
@@ -24,111 +24,177 @@ export default {
24
24
  GlTooltip: GlTooltipDirective,
25
25
  },
26
26
  props: {
27
+ /**
28
+ * Label text for the start date picker.
29
+ */
27
30
  fromLabel: {
28
31
  type: String,
29
32
  required: false,
30
33
  default: 'From',
31
34
  },
35
+ /**
36
+ * Label text for the end date picker.
37
+ */
32
38
  toLabel: {
33
39
  type: String,
34
40
  required: false,
35
41
  default: 'To',
36
42
  },
43
+ /**
44
+ * Accessible label for the start date input.
45
+ */
37
46
  fromAriaLabel: {
38
47
  type: String,
39
48
  required: false,
40
49
  default: 'From date',
41
50
  },
51
+ /**
52
+ * Accessible label for the end date input.
53
+ */
42
54
  toAriaLabel: {
43
55
  type: String,
44
56
  required: false,
45
57
  default: 'To date',
46
58
  },
59
+ /**
60
+ * The selected date range object containing `startDate` and `endDate` properties.
61
+ */
47
62
  value: {
48
63
  type: Object,
49
64
  required: false,
50
65
  default: null,
51
66
  },
67
+ /**
68
+ * Internationalization object for Pikaday labels and month/day names.
69
+ */
52
70
  i18n: {
53
71
  type: Object,
54
72
  required: false,
55
73
  default: null,
56
74
  },
75
+ /**
76
+ * The earliest selectable date for the start date picker.
77
+ */
57
78
  defaultMinDate: {
58
79
  type: Date,
59
80
  required: false,
60
81
  default: null,
61
82
  },
83
+ /**
84
+ * The latest selectable date for the end date picker.
85
+ */
62
86
  defaultMaxDate: {
63
87
  type: Date,
64
88
  required: false,
65
89
  default: null,
66
90
  },
91
+ /**
92
+ * Initial value for the start date.
93
+ */
67
94
  defaultStartDate: {
68
95
  type: Date,
69
96
  required: false,
70
97
  default: null,
71
98
  },
99
+ /**
100
+ * Initial value for the end date.
101
+ */
72
102
  defaultEndDate: {
73
103
  type: Date,
74
104
  required: false,
75
105
  default: null,
76
106
  },
107
+ /**
108
+ * Maximum number of days allowed between start and end date. Set to 0 for no limit.
109
+ */
77
110
  maxDateRange: {
78
111
  type: Number,
79
112
  required: false,
80
113
  default: 0,
81
114
  },
115
+ /**
116
+ * Additional CSS class(es) for the start date picker container.
117
+ */
82
118
  startPickerClass: {
83
119
  type: String,
84
120
  required: false,
85
121
  default: '',
86
122
  },
123
+ /**
124
+ * Selector of element that triggers the start date picker.
125
+ */
87
126
  startPickerTarget: {
88
127
  type: String,
89
128
  required: false,
90
129
  default: '',
91
130
  },
131
+ /**
132
+ * DOM node selector to render the start date calendar into.
133
+ */
92
134
  startPickerContainer: {
93
135
  type: String,
94
136
  required: false,
95
137
  default: '',
96
138
  },
139
+ /**
140
+ * Validation state for the start date picker input.
141
+ */
97
142
  startPickerState: {
98
143
  type: Boolean,
99
144
  required: false,
100
145
  default: null,
101
146
  },
147
+ /**
148
+ * Additional CSS class(es) for the end date picker container.
149
+ */
102
150
  endPickerClass: {
103
151
  type: String,
104
152
  required: false,
105
153
  default: '',
106
154
  },
155
+ /**
156
+ * Selector of element that triggers the end date picker.
157
+ */
107
158
  endPickerTarget: {
108
159
  type: String,
109
160
  required: false,
110
161
  default: '',
111
162
  },
163
+ /**
164
+ * DOM node selector to render the end date calendar into.
165
+ */
112
166
  endPickerContainer: {
113
167
  type: String,
114
168
  required: false,
115
169
  default: '',
116
170
  },
171
+ /**
172
+ * Validation state for the end date picker input.
173
+ */
117
174
  endPickerState: {
118
175
  type: Boolean,
119
176
  required: false,
120
177
  default: null,
121
178
  },
179
+ /**
180
+ * Additional CSS class(es) for the label elements.
181
+ */
122
182
  labelClass: {
123
183
  type: String,
124
184
  required: false,
125
185
  default: '',
126
186
  },
187
+ /**
188
+ * Additional CSS class for the Pikaday calendar theme.
189
+ */
127
190
  theme: {
128
191
  type: String,
129
192
  required: false,
130
193
  default: '',
131
194
  },
195
+ /**
196
+ * Whether to allow selecting the same date for start and end.
197
+ */
132
198
  sameDaySelection: {
133
199
  type: Boolean,
134
200
  required: false,
@@ -150,13 +216,16 @@ export default {
150
216
  required: false,
151
217
  default: '',
152
218
  },
219
+ /**
220
+ * Whether the start date picker should be open on mount.
221
+ */
153
222
  startOpened: {
154
223
  type: Boolean,
155
224
  required: false,
156
225
  default: false,
157
226
  },
158
227
  /**
159
- * Maximum width of the Datepicker
228
+ * Maximum width of the Datepicker.
160
229
  */
161
230
  width: {
162
231
  type: String,
@@ -408,7 +408,7 @@ export default {
408
408
  :search-button-attributes="searchButtonAttributes"
409
409
  :show-search-button="showSearchButton"
410
410
  :disabled="viewOnly"
411
- data-testid="filtered-search-input"
411
+ :wrapper-attributes="{ 'data-testid': 'filtered-search-input' }"
412
412
  @submit="submit"
413
413
  @input="applyNewValue"
414
414
  @history-item-selected="$emit('history-item-selected', $event)"
@@ -47,6 +47,14 @@ export default {
47
47
  required: false,
48
48
  default: '',
49
49
  },
50
+ /**
51
+ * HTML attributes to apply to the wrapper element.
52
+ */
53
+ wrapperAttributes: {
54
+ type: Object,
55
+ required: false,
56
+ default: () => ({}),
57
+ },
50
58
  },
51
59
  data() {
52
60
  return {
@@ -68,7 +76,7 @@ export default {
68
76
  };
69
77
  </script>
70
78
  <template>
71
- <div role="group" class="input-group">
79
+ <div role="group" class="input-group" v-bind="wrapperAttributes">
72
80
  <div v-if="activeOption || $scopedSlots.prepend" class="input-group-prepend">
73
81
  <!-- @slot Is rendered in front of the input field. -->
74
82
  <slot name="prepend"></slot>
@@ -146,7 +146,7 @@
146
146
  }
147
147
 
148
148
  &:hover {
149
- .gl-new-dropdown-item-content {
149
+ .gl-new-dropdown-item-content:not(:disabled):not(.disabled) {
150
150
  color: var(--gl-action-danger-foreground-color-hover);
151
151
  background-color: var(--gl-action-danger-background-color-hover);
152
152
  }
@@ -1,4 +1,4 @@
1
- export const mockPathItems = [
1
+ export const mockPathItems = () => [
2
2
  {
3
3
  title: 'First',
4
4
  metric: '1d',
@@ -125,6 +125,14 @@ export default {
125
125
  required: false,
126
126
  default: true,
127
127
  },
128
+ /**
129
+ * HTML attributes to apply to the wrapper element.
130
+ */
131
+ wrapperAttributes: {
132
+ type: Object,
133
+ required: false,
134
+ default: () => ({}),
135
+ },
128
136
  },
129
137
  data() {
130
138
  return {
@@ -206,6 +214,7 @@ export default {
206
214
  <gl-form-input-group
207
215
  class="gl-search-box-by-click"
208
216
  :class="{ 'gl-search-box-by-click-with-search-button': showSearchButton }"
217
+ :wrapper-attributes="wrapperAttributes"
209
218
  >
210
219
  <template v-if="historyItems" #prepend>
211
220
  <gl-disclosure-dropdown
@@ -9,6 +9,7 @@ import {
9
9
  getTooltipTitle,
10
10
  getTooltipContent,
11
11
  getTooltipAxisConfig,
12
+ getTooltipParams,
12
13
  } from '../../../../utils/charts/config';
13
14
 
14
15
  import TooltipDefaultFormat from './tooltip_default_format/tooltip_default_format.vue';
@@ -242,7 +243,7 @@ export default {
242
243
 
243
244
  this.title = getTooltipTitle(params, titleAxisName, dimensionIndex);
244
245
  this.content = getTooltipContent(params, valueAxisName, metricIndex);
245
- this.params = params;
246
+ this.params = getTooltipParams(params, options);
246
247
  },
247
248
  },
248
249
  },
@@ -1,5 +1,6 @@
1
1
  <script>
2
2
  import merge from 'lodash/merge';
3
+ import groupBy from 'lodash/groupBy';
3
4
  import {
4
5
  defaultChartOptions,
5
6
  grid,
@@ -310,12 +311,22 @@ export default {
310
311
  getTooltipContent({ params }) {
311
312
  if (!params) return {};
312
313
 
313
- const tooltipContentEntries = params.seriesData
314
- .toSorted((a, b) => b.seriesIndex - a.seriesIndex) // Invert stacking order so it matches chart (see https://github.com/apache/echarts/issues/14700)
315
- .map(({ seriesName = '', value, borderColor }) => [
314
+ // Group by stack and invert order within so it matches `stacked` presentation (see https://github.com/apache/echarts/issues/14700).
315
+ const seriesDataGroupedByStack = groupBy(
316
+ params.seriesData,
317
+ ({ stack, seriesIndex }) => stack ?? seriesIndex,
318
+ );
319
+
320
+ const sortedSeriesData = Object.values(seriesDataGroupedByStack).flatMap((seriesDataStack) =>
321
+ seriesDataStack.toSorted((a, b) => b.seriesIndex - a.seriesIndex),
322
+ );
323
+
324
+ const tooltipContentEntries = sortedSeriesData.map(
325
+ ({ seriesName = '', value, borderColor, color }) => [
316
326
  seriesName,
317
- { value, color: borderColor },
318
- ]);
327
+ { value, color: borderColor ?? color },
328
+ ],
329
+ );
319
330
 
320
331
  return Object.fromEntries(tooltipContentEntries);
321
332
  },
@@ -94,6 +94,14 @@ export default {
94
94
  required: false,
95
95
  default: '',
96
96
  },
97
+ /**
98
+ * The string to render as the panel subtitle.
99
+ */
100
+ subtitle: {
101
+ type: String,
102
+ required: false,
103
+ default: '',
104
+ },
97
105
  /**
98
106
  * Set to `true` to show the loading state.
99
107
  */
@@ -197,8 +205,8 @@ export default {
197
205
  :class="containerClasses"
198
206
  >
199
207
  <div class="gl-flex gl-h-full gl-flex-col">
200
- <div class="gl-flex gl-items-start gl-justify-between" data-testid="panel-title">
201
- <div class="gl-flex gl-items-center gl-overflow-hidden gl-pb-3">
208
+ <div class="gl-mb-3 gl-flex gl-items-start gl-justify-between" data-testid="panel-title">
209
+ <div class="gl-flex gl-items-center gl-overflow-hidden">
202
210
  <gl-icon
203
211
  v-if="hasTitleIcon"
204
212
  class="gl-mr-2"
@@ -207,12 +215,22 @@ export default {
207
215
  data-testid="panel-title-icon"
208
216
  />
209
217
 
210
- <gl-truncate
211
- v-if="hasTitle"
212
- class="gl-min-w-0 gl-font-bold gl-text-default"
213
- :text="title"
214
- with-tooltip
215
- />
218
+ <div class="gl-min-w-0">
219
+ <gl-truncate
220
+ v-if="hasTitle"
221
+ class="gl-font-bold gl-text-default"
222
+ :text="title"
223
+ with-tooltip
224
+ />
225
+
226
+ <p
227
+ v-if="subtitle"
228
+ class="gl-mb-0 gl-mt-1 gl-text-sm gl-text-subtle"
229
+ data-testid="panel-subtitle"
230
+ >
231
+ {{ subtitle }}
232
+ </p>
233
+ </div>
216
234
 
217
235
  <template v-if="hasTitlePopover">
218
236
  <gl-icon
@@ -280,7 +298,7 @@ export default {
280
298
 
281
299
  <div
282
300
  v-if="$scopedSlots.filters"
283
- class="gl-flex gl-items-center gl-justify-end gl-pb-2"
301
+ class="gl-flex gl-items-center gl-justify-end"
284
302
  data-testid="panel-filters-container"
285
303
  >
286
304
  <!-- @slot The filter section to add additional UI elements for filtering, grouping, etc. -->
@@ -2,6 +2,12 @@ import isFunction from 'lodash/isFunction';
2
2
 
3
3
  let observer = null;
4
4
 
5
+ // Exported for testing purposes only
6
+ export const resetObserver = () => {
7
+ observer?.disconnect();
8
+ observer = null;
9
+ };
10
+
5
11
  const attachObserver = (el, resizeHandler) => {
6
12
  if (!isFunction(resizeHandler)) {
7
13
  throw TypeError('directive value must be a function');
@@ -486,6 +486,34 @@ export const getTooltipContent = (params = null, valueAxisName = null, metricInd
486
486
  }, {});
487
487
  };
488
488
 
489
+ /**
490
+ * Enriches tooltip parameters with stack information for proper series ordering.
491
+ *
492
+ * In stacked column charts, series are grouped by their `stack` property to determine
493
+ * visual stacking and tooltip display order. ECharts doesn't provide stack data
494
+ * in tooltip params by default, so this function manually adds it by mapping
495
+ * series names to their configured stack values.
496
+ *
497
+ * @param {Object} params - Tooltip parameters from ECharts
498
+ * @param {Object} options - Chart configuration with series definitions
499
+ * @returns {Object} Enhanced tooltip parameters with stack data
500
+ */
501
+ export const getTooltipParams = (params = null, options) => {
502
+ if (!params) return null;
503
+
504
+ const { series: optionsSeries = [] } = options ?? {};
505
+
506
+ const seriesStacks = Object.fromEntries(optionsSeries.map(({ name, stack }) => [name, stack]));
507
+
508
+ return {
509
+ ...params,
510
+ seriesData: params.seriesData.map((series) => ({
511
+ ...series,
512
+ stack: seriesStacks[series.seriesName],
513
+ })),
514
+ };
515
+ };
516
+
489
517
  /**
490
518
  * The method works well if tooltip content should be against y-axis values.
491
519
  * However, for bar charts, the tooltip should be against x-axis values.
@@ -106,6 +106,13 @@ export const mockDefaultStackedBarData = [
106
106
  { name: 'Fun 4', data: [8, 9, 5, 40, 13, 19, 58, 21, 47, 59, 23, 46] },
107
107
  ];
108
108
 
109
+ export const mockMultiStackBarData = [
110
+ { name: 'Fun 1', data: [58, 49, 38, 23, 27, 68, 38, 35, 7, 64, 65, 31], stack: 'stack-1' },
111
+ { name: 'Fun 2', data: [8, 6, 34, 19, 9, 7, 17, 25, 14, 7, 10, 32], stack: 'stack-1' },
112
+ { name: 'Fun 3', data: [67, 60, 66, 32, 61, 54, 13, 50, 16, 11, 47, 28], stack: 'stack-2' },
113
+ { name: 'Fun 4', data: [8, 9, 5, 40, 13, 19, 58, 21, 47, 59, 23, 46], stack: 'stack-2' },
114
+ ];
115
+
109
116
  export const mockDefaultStackedLineData = [
110
117
  { name: 'Stacked median line', data: [67, 60, 66, 32, 61, 54, 13, 50, 16, 11, 47, 28] },
111
118
  ];
@@ -386,7 +386,7 @@ export const BModal = /*#__PURE__*/ extend({
386
386
  if (this.isClosing) {
387
387
  // If we are in the process of closing, wait until hidden before re-opening
388
388
  /* istanbul ignore next */
389
- this.$once(EVENT_NAME_HIDDEN, this.show)
389
+ this.$_showOnHidden = true
390
390
  /* istanbul ignore next */
391
391
  return
392
392
  }
@@ -545,6 +545,11 @@ export const BModal = /*#__PURE__*/ extend({
545
545
  // TODO: Need to find a way to pass the `trigger` property
546
546
  // to the `hidden` event, not just only the `hide` event
547
547
  this.emitEvent(this.buildEvent(EVENT_NAME_HIDDEN))
548
+ // Handle pending show request (show was called while closing)
549
+ if (this.$_showOnHidden) {
550
+ this.$_showOnHidden = false
551
+ this.show()
552
+ }
548
553
  })
549
554
  },
550
555
  emitEvent(bvEvent) {
@@ -142,6 +142,10 @@ export const filteringMixin = extend({
142
142
  }
143
143
  if (isFiltered) {
144
144
  this.$emit(EVENT_NAME_FILTERED, filteredItems, filteredItems.length)
145
+ // Clear selection on filter change if selectable (defined in mixin-selectable.js)
146
+ if (this.hasSelectableRowClick) {
147
+ this.clearSelected()
148
+ }
145
149
  }
146
150
  this.isFiltered = isFiltered
147
151
  },
@@ -151,6 +155,10 @@ export const filteringMixin = extend({
151
155
  // `false` so that users can update their pagination controls
152
156
  const { localItems } = this
153
157
  this.$emit(EVENT_NAME_FILTERED, localItems, localItems.length)
158
+ // Clear selection on filter change if selectable (defined in mixin-selectable.js)
159
+ if (this.hasSelectableRowClick) {
160
+ this.clearSelected()
161
+ }
154
162
  }
155
163
  }
156
164
  },
@@ -127,6 +127,10 @@ export const itemsMixin = extend({
127
127
  // Emit context information for external paging/filtering/sorting handling
128
128
  if (!looseEqual(newValue, oldValue)) {
129
129
  this.$emit(EVENT_NAME_CONTEXT_CHANGED, newValue)
130
+ // Clear selection on context change if selectable (defined in mixin-selectable.js)
131
+ if (this.hasSelectableRowClick) {
132
+ this.clearSelected()
133
+ }
130
134
  }
131
135
  }
132
136
  },
@@ -101,15 +101,15 @@ export const providerMixin = extend({
101
101
  },
102
102
  methods: {
103
103
  refresh() {
104
- const { items, refresh, computedBusy } = safeVueInstance(this)
104
+ const { items, computedBusy } = safeVueInstance(this)
105
105
 
106
106
  // Public Method: Force a refresh of the provider function
107
- this.$off(EVENT_NAME_REFRESHED, refresh)
107
+ this.$_pendingRefresh = false
108
108
  if (computedBusy) {
109
109
  // Can't force an update when forced busy by user (busy prop === true)
110
110
  if (this.localBusy && this.hasProvider) {
111
111
  // But if provider running (localBusy), re-schedule refresh once `refreshed` emitted
112
- this.$on(EVENT_NAME_REFRESHED, refresh)
112
+ this.$_pendingRefresh = true
113
113
  }
114
114
  } else {
115
115
  this.clearSelected()
@@ -130,6 +130,11 @@ export const providerMixin = extend({
130
130
  if (this.id) {
131
131
  this.emitOnRoot(ROOT_EVENT_NAME_REFRESHED, this.id)
132
132
  }
133
+ // Handle pending refresh request
134
+ if (this.$_pendingRefresh) {
135
+ this.$_pendingRefresh = false
136
+ this.refresh()
137
+ }
133
138
  },
134
139
  _providerUpdate() {
135
140
  // Refresh the provider function items.
@@ -180,7 +185,7 @@ export const providerMixin = extend({
180
185
  // and clear the busy state
181
186
  warn(`Provider function error [${e.name}] ${e.message}.`, NAME_TABLE)
182
187
  this.localBusy = false
183
- this.$off(EVENT_NAME_REFRESHED, this.refresh)
188
+ this.$_pendingRefresh = false
184
189
  }
185
190
  })
186
191
  }
@@ -1,10 +1,5 @@
1
1
  import { extend } from '../../../vue'
2
- import {
3
- EVENT_NAME_CONTEXT_CHANGED,
4
- EVENT_NAME_FILTERED,
5
- EVENT_NAME_ROW_CLICKED,
6
- EVENT_NAME_ROW_SELECTED
7
- } from '../../../constants/events'
2
+ import { EVENT_NAME_ROW_SELECTED } from '../../../constants/events'
8
3
  import { PROP_TYPE_BOOLEAN, PROP_TYPE_STRING } from '../../../constants/props'
9
4
  import { arrayIncludes, createArray } from '../../../utils/array'
10
5
  import { identity } from '../../../utils/identity'
@@ -104,16 +99,14 @@ export const selectableMixin = extend({
104
99
  this.clearSelected()
105
100
  }
106
101
  },
107
- selectable(newValue) {
102
+ selectable() {
108
103
  this.clearSelected()
109
- this.setSelectionHandlers(newValue)
110
104
  },
111
105
  selectMode() {
112
106
  this.clearSelected()
113
107
  },
114
- hasSelectableRowClick(newValue) {
108
+ hasSelectableRowClick() {
115
109
  this.clearSelected()
116
- this.setSelectionHandlers(!newValue)
117
110
  },
118
111
  selectedRows(selectedRows, oldValue) {
119
112
  if (this.isSelectable && !looseEqual(selectedRows, oldValue)) {
@@ -128,12 +121,6 @@ export const selectableMixin = extend({
128
121
  }
129
122
  }
130
123
  },
131
- beforeMount() {
132
- // Set up handlers if needed
133
- if (this.isSelectable) {
134
- this.setSelectionHandlers(true)
135
- }
136
- },
137
124
  methods: {
138
125
  // Public methods
139
126
  selectRow(index) {
@@ -193,14 +180,6 @@ export const selectableMixin = extend({
193
180
  'aria-selected': !this.isSelectable ? null : this.isRowSelected(index) ? 'true' : 'false'
194
181
  }
195
182
  },
196
- setSelectionHandlers(on) {
197
- const method = on && !this.noSelectOnClick ? '$on' : '$off'
198
- // Handle row-clicked event
199
- this[method](EVENT_NAME_ROW_CLICKED, this.selectionHandler)
200
- // Clear selection on filter, pagination, and sort changes
201
- this[method](EVENT_NAME_FILTERED, this.clearSelected)
202
- this[method](EVENT_NAME_CONTEXT_CHANGED, this.clearSelected)
203
- },
204
183
  selectionHandler(item, index, event) {
205
184
  /* istanbul ignore if: should never happen */
206
185
  if (!this.isSelectable || this.noSelectOnClick) {
@@ -1,9 +1,5 @@
1
1
  import { extend } from '../../../vue'
2
- import {
3
- EVENT_NAME_HEAD_CLICKED,
4
- EVENT_NAME_SORT_CHANGED,
5
- MODEL_EVENT_NAME_PREFIX
6
- } from '../../../constants/events'
2
+ import { EVENT_NAME_SORT_CHANGED, MODEL_EVENT_NAME_PREFIX } from '../../../constants/events'
7
3
  import {
8
4
  PROP_TYPE_ARRAY_STRING,
9
5
  PROP_TYPE_BOOLEAN,
@@ -140,16 +136,6 @@ export const sortingMixin = extend({
140
136
  }
141
137
  },
142
138
  watch: {
143
- /* istanbul ignore next: pain in the butt to test */
144
- isSortable(newValue) {
145
- if (newValue) {
146
- if (this.isSortable) {
147
- this.$on(EVENT_NAME_HEAD_CLICKED, this.handleSort)
148
- }
149
- } else {
150
- this.$off(EVENT_NAME_HEAD_CLICKED, this.handleSort)
151
- }
152
- },
153
139
  [MODEL_PROP_NAME_SORT_DESC](newValue) {
154
140
  /* istanbul ignore next */
155
141
  if (newValue === this.localSortDesc) {
@@ -177,11 +163,6 @@ export const sortingMixin = extend({
177
163
  }
178
164
  }
179
165
  },
180
- created() {
181
- if (this.isSortable) {
182
- this.$on(EVENT_NAME_HEAD_CLICKED, this.handleSort)
183
- }
184
- },
185
166
  methods: {
186
167
  // Handlers
187
168
  // Need to move from thead-mixin
@@ -71,12 +71,18 @@ export const tbodyMixin = extend({
71
71
  },
72
72
  // Emits a row event, with the item object, row index and original event
73
73
  emitTbodyRowEvent(type, event) {
74
- if (type && this.hasListener(type) && event && event.target) {
74
+ if (type && event && event.target) {
75
75
  const rowIndex = this.getTbodyTrIndex(event.target)
76
76
  if (rowIndex > -1) {
77
77
  // The array of TRs correlate to the `computedItems` array
78
78
  const item = this.computedItems[rowIndex]
79
- this.$emit(type, item, rowIndex, event)
79
+ if (this.hasListener(type)) {
80
+ this.$emit(type, item, rowIndex, event)
81
+ }
82
+ // Call selectionHandler directly if selectable (defined in mixin-selectable.js)
83
+ if (type === EVENT_NAME_ROW_CLICKED && this.hasSelectableRowClick) {
84
+ this.selectionHandler(item, rowIndex, event)
85
+ }
80
86
  }
81
87
  }
82
88
  },
@@ -59,6 +59,10 @@ export const theadMixin = extend({
59
59
  }
60
60
  stopEvent(event)
61
61
  this.$emit(EVENT_NAME_HEAD_CLICKED, field.key, field, event, isFoot)
62
+ // Call handleSort directly if sortable (defined in mixin-sorting.js)
63
+ if (this.isSortable) {
64
+ this.handleSort(field.key, field, event, isFoot)
65
+ }
62
66
  },
63
67
  renderThead(isFoot = false) {
64
68
  const {