@gitlab/ui 46.1.0 → 47.0.1

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 (50) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/components/base/breadcrumb/breadcrumb.js +1 -1
  3. package/dist/components/base/datepicker/datepicker.js +17 -11
  4. package/dist/components/base/dropdown/dropdown.js +22 -5
  5. package/dist/components/base/filtered_search/filtered_search.js +1 -1
  6. package/dist/components/base/form/form_checkbox_tree/form_checkbox_tree.js +1 -1
  7. package/dist/components/base/form/form_checkbox_tree/models/tree.js +0 -1
  8. package/dist/components/base/infinite_scroll/infinite_scroll.js +17 -26
  9. package/dist/components/base/pagination/pagination.js +3 -1
  10. package/dist/components/base/search_box_by_click/search_box_by_click.js +1 -1
  11. package/dist/components/base/sorting/sorting_item.js +9 -2
  12. package/dist/components/charts/column/column.js +0 -1
  13. package/dist/components/utilities/intersperse/intersperse.js +1 -1
  14. package/dist/utility_classes.css +1 -1
  15. package/dist/utility_classes.css.map +1 -1
  16. package/package.json +7 -7
  17. package/src/components/base/alert/alert.spec.js +3 -3
  18. package/src/components/base/breadcrumb/breadcrumb.spec.js +0 -2
  19. package/src/components/base/breadcrumb/breadcrumb.vue +1 -1
  20. package/src/components/base/datepicker/datepicker.vue +18 -11
  21. package/src/components/base/daterange_picker/daterange_picker.spec.js +1 -3
  22. package/src/components/base/dropdown/dropdown.spec.js +37 -0
  23. package/src/components/base/dropdown/dropdown.stories.js +24 -1
  24. package/src/components/base/dropdown/dropdown.vue +22 -3
  25. package/src/components/base/filtered_search/filtered_search.vue +29 -30
  26. package/src/components/base/form/form_checkbox_tree/form_checkbox_tree.vue +1 -1
  27. package/src/components/base/form/form_checkbox_tree/models/tree.js +0 -1
  28. package/src/components/base/form/form_group/form_group.spec.js +1 -0
  29. package/src/components/base/infinite_scroll/infinite_scroll.vue +15 -24
  30. package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.spec.js +1 -0
  31. package/src/components/base/new_dropdowns/listbox/listbox.spec.js +1 -0
  32. package/src/components/base/new_dropdowns/listbox/listbox.stories.js +64 -99
  33. package/src/components/base/pagination/pagination.spec.js +1 -1
  34. package/src/components/base/pagination/pagination.vue +6 -4
  35. package/src/components/base/search_box_by_click/search_box_by_click.spec.js +6 -5
  36. package/src/components/base/search_box_by_click/search_box_by_click.vue +1 -1
  37. package/src/components/base/sorting/sorting.spec.js +0 -17
  38. package/src/components/base/sorting/sorting_item.spec.js +3 -1
  39. package/src/components/base/sorting/sorting_item.vue +9 -10
  40. package/src/components/base/tabs/tab/tab.spec.js +2 -3
  41. package/src/components/base/tabs/tabs/scrollable_tabs.spec.js +3 -1
  42. package/src/components/charts/column/column.vue +1 -1
  43. package/src/components/utilities/intersection_observer/intersection_observer.spec.js +1 -1
  44. package/src/components/utilities/intersperse/intersperse.vue +1 -1
  45. package/src/components/utilities/sprintf/sprintf.spec.js +1 -5
  46. package/src/components/utilities/truncate/truncate.spec.js +4 -5
  47. package/src/directives/hover_load/hover_load.spec.js +1 -1
  48. package/src/directives/safe_link/safe_link.spec.js +11 -13
  49. package/src/scss/utilities.scss +2 -12
  50. package/src/scss/utility-mixins/spacing.scss +1 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "46.1.0",
