@gitlab/ui 128.14.3 → 128.15.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "128.14.3",
3
+ "version": "128.15.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -129,7 +129,7 @@
129
129
  "@yarnpkg/lockfile": "^1.1.0",
130
130
  "acorn": "^8.16.0",
131
131
  "acorn-walk": "^8.3.5",
132
- "autoprefixer": "10.4.24",
132
+ "autoprefixer": "10.4.27",
133
133
  "axe-playwright": "^2.2.2",
134
134
  "babel-loader": "^9.2.1",
135
135
  "cypress": "15.10.0",
@@ -16,22 +16,22 @@
16
16
  @apply gl-mb-0;
17
17
  }
18
18
 
19
- &:hover {
19
+ &:not(.disabled):hover {
20
20
  .gl-new-dropdown-item-content {
21
21
  color: var(--gl-dropdown-option-text-color-hover);
22
22
  background-color: var(--gl-dropdown-option-background-color-unselected-hover);
23
23
  }
24
24
  }
25
25
 
26
- &:focus {
26
+ &:not(.disabled):focus {
27
27
  .gl-new-dropdown-item-content {
28
28
  color: var(--gl-dropdown-option-text-color-focus);
29
29
  background-color: var(--gl-dropdown-option-background-color-unselected-focus);
30
30
  }
31
31
  }
32
32
 
33
- &:active,
34
- &:focus:active {
33
+ &:not(.disabled):active,
34
+ &:not(.disabled):focus:active {
35
35
  .gl-new-dropdown-item-content {
36
36
  color: var(--gl-dropdown-option-text-color-active);
37
37
  background-color: var(--gl-dropdown-option-background-color-unselected-active);
@@ -43,7 +43,13 @@
43
43
  background-color: var(--gl-dropdown-option-background-color-selected-default);
44
44
  }
45
45
 
46
- &:hover {
46
+ &.disabled {
47
+ .gl-new-dropdown-item-content {
48
+ background-color: var(--gl-background-color-disabled);
49
+ }
50
+ }
51
+
52
+ &:not(.disabled):hover {
47
53
  .gl-new-dropdown-item-content {
48
54
  background-color: var(--gl-dropdown-option-background-color-selected-hover);
49
55
  }
@@ -53,7 +59,7 @@
53
59
  }
54
60
  }
55
61
 
56
- &:focus {
62
+ &:not(.disabled):focus {
57
63
  .gl-new-dropdown-item-content {
58
64
  background-color: var(--gl-dropdown-option-background-color-selected-focus);
59
65
  }
@@ -63,8 +69,8 @@
63
69
  }
64
70
  }
65
71
 
66
- &:active,
67
- &:focus:active {
72
+ &:not(.disabled):active,
73
+ &:not(.disabled):focus:active {
68
74
  .gl-new-dropdown-item-content {
69
75
  background-color: var(--gl-dropdown-option-background-color-selected-active);
70
76
  }
@@ -79,9 +85,9 @@
79
85
  outline: none;
80
86
  }
81
87
 
82
- &:active,
83
- &:focus,
84
- &:focus:active {
88
+ &:not(.disabled):active,
89
+ &:not(.disabled):focus,
90
+ &:not(.disabled):focus:active {
85
91
  .gl-new-dropdown-item-content {
86
92
  @include gl-focus($inset: true);
87
93
  }
@@ -100,6 +106,20 @@
100
106
  @include gl-focus($inset: true);
101
107
  }
102
108
 
109
+ &.disabled {
110
+ cursor: not-allowed;
111
+ user-select: none;
112
+
113
+ .gl-new-dropdown-item-content {
114
+ color: var(--gl-text-color-disabled);
115
+ pointer-events: none;
116
+
117
+ .gl-new-dropdown-item-check-icon {
118
+ color: var(--gl-icon-color-disabled);
119
+ }
120
+ }
121
+ }
122
+
103
123
  .gl-new-dropdown-item-content {
104
124
  transition:
105
125
  background-color $gl-transition-duration-fast $gl-easing-out-cubic,
@@ -603,10 +603,10 @@ export default {
603
603
 
604
604
  /**
605
605
  * Every time the list of items changes, and there is a search string,
606
- * we want to visually highlight the first item
606
+ * we want to visually highlight the first enabled item
607
607
  */
608
608
  if (this.searchHasOptions) {
609
- this.nextFocusedItemIndex = 0;
609
+ this.nextFocusedItemIndex = this.getFirstEnabledIndex();
610
610
  } else {
611
611
  this.nextFocusedItemIndex = null;
612
612
  }
@@ -677,16 +677,22 @@ export default {
677
677
  this.focusSearchInput();
678
678
 
679
679
  /**
680
- * If the search string is not empty, highlight the first item
680
+ * If the search string is not empty, highlight the first enabled item
681
681
  */
682
682
  if (this.searchHasOptions) {
683
- this.nextFocusedItemIndex = 0;
684
- // Set activeItemId for the first item
685
- const firstItem = this.flattenedOptions[0];
683
+ const firstEnabledIndex = this.getFirstEnabledIndex();
684
+ this.nextFocusedItemIndex = firstEnabledIndex;
685
+ // Set activeItemId for the first enabled item
686
+ const firstItem = this.flattenedOptions[firstEnabledIndex];
686
687
  this.activeItemId = this.generateItemId(firstItem);
687
688
  }
688
689
  } else {
689
- this.focusItem(this.selectedIndices[0] ?? 0, this.getFocusableListItemElements());
690
+ const selectedIndex = this.selectedIndices[0];
691
+ const initialIndex =
692
+ selectedIndex !== undefined && !this.flattenedOptions[selectedIndex]?.disabled
693
+ ? selectedIndex
694
+ : this.getFirstEnabledIndex();
695
+ this.focusItem(initialIndex, this.getFocusableListItemElements());
690
696
  }
691
697
  /**
692
698
  * Emitted when dropdown is shown
@@ -707,13 +713,26 @@ export default {
707
713
  this.$emit('blur', event);
708
714
  },
709
715
  getNextIndex(currentIndex, keyCode, totalLength) {
710
- // For UP: move up or wrap to end
711
- if (keyCode === ARROW_UP) {
712
- return currentIndex > 0 ? currentIndex - 1 : totalLength - 1;
716
+ const direction = keyCode === ARROW_UP ? -1 : 1;
717
+ let nextIndex = currentIndex;
718
+
719
+ for (let i = 0; i < totalLength; i += 1) {
720
+ nextIndex += direction;
721
+ if (nextIndex < 0) nextIndex = totalLength - 1;
722
+ if (nextIndex >= totalLength) nextIndex = 0;
723
+
724
+ if (!this.flattenedOptions[nextIndex]?.disabled) {
725
+ return nextIndex;
726
+ }
713
727
  }
714
728
 
715
- // For DOWN: move down or wrap to start
716
- return currentIndex < totalLength - 1 ? currentIndex + 1 : 0;
729
+ return currentIndex;
730
+ },
731
+ getFirstEnabledIndex() {
732
+ return this.flattenedOptions.findIndex((item) => !item.disabled);
733
+ },
734
+ getLastEnabledIndex() {
735
+ return this.flattenedOptions.findLastIndex((item) => !item.disabled);
717
736
  },
718
737
  handleListNavigation(keyCode, elements) {
719
738
  const currentIndex = this.nextFocusedItemIndex ?? -1;
@@ -731,16 +750,16 @@ export default {
731
750
 
732
751
  switch (code) {
733
752
  case HOME:
734
- // Jump to first item if searchable or not in search input
753
+ // Jump to first enabled item if searchable or not in search input
735
754
  if (this.searchable || !isSearchInput) {
736
- this.focusItem(0, elements, this.searchable);
755
+ this.focusItem(this.getFirstEnabledIndex(), elements, this.searchable);
737
756
  }
738
757
  break;
739
758
 
740
759
  case END:
741
- // Jump to last item if searchable or not in search input
760
+ // Jump to last enabled item if searchable or not in search input
742
761
  if (this.searchable || !isSearchInput) {
743
- this.focusItem(elements.length - 1, elements, this.searchable);
762
+ this.focusItem(this.getLastEnabledIndex(), elements, this.searchable);
744
763
  }
745
764
  break;
746
765
 
@@ -753,9 +772,9 @@ export default {
753
772
  break;
754
773
 
755
774
  case ARROW_DOWN:
756
- // Focus first item from search input, otherwise navigate down
775
+ // Focus first enabled item from search input, otherwise navigate down
757
776
  if (isSearchInput && !this.searchable) {
758
- this.focusItem(0, elements);
777
+ this.focusItem(this.getFirstEnabledIndex(), elements);
759
778
  } else {
760
779
  this.handleListNavigation(ARROW_DOWN, elements);
761
780
  }
@@ -763,10 +782,12 @@ export default {
763
782
 
764
783
  case ENTER:
765
784
  if (isSearchInput) {
766
- // Toggle selection of highlighted item if one exists
785
+ // Toggle selection of highlighted item if one exists and is not disabled
767
786
  if (elements.length > 0 && this.nextFocusedItemIndex !== null) {
768
787
  const highlightedItem = this.flattenedOptions[this.nextFocusedItemIndex];
769
- this.onSelect(highlightedItem, !this.isSelected(highlightedItem));
788
+ if (!highlightedItem?.disabled) {
789
+ this.onSelect(highlightedItem, !this.isSelected(highlightedItem));
790
+ }
770
791
  }
771
792
  } else {
772
793
  stop = false;
@@ -834,6 +855,9 @@ export default {
834
855
  isFocused(item) {
835
856
  return this.nextFocusedItemIndex === this.flattenedOptions.indexOf(item);
836
857
  },
858
+ isDisabled(item) {
859
+ return item.disabled;
860
+ },
837
861
  onSingleSelect(value, isSelected) {
838
862
  if (isSelected) {
839
863
  /**
@@ -1109,6 +1133,7 @@ export default {
1109
1133
  :is-selected="isSelected(item)"
1110
1134
  :is-focused="isFocused(item)"
1111
1135
  :is-check-centered="isCheckCentered"
1136
+ :is-disabled="isDisabled(item)"
1112
1137
  v-bind="listboxItemMoreItemsAriaAttributes(index)"
1113
1138
  @select="onSelect(item, $event)"
1114
1139
  >
@@ -1140,6 +1165,7 @@ export default {
1140
1165
  :is-selected="isSelected(option)"
1141
1166
  :is-focused="isFocused(option)"
1142
1167
  :is-check-centered="isCheckCentered"
1168
+ :is-disabled="isDisabled(option)"
1143
1169
  @select="onSelect(option, $event)"
1144
1170
  >
1145
1171
  <!-- @slot Custom template of the listbox item -->
@@ -29,6 +29,11 @@ export default {
29
29
  default: false,
30
30
  required: false,
31
31
  },
32
+ isDisabled: {
33
+ type: Boolean,
34
+ default: false,
35
+ required: false,
36
+ },
32
37
  },
33
38
  computed: {
34
39
  checkedClasses() {
@@ -40,7 +45,13 @@ export default {
40
45
  },
41
46
  },
42
47
  methods: {
48
+ onMousedown(event) {
49
+ if (this.isDisabled) {
50
+ stopEvent(event);
51
+ }
52
+ },
43
53
  toggleSelection() {
54
+ if (this.isDisabled) return;
44
55
  this.$emit('select', !this.isSelected);
45
56
  },
46
57
  onKeydown(event) {
@@ -57,10 +68,18 @@ export default {
57
68
 
58
69
  <template>
59
70
  <li
60
- :class="['gl-new-dropdown-item', { 'gl-new-dropdown-item-highlighted': isHighlighted }]"
71
+ :class="[
72
+ 'gl-new-dropdown-item',
73
+ {
74
+ 'gl-new-dropdown-item-highlighted': isHighlighted,
75
+ disabled: isDisabled,
76
+ },
77
+ ]"
61
78
  role="option"
62
79
  :tabindex="isFocused ? 0 : -1"
63
80
  :aria-selected="isSelected"
81
+ :aria-disabled="isDisabled"
82
+ @mousedown="onMousedown"
64
83
  @click="toggleSelection"
65
84
  @keydown="onKeydown"
66
85
  >
@@ -14,14 +14,17 @@ export const mockOptions = [
14
14
  {
15
15
  value: 'leg',
16
16
  text: 'Legal',
17
+ disabled: true,
17
18
  },
18
19
  {
19
20
  value: 'eng',
20
21
  text: 'Engineering',
22
+ disabled: true,
21
23
  },
22
24
  {
23
25
  value: 'sales',
24
26
  text: 'Sales',
27
+ disabled: false,
25
28
  },
26
29
  {
27
30
  value: 'marketing',
@@ -30,6 +33,7 @@ export const mockOptions = [
30
33
  {
31
34
  value: 'acc',
32
35
  text: 'Accounting',
36
+ disabled: true,
33
37
  },
34
38
  {
35
39
  value: 'hr',
@@ -50,6 +54,7 @@ export const mockOptions = [
50
54
  {
51
55
  value: null,
52
56
  text: 'None',
57
+ disabled: true,
53
58
  },
54
59
  ];
55
60
 
@@ -134,6 +139,12 @@ export const mockUsers = [
134
139
  secondaryText: '@ohoral',
135
140
  icon: 'bar',
136
141
  },
142
+ {
143
+ value: 'jdoe',
144
+ text: 'Jane Doe',
145
+ secondaryText: '@jdoe',
146
+ disabled: true,
147
+ },
137
148
  {
138
149
  value: 'markian',
139
150
  text: 'Mark Florian',