@carbon/vue 2.18.0-canary.0 → 2.19.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 (31) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/dist/carbon-vue.common.js +520 -194
  3. package/dist/carbon-vue.common.js.map +1 -1
  4. package/dist/carbon-vue.umd.js +520 -194
  5. package/dist/carbon-vue.umd.js.map +1 -1
  6. package/dist/carbon-vue.umd.min.js +3 -3
  7. package/dist/carbon-vue.umd.min.js.map +1 -1
  8. package/package.json +2 -2
  9. package/src/components/cv-combo-box/cv-combo-box.vue +15 -11
  10. package/src/components/cv-data-table/_cv-data-table-row-inner.vue +11 -3
  11. package/src/components/cv-data-table/cv-data-table-action.vue +1 -1
  12. package/src/components/cv-data-table/cv-data-table-row.vue +4 -2
  13. package/src/components/cv-data-table/cv-data-table-skeleton.vue +24 -3
  14. package/src/components/cv-data-table/cv-data-table.vue +17 -10
  15. package/src/components/cv-date-picker/cv-date-picker.vue +13 -7
  16. package/src/components/cv-dropdown/cv-dropdown.vue +11 -6
  17. package/src/components/cv-modal/cv-modal.vue +18 -12
  18. package/src/components/cv-multi-select/cv-multi-select.vue +27 -11
  19. package/src/components/cv-number-input/cv-number-input.vue +13 -6
  20. package/src/components/cv-overflow-menu/cv-overflow-menu-item.vue +3 -1
  21. package/src/components/cv-select/cv-select.vue +16 -7
  22. package/src/components/cv-tabs/cv-tabs.vue +160 -51
  23. package/src/components/cv-text-area/cv-text-area.vue +18 -5
  24. package/src/components/cv-text-input/cv-text-input.vue +13 -6
  25. package/src/components/cv-time-picker/cv-time-picker.vue +17 -6
  26. package/src/components/cv-toggle/cv-toggle-skeleton.vue +21 -0
  27. package/src/components/cv-toggle/index.js +2 -1
  28. package/src/components/cv-ui-shell/cv-header.vue +9 -1
  29. package/src/components/cv-ui-shell/cv-side-nav-link.vue +9 -1
  30. package/src/components/cv-ui-shell/cv-side-nav-menu.vue +9 -1
  31. package/src/index.js +2 -2
@@ -1,26 +1,29 @@
1
1
  <template>
2
- <div class="cv-tabs">
2
+ <div class="cv-tabs" @focusout="onFocusout" @focusin="onFocusin">
3
3
  <div
4
4
  data-tabs
5
5
  class="cv-tab bx--tabs"
6
6
  role="navigation"
7
7
  v-on="$listeners"
8
8
  v-bind="$attrs"
9
- @keydown.right.prevent="moveRight"
10
- @keydown.left.prevent="moveLeft"
9
+ @keydown.right.prevent="onRight"
10
+ @keydown.left.prevent="onLeft"
11
+ @keydown.down.prevent="onDown"
12
+ @keydown.up.prevent="onUp"
13
+ @keydown.esc.prevent="onEsc"
11
14
  >
12
- <cv-dropdown
15
+ <div
13
16
  class="bx--tabs-trigger"
14
- :value="`${selectedId}`"
15
- @change="onDropChange"
16
- :form-item="false"
17
- :style="triggerStyleOverride"
17
+ :class="{ ' bx--tabs-trigger--open': open }"
18
+ tabindex="0"
19
+ ref="trigger"
20
+ @click="onClick"
21
+ @keydown.enter.prevent="onClick"
18
22
  >
19
- <cv-dropdown-item v-for="tab in tabs" :key="`drop-${tab.uid}`" :value="`${tab.uid}`">{{
20
- tab.label
21
- }}</cv-dropdown-item>
22
- </cv-dropdown>
23
- <ul class="bx--tabs__nav bx--tabs__nav--hidden" role="tablist">
23
+ <a href="javascript:void(0)" class="bx--tabs-trigger-text" tabindex="-1">{{ currentTabLabel }}</a>
24
+ <chevron-down-glyph />
25
+ </div>
26
+ <ul class="bx--tabs__nav" :class="{ 'bx--tabs__nav--hidden': !open }" role="tablist">
24
27
  <li
