@mozaic-ds/vue 0.19.1 → 0.20.0-beta.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": "@mozaic-ds/vue",
3
- "version": "0.19.1",
3
+ "version": "0.20.0-beta.0",
4
4
  "description": "Vue.js implementation of Mozaic Design System",
5
5
  "author": "Adeo - Mozaic Design System",
6
6
  "scripts": {
@@ -27,6 +27,9 @@
27
27
  "@mozaic-ds/icons": "1.34.0",
28
28
  "@mozaic-ds/styles": "1.33.0",
29
29
  "@mozaic-ds/web-fonts": "1.22.0",
30
+ "@vue/composition-api": "^1.6.2",
31
+ "@vueuse/components": "^8.6.0",
32
+ "@vueuse/core": "^8.6.0",
30
33
  "core-js": "^3.18.3",
31
34
  "libphonenumber-js": "1.9.50",
32
35
  "postcss-scss": "^4.0.1",
@@ -3,37 +3,41 @@
3
3
  ref="autocomplete"
4
4
  class="mc-autocomplete"
5
5
  :class="{ 'mc-autocomplete--multi': multiple }"
6
- :style="tagStyle"
7
- @keyup.esc="isOpen = true"
8
6
  >
9
7
  <m-tag
10
- v-if="multiple && selectedItems().length > 0"
8
+ v-if="multiple && modelValue.length > 0"
11
9
  id="tag"
12
10
  type="removable"
13
- :label="selectedItems().length.toString() + ' ' + labelTag"
11
+ :label="selectedItems.length.toString() + ' ' + labelTag"
14
12
  class="mc-autocomplete__tag"
15
13
  size="s"
16
14
  @remove-tag="removeElementsFromList()"
17
15
  />
18
16
  <m-text-input
19
- v-model="itemDisplayed"
17
+ v-model="state.itemDisplayed"
20
18
  :placeholder="placeholder"
21
- text-input-field-class="mc-autocomplete__trigger"
19
+ class="mc-autocomplete__trigger"
22
20
  icon-position="left"
23
21
  icon="DisplaySearch48"
24
22
  autocomplete="off"
25
23
  :style="{ width: boxWidth + 'px' }"
26
- @input="filerList"
27
- @click="isOpen = true"
24
+ @update:modelValue="updateDropdown"
25
+ @change="$emit('input-change', $event.target.value)"
26
+ @click="state.open = true"
28
27
  />
29
28
  <m-list-box
30
- :open="isOpen"
31
- :items="sort ? orderedItems() : itemListForDropdown"
29
+ ref="listbox"
30
+ v-model="state.selected"
31
+ :open="state.open"
32
+ :items="sort ? orderedItems() : state.itemListForDropdown"
32
33
  :multiple="multiple"
33
34
  :empty-search-label="emptySearchLabel"
34
35
  :style="{ width: boxWidth + 'px' }"
35
- @update:itemSelected="updateList"
36
- @close-list-box="isOpen = false"
36
+ :data-key-expr="dataKeyExpr"
37
+ :data-text-expr="dataTextExpr"
38
+ :data-value-expr="dataValueExpr"
39
+ @update:modelValue="(selected) => updateList(selected)"
40
+ @close-list-box="state.open = false"
37
41
  >
38
42
  <template #item="{ item }">
39
43
  <slot name="item" :item="item"> </slot>
@@ -43,14 +47,31 @@
43
47
  </template>
44
48
 
45
49
  <script>
46
- import MTextInput from '../textinput/MTextInput.vue';
47
- import MTag from '../tags/MTag.vue';
50
+ import Vue from 'vue';
51
+ import VueCompositionAPI from '@vue/composition-api';
52
+ Vue.use(VueCompositionAPI);
53
+
54
+ import {
55
+ ref,
56
+ reactive,
57
+ onMounted,
58
+ computed,
59
+ nextTick,
60
+ } from '@vue/composition-api';
61
+
62
+ import { onClickOutside } from '@vueuse/core';
48
63
  import MListBox from '../listbox/MListBox.vue';
64
+ import MTag from '../tags/MTag.vue';
65
+ import MTextInput from '../textinput/MTextInput.vue';
49
66
 
50
67
  export default {
51
68
  name: 'MAutocomplete',
52
69
 
53
- components: { MListBox, MTag, MTextInput },
70
+ components: {
71
+ MListBox,
72
+ MTag,
73
+ MTextInput,
74
+ },
54
75
 
55
76
  props: {
56
77
  multiple: {
@@ -82,54 +103,55 @@ export default {
82
103
  type: String,
83
104
  default: '',
84
105
  },
85
- open: {
86
- type: Boolean,
87
- default: false,
106
+ modelValue: {
107
+ type: Array,
108
+ default: () => [],
88
109
  },
89
- },
90
- data() {
91
- return {
92
- itemListForDropdown: this.$props.items,
93
- selected: this.$props.items,
94
- itemDisplayed: '',
95
- isOpen: this.$props.open,
96
- tagWidth: '0px',
97
- boxWidth: '288px',
98
- };
99
- },
100
- computed: {
101
- tagStyle() {
102
- return {
103
- '--tag-width': this.tagWidth,
104
- };
110
+ dataKeyExpr: {
111
+ type: String,
112
+ default: 'id',
105
113
  },
106
- boxStyle() {
107
- return {
108
- '--box-width': this.boxWidth,
109
- };
114
+ dataTextExpr: {
115
+ type: String,
116
+ default: 'text',
110
117
  },
111
- },
112
- mounted() {
113
- this.selectedItems();
114
- this.tagWidthCalcul();
115
- this.boxWidthCalcul();
116
- },
117
- methods: {
118
- tagWidthCalcul() {
119
- this.$nextTick(() => {
120
- this.tagWidth =
121
- document && document.querySelector('.mc-autocomplete__tag')
122
- ? document.querySelector('.mc-autocomplete__tag').clientWidth + 'px'
123
- : '0px';
124
- });
118
+ dataSelectedExpr: {
119
+ type: String,
120
+ default: 'selected',
125
121
  },
126
- selectedItems() {
127
- return this.selected.filter((item) => {
128
- return item.selected;
129
- });
122
+ dataValueExpr: {
123
+ type: String,
124
+ default: 'text',
130
125
  },
131
- orderedItems() {
132
- this.itemListForDropdown.sort((a, b) => {
126
+ },
127
+
128
+ emits: ['update:modelValue', 'list-removed', 'list-filtered', 'input-change'],
129
+
130
+ setup(props, { emit }) {
131
+ const autocomplete = ref(null);
132
+ const listbox = ref(null);
133
+
134
+ const state = reactive({
135
+ open: false,
136
+ itemListForDropdown: props.items,
137
+ selected: [],
138
+ itemDisplayed: '',
139
+ tagWidth: '0px',
140
+ });
141
+
142
+ onMounted(() => {
143
+ manageTag();
144
+ });
145
+
146
+ const selectedItems = computed(() =>
147
+ state.selected.filter((item) => {
148
+ return item[props.dataValueExpr];
149
+ })
150
+ );
151
+
152
+ const orderedItems = () => {
153
+ // Order by selected then by id
154
+ return Array.from(state.itemListForDropdown).sort((a, b) => {
133
155
  if (a.selected === b.selected) {
134
156
  return a.id - b.id;
135
157
  } else if (a.selected < b.selected) {
@@ -138,52 +160,67 @@ export default {
138
160
  return -1;
139
161
  }
140
162
  });
141
- },
142
- updateList(list) {
143
- if (!this.$props.multiple && list) {
144
- this.itemDisplayed = list[0].text;
163
+ };
164
+
165
+ const boxWidth = computed(() =>
166
+ autocomplete.value ? autocomplete.value.clientWidth : ''
167
+ );
168
+
169
+ onClickOutside(listbox, () => (state.open = false));
170
+
171
+ const updateList = (list) => {
172
+ if (!props.multiple && list) {
173
+ state.itemDisplayed = list[0].value;
145
174
  } else {
146
- this.isOpen = true;
147
- this.selectedItems();
175
+ state.open = true;
176
+ // manageTag();
148
177
  }
149
- this.itemListForDropdown.forEach((elem) => {
150
- if (elem.id === list.id) {
151
- elem.selected = false;
152
- }
153
- });
154
- this.tagWidthCalcul();
155
- this.$emit(
156
- 'update:modelValue',
157
- this.$props.multiple ? this.selectedItems().value : list
158
- );
159
- },
160
- removeElementsFromList() {
161
- this.itemListForDropdown.forEach((elem) => {
162
- elem.selected = false;
163
- });
164
- this.selectedItems();
165
- this.tagWidthCalcul();
166
- this.$emit('list-removed');
167
- },
168
- filerList(value) {
169
- if (value.length && this.$props.filter) {
170
- this.$props.filter(value);
178
+ state.selected = list;
179
+ emit('update:modelValue', list);
180
+ };
181
+
182
+ const removeElementsFromList = () => {
183
+ state.selected = [];
184
+ // manageTag();
185
+ emit('update:modelValue', state.selected);
186
+ emit('list-removed');
187
+ };
188
+
189
+ const updateDropdown = (value) => {
190
+ if (value.length && props.filter) {
191
+ props.filter(value);
171
192
  } else if (value.length) {
172
- this.itemListForDropdown = this.itemListForDropdown.filter((item) =>
173
- item.text.toUpperCase().includes(value.toUpperCase())
193
+ state.itemListForDropdown = props.items.filter((item) =>
194
+ item[props.dataTextExpr].toUpperCase().includes(value.toUpperCase())
174
195
  );
175
196
  } else {
176
- this.itemListForDropdown = this.$props.items;
197
+ state.itemListForDropdown = props.items;
177
198
  }
178
- this.$emit('list-filtered', this.itemListForDropdown);
179
- },
180
- boxWidthCalcul() {
181
- this.$nextTick(() => {
182
- this.boxWidth = document.querySelector('.mc-autocomplete').clientWidth;
183
- console.log(this.boxWidth);
199
+ emit('update:modelValue', state.selected);
200
+ emit('list-filtered', state.itemListForDropdown);
201
+ };
202
+
203
+ const manageTag = () => {
204
+ nextTick(() => {
205
+ state.tagWidth =
206
+ document && document.querySelector('.mc-autocomplete__tag')
207
+ ? document.querySelector('.mc-autocomplete__tag').clientWidth + 'px'
208
+ : '0px';
184
209
  });
185
- return;
186
- },
210
+ };
211
+
212
+ return {
213
+ autocomplete,
214
+ listbox,
215
+ state,
216
+ selectedItems,
217
+ orderedItems,
218
+ boxWidth,
219
+ updateList,
220
+ removeElementsFromList,
221
+ updateDropdown,
222
+ manageTag,
223
+ };
187
224
  },
188
225
  };
189
226
  </script>
@@ -1,41 +1,51 @@
1
1
  <template>
2
2
  <ul
3
- v-if="items.length > 0"
3
+ v-if="state.selectableItems.length > 0"
4
4
  ref="listbox"
5
5
  role="listbox"
6
- class="mc-listbox"
6
+ class="mc-listbox mc-listbox--multi"
7
7
  aria-labelledby="listbox"
8
- :class="{ 'is-open': open, 'mc-listbox--multi': multiple }"
8
+ :class="{ 'is-open': open }"
9
9
  >
10
10
  <li
11
- v-for="item in selectableItems"
12
- :key="item.id"
11
+ v-for="item in state.selectableItems"
12
+ :key="item[dataKeyExpr]"
13
13
  class="mc-listbox__item"
14
- @change="$emit('update:itemSelected', item)"
15
- @click.self="updateList(item.id, item.text, !item.selected, true)"
16
14
  >
17
15
  <slot name="item" :item="item">
18
- <span class="mc-listbox__text">{{ item.text }} </span>
16
+ <label
17
+ :for="`checkbox-dropdown-${item[dataKeyExpr]}-${uuid}`"
18
+ class="mc-listbox__label"
19
+ >{{ item[dataTextExpr] }}
20
+ </label>
19
21
  </slot>
20
22
  <m-checkbox
21
- v-if="multiple"
22
- :id="`checkbox-dropdown-${item.id}`"
23
- v-model="selectableItems.find((elem) => elem.id === item.id).selected"
23
+ :id="`checkbox-dropdown-${item[dataKeyExpr]}-${uuid}`"
24
+ :class="{ hideCheckbox: !multiple }"
24
25
  class="mc-listbox__input"
25
- @change="updateList(item.id, item.text, !item.selected, $e)"
26
+ :checked="updateModelValue(true, item)"
27
+ @update:modelValue="(v) => updateList(v, item[dataValueExpr])"
26
28
  />
27
29
  </li>
28
30
  </ul>
29
- <div v-else class="mc-list-box__empty">
31
+ <div v-else class="mc-listbox__empty">
30
32
  {{ emptySearchLabel }}
31
33
  </div>
32
34
  </template>
35
+
33
36
  <script>
37
+ import Vue from 'vue';
38
+ import VueCompositionAPI from '@vue/composition-api';
39
+ Vue.use(VueCompositionAPI);
40
+ import { reactive, watch, ref } from '@vue/composition-api';
34
41
  import MCheckbox from '../checkbox/MCheckbox.vue';
42
+
35
43
  export default {
36
44
  name: 'MListbox',
37
45
 
38
- components: { MCheckbox },
46
+ components: {
47
+ MCheckbox,
48
+ },
39
49
 
40
50
  props: {
41
51
  open: {
@@ -58,44 +68,80 @@ export default {
58
68
  type: Boolean,
59
69
  default: false,
60
70
  },
61
- },
62
- data() {
63
- return {
64
- selectableItems: null,
65
- selected: [],
66
- };
67
- },
68
- watch: {
69
- items: {
70
- handler: function (val) {
71
- this.selectableItems = val;
72
- },
73
- immediate: true,
71
+ dataKeyExpr: {
72
+ type: String,
73
+ default: 'id',
74
+ },
75
+ dataTextExpr: {
76
+ type: String,
77
+ default: 'text',
78
+ },
79
+ dataValueExpr: {
80
+ type: String,
81
+ default: 'text',
82
+ },
83
+ modelValue: {
84
+ type: Array,
85
+ default: () => [],
74
86
  },
75
87
  },
76
- methods: {
77
- updateList(id, text, value, isCheckboxUpdate) {
78
- if (!this.multiple) {
79
- this.$emit('update:itemSelected', [{ id, selected: value, text }]);
80
88
 
81
- this.$emit('close-list-box');
89
+ emits: ['update:itemSelected', 'close-list-box', 'update:modelValue'],
90
+
91
+ setup(props, { emit }) {
92
+ const listbox = ref(null);
93
+
94
+ let uuid = Math.random();
95
+
96
+ const state = reactive({
97
+ selectableItems: props.items,
98
+ selected: [],
99
+ });
100
+
101
+ const updateList = (checked, value) => {
102
+ if (!props.multiple) {
103
+ emit('update:modelValue', [{ value }]);
104
+ emit('close-list-box');
82
105
  return;
83
106
  }
84
107
 
85
- if (
86
- isCheckboxUpdate &&
87
- this.selectableItems.find((item) => item.id === id)
88
- ) {
89
- this.selectableItems.find((item) => item.id === id).selected = value;
108
+ if (checked) {
109
+ state.selected = [...state.selected, { [props.dataValueExpr]: value }];
110
+ } else {
111
+ state.selected = state.selected.filter(
112
+ (item) => item[props.dataValueExpr] !== value
113
+ );
90
114
  }
115
+ emit('update:modelValue', state.selected);
116
+ };
91
117
 
92
- if (value) {
93
- this.selected = [...this.selected, { id, selected: value, text }];
118
+ const updateModelValue = (checked, value) => {
119
+ state.selected = props.modelValue;
120
+ if (state.selected) {
121
+ return (
122
+ state.selected.filter(
123
+ (item) => item[props.dataValueExpr] === value[props.dataValueExpr]
124
+ ).length > 0
125
+ );
94
126
  } else {
95
- this.selected = this.selected.filter((item) => item.id !== id);
127
+ return undefined;
96
128
  }
97
- this.$emit('update:itemSelected', this.selectableItems);
98
- },
129
+ };
130
+
131
+ watch(
132
+ () => props.items,
133
+ (nextItems) => {
134
+ state.selectableItems = nextItems;
135
+ }
136
+ );
137
+
138
+ return {
139
+ listbox,
140
+ uuid,
141
+ state,
142
+ updateList,
143
+ updateModelValue,
144
+ };
99
145
  },
100
146
  };
101
147
  </script>