@carbon/vue 2.6.0 → 2.8.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,7 +1,7 @@
1
1
  {
2
2
  "name": "@carbon/vue",
3
3
  "description": "A collection of components for the Carbon Design System built using Vue.js",
4
- "version": "2.6.0",
4
+ "version": "2.8.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -63,13 +63,13 @@
63
63
  "sideEffects": true,
64
64
  "dependencies": {
65
65
  "@carbon/icons-vue": "10.3.0",
66
- "carbon-components": "10.3.0",
66
+ "carbon-components": "10.3.1",
67
67
  "flatpickr": "4.6.1",
68
68
  "vue": "^2.6.10"
69
69
  },
70
70
  "devDependencies": {
71
- "@vue/cli-service": "^3.8.0",
71
+ "@vue/cli-service": "^3.8.4",
72
72
  "vue-template-compiler": "^2.6.10"
73
73
  },
74
- "gitHead": "fd2c0a1e5b4a76f7ecd727bdad260f7dddbedcb4"
74
+ "gitHead": "7bb957622b2f4a4a5a3458c19daec5d11283509c"
75
75
  }
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="cv-checkbox bx--form-item bx--checkbox-wrapper">
2
+ <div class="cv-checkbox bx--checkbox-wrapper" :class="{ 'bx--form-item': formItem }">
3
3
  <label
4
4
  :class="[
5
5
  'bx--checkbox-label',