25
28
  v-for="tab in tabs"
26
29
  :key="tab.uid"
@@ -40,6 +43,7 @@
40
43
  :aria-controls="tab.uid"
41
44
  :id="`${tab.uid}-link`"
42
45
  @click="onTabClick(tab.uid)"
46
+ @keydown.enter.prevent="onTabEnter(tab.uid)"
43
47
  ref="link"
44
48
  >{{ tab.label }}</a
45
49
  >
@@ -53,20 +57,22 @@
53
57
  </template>
54
58
 
55
59
  <script>
56
- import CvDropdown from '../cv-dropdown/cv-dropdown';
57
- import CvDropdownItem from '../cv-dropdown/cv-dropdown-item';
60
+ import ChevronDownGlyph from '@carbon/icons-vue/es/chevron--down';
58
61
 
59
62
  export default {
60
63
  name: 'CvTabs',
61
- components: { CvDropdown, CvDropdownItem },
62
64
  props: {
63
65
  noDefaultToFirst: Boolean,
64
66
  },
67
+ components: { ChevronDownGlyph },
65
68
  data() {
66
69
  return {
67
70
  tabs: [],
68
71
  selectedId: undefined,
69
72
  disabledTabs: [],
73
+ open: false,
74
+ lastDisplayProp: undefined,
75
+ // data is open
70
76
  };
71
77
  },
72
78
  created() {
@@ -77,13 +83,59 @@ export default {
77
83
  this.$on('cv:disabled', srcComponent => this.onCvDisabled(srcComponent));
78
84
  this.$on('cv:enabled', srcComponent => this.onCvEnabled(srcComponent));
79
85
  },
86
+ mounted() {},
80
87
  computed: {
81
88
  triggerStyleOverride() {
82
89
  // <style carbon tweaks - DO NOT USE STYLE TAG as it causes SSR issues
83
90
  return { padding: 0 };
84
91
  },
92
+ currentTabLabel() {
93
+ const index = this.tabs.findIndex(tab => tab.uid === this.selectedId);
94
+
95
+ return index > -1 ? this.tabs[index].label : '';
96
+ },
85
97
  },
86
98
  methods: {
99
+ onFocusin(ev) {
100
+ if (ev.target.classList.contains('bx--tabs__nav-link') || ev.target.classList.contains('bx--tabs-trigger')) {
101
+ // record display prop state
102
+ this.lastDisplayProp = window.getComputedStyle(this.$refs.trigger).getPropertyValue('display');
103
+ } else {
104
+ this.lastDisplayProp = undefined;
105
+ }
106
+ },
107
+ onFocusout(ev) {
108
+ // works with onFocusin to determine whether focus needs to be set to a tab or trigger
109
+ const displayProp = window.getComputedStyle(this.$refs.trigger).getPropertyValue('display');
110
+ if (ev.relatedTarget) {
111
+ if (
112
+ ev.relatedTarget.classList.contains('bx--tabs__nav-link') ||
113
+ ev.relatedTarget.classList.contains('bx--tabs-trigger')
114
+ ) {
115
+ return; // no need to do anything - focus is going somewhere
116
+ } else {
117
+ this.open = false;
118
+ }
119
+ } else {
120
+ if (this.lastDisplayProp && this.lastDisplayProp !== displayProp) {
121
+ if (displayProp === 'none') {
122
+ // focus on selected tab
123
+ const currentTabLink = this.$refs.link.find(link => link.getAttribute('aria-controls') === this.selectedId);
124
+ if (currentTabLink) {
125
+ currentTabLink.focus();
126
+ }
127
+ } else {
128
+ this.$refs.trigger.focus();
129
+ }
130
+ } else {
131
+ this.open = false;
132
+ }
133
+ }
134
+ },
135
+ onWindowResize() {
136
+ // check whether trigger is displayed
137
+ this.dataDropdownShown = window.getComputedStyle(this.$refs.trigger).getPropertyValue('display') !== 'none';
138
+ },
87
139
  onDropChange(val) {
88
140
  this.onTabClick(val);
89
141
  },
@@ -126,6 +178,12 @@ export default {
126
178
  }
127
179
  }
128
180
 
181
+ this.open = false;
182
+ if (this.$refs.trigger) {
183
+ // following code build sometimes trigger is not yet available
184
+ this.$refs.trigger.focus();
185
+ }
186
+
129
187
  this.$emit('tab-selected', newIndex);
130
188
  }
131
189
  }