3
+ "version": "47.0.1",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -78,7 +78,7 @@
78
78
  "devDependencies": {
79
79
  "@arkweid/lefthook": "0.7.7",
80
80
  "@babel/core": "^7.19.3",
81
- "@babel/preset-env": "^7.19.3",
81
+ "@babel/preset-env": "^7.19.4",
82
82
  "@gitlab/eslint-plugin": "18.1.0",
83
83
  "@gitlab/stylelint-config": "4.1.0",
84
84
  "@gitlab/svgs": "3.4.0",
@@ -102,12 +102,12 @@
102
102
  "babel-plugin-require-context-hook": "^1.0.0",
103
103
  "babel-preset-vue": "^2.0.2",
104
104
  "bootstrap": "4.5.3",
105
- "cypress": "^10.9.0",
105
+ "cypress": "^10.10.0",
106
106
  "emoji-regex": "^10.0.0",
107
- "eslint": "8.24.0",
107
+ "eslint": "8.25.0",
108
108
  "eslint-import-resolver-jest": "3.0.2",
109
109
  "eslint-plugin-cypress": "2.12.1",
110
- "eslint-plugin-storybook": "0.6.4",
110
+ "eslint-plugin-storybook": "0.6.5",
111
111
  "file-loader": "^4.2.0",
112
112
  "glob": "^7.2.0",
113
113
  "identity-obj-proxy": "^3.0.0",
@@ -142,9 +142,9 @@
142
142
  "stylelint": "14.9.1",
143
143
  "stylelint-config-prettier": "9.0.3",
144
144
  "stylelint-prettier": "2.0.0",
145
- "vue": "2.7.10",
145
+ "vue": "2.7.13",
146
146
  "vue-loader": "^15.8.3",
147
- "vue-template-compiler": "2.7.10"
147
+ "vue-template-compiler": "2.7.13"
148
148
  },