@@ -38,6 +38,7 @@ export default {
38
38
  props: {
39
39
  label: String,
40
40
  mixed: Boolean,
41
+ formItem: { type: Boolean, default: true },
41
42
  },
42
43
  watch: {
43
44
  mixed() {
@@ -23,8 +23,11 @@
23
23
  </div>
24
24
  <div class="bx--batch-summary">
25
25
  <p class="bx--batch-summary__para">
26
- <span data-items-selected>{{ dataRowsSelected.length }}</span>
27
- items selected
26
+ <span data-items-selected>
27
+ <slot name="items-selected" v-bind:scope="{ count: dataRowsSelected.length }"
28
+ >{{ dataRowsSelected.length }} items selected</slot
29
+ >
30
+ </span>
28
31
  </p>
29
32
  </div>
30
33
  </div>
@@ -18,9 +18,7 @@
18
18
  <template>
19
19
  <div :class="{ 'bx--form-item': formItem }">
20
20
  <div class="bx--dropdown__wrapper" :class="{ 'bx--dropdown__wrapper--inline': inline, 'cv-dropdown': !formItem }">
21
- <label v-if="label" :for="uid" class="bx--label" :class="{ 'bx--label--disabled': $attrs.disabled }">
22
- {{ label }}
23
- </label>
21
+ <label v-if="label" class="bx--label" :class="{ 'bx--label--disabled': $attrs.disabled }">{{ label }}</label>
24
22
 
25
23
  <div
26
24
  v-if="!inline && isHelper"
@@ -80,7 +78,6 @@
80
78
  </template>
81
79
 
82
80
  <script>
83
- import uidMixin from '../../mixins/uid-mixin';
84
81
  import themeMixin from '../../mixins/theme-mixin';
85
82
  import WarningFilled16 from '@carbon/icons-vue/es/warning--filled/16';
86
83
  import ChevronDown16 from '@carbon/icons-vue/es/chevron--down/16';
@@ -88,7 +85,7 @@ import ChevronDown16 from '@carbon/icons-vue/es/chevron--down/16';
88
85
  export default {
89
86
  name: 'CvDropdownInner',
90
87
  inheritAttrs: false,
91
- mixins: [uidMixin, themeMixin],
88
+ mixins: [themeMixin],
92
89
  components: { WarningFilled16, ChevronDown16 },
93
90
  props: {
94
91
  formItem: { type: Boolean, default: true },
@@ -0,0 +1,290 @@
1
+ <template>
2
+ <div
3
+ class="cv-multi-select bx--multi-select__wrapper bx--list-box__wrapper"
4
+ :class="{
5
+ 'bx--multi-select__wrapper--inline bx--list-box__wrapper--inline': inline,
6
+ 'bx--multi-select__wrapper--inline--invalid bx--list-box__wrapper--inline--invalid': inline && isInvalid,
7
+ }"
8
+ @focusout="onFocusOut"
9
+ >
10
+ <label v-if="title" :for="uid" class="bx--label" :class="{ 'bx--label--disabled': $attrs.disabled }">
11
+ {{ title }}
12
+ </label>
13
+
14
+ <div
15
+ v-if="!inline && isHelper"
16
+ class="bx--form__helper-text"
17
+ :class="{ 'bx--form__helper-text--disabled': $attrs.disabled }"
18
+ >
19
+ <slot name="helper-text">{{ helperText }}</slot>
20
+ </div>
21
+
22
+ <div
23
+ role="listbox"
24
+ tabindex="-1"
25
+ class="bx--multi-select bx--list-box"
26
+ :class="{
27
+ 'bx--list-box--light': theme === 'light',
28
+ 'bx--multi-select--expanded': open,
29
+ 'bx--multi-select--invalid': isInvalid,
30
+ 'bx--multi-select--disabled bx--list-box--disabled': $attrs.disabled,
31
+ 'bx--multi-select--inline bx--list-box--inline': inline,
32
+ }"
33
+ :data-invalid="isInvalid"
34
+ v-bind="$attrs"
35
+ @keydown.down.prevent="onDown"
36
+ @keydown.up.prevent="onUp"
37
+ @keydown.enter.prevent="onClick"
38
+ @keydown.space.prevent="onSpace"
39
+ @keydown.esc.prevent="onEsc"
40
+ @click="onClick"
41
+ >
42
+ <WarningFilled16 v-if="isInvalid" class="bx--list-box__invalid-icon" />
43
+ <div
44
+ role="button"
45
+ aria-haspopup="true"
46
+ :aria-expanded="open"
47
+ :aria-owns="uid"
48
+ :aria-controls="uid"
49
+ class="bx--list-box__field"
50
+ tabindex="0"
51
+ type="button"
52
+ :aria-label="open ? 'close menu' : 'open menu'"
53
+ data-toggle="true"
54
+ ref="button"
55
+ >
56
+ <cv-tag
57
+ v-show="dataValue.length > 0"
58
+ kind="filter"
59
+ :label="`${dataValue.length}`"
60
+ @remove="clearValues"
61
+ ref="tag"
62
+ />
63
+ <span class="bx--list-box__label">{{ label }}</span>
64
+ <div class="bx--list-box__menu-icon" :class="{ 'bx--list-box__menu-icon--open': open }" role="button">
65
+ <chevron-down-16 :aria-label="open ? 'Close menu' : 'Open menu'" />
66
+ </div>
67
+ </div>
68
+
69
+ <div v-show="open" :id="uid" class="bx--list-box__menu" role="listbox" ref="list">
70
+ <div
71
+ v-for="(item, index) in dataOptions"
72
+ :key="`multi-select-${index}`"
73
+ class="bx--list-box__menu-item"
74
+ :class="{ 'bx--list-box__menu-item--highlighted': highlighted === item.value }"
75
+ ref="option"
76
+ @click.stop.prevent="onItemClick(item.value)"
77
+ @mousemove="onMousemove(item.value)"
78
+ @mousedown.prevent
79
+ >
80
+ <div class="bx--list-box__menu-item__option">
81
+ <cv-checkbox
82
+ tabindex="-1"
83
+ :form-item="false"
84
+ v-model="dataValue"
85
+ :value="item.value"
86
+ :name="item.name"
87
+ :data-test="item.name"
88
+ :label="item.label"
89
+ style="pointer-events: none;"
90
+ />
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ <div v-if="isInvalid && !inline" class="bx--form-requirement">
96
+ <slot name="invalid-message">{{ invalidMessage }}</slot>
97
+ </div>
98
+ </div>
99
+ </template>
100
+
101
+ <script>
102
+ import themeMixin from '../../mixins/theme-mixin';
103
+ import WarningFilled16 from '@carbon/icons-vue/es/warning--filled/16';
104
+ import ChevronDown16 from '@carbon/icons-vue/es/chevron--down/16';
105
+ import uidMixin from '../../mixins/uid-mixin';
106
+ import CvCheckbox from '../cv-checkbox/cv-checkbox';
107
+ import CvTag from '../cv-tag/cv-tag';
108
+
109
+ const TOP_AFTER_REOPEN = 0;
110
+ const TOP = 1;
111
+ const FIXED = 2;
112
+ const selectionFeedbackOptions = ['top-after-reopen', 'top', 'fixed'];
113
+
114
+ export default {
115
+ name: 'CvMultiSelect',
116
+ inheritAttrs: false,
117
+ mixins: [themeMixin, uidMixin],
118
+ components: { WarningFilled16, ChevronDown16, CvCheckbox, CvTag },
119
+ props: {
120
+ inline: Boolean,
121
+ invalidMessage: { type: String, default: null },
122
+ helperText: { type: String, default: null },
123
+ title: String,
124
+ label: {
125
+ type: String,
126
+ default: 'Choose options',
127
+ },
128
+ value: { type: Array, default: () => [] },
129
+ // initial value of the multi-select,
130
+ // options in the form
131
+ // [{ label: '', value: '', name: ''}]
132
+ options: {
133
+ type: Array,
134
+ required: true,
135
+ validator(list) {
136
+ const result = list.every(
137
+ item => typeof item.name === 'string' && typeof item.label === 'string' && typeof item.value === 'string'
138
+ );
139
+ if (!result) {
140
+ console.warn('CvMultiSelect - all options must have name, label and value');
141
+ }
142
+ return result;
143
+ },
144
+ },
145
+ selectionFeedback: {
146
+ type: String,
147
+ default: selectionFeedbackOptions[TOP_AFTER_REOPEN],
148
+ validator(val) {
149
+ if (!selectionFeedbackOptions.includes(val)) {
150
+ console.warn(`CvMultiSelect: invalid value "${val}", use one of ${selectionFeedbackOptions}`);
151
+ return false;
152
+ }
153
+ return true;
154
+ },
155
+ },
156
+ },
157
+ data() {
158
+ return {
159
+ open: false,
160
+ dataOptions: null,
161
+ dataValue: this.value,
162
+ highlighted: '',
163
+ };
164
+ },
165
+ model: {
166
+ prop: 'value',
167
+ event: 'change',
168
+ },
169
+ watch: {
170
+ value() {
171
+ this.dataValue = this.value;
172
+ },
173
+ options() {
174
+ this.sortOptions();
175
+ },
176
+ selectionFeedback() {
177
+ this.sortOptions();
178
+ },
179
+ },
180
+ created() {
181
+ this.sortOptions();
182
+ },
183
+ computed: {
184
+ isInvalid() {
185
+ return this.$slots['invalid-message'] !== undefined || (this.invalidMessage && this.invalidMessage.length);
186
+ },
187
+ isHelper() {
188
+ return this.$slots['helper-text'] !== undefined || (this.helperText && this.helperText.length);
189
+ },
190
+ },
191
+ methods: {
192
+ doMove(up) {
193
+ // requery could have changed
194
+ const currentHighlight = this.dataOptions.findIndex(item => item.value === this.highlighted);
195
+ let newHiglight;
196
+
197
+ if (up) {
198
+ if (currentHighlight <= 0) {
199
+ newHiglight = this.dataOptions.length - 1;
200
+ } else {
201
+ newHiglight = currentHighlight - 1;
202
+ }
203
+ } else {
204
+ if (currentHighlight >= this.dataOptions.length - 1) {
205
+ newHiglight = 0;
206
+ } else {
207
+ newHiglight = currentHighlight + 1;
208
+ }
209
+ }
210
+ this.highlighted = this.dataOptions[newHiglight].value;
211
+ if (
212
+ this.$refs.list.scrollTop > this.$refs.option[newHiglight].offsetTop ||
213
+ this.$refs.list.scrollTop + this.$refs.list.clientHeight <
214
+ this.$refs.option[newHiglight].offsetTop + this.$refs.option[newHiglight].offsetHeight
215
+ ) {
216
+ this.$refs.option[newHiglight].scrollIntoView();
217
+ }
218
+ },
219
+ sortOptions() {
220
+ this.dataOptions = this.options.slice(0);
221
+
222
+ if (!this.sorting && this.selectionFeedback !== selectionFeedbackOptions[FIXED]) {
223
+ // if included in data value move to top
224
+ this.dataOptions.sort(
225
+ (a, b) => (this.dataValue.includes(a.value) ? -1 : 1) - (this.dataValue.includes(b.value) ? -1 : 1)
226
+ );
227
+ }
228
+ },
229
+ doOpen(newVal) {
230
+ if (newVal && this.selectionFeedback === selectionFeedbackOptions[TOP_AFTER_REOPEN]) {
231
+ this.sortOptions();
232
+ }
233
+ this.open = newVal;
234
+ },
235
+ onDown() {
236
+ if (!this.open) {
237
+ this.doOpen(true);
238
+ } else {
239
+ this.doMove(false);
240
+ }
241
+ },
242
+ onUp() {
243
+ if (this.open) {
244
+ this.doMove(true);
245
+ }
246
+ },
247
+ onEsc() {
248
+ this.doOpen(false);
249
+ this.$el.focus();
250
+ },
251
+ onSpace() {
252
+ this.onItemClick(this.highlighted);
253
+ },
254
+ onClick(ev) {
255
+ this.doOpen(!this.open);
256
+ if (!this.open) {
257
+ this.$refs.button.focus();
258
+ }
259
+ },
260
+ clearValues() {
261
+ this.dataValue = [];
262
+ this.$refs.button.focus();
263
+ this.$emit('change', this.dataValue);
264
+ },
265
+ onFocusOut(ev) {
266
+ if (!this.$el.contains(ev.relatedTarget) && !this.$refs.tag.$el.contains(ev.target)) {
267
+ this.doOpen(false);
268
+ }
269
+ },
270
+ onMousemove(val) {
271
+ this.highlighted = val;
272
+ },
273
+ onItemClick(val) {
274
+ const index = this.dataValue.findIndex(item => val === item);
275
+ if (index > -1) {
276
+ this.dataValue.splice(index, 1);
277
+ } else {
278
+ this.dataValue.push(val);
279
+ }
280
+ if (this.selectionFeedback === selectionFeedbackOptions[TOP]) {
281
+ this.sortOptions();
282
+ }
283
+ this.$refs.button.focus();
284
+ this.$emit('change', this.dataValue);
285
+ },
286
+ },
287
+ };
288
+ </script>
289
+
290
+ <style lang="scss"></style>
@@ -18,7 +18,9 @@
18
18
  </cv-select>
19
19
 
20
20
  <span class="bx--pagination__text">
21
- <span data-displayed-item-range>{{ rangeText }}</span>
21
+ <span data-displayed-item-range>
22
+ <slot name="range-text" v-bind:scope="rangeProps">{{ rangeText }}</slot>
23
+ </span>
22
24
  </span>
23
25
  </div>
24
26
 
@@ -41,7 +43,9 @@
41
43
  >{{ pageNumber }}</cv-select-option
42
44
  >
43
45
  </cv-select>
44
- <span class="bx--pagination__text">{{ pageOfPages }}</span>
46
+ <span class="bx--pagination__text">
47
+ <slot name="of-n-pages" v-bind:scope="ofNPagesProps">{{ pageOfPages }}</slot>
48
+ </span>
45
49
 
46
50
  <span v-if="pages.length == 0">{{ pageValue }}</span>
47
51
 
@@ -174,25 +178,38 @@ export default {
174
178
  noWayForward() {
175
179
  return this.pageValue === this.pageCount;
176
180
  },
181
+ ofNPagesProps() {
182
+ return {
183
+ pages: this.pageCount,
184
+ items: this.numberOfItems,
185
+ };
186
+ },
177
187
  pageOfPages() {
188
+ const { pages, items } = this.ofNPagesProps;
178
189
  // console.log(this.pageValue, this.pageCount);
179
- if (this.numberOfItems !== Infinity) {
180
- return `of ${this.pageCount} pages`;
190
+ if (items !== Infinity) {
191
+ return `of ${pages} pages`;
181
192
  }
182
193
  return '';
183
194
  },
195
+ rangeProps() {
196
+ return {
197
+ start: this.firstItem,
198
+ end: Math.min(this.firstItem + parseInt(this.pageSizeValue, 10) - 1, this.numberOfItems),
199
+ items: this.numberOfItems,
200
+ };
201
+ },
184
202
  rangeText() {
185
- const start = this.firstItem;
186
- const end = Math.min(start + parseInt(this.pageSizeValue, 10) - 1, this.numberOfItems);
203
+ const { start, end, items } = this.rangeProps;
187
204
 
188
- if (this.numberOfItems !== Infinity) {
189
- return `${start}-${end} of ${this.numberOfItems} items`;
205
+ if (items !== Infinity) {
206
+ return `${start}-${end} of ${items} items`;
190
207
  } else {
191
208
  return `${start}-${end}`;
192
209
  }
193
210
  },
194
211
  internalValue() {
195
- return { start: this.firstItem, length: parseInt(this.pageSizeValue) };
212
+ return { start: this.firstItem, page: this.pageValue, length: parseInt(this.pageSizeValue) };
196
213
  },
197
214
  },
198
215
  methods: {
@@ -14,9 +14,9 @@
14
14
  <Close16
15
15
  v-if="isFilter"
16
16
  aria-label="Clear filter"
17
- @click="$emit('remove')"
17
+ @click.stop.prevent="$emit('remove')"
18
18
  tabindex="0"
19
- @keydown.enter.prevent="$emit('remove')"
19
+ @keydown.enter.stop.prevent="$emit('remove')"
20
20
  @keydown.space.prevent
21
21
  @keyup.space.prevent="$emit('remove')"
22
22
  />
@@ -26,27 +26,16 @@
26
26
  :type="dataType"
27
27
  ref="input"
28
28
  />
29
+ <button
30
+ v-if="isPassword"
31
+ class="bx--text-input--password__visibility"
32
+ :aria-label="isPasswordVisible ? 'Hide password' : 'Show password'"
33
+ @click="togglePasswordVisibility"
34
+ >
35
+ <ViewOff16 v-if="isPasswordVisible" class="bx--icon-visibility-off" />
36
+ <View16 v-else class="bx--icon-visibility-off" />
37
+ </button>
29
38
  </div>
30
- <button
31
- v-if="isPassword"
32
- class="bx--text-input--password__visibility bx--tooltip__trigger bx--tooltip--icon__bottom"
33
- :aria-label="isPasswordVisible ? 'Hide password' : 'Show password'"
34
- @click="togglePasswordVisibility"
35
- >
36
- <svg v-if="isPasswordVisible" class="icon--visibility-off" width="16" height="16" viewBox="0 0 16 16">
37
- <path
38
- d="M11.846 3.45L15.293.007 16 .714l-3.284 3.281c1.261.902 2.377 2.212 3.347 3.93C14.02 11.642 11.333 13.5 8 13.5c-1.392 0-2.667-.324-3.822-.973L.703 16l-.706-.708 3.323-3.32C2.071 11.042.976 9.694.035 7.924 2.012 4.308 4.667 2.5 8 2.5c1.395 0 2.677.317 3.846.95zm-6.928 8.338c.944.477 1.97.712 3.082.712 2.795 0 5.076-1.483 6.907-4.568-.866-1.417-1.833-2.486-2.91-3.219l-1.55 1.55a3 3 0 0 1-4.185 4.182l-1.344 1.343zm-.882-.533l1.518-1.517A3 3 0 0 1 9.74 5.556l1.364-1.363A7.02 7.02 0 0 0 8 3.5c-2.798 0-5.047 1.439-6.819 4.432.842 1.465 1.792 2.568 2.855 3.323zm2.948-1.532a2 2 0 0 0 2.74-2.738l-2.74 2.738zm-.707-.707l2.74-2.738a2 2 0 0 0-2.74 2.738z"
39
- ></path>
40
- </svg>
41
- <svg v-else class="icon--visibility-on" width="16" height="16" viewBox="0 0 16 11">
42
- <path
43
- d="M8 7.5c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 1c-1.7 0-3-1.3-3-3s1.3-3 3-3 3 1.3 3 3-1.3 3-3 3z"
44
- ></path>
45
- <path
46
- d="M8 10c2.8 0 5.1-1.5 6.9-4.6C13.1 2.5 10.8 1 8 1 5.2 1 3 2.4 1.2 5.4 2.9 8.6 5.2 10 8 10zM8 0c3.3 0 6 1.8 8.1 5.4C14 9.2 11.3 11 8 11S2 9.2 0 5.5C2 1.9 4.6 0 8 0z"
47
- ></path>
48
- </svg>
49
- </button>
50
39
  <div class="bx--form-requirement" v-if="isInvalid">
51
40
  <slot name="invalid-message">{{ invalidMessage }}</slot>
52
41
  </div>
@@ -57,10 +46,12 @@
57
46
  import uidMixin from '../../mixins/uid-mixin';
58
47
  import themeMixin from '../../mixins/theme-mixin';
59
48
  import WarningFilled16 from '@carbon/icons-vue/es/warning--filled/16';
49
+ import View16 from '@carbon/icons-vue/es/view/16';
50
+ import ViewOff16 from '@carbon/icons-vue/es/view--off/16';
60
51
 
61
52
  export default {
62
53
  name: 'CvTextInput',
63
- components: { WarningFilled16 },
54
+ components: { WarningFilled16, View16, ViewOff16 },
64
55
  mixins: [uidMixin, themeMixin],
65
56
  inheritAttrs: false,
66
57
  props: {
@@ -10,8 +10,8 @@
10
10
  @click="gaToggle"
11
11
  @focusout="gaFocusout"
12
12
  >
13
- <Close20 v-if="active" />
14
- <Menu20 v-if="!active" />
13
+ <Close20 v-if="dataActive" />
14
+ <Menu20 v-if="!dataActive" />
15
15
  </button>
16
16
  </template>
17
17
 
@@ -43,7 +43,7 @@ export default {
43
43
  // watch width transition and
44
44
  },
45
45
  watch: {
46
- expanded() {
46
+ active() {
47
47
  if (this.active !== this.dataActive) {
48
48
  this.gaToggle();
49
49
  }
@@ -13,7 +13,6 @@
13
13
  @keydown.space.prevent
14
14
  @keyup.space.prevent="doToggle"
15
15
  @keydown.enter.prevent="doToggle"
16
- @focusout="onFocusout"
17
16
  >
18
17
  <cv-side-nav-icon v-if="hasIcon">
19
18
  <slot name="nav-icon" />
@@ -24,10 +23,10 @@
24
23
  small
25
24
  :aria-label="expanded ? 'collapse nav menu' : 'expand nav menu'"
26
25
  >
27
- <ChevronDown20 />
26
+ <ChevronDown20 class="cv-side-nav-menu__chevron-svg" />
28
27
  </cv-side-nav-icon>
29
28
  </button>
30
- <ul class="bx--side-nav__menu" role="menu" ref="menu" @focusout="onFocusout">
29
+ <ul class="bx--side-nav__menu" role="menu" ref="menu">
31
30
  <slot></slot>
32
31
  </ul>
33
32
  </li>
@@ -58,13 +57,13 @@ export default {
58
57
  doToggle() {
59
58
  this.expanded = !this.expanded;
60
59
  },
61
- onFocusout(ev) {
62
- if (!(this.$el.contains(ev.relatedTarget) || this.$refs.menu.contains(ev.relatedTarget))) {
63
- this.expanded = false;
64
- }
65
- },
66
60
  },
67
61
  };
68
62
  </script>
69
63
 
70
- <style lang="scss"></style>
64
+ <style lang="scss">
65
+ .cv-side-nav-menu .cv-side-nav-menu__chevron-svg {
66
+ // prevent safari issue
67
+ will-change: initial !important;
68
+ }
69
+ </style>