@@ -166,64 +224,115 @@ export default {
166
224
  isAllTabsDisabled() {
167
225
  return this.disabledTabs.length === this.tabs.length;
168
226
  },
169
- moveLeft() {
227
+ onTabEnter(id) {
228
+ // const newIndex = this.tabs.findIndex(tab => tab.uid === id);
229
+
230
+ this.onTabClick(id);
231
+ this.$refs.trigger.focus();
232
+ },
233
+ onClick() {
234
+ this.open = !this.open;
235
+ },
236
+ onEsc() {
237
+ this.open = false;
238
+ },
239
+ onUp() {
170
240
  if (this.isAllTabsDisabled()) {
171
241
  return;
172
242
  }
173
- let newId;
174
- let newIndex;
175
243
 
176
- if (this.selectedId) {
177
- newIndex = this.tabs.indexOf(this.selectedId);
178
- }
244
+ const displayProp = window.getComputedStyle(this.$refs.trigger).getPropertyValue('display');
179
245
 
180
- if (newIndex > 0) {
181
- newIndex--;
182
- } else {
183
- newIndex = this.tabs.length - 1;
246
+ if (displayProp !== 'none') {
247
+ const el = document.activeElement;
248
+ let id;
249
+ if (el.classList.contains('bx--tabs__nav-link')) {
250
+ id = el.getAttribute('aria-controls');
251
+ } else {
252
+ id = this.selectedId;
253
+ }
254
+
255
+ const newIndex = this.move(id, false);
256
+ this.$refs.link[newIndex].focus();
257
+ }
258
+ },
259
+ onDown() {
260
+ if (this.isAllTabsDisabled()) {
261
+ return;
184
262
  }
185
- newId = this.tabs[newIndex].uid;
186
263
 
187
- while (this.disabledTabs.indexOf(newId) !== -1) {
188
- if (newIndex > 0) {
189
- newIndex--;
264
+ const displayProp = window.getComputedStyle(this.$refs.trigger).getPropertyValue('display');
265
+
266
+ if (displayProp !== 'none') {
267
+ if (!this.open) {
268
+ this.open = true;
190
269
  } else {
191
- newIndex = this.tabs.length - 1;
270
+ const el = document.activeElement;
271
+ let id;
272
+ if (el.classList.contains('bx--tabs__nav-link')) {
273
+ id = el.getAttribute('aria-controls');
274
+ } else {
275
+ id = this.selectedId;
276
+ }
277
+
278
+ const newIndex = this.move(id, true);
279
+ this.$refs.link[newIndex].focus();
192
280
  }
193
- newId = this.tabs[newIndex].uid;
194
281
  }
195
- this.onTabClick(newId);
196
- this.$refs.link[newIndex].focus();
197
282
  },
198
- moveRight() {
283
+ onLeft() {
199
284
  if (this.isAllTabsDisabled()) {
200
285
  return;
201
286
  }
202
- let newId;
203
- let newIndex;
204
287
 
205
- if (this.selectedId) {
206
- newIndex = this.tabs.indexOf(this.selectedId);
288
+ const displayProp = window.getComputedStyle(this.$refs.trigger).getPropertyValue('display');
289
+ if (displayProp === 'none') {
290
+ const newIndex = this.move(this.selectedId, false);
291
+ const newId = this.tabs[newIndex].uid;
292
+
293
+ this.onTabClick(newId);
294
+ this.$refs.link[newIndex].focus();
207
295
  }
296
+ },
297
+ move(id, next) {
298
+ let newIndex;
299
+ let newId;
208
300
 
209
- if (newIndex < this.tabs.length - 1) {
210
- newIndex++;
211
- } else {
212
- newIndex = 0;
301
+ newIndex = this.tabs.findIndex(tab => tab.uid === id);
302
+
303
+ if (newIndex > -1) {
304
+ newIndex = next ? newIndex + 1 : newIndex - 1;
305
+ }
306
+ if (newIndex < 0 || newIndex >= this.tabs.length) {
307
+ newIndex = next ? 0 : this.tabs.length - 1;
213
308
  }
214
- newId = this.tabs[newIndex].uid;
215
309
 
216
- while (this.disabledTabs.indexOf(newIndex) !== -1) {
217
- if (newIndex < this.tabs.length - 1) {
218
- newIndex++;
219
- } else {
220
- newIndex = 0;
310
+ newId = this.tabs[newIndex].uid;
311
+ while (newId === this.selectedId || this.disabledTabs.indexOf(newId) !== -1) {
312
+ if (newIndex > -1) {
313
+ newIndex = next ? newIndex + 1 : newIndex - 1;
314
+ }
315
+ if (newIndex < 0 || newIndex >= this.tabs.length) {
316
+ newIndex = next ? 0 : this.tabs.length - 1;
221
317
  }
222
318
  newId = this.tabs[newIndex].uid;
223
319
  }
224
320
 
225
- this.onTabClick(newId);
226
- this.$refs.link[newIndex].focus();
321
+ return newIndex;
322
+ },
323
+ onRight() {
324
+ if (this.isAllTabsDisabled()) {
325
+ return;
326
+ }
327
+
328
+ const displayProp = window.getComputedStyle(this.$refs.trigger).getPropertyValue('display');
329
+ if (displayProp === 'none') {
330
+ const newIndex = this.move(this.selectedId, true);
331
+ const newId = this.tabs[newIndex].uid;
332
+
333
+ this.onTabClick(newId);
334
+ this.$refs.link[newIndex].focus();
335
+ }
227
336
  },
228
337
  selected(index) {
229
338
  let selItem = this.tabs[index ? index : -1];
@@ -46,6 +46,18 @@ export default {
46
46
  label: String,
47
47
  value: String,
48
48
  },
49
+ data() {
50
+ return {
51
+ isHelper: false,
52
+ isInvalid: false,
53
+ };
54
+ },
55
+ mounted() {
56
+ this.checkSlots();
57
+ },
58
+ beforeUpdate() {
59
+ this.checkSlots();
60
+ },
49
61
  computed: {
50
62
  // Bind listeners at the component level to the embedded input element and
51
63
  // add our own input listener to service the v-model. See:
@@ -56,11 +68,12 @@ export default {
56
68
  input: event => this.$emit('input', event.target.value),
57
69
  };
58
70
  },
59
- isInvalid() {
60
- return this.$slots['invalid-message'] !== undefined || (this.invalidMessage && this.invalidMessage.length);
61
- },
62
- isHelper() {
63
- return this.$slots['helper-text'] || (this.helperText && this.helperText.length);
71
+ },
72
+ methods: {
73
+ checkSlots() {
74
+ // NOTE: this.$slots is not reactive so needs to be managed on beforeUpdate
75
+ this.isInvalid = !!(this.$slots['invalid-message'] || (this.invalidMessage && this.invalidMessage.length));
76
+ this.isHelper = !!(this.$slots['helper-text'] || (this.helperText && this.helperText.length));
64
77
  },
65
78
  },
66
79
  };
@@ -70,8 +70,16 @@ export default {
70
70
  return {
71
71
  dataPasswordVisible: this.isPassword && this.passwordVisible,
72
72
  dataType: this.type,
73
+ isHelper: false,
74
+ isInvalid: false,
73
75
  };
74
76
  },
77
+ mounted: function() {
78
+ this.checkSlots();
79
+ },
80
+ beforeUpdate() {
81
+ this.checkSlots();
82
+ },
75
83
  watch: {
76
84
  passwordVisible() {
77
85
  if (this.passwordVisible !== this.dataPasswordVisible) {
@@ -92,12 +100,6 @@ export default {
92
100
  input: event => this.$emit('input', event.target.value),
93
101
  };
94
102
  },
95
- isInvalid() {
96
- return this.$slots['invalid-message'] !== undefined || (this.invalidMessage && this.invalidMessage.length);
97
- },
98
- isHelper() {
99
- return this.$slots['helper-text'] || (this.helperText && this.helperText.length);
100
- },
101
103
  isPassword() {
102
104
  return this.type === 'password';
103
105
  },
@@ -109,6 +111,11 @@ export default {
109
111
  },
110
112
  },
111
113
  methods: {
114
+ checkSlots() {
115
+ // NOTE: this.$slots is not reactive so needs to be managed on beforeUpdate
116
+ this.isInvalid = !!(this.$slots['invalid-message'] || (this.invalidMessage && this.invalidMessage.length));
117
+ this.isHelper = !!(this.$slots['helper-text'] || (this.helperText && this.helperText.length));
118
+ },
112
119
  togglePasswordVisibility() {
113
120
  const currentValue = this.$refs.input.value;
114
121
  this.dataPasswordVisible = !this.dataPasswordVisible;
@@ -80,13 +80,18 @@ export default {
80
80
  timezones: { type: Array, default: () => [] },
81
81
  timezonesSelectLabel: { type: String, default: 'Select time zone' },
82
82
  },
83
+ data() {
84
+ return {
85
+ isInvalid: false,
86
+ };
87
+ },
88
+ mounted() {
89
+ this.checkSlots();
90
+ },
91
+ beforeUpdate() {
92
+ this.checkSlots();
93
+ },
83
94
  computed: {
84
- isInvalid() {
85
- return (
86
- (this.$slots['invalid-message'] && this.$slots['invalid-message'].length) ||
87
- (this.invalidMessage && this.invalidMessage.length > 0)
88
- );
89
- },
90
95
  validAmpm() {
91
96
  let result = this.ampm;
92
97
  if (!['AM', 'PM'].includes(this.ampm)) {
@@ -111,5 +116,11 @@ export default {
111
116
  return result;
112
117
  },
113
118
  },
119
+ methods: {
120
+ checkSlots() {
121
+ // NOTE: this.$slots is not reactive so needs to be managed on beforeUpdate
122
+ this.isInvalid = !!(this.$slots['invalid-message'] || (this.invalidMessage && this.invalidMessage.length));
123
+ },
124
+ },
114
125
  };
115
126
  </script>
@@ -0,0 +1,21 @@
1
+ <template>
2
+ <div class="cv-toggle bx--form-item">
3
+ <label class="bx--toggle__label bx--skeleton" :for="uid" :aria-label="label ? 'Toggle is loading' : label">
4
+ <span class="bx--toggle__label-text" />
5
+ <span class="bx--toggle__text--left" />
6
+ <span class="bx--toggle__appearance" />
7
+ <span class="bx--toggle__text--right" />
8
+ </label>
9
+ </div>
10
+ </template>
11
+
12
+ <script>
13
+ import uidMixin from '../../mixins/uid-mixin';
14
+ export default {
15
+ name: 'CvToggleSkeleton',
16
+ mixins: [uidMixin],
17
+ props: {
18
+ label: { type: String, default: 'Toggle is loading' },
19
+ },
20
+ };
21
+ </script>
@@ -1,2 +1,3 @@
1
1
  import CvToggle from './cv-toggle';
2
- export { CvToggle };
2
+ import CvToggleSkeleton from './cv-toggle-skeleton';
3
+ export { CvToggle, CvToggleSkeleton };
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <header class="cv-header bx--header" role="banner" data-header>
3
3
  <slot />
4
- <div v-if="$slots['header-global']" class="bx--header__global">
4
+ <div v-if="hasGlobalHeader" class="bx--header__global">
5
5
  <slot name="header-global" />
6
6
  </div>
7
7
  <slot name="left-panels" />
@@ -26,8 +26,16 @@ export default {
26
26
  return {
27
27
  panelControllers: [],
28
28
  panels: [],
29
+ hasGlobalHeader: false,
29
30
  };
30
31
  },
32
+ mounted() {
33
+ // NOTE: this.$slots is not reactive so needs to be managed on beforeUpdate
34
+ this.hasGlobalHeader = !!this.$slots['header-global'];
35
+ },
36
+ beforeUpdate() {
37
+ this.hasGlobalHeader = !!this.$slots['header-global'];
38
+ },
31
39
  computed: {
32
40
  isCvHeader() {
33
41
  return true;
@@ -7,7 +7,7 @@
7
7
  v-bind="{ ...$attrs, ...linkProps }"
8
8
  :class="{ 'bx--side-nav__link--current': active }"
9
9
  >
10
- <cv-side-nav-icon v-if="$slots['nav-icon'] !== undefined" small>
10
+ <cv-side-nav-icon v-if="hasNavIcon" small>
11
11
  <slot name="nav-icon" />
12
12
  </cv-side-nav-icon>
13
13
  <cv-side-nav-link-text>
@@ -31,5 +31,13 @@ export default {
31
31
  active: Boolean,
32
32
  icon: Object,
33
33
  },
34
+ data() {
35
+ return {
36
+ hasNavIcon: this.$slots['nav-icon'],
37
+ };
38
+ },
39
+ beforeUpdate() {
40
+ this.hasNavIcon = !!this.$slots['nav-icon'];
41
+ },
34
42
  };
35
43
  </script>
@@ -46,11 +46,19 @@ export default {
46
46
  data() {
47
47
  return {
48
48
  expanded: false,
49
+ hasNavIcon: false,
49
50
  };
50
51
  },
52
+ mounted() {
53
+ // NOTE: this.$slots is not reactive so needs to be managed on beforeUpdate
54
+ this.hasNavIcon = !!this.$slots['nav-icon'];
55
+ },
56
+ beforeUpdate() {
57
+ this.hasNavIcon = !!this.$slots['nav-icon'];
58
+ },
51
59
  computed: {
52
60
  hasIcon() {
53
- return this.$slots['nav-icon'] !== undefined;
61
+ return !!this.$slots['nav-icon'];
54
62
  },
55
63
  },
56
64
  methods: {
package/src/index.js CHANGED
@@ -73,7 +73,7 @@ import { CvTextInput } from './components/cv-text-input';
73
73
  import { CvTile } from './components/cv-tile';
74
74
  import { CvTimePicker } from './components/cv-time-picker';
75
75
  import { CvToastNotification } from './components/cv-toast-notification';
76
- import { CvToggle } from './components/cv-toggle';
76
+ import { CvToggle, CvToggleSkeleton } from './components/cv-toggle';
77
77
  import { CvToolbar, CvToolbarTitle, CvToolbarSearch, CvToolbarOption, CvToolbarDivider } from './components/cv-toolbar';
78
78
  import { CvTooltip, CvInteractiveTooltip, CvDefinitionTooltip } from './components/cv-tooltip';
79
79
  import {
@@ -135,7 +135,7 @@ export { CvTextInput };
135
135
  export { CvTile };
136
136
  export { CvTimePicker };
137
137
  export { CvToastNotification };
138
- export { CvToggle };
138
+ export { CvToggle, CvToggleSkeleton };
139
139
  export { CvToolbar, CvToolbarTitle, CvToolbarSearch, CvToolbarOption, CvToolbarDivider };
140
140
  export { CvTooltip, CvInteractiveTooltip, CvDefinitionTooltip };
141
141
  export {