@gitlab/ui 137.2.2 → 137.2.4

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/README.md CHANGED
@@ -132,7 +132,7 @@ To publish new versions of GitLab UI, see [publishing packages](../../doc/publis
132
132
 
133
133
  ## Contributing guide
134
134
 
135
- Please review the [design system contribution process](https://design.gitlab.com/get-started/contributing)
135
+ Please review the [design system contribution process](https://design.gitlab.com/contribute/contributing)
136
136
  for an overview of how to contribute to Pajamas.
137
137
 
138
138
  For GitLab UI–specific details, refer to [Contributing to GitLab UI](CONTRIBUTING.md) for
@@ -105,9 +105,11 @@ var script = {
105
105
  }
106
106
  },
107
107
  computed: {
108
+ hasContent() {
109
+ return Boolean(this.$scopedSlots.default);
110
+ },
108
111
  hasIconOnly() {
109
- // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
110
- return Boolean(this.icon && Object.keys(this.$slots).length === 0);
112
+ return Boolean(this.icon) && !this.hasContent;
111
113
  },
112
114
  isCircularIcon() {
113
115
  return this.iconOpticallyAligned || ['issue-open-m', 'issue-close'].includes(this.icon);
@@ -142,7 +144,7 @@ var script = {
142
144
  },
143
145
  classes() {
144
146
  return ['gl-badge', 'badge', 'badge-pill', variantClass[this.variant], {
145
- '!gl-px-2': !this.$scopedSlots.default
147
+ '!gl-px-2': !this.hasContent
146
148
  }];
147
149
  }
148
150
  },
@@ -160,7 +162,7 @@ var script = {
160
162
  const __vue_script__ = script;
161
163
 
162
164
  /* template */
163
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c(_vm.computedTag,_vm._g(_vm._b({tag:"component",class:_vm.classes,attrs:{"role":_vm.role}},'component',_vm.computedProps,false),_vm.$listeners),[(_vm.icon)?_c('gl-icon',{staticClass:"gl-badge-icon",class:{ '-gl-ml-2': _vm.isCircularIcon },attrs:{"size":_vm.iconSizeComputed,"name":_vm.icon}}):_vm._e(),_vm._v(" "),(_vm.$slots.default)?_c('span',{staticClass:"gl-badge-content"},[_vm._t("default")],2):_vm._e()],1)};
165
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c(_vm.computedTag,_vm._g(_vm._b({tag:"component",class:_vm.classes,attrs:{"role":_vm.role}},'component',_vm.computedProps,false),_vm.$listeners),[(_vm.icon)?_c('gl-icon',{staticClass:"gl-badge-icon",class:{ '-gl-ml-2': _vm.isCircularIcon && _vm.hasContent },attrs:{"size":_vm.iconSizeComputed,"name":_vm.icon}}):_vm._e(),_vm._v(" "),(_vm.hasContent)?_c('span',{staticClass:"gl-badge-content"},[_vm._t("default")],2):_vm._e()],1)};
164
166
  var __vue_staticRenderFns__ = [];
165
167
 
166
168
  /* style */
@@ -304,21 +304,25 @@ var script = {
304
304
  },
305
305
  watch: {
306
306
  value(val) {
307
- if (!areDatesEqual(val, this.calendar.getDate())) {
307
+ if (this.calendar && !areDatesEqual(val, this.calendar.getDate())) {
308
308
  this.calendar.setDate(val, true);
309
309
  }
310
310
  },
311
311
  minDate(minDate) {
312
- this.calendar.setMinDate(minDate);
312
+ var _this$calendar;
313
+ (_this$calendar = this.calendar) === null || _this$calendar === void 0 ? void 0 : _this$calendar.setMinDate(minDate);
313
314
  },
314
315
  maxDate(maxDate) {
315
- this.calendar.setMaxDate(maxDate);
316
+ var _this$calendar2;
317
+ (_this$calendar2 = this.calendar) === null || _this$calendar2 === void 0 ? void 0 : _this$calendar2.setMaxDate(maxDate);
316
318
  },
317
319
  startRange(startRange) {
318
- this.calendar.setStartRange(startRange);
320
+ var _this$calendar3;
321
+ (_this$calendar3 = this.calendar) === null || _this$calendar3 === void 0 ? void 0 : _this$calendar3.setStartRange(startRange);
319
322
  },
320
323
  endRange(endRange) {
321
- this.calendar.setEndRange(endRange);
324
+ var _this$calendar4;
325
+ (_this$calendar4 = this.calendar) === null || _this$calendar4 === void 0 ? void 0 : _this$calendar4.setEndRange(endRange);
322
326
  }
323
327
  },
324
328
  mounted() {
@@ -380,7 +384,9 @@ var script = {
380
384
  }
381
385
  },
382
386
  beforeDestroy() {
383
- this.calendar.destroy();
387
+ var _this$calendar5;
388
+ // Vue 3 can unmount a component before `mounted` ran, so `calendar` may not exist yet.
389
+ (_this$calendar5 = this.calendar) === null || _this$calendar5 === void 0 ? void 0 : _this$calendar5.destroy();
384
390
  },
385
391
  methods: {
386
392
  // is used to open datepicker programmatically
@@ -15,11 +15,16 @@ const defaultEventType = click;
15
15
  */
16
16
  const activeEventTypes = new Set();
17
17
  let lastMousedown = null;
18
+ let isMouseDown = false;
18
19
  const globalListener = event => {
19
20
  callbacks.forEach((_ref, element) => {
20
21
  let callback = _ref.callback,
21
22
  eventTypes = _ref.eventTypes;
22
- const originalEvent = event.type === click ? lastMousedown || event : event;
23
+ // Safari doesn't focus <button> on click; focus moves to <body>, firing a focusin outside the
24
+ // dropdown before the button's own click handler runs. Fall back to the mousedown target only
25
+ // while the mouse is still down, so a later focusin (e.g. Tab) isn't judged by a stale target.
26
+ const usesMousedownFallback = event.type === click || event.type === focusin && isMouseDown;
27
+ const originalEvent = usesMousedownFallback ? lastMousedown || event : event;
23
28
  if (
24
29
  // Ignore events that aren't targeted outside the element
25
30
  element.contains(originalEvent.target) ||
@@ -44,16 +49,33 @@ const globalListener = event => {
44
49
  // We need to listen for mouse events because text selection fires click event only when selection ends.
45
50
  // This means that the click event target could differ from the element where it originally started.
46
51
  // As example: if we use mouse events we could guarantee that selecting text within a dropdown won't close it.
52
+ // This also covers the Safari focusin case described above, since that focusin is triggered by the
53
+ // same mousedown.
47
54
  const onMousedown = event => {
48
55
  lastMousedown = event;
56
+ isMouseDown = true;
49
57
  };
58
+
59
+ // Only clear the "is the mouse down" flag here. lastMousedown must survive until the click
60
+ // fires (click comes after mouseup), or releasing a text selection outside the element would
61
+ // wrongly close the dropdown.
62
+ const onMouseup = () => {
63
+ isMouseDown = false;
64
+ };
65
+ const mousedown = 'mousedown';
66
+ const mouseup = 'mouseup';
50
67
  const startListening = eventTypes => {
51
68
  eventTypes.forEach(eventType => {
52
69
  if (!activeEventTypes.has(eventType)) {
53
70
  // Listening to mousedown events, ensures that a text selection doesn't trigger the
54
- // GlOutsideDirective 'click' callback if the selection started within the target element.
55
- if (eventType === click) {
56
- document.addEventListener('mousedown', onMousedown);
71
+ // GlOutsideDirective 'click' callback if the selection started within the target element,
72
+ // and that a Safari focus-loss 'focusin' doesn't trigger the callback either. Tracked via
73
+ // activeEventTypes too, so it's only ever bound/unbound once, regardless of whether 'click'
74
+ // and 'focusin' are (de)activated together or separately.
75
+ if ((eventType === click || eventType === focusin) && !activeEventTypes.has(mousedown)) {
76
+ document.addEventListener(mousedown, onMousedown);
77
+ document.addEventListener(mouseup, onMouseup);
78
+ activeEventTypes.add(mousedown);
57
79
  }
58
80
 
59
81
  // Added { capture: true } to all event types to prevent the behavior discussed in https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/1686#note_412545027
@@ -66,6 +88,7 @@ const startListening = eventTypes => {
66
88
  }
67
89
  });
68
90
  lastMousedown = null;
91
+ isMouseDown = false;
69
92
  };
70
93
  const stopListening = eventTypesToUnbind => {
71
94
  eventTypesToUnbind.forEach(eventType => {
@@ -79,8 +102,12 @@ const stopListening = eventTypesToUnbind => {
79
102
  }
80
103
  }
81
104
  });
82
- if (eventTypesToUnbind.includes(click) && !activeEventTypes.has(click)) {
83
- document.removeEventListener('mousedown', onMousedown);
105
+
106
+ // Only unbind mousedown once neither 'click' nor 'focusin' need it anymore.
107
+ if (activeEventTypes.has(mousedown) && !activeEventTypes.has(click) && !activeEventTypes.has(focusin)) {
108
+ document.removeEventListener(mousedown, onMousedown);
109
+ document.removeEventListener(mouseup, onMouseup);
110
+ activeEventTypes.delete(mousedown);
84
111
  }
85
112
  };
86
113
  function parseBinding(_ref3) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "137.2.2",
3
+ "version": "137.2.4",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -126,7 +126,7 @@
126
126
  "@storybook/vue-webpack5": "^7.6.24",
127
127
  "@storybook/vue3": "^7.6.24",
128
128
  "@storybook/vue3-webpack5": "^7.6.24",
129
- "@types/jest-image-snapshot": "^6.4.1",
129
+ "@types/jest-image-snapshot": "^6.4.2",
130
130
  "@vue/compat": "3.5.32",
131
131
  "@vue/compiler-sfc": "3.5.32",
132
132
  "@vue/server-renderer": "3.5.32",
@@ -137,11 +137,11 @@
137
137
  "@yarnpkg/lockfile": "^1.1.0",
138
138
  "acorn": "^8.18.0",
139
139
  "acorn-walk": "^8.3.5",
140
- "autoprefixer": "10.5.5",
140
+ "autoprefixer": "10.6.0",
141
141
  "axe-playwright": "^2.2.2",
142
142
  "babel-loader": "^9.2.1",
143
143
  "cypress": "15.21.1",
144
- "cypress-real-events": "^1.15.0",
144
+ "cypress-real-events": "^1.15.1",
145
145
  "dompurify": "^3.1.2",
146
146
  "emoji-regex": "^10.6.0",
147
147
  "gitlab-api-async-iterator": "^1.3.1",
@@ -169,7 +169,7 @@
169
169
  "rollup-plugin-string": "^3.0.0",
170
170
  "rollup-plugin-svg": "^2.0.0",
171
171
  "rollup-plugin-vue": "^5.1.9",
172
- "sass": "^1.104.0",
172
+ "sass": "^1.104.1",
173
173
  "sass-loader": "^10.5.2",
174
174
  "sass-true": "^9",
175
175
  "start-server-and-test": "^2.1.5",
@@ -110,9 +110,11 @@ export default {
110
110
  },
111
111
  },
112
112
  computed: {
113
+ hasContent() {
114
+ return Boolean(this.$scopedSlots.default);
115
+ },
113
116
  hasIconOnly() {
114
- // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
115
- return Boolean(this.icon && Object.keys(this.$slots).length === 0);
117
+ return Boolean(this.icon) && !this.hasContent;
116
118
  },
117
119
  isCircularIcon() {
118
120
  return this.iconOpticallyAligned || ['issue-open-m', 'issue-close'].includes(this.icon);
@@ -141,7 +143,7 @@ export default {
141
143
  'badge',
142
144
  'badge-pill',
143
145
  variantClass[this.variant],
144
- { '!gl-px-2': !this.$scopedSlots.default },
146
+ { '!gl-px-2': !this.hasContent },
145
147
  ];
146
148
  },
147
149
  },
@@ -168,11 +170,10 @@ export default {
168
170
  v-if="icon"
169
171
  class="gl-badge-icon"
170
172
  :size="iconSizeComputed"
171
- :class="{ '-gl-ml-2': isCircularIcon }"
173
+ :class="{ '-gl-ml-2': isCircularIcon && hasContent }"
172
174
  :name="icon"
173
175
  />
174
- <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
175
- <span v-if="$slots.default" class="gl-badge-content">
176
+ <span v-if="hasContent" class="gl-badge-content">
176
177
  <slot></slot>
177
178
  </span>
178
179
  </component>
@@ -313,21 +313,21 @@ export default {
313
313
  },
314
314
  watch: {
315
315
  value(val) {
316
- if (!areDatesEqual(val, this.calendar.getDate())) {
316
+ if (this.calendar && !areDatesEqual(val, this.calendar.getDate())) {
317
317
  this.calendar.setDate(val, true);
318
318
  }
319
319
  },
320
320
  minDate(minDate) {
321
- this.calendar.setMinDate(minDate);
321
+ this.calendar?.setMinDate(minDate);
322
322
  },
323
323
  maxDate(maxDate) {
324
- this.calendar.setMaxDate(maxDate);
324
+ this.calendar?.setMaxDate(maxDate);
325
325
  },
326
326
  startRange(startRange) {
327
- this.calendar.setStartRange(startRange);
327
+ this.calendar?.setStartRange(startRange);
328
328
  },
329
329
  endRange(endRange) {
330
- this.calendar.setEndRange(endRange);
330
+ this.calendar?.setEndRange(endRange);
331
331
  },
332
332
  },
333
333
  mounted() {
@@ -396,7 +396,8 @@ export default {
396
396
  }
397
397
  },
398
398
  beforeDestroy() {
399
- this.calendar.destroy();
399
+ // Vue 3 can unmount a component before `mounted` ran, so `calendar` may not exist yet.
400
+ this.calendar?.destroy();
400
401
  },
401
402
  methods: {
402
403
  // is used to open datepicker programmatically
@@ -15,10 +15,15 @@ const defaultEventType = click;
15
15
  */
16
16
  const activeEventTypes = new Set();
17
17
  let lastMousedown = null;
18
+ let isMouseDown = false;
18
19
 
19
20
  const globalListener = (event) => {
20
21
  callbacks.forEach(({ callback, eventTypes }, element) => {
21
- const originalEvent = event.type === click ? lastMousedown || event : event;
22
+ // Safari doesn't focus <button> on click; focus moves to <body>, firing a focusin outside the
23
+ // dropdown before the button's own click handler runs. Fall back to the mousedown target only
24
+ // while the mouse is still down, so a later focusin (e.g. Tab) isn't judged by a stale target.
25
+ const usesMousedownFallback = event.type === click || (event.type === focusin && isMouseDown);
26
+ const originalEvent = usesMousedownFallback ? lastMousedown || event : event;
22
27
  if (
23
28
  // Ignore events that aren't targeted outside the element
24
29
  element.contains(originalEvent.target) ||
@@ -45,17 +50,35 @@ const globalListener = (event) => {
45
50
  // We need to listen for mouse events because text selection fires click event only when selection ends.
46
51
  // This means that the click event target could differ from the element where it originally started.
47
52
  // As example: if we use mouse events we could guarantee that selecting text within a dropdown won't close it.
53
+ // This also covers the Safari focusin case described above, since that focusin is triggered by the
54
+ // same mousedown.
48
55
  const onMousedown = (event) => {
49
56
  lastMousedown = event;
57
+ isMouseDown = true;
50
58
  };
51
59
 
60
+ // Only clear the "is the mouse down" flag here. lastMousedown must survive until the click
61
+ // fires (click comes after mouseup), or releasing a text selection outside the element would
62
+ // wrongly close the dropdown.
63
+ const onMouseup = () => {
64
+ isMouseDown = false;
65
+ };
66
+
67
+ const mousedown = 'mousedown';
68
+ const mouseup = 'mouseup';
69
+
52
70
  const startListening = (eventTypes) => {
53
71
  eventTypes.forEach((eventType) => {
54
72
  if (!activeEventTypes.has(eventType)) {
55
73
  // Listening to mousedown events, ensures that a text selection doesn't trigger the
56
- // GlOutsideDirective 'click' callback if the selection started within the target element.
57
- if (eventType === click) {
58
- document.addEventListener('mousedown', onMousedown);
74
+ // GlOutsideDirective 'click' callback if the selection started within the target element,
75
+ // and that a Safari focus-loss 'focusin' doesn't trigger the callback either. Tracked via
76
+ // activeEventTypes too, so it's only ever bound/unbound once, regardless of whether 'click'
77
+ // and 'focusin' are (de)activated together or separately.
78
+ if ((eventType === click || eventType === focusin) && !activeEventTypes.has(mousedown)) {
79
+ document.addEventListener(mousedown, onMousedown);
80
+ document.addEventListener(mouseup, onMouseup);
81
+ activeEventTypes.add(mousedown);
59
82
  }
60
83
 
61
84
  // Added { capture: true } to all event types to prevent the behavior discussed in https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/1686#note_412545027
@@ -67,6 +90,7 @@ const startListening = (eventTypes) => {
67
90
  });
68
91
 
69
92
  lastMousedown = null;
93
+ isMouseDown = false;
70
94
  };
71
95
 
72
96
  const stopListening = (eventTypesToUnbind) => {
@@ -79,8 +103,15 @@ const stopListening = (eventTypesToUnbind) => {
79
103
  }
80
104
  });
81
105
 
82
- if (eventTypesToUnbind.includes(click) && !activeEventTypes.has(click)) {
83
- document.removeEventListener('mousedown', onMousedown);
106
+ // Only unbind mousedown once neither 'click' nor 'focusin' need it anymore.
107
+ if (
108
+ activeEventTypes.has(mousedown) &&
109
+ !activeEventTypes.has(click) &&
110
+ !activeEventTypes.has(focusin)
111
+ ) {
112
+ document.removeEventListener(mousedown, onMousedown);
113
+ document.removeEventListener(mouseup, onMouseup);
114
+ activeEventTypes.delete(mousedown);
84
115
  }
85
116
  };
86
117