149
149
  "release": {
150
150
  "branches": [
@@ -133,9 +133,9 @@ describe('Alert component', () => {
133
133
  const button = buttons.at(0);
134
134
  expect(button.text()).toContain(secondaryButtonText);
135
135
 
136
- const attrs = button.attributes();
137
- expect('href' in attrs).toBe(false);
138
- expect(attrs.category).toEqual(buttonCategoryOptions.secondary);
136
+ const props = button.props();
137
+ expect('href' in props).toBe(false);
138
+ expect(props.category).toEqual(buttonCategoryOptions.secondary);
139
139
  });
140
140
 
141
141
  it('emits a secondaryAction event when secondary button is clicked', () => {
@@ -1,6 +1,5 @@
1
1
  import { shallowMount } from '@vue/test-utils';
2
2
  import { nextTick } from 'vue';
3
- import { createMockDirective } from '~helpers/vue_mock_directive';
4
3
  import Breadcrumb, { COLLAPSE_AT_SIZE } from './breadcrumb.vue';
5
4
  import GlBreadcrumbItem from './breadcrumb_item.vue';
6
5
 
@@ -42,7 +41,6 @@ describe('Breadcrumb component', () => {
42
41
  avatar: '<div data-testid="avatar-slot"></div>',
43
42
  separator: '<div data-testid="separator-slot"></div>',
44
43
  },
45
- directives: { GlTooltip: createMockDirective('gl-tooltip') },
46
44
  stubs: {
47
45
  GlBreadcrumbItem,
48
46
  },
@@ -88,8 +88,8 @@ export default {
88
88
  <slot name="avatar"></slot>
89
89
  <b-breadcrumb class="gl-breadcrumb-list" v-bind="$attrs" v-on="$listeners">
90
90
  <template v-for="(item, index) in items">
91
+ <!-- eslint-disable-next-line vue/valid-v-for -->
91
92
  <gl-breadcrumb-item
92
- :key="index"
93
93
  :ref="isFirstItem(index) ? 'firstItem' : null"
94
94
  :text="item.text"
95
95
  :href="item.href"
@@ -179,7 +179,7 @@ export default {
179
179
  },
180
180
  computed: {
181
181
  formattedDate() {
182
- return this.calendar && this.calendar.toString();
182
+ return this.$options.pikaday.calendar && this.$options.pikaday.calendar.toString();
183
183
  },
184
184
  customTrigger() {
185
185
  return isString(this.target) && this.target !== '';
@@ -207,21 +207,21 @@ export default {
207
207
  },
208
208
  watch: {
209
209
  value(val) {
210
- if (!areDatesEqual(val, this.calendar.getDate())) {
211
- this.calendar.setDate(val, true);
210
+ if (!areDatesEqual(val, this.$options.pikaday.calendar.getDate())) {
211
+ this.$options.pikaday.calendar.setDate(val, true);
212
212
  }
213
213
  },
214
214
  minDate(minDate) {
215
- this.calendar.setMinDate(minDate);
215
+ this.$options.pikaday.calendar.setMinDate(minDate);
216
216
  },
217
217
  maxDate(maxDate) {
218
- this.calendar.setMaxDate(maxDate);
218
+ this.$options.pikaday.calendar.setMaxDate(maxDate);
219
219
  },
220
220
  startRange(startRange) {
221
- this.calendar.setStartRange(startRange);
221
+ this.$options.pikaday.calendar.setStartRange(startRange);
222
222
  },
223
223
  endRange(endRange) {
224
- this.calendar.setEndRange(endRange);
224
+ this.$options.pikaday.calendar.setEndRange(endRange);
225
225
  },
226
226
  },
227
227
  mounted() {
@@ -279,14 +279,14 @@ export default {
279
279
  pikadayConfig.i18n = this.i18n;
280
280
  }
281
281
 
282
- this.calendar = new Pikaday(pikadayConfig);
282
+ this.$options.pikaday.calendar = new Pikaday(pikadayConfig);
283
283
 
284
284
  if (this.startOpened) {
285
- this.calendar.show();
285
+ this.$options.pikaday.calendar.show();
286
286
  }
287
287
  },
288
288
  beforeDestroy() {
289
- this.calendar.destroy();
289
+ this.$options.pikaday.calendar.destroy();
290
290
  },
291
291
  methods: {
292
292
  selected(date) {
@@ -324,11 +324,18 @@ export default {
324
324
  onKeydown() {
325
325
  if (this.textInput === '') {
326
326
  const resetDate = this.minDate || null;
327
- this.calendar.setDate(resetDate);
327
+ this.$options.pikaday.calendar.setDate(resetDate);
328
328
  this.selected(resetDate);
329
329
  }
330
330
  },
331
331
  },
332
+
333
+ // Vue3 will make this.$options shallow-readonly
334
+ // that means that in order to store anything in $options
335
+ // we need an object as a container
336
+ pikaday: {
337
+ calendar: null,
338
+ },
332
339
  };
333
340
  </script>
334
341
 
@@ -242,9 +242,7 @@ describe('Daterange Picker', () => {
242
242
  const findTooltipIcon = () => wrapper.findComponent(Icon);
243
243
 
244
244
  const slots = {
245
- default: `<template #default="{ daysSelected }">
246
- {{ daysSelected }} days selected
247
- </template>`,
245
+ default: `<div>{{ props.daysSelected }} days selected</div>`,
248
246
  };
249
247
 
250
248
  it('does not show default slot or tooltip icon by default', () => {
@@ -1,5 +1,6 @@
1
1
  import { mount } from '@vue/test-utils';
2
2
 
3
+ import { BDropdown } from 'bootstrap-vue';
3
4
  import { dropdownVariantOptions } from '../../../utils/constants';
4
5
  import GlLoadingIcon from '../loading_icon/loading_icon.vue';
5
6
  import GlDropdown from './dropdown.vue';
@@ -34,6 +35,7 @@ describe('new dropdown', () => {
34
35
  const findClearAll = () => findByTestId('clear-all-button');
35
36
  const findHighlightedItemsTitle = () => findByTestId('highlighted-items-title');
36
37
  const findHighlightedItems = () => findByTestId('highlighted-items');
38
+ const findDropdown = () => wrapper.findComponent(BDropdown);
37
39
 
38
40
  it('renders when text is null', () => {
39
41
  buildWrapper({ text: null });
@@ -417,4 +419,39 @@ describe('new dropdown', () => {
417
419
  });
418
420
  });
419
421
  });
422
+
423
+ describe('popperOpts prop', () => {
424
+ it('combines the passed in popperOpts with the default popperOpts', () => {
425
+ const popperOpts = {
426
+ modifiers: {
427
+ flip: {
428
+ flipVariationsByContent: false,
429
+ },
430
+ },
431
+ };
432
+ buildWrapper({ popperOpts });
433
+
434
+ expect(findDropdown().props('popperOpts')).toEqual({
435
+ modifiers: {
436
+ flip: {
437
+ flipVariationsByContent: false,
438
+ padding: 28,
439
+ },
440
+ },
441
+ });
442
+ });
443
+
444
+ it('uses the default popperOpts prop', () => {
445
+ buildWrapper();
446
+
447
+ expect(findDropdown().props('popperOpts')).toEqual({
448
+ modifiers: {
449
+ flip: {
450
+ flipVariationsByContent: true,
451
+ padding: 28,
452
+ },
453
+ },
454
+ });
455
+ });
456
+ });
420
457
  });
@@ -84,7 +84,7 @@ const withContainer = (template, containerHeight = 150) => `
84
84
  ${template}
85
85
  </div>`;
86
86
 
87
- function wrap(template, containerHeight) {
87
+ function wrap(template, containerHeight, cssClass) {
88
88
  return withContainer(
89
89
  `
90
90
  <gl-dropdown
@@ -109,6 +109,7 @@ function wrap(template, containerHeight) {
109
109
  :highlighted-items-title-class="highlightedItemsTitleClass"
110
110
  :loading="loading"
111
111
  :right="right"
112
+ class="${cssClass}"
112
113
  >
113
114
  ${template}
114
115
  </gl-dropdown>`,
@@ -514,6 +515,28 @@ WithHighlightedItems.args = generateProps({
514
515
  highlightedItemsTitleClass: 'gl-px-5',
515
516
  });
516
517
 
518
+ export const OnRightEdge = (args, { argTypes }) => ({
519
+ props: Object.keys(argTypes),
520
+ components,
521
+ template: wrap(
522
+ `
523
+ <gl-dropdown-item>First item</gl-dropdown-item>
524
+ <gl-dropdown-item>Second item</gl-dropdown-item>
525
+ <gl-dropdown-item>Third item</gl-dropdown-item>
526
+ <gl-dropdown-item>Fourth item</gl-dropdown-item>
527
+ `,
528
+ 200,
529
+ 'gl-display-block gl-text-right'
530
+ ),
531
+ mounted() {
532
+ clickDropdown(this);
533
+ },
534
+ updated() {
535
+ addClass(this);
536
+ },
537
+ });
538
+ OnRightEdge.args = generateProps({ text: 'Some dropdown' });
539
+
517
540
  export default {
518
541
  title: 'base/dropdown',
519
542
  component: GlDropdown,
@@ -1,7 +1,9 @@
1
- <!-- eslint-disable vue/multi-word-component-names -->
1
+ <!-- eslint-disable vue/multi-word-component-names vue/one-component-per-file -->
2
2
  <script>
3
+ import Vue from 'vue';
3
4
  import { BDropdown } from 'bootstrap-vue';
4
5
  import { isVisible, selectAll } from 'bootstrap-vue/src/utils/dom';
6
+ import { merge } from 'lodash';
5
7
  import {
6
8
  buttonCategoryOptions,
7
9
  buttonSizeOptions,
@@ -24,13 +26,21 @@ const Selector = {
24
26
  };
25
27
 
26
28
  // see https://gitlab.com/gitlab-org/gitlab-ui/merge_requests/130#note_126406721
27
- const ExtendedBDropdown = {
28
- extends: BDropdown,
29
+ const ExtendedBDropdown = Vue.extend(BDropdown, {
29
30
  methods: {
30
31
  getItems() {
31
32
  return filterVisible(selectAll(Selector.ITEM_SELECTOR, this.$refs.menu));
32
33
  },
33
34
  },
35
+ });
36
+
37
+ export const DefaultPopperOptions = {
38
+ modifiers: {
39
+ flip: {
40
+ flipVariationsByContent: true,
41
+ padding: 28,
42
+ },
43
+ },
34
44
  };
35
45
 
36
46
  export default {
@@ -146,6 +156,11 @@ export default {
146
156
  required: false,
147
157
  default: false,
148
158
  },
159
+ popperOpts: {
160
+ type: Object,
161
+ required: false,
162
+ default: null,
163
+ },
149
164
  },
150
165
  computed: {
151
166
  renderCaret() {
@@ -199,6 +214,9 @@ export default {
199
214
  (this.hasHighlightedItemsContent && this.showHighlightedItemsTitle) || this.showClearAll
200
215
  );
201
216
  },
217
+ popperOptions() {
218
+ return merge({}, DefaultPopperOptions, this.popperOpts);
219
+ },
202
220
  },
203
221
  methods: {
204
222
  hasSlotContents(slotName) {
@@ -227,6 +245,7 @@ export default {
227
245
  :block="block"
228
246
  :disabled="disabled || loading"
229
247
  :right="right"
248
+ :popper-opts="popperOptions"
230
249
  v-on="$listeners"
231
250
  >
232
251
  <div class="gl-new-dropdown-inner">
@@ -368,36 +368,35 @@ export default {
368
368
  class="gl-filtered-search-scrollable"
369
369
  :class="{ 'gl-bg-gray-10! gl-inset-border-1-gray-100!': viewOnly }"
370
370
  >
371
- <template v-for="(token, idx) in tokens">
372
- <component
373
- :is="getTokenComponent(token.type)"
374
- ref="tokens"
375
- :key="token.id"
376
- v-model="token.value"
377
- :config="getTokenEntry(token.type)"
378
- :active="activeTokenIdx === idx"
379
- :cursor-position="intendedCursorPosition"
380
- :available-tokens="currentAvailableTokens"
381
- :current-value="tokens"
382
- :index="idx"
383
- :placeholder="termPlaceholder"
384
- :show-friendly-text="showFriendlyText"
385
- :search-input-attributes="searchInputAttributes"
386
- :view-only="viewOnly"
387
- :is-last-token="isLastToken(idx)"
388
- class="gl-filtered-search-item"
389
- :class="{ 'gl-filtered-search-last-item': isLastToken(idx) && !viewOnly }"
390
- @activate="activate(idx)"
391
- @deactivate="deactivate(token)"
392
- @destroy="destroyToken(idx, $event)"
393
- @replace="replaceToken(idx, $event)"
394
- @complete="completeToken"
395
- @submit="submit"
396
- @split="createTokens(idx, $event)"
397
- @previous="activatePreviousToken"
398
- @next="activateNextToken"
399
- />
400
- </template>
371
+ <component
372
+ :is="getTokenComponent(token.type)"
373
+ v-for="(token, idx) in tokens"
374
+ ref="tokens"
375
+ :key="token.id"
376
+ v-model="token.value"
377
+ :config="getTokenEntry(token.type)"
378
+ :active="activeTokenIdx === idx"
379
+ :cursor-position="intendedCursorPosition"
380
+ :available-tokens="currentAvailableTokens"
381
+ :current-value="tokens"
382
+ :index="idx"
383
+ :placeholder="termPlaceholder"
384
+ :show-friendly-text="showFriendlyText"
385
+ :search-input-attributes="searchInputAttributes"
386
+ :view-only="viewOnly"
387
+ :is-last-token="isLastToken(idx)"
388
+ class="gl-filtered-search-item"
389
+ :class="{ 'gl-filtered-search-last-item': isLastToken(idx) && !viewOnly }"
390
+ @activate="activate(idx)"
391
+ @deactivate="deactivate(token)"
392
+ @destroy="destroyToken(idx, $event)"
393
+ @replace="replaceToken(idx, $event)"
394
+ @complete="completeToken"
395
+ @submit="submit"
396
+ @split="createTokens(idx, $event)"
397
+ @previous="activatePreviousToken"
398
+ @next="activateNextToken"
399
+ />
401
400
  </div>
402
401
  <portal-target
403
402
  ref="menu"
@@ -106,7 +106,7 @@ export default {
106
106
  class="gl-form-checkbox-tree-toggle-all"
107
107
  :checked="tree.allOptionsChecked"
108
108
  :indeterminate="tree.someOptionsChecked"
109
- @change="tree.toggleAllOptions"
109
+ @change="tree.toggleAllOptions($event)"
110
110
  >
111
111
  {{ toggleAllLabel }}
112
112
  </gl-form-checkbox>
@@ -5,7 +5,6 @@ export class Tree {
5
5
  constructor(options, selected) {
6
6
  this.treeDepth = 0;
7
7
  this.nodes = {};
8
- this.toggleAllOptions = this.toggleAllOptions.bind(this);
9
8
 
10
9
  this.initNodes(options, selected);
11
10
  this.initIndeterminateStates();
@@ -13,6 +13,7 @@ describe('Form group component', () => {
13
13
  const createComponent = (options) => {
14
14
  wrapper = shallowMount(GlFormGroup, {
15
15
  ...options,
16
+ stubs: { BFormGroup },
16
17
  });
17
18
  };
18
19
 
@@ -56,8 +56,21 @@ export default {
56
56
  const { scrollHeight, scrollTop } = this.$refs.infiniteContainer;
57
57
  // Only when scrolled to the top
58
58
  if (scrollHeight !== 0 && scrollTop === 0) {
59
- // Store scrollHeight to know how far to scroll
60
- this.$options.adjustScrollHeight = scrollHeight;
59
+ // Wait until the DOM is fully updated to adjust scroll
60
+ this.$nextTick(() => {
61
+ const { scrollHeight: newScrollHeight } = this.$refs.infiniteContainer;
62
+
63
+ // New scrollTop is the new height, minus the old height
64
+ // minus a small space to allow the user to trigger a scroll once more
65
+ let top = newScrollHeight - scrollHeight - adjustScrollGap;
66
+
67
+ // Never adjust to 0, or a new event may be be triggered
68
+ if (top < 1) {
69
+ top = 1;
70
+ }
71
+
72
+ this.scrollTo({ top });
73
+ });
61
74
  }
62
75
  }
63
76
  },
@@ -72,28 +85,6 @@ export default {
72
85
  });
73
86
  },
74
87
 
75
- updated() {
76
- // Wait until the DOM is fully updated to adjust scroll
77
- this.$nextTick(() => {
78
- if (this.$options.adjustScrollHeight) {
79
- const { scrollHeight } = this.$refs.infiniteContainer;
80
-
81
- // New scrollTop is the new height, minus the old height
82
- // minus a small space to allow the user to trigger a scroll once more
83
- let top = scrollHeight - this.$options.adjustScrollHeight - adjustScrollGap;
84
-
85
- // Never adjust to 0, or a new event may be be triggered
86
- if (top < 1) {
87
- top = 1;
88
- }
89
-
90
- this.scrollTo({ top });
91
- // Prevent subsequent updates
92
- this.$options.adjustScrollHeight = null;
93
- }
94
- });
95
- },
96
-
97
88
  methods: {
98
89
  /**
99
90
  * Scroll to the top of the container, leaving a gap
@@ -35,6 +35,7 @@ describe('base dropdown', () => {
35
35
  slots,
36
36
  attachTo: document.body,
37
37
  });
38
+ return nextTick();
38
39
  };
39
40
 
40
41
  beforeEach(() => {
@@ -161,6 +161,7 @@ describe('GlListbox', () => {
161
161
  }
162
162
  findBaseDropdown().vm.$emit(GL_DROPDOWN_SHOWN);
163
163
  await nextTick();
164
+ await nextTick();
164
165
  };
165
166
 
166
167
  it('should re-emit the event', async () => {