wave-ui 3.7.1 → 3.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/dist/wave-ui.cjs.js +1 -1
- package/dist/wave-ui.css +1 -1
- package/dist/wave-ui.es.js +892 -688
- package/dist/wave-ui.umd.js +1 -1
- package/package.json +14 -15
- package/src/wave-ui/components/index.js +1 -0
- package/src/wave-ui/components/w-autocomplete.vue +309 -0
- package/src/wave-ui/components/w-card.vue +2 -0
- package/src/wave-ui/components/w-input.vue +13 -11
- package/src/wave-ui/components/w-list.vue +2 -2
- package/src/wave-ui/components/w-select.vue +60 -47
- package/src/wave-ui/components/w-textarea.vue +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "An emerging UI framework for Vue.js (2 & 3) with only the bright side. :sunny:",
|
|
5
5
|
"author": "Antoni Andre <antoniandre.web@gmail.com>",
|
|
6
6
|
"homepage": "https://antoniandre.github.io/wave-ui",
|
|
@@ -50,32 +50,31 @@
|
|
|
50
50
|
"publish-doc": "npm run build && npm run build-bundle && git add . && git commit -m 'Publish documentation on Github.' && git push && git push --tag"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@babel/core": "^7.
|
|
54
|
-
"@babel/eslint-parser": "^7.22.
|
|
55
|
-
"@
|
|
56
|
-
"@faker-js/faker": "^7.6.0",
|
|
53
|
+
"@babel/core": "^7.23.2",
|
|
54
|
+
"@babel/eslint-parser": "^7.22.15",
|
|
55
|
+
"@faker-js/faker": "^8.2.0",
|
|
57
56
|
"@mdi/font": "^6.9.96",
|
|
58
57
|
"@vitejs/plugin-vue": "^3.2.0",
|
|
59
|
-
"@vue/compiler-sfc": "3.
|
|
60
|
-
"autoprefixer": "^10.4.
|
|
61
|
-
"axios": "^1.
|
|
62
|
-
"eslint": "^8.
|
|
63
|
-
"eslint-plugin-vue": "^9.
|
|
58
|
+
"@vue/compiler-sfc": "3.3.4",
|
|
59
|
+
"autoprefixer": "^10.4.16",
|
|
60
|
+
"axios": "^1.6.0",
|
|
61
|
+
"eslint": "^8.52.0",
|
|
62
|
+
"eslint-plugin-vue": "^9.18.1",
|
|
64
63
|
"font-awesome": "^4.7.0",
|
|
65
64
|
"gsap": "^3.12.2",
|
|
66
65
|
"ionicons": "^4.6.3",
|
|
67
66
|
"material-design-icons": "^3.0.1",
|
|
68
|
-
"postcss": "^8.4.
|
|
67
|
+
"postcss": "^8.4.31",
|
|
69
68
|
"pug": "^3.0.2",
|
|
70
69
|
"rollup-plugin-delete": "^2.0.0",
|
|
71
|
-
"sass": "^1.
|
|
72
|
-
"simple-syntax-highlighter": "^
|
|
70
|
+
"sass": "^1.69.5",
|
|
71
|
+
"simple-syntax-highlighter": "^3.0.2",
|
|
73
72
|
"splitpanes": "^3.1.5",
|
|
74
73
|
"standard": "^17.1.0",
|
|
75
74
|
"vite": "^3.2.7",
|
|
76
75
|
"vite-svg-loader": "^4.0.0",
|
|
77
|
-
"vue": "^3.3.
|
|
78
|
-
"vue-router": "^4.2.
|
|
76
|
+
"vue": "^3.3.7",
|
|
77
|
+
"vue-router": "^4.2.5",
|
|
79
78
|
"vueperslides": "^3.5.1",
|
|
80
79
|
"vuex": "^4.1.0"
|
|
81
80
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as WAccordion } from './w-accordion.vue'
|
|
2
2
|
export { default as WAlert } from './w-alert.vue'
|
|
3
|
+
export { default as WAutocomplete } from './w-autocomplete.vue'
|
|
3
4
|
export { default as WApp } from './w-app.vue'
|
|
4
5
|
export { default as WBadge } from './w-badge.vue'
|
|
5
6
|
export { default as WBreadcrumbs } from './w-breadcrumbs.vue'
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
.w-autocomplete(:class="classes" @click="onClick")
|
|
3
|
+
template(v-if="selection.length")
|
|
4
|
+
.w-autocomplete__selection(v-for="(item, i) in selection")
|
|
5
|
+
span(v-html="item[itemLabelKey]")
|
|
6
|
+
w-button(@click.stop="unselectItem(i)" icon="i-cross" xs text color="currentColor")
|
|
7
|
+
.w-autocomplete__placeholder(v-if="!selection.length && !keywords && placeholder" v-html="placeholder")
|
|
8
|
+
input.w-autocomplete__input(
|
|
9
|
+
ref="input"
|
|
10
|
+
v-model="keywords"
|
|
11
|
+
@focus="onFocus"
|
|
12
|
+
@keydown="onKeydown"
|
|
13
|
+
@drop="onDrop"
|
|
14
|
+
@compositionstart="onCompositionStart"
|
|
15
|
+
@compositionupdate="onCompositionUpdate"
|
|
16
|
+
v-on="$listeners")
|
|
17
|
+
w-transition-slide-fade(y)
|
|
18
|
+
ul.w-autocomplete__menu(v-if="menuOpen" ref="menu")
|
|
19
|
+
li(
|
|
20
|
+
v-for="(item, i) in filteredItems"
|
|
21
|
+
:key="i"
|
|
22
|
+
@click.stop="selectItem(item)"
|
|
23
|
+
:class="{ highlighted: highlightedItem === item.uid }")
|
|
24
|
+
span(v-html="item[itemLabelKey]")
|
|
25
|
+
li.w-autocomplete__no-match(
|
|
26
|
+
v-if="!filteredItems.length"
|
|
27
|
+
:class="{ 'w-autocomplete__no-match--default': !$slots.noMatch }")
|
|
28
|
+
slot(name="no-match")
|
|
29
|
+
.caption(v-html="noMatch ?? 'No match.'")
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script>
|
|
33
|
+
export default {
|
|
34
|
+
name: 'w-autocomplete',
|
|
35
|
+
|
|
36
|
+
props: {
|
|
37
|
+
items: { type: Array, required: true },
|
|
38
|
+
modelValue: { type: [String, Number, Array] }, // String or Number if single selections, Array if multiple.
|
|
39
|
+
placeholder: { type: String },
|
|
40
|
+
openOnKeydown: { type: Boolean }, // By default the menu is always open for selection.
|
|
41
|
+
multiple: { type: Boolean },
|
|
42
|
+
// When multiple is on, prevents duplicate items selections by default, unless this is set to true.
|
|
43
|
+
allowDuplicates: { type: Boolean },
|
|
44
|
+
noMatch: { type: String },
|
|
45
|
+
// Contains the unique selection value for each item.
|
|
46
|
+
// Can be a numeric ID, a slug, etc. (outside of Wave UI)
|
|
47
|
+
itemValueKey: { type: String, default: 'value' },
|
|
48
|
+
// Contains the string to display for each item.
|
|
49
|
+
itemLabelKey: { type: String, default: 'label' },
|
|
50
|
+
// Contains the string to search keywords into for each item.
|
|
51
|
+
// This can for instance be an aggregation of multiple fields (outside of Wave UI).
|
|
52
|
+
itemSearchableKey: { type: String, default: 'searchable' }
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
emits: ['input'],
|
|
56
|
+
|
|
57
|
+
data: () => ({
|
|
58
|
+
keywords: '',
|
|
59
|
+
selection: [],
|
|
60
|
+
menuOpen: false,
|
|
61
|
+
highlightedItem: null
|
|
62
|
+
}),
|
|
63
|
+
|
|
64
|
+
computed: {
|
|
65
|
+
// Keep the autocomplete matching as fast as possible by caching optimized search strings.
|
|
66
|
+
normalizedKeywords () {
|
|
67
|
+
return this.normalize(this.keywords)
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// Keep the autocomplete matching as fast as possible by caching optimized search strings.
|
|
71
|
+
// An array of optimized strings.
|
|
72
|
+
normalizedSelection () {
|
|
73
|
+
return this.selection.map(item => this.normalize(item?.searchable))
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// Keep the autocomplete matching as fast as possible by caching optimized search strings.
|
|
77
|
+
optimizedItemsForSearch () {
|
|
78
|
+
return this.items.map((item, i) => ({
|
|
79
|
+
...item,
|
|
80
|
+
uid: i,
|
|
81
|
+
searchable: this.normalize(item[this.itemSearchableKey] || '')
|
|
82
|
+
}))
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
filteredItems () {
|
|
86
|
+
let items = this.optimizedItemsForSearch // Array of objects.
|
|
87
|
+
const selection = this.normalizedSelection.join(',') // Optimized string of coma separated words.
|
|
88
|
+
const isItemNotSelected = item => !selection.includes(item.searchable)
|
|
89
|
+
|
|
90
|
+
if (this.keywords) {
|
|
91
|
+
items = items.filter(item => {
|
|
92
|
+
if (!item.searchable.includes(this.normalizedKeywords)) return false
|
|
93
|
+
else if (this.multiple && !this.allowDuplicates) return isItemNotSelected(item)
|
|
94
|
+
else return true
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
else if (this.multiple && !this.allowDuplicates) items = items.filter(isItemNotSelected)
|
|
99
|
+
|
|
100
|
+
return items
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
highlightedItemIndex () {
|
|
104
|
+
if (this.highlightedItem === null) return -1
|
|
105
|
+
return this.filteredItems.findIndex(item => item.uid === this.highlightedItem)
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
classes () {
|
|
109
|
+
return {
|
|
110
|
+
'w-autocomplete--open': this.menuOpen
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
methods: {
|
|
116
|
+
// Replace all the accents and non-latin characters with equivalent letters. E.g. é -> e.
|
|
117
|
+
normalize (string) {
|
|
118
|
+
return string.toLowerCase().normalize('NFKD').replace(/\p{Diacritic}/gu, '').replace(/œ/g, 'oe')
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// Selection can be made from click or enter key.
|
|
122
|
+
selectItem (item) {
|
|
123
|
+
if (!this.multiple) this.selection = []
|
|
124
|
+
this.selection.push(item)
|
|
125
|
+
this.highlightedItem = item.uid
|
|
126
|
+
this.keywords = ''
|
|
127
|
+
this.$emit('input', this.multiple ? this.selection.map(item => item[this.itemValueKey]) : item[this.itemValueKey])
|
|
128
|
+
this.$refs.input.focus()
|
|
129
|
+
if (!this.multiple) this.closeMenu()
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
unselectItem (i) {
|
|
133
|
+
this.selection.splice(i ?? this.selection.length - 1, 1)
|
|
134
|
+
this.highlightedItem = null
|
|
135
|
+
this.$emit('input', null)
|
|
136
|
+
this.$refs.input.focus()
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
onClick () {
|
|
140
|
+
if (!this.openOnKeydown) this.openMenu()
|
|
141
|
+
this.$refs.input.focus()
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
onFocus () {
|
|
145
|
+
if (!this.openOnKeydown) this.openMenu()
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
onKeydown (e) {
|
|
149
|
+
const itemsCount = this.filteredItems.length
|
|
150
|
+
// `e.key.length === 1`: is all the keyboard keys that generate a character.
|
|
151
|
+
if (!this.openOnKeydown || ((this.keywords || e.key.length === 1) && !this.menuOpen)) this.openMenu()
|
|
152
|
+
|
|
153
|
+
// Delete key.
|
|
154
|
+
if (e.keyCode === 8 && (!this.keywords || (!e.target.selectionStart && !e.target.selectionEnd))) {
|
|
155
|
+
this.unselectItem()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Enter key.
|
|
159
|
+
else if (e.keyCode === 13) {
|
|
160
|
+
e.preventDefault() // Prevent form submissions.
|
|
161
|
+
if (this.highlightedItemIndex >= 0) this.selectItem(this.filteredItems[this.highlightedItemIndex])
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Up & down arrow keys.
|
|
165
|
+
else if ([38, 40].includes(e.keyCode)) {
|
|
166
|
+
e.preventDefault() // Prevent moving the cursor to the left of the text while selecting item.
|
|
167
|
+
let index = this.highlightedItemIndex
|
|
168
|
+
if (index === -1) index = e.keyCode === 38 ? itemsCount - 1 : 0
|
|
169
|
+
else index = (index + (e.keyCode === 38 ? -1 : 1) + itemsCount) % itemsCount // Never out of range.
|
|
170
|
+
|
|
171
|
+
this.highlightedItem = this.filteredItems[index]?.uid || 0
|
|
172
|
+
|
|
173
|
+
// Scroll the container if highlighted item is not in view.
|
|
174
|
+
const menuEl = this.$refs.menu
|
|
175
|
+
if (menuEl) {
|
|
176
|
+
const { offsetHeight: itemElHeight, offsetTop: itemElTop } = menuEl.childNodes[index] || {}
|
|
177
|
+
if (menuEl.scrollTop + menuEl.offsetHeight - itemElHeight < itemElTop) {
|
|
178
|
+
menuEl.scrollTop = itemElTop - menuEl.offsetHeight + itemElHeight
|
|
179
|
+
}
|
|
180
|
+
else if (menuEl.scrollTop > itemElTop) menuEl.scrollTop = itemElTop
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// `e.key.length === 1`: allow all control keys but no character creation.
|
|
185
|
+
else if (!this.multiple && this.selection.length && (e.key.length === 1)) e.preventDefault()
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
// On drag & drop of a text in the input field, don't paste if single selection and already selected.
|
|
189
|
+
onDrop (e) {
|
|
190
|
+
if (!this.multiple && this.selection.length) e.preventDefault()
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// When starting a sequence of keys that produces a character.
|
|
194
|
+
onCompositionStart (e) {
|
|
195
|
+
// e.preventDefault() does not work. https://stackoverflow.com/a/77556830/2012407
|
|
196
|
+
if (!this.multiple && this.selection.length) e.target.setAttribute('readonly', true)
|
|
197
|
+
},
|
|
198
|
+
onCompositionUpdate (e) {
|
|
199
|
+
if (!this.multiple && this.selection.length) setTimeout(() => e.target.removeAttribute('readonly'), 200)
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
openMenu () {
|
|
203
|
+
if (this.menuOpen) return
|
|
204
|
+
this.menuOpen = true
|
|
205
|
+
document.addEventListener('click', this.onDocumentClick)
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
closeMenu () {
|
|
209
|
+
this.menuOpen = false
|
|
210
|
+
document.removeEventListener('click', this.onDocumentClick)
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
onDocumentClick (e) {
|
|
214
|
+
if (!this.$el.contains(e.target) && !this.$el.isSameNode(e.target)) this.closeMenu()
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
created () {
|
|
219
|
+
if (this.modelValue) {
|
|
220
|
+
const arrayOfValues = Array.isArray(this.modelValue) ? this.modelValue : [this.modelValue]
|
|
221
|
+
arrayOfValues.forEach(value => {
|
|
222
|
+
this.selection.push(this.optimizedItemsForSearch.find(item => item[this.itemValueKey] === +value))
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
beforeUnmount () {
|
|
228
|
+
document.removeEventListener('click', this.onDocumentClick)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
</script>
|
|
232
|
+
|
|
233
|
+
<style lang="scss">
|
|
234
|
+
.w-autocomplete {
|
|
235
|
+
display: flex;
|
|
236
|
+
flex-wrap: wrap;
|
|
237
|
+
gap: 4px;
|
|
238
|
+
position: relative;
|
|
239
|
+
border-radius: 4px;
|
|
240
|
+
border: 1px solid rgba(#000, 0.2);
|
|
241
|
+
padding: 2px 4px;
|
|
242
|
+
user-select: none;
|
|
243
|
+
|
|
244
|
+
&--open {
|
|
245
|
+
border-bottom-left-radius: 0;
|
|
246
|
+
border-bottom-right-radius: 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
&__selection {
|
|
250
|
+
display: flex;
|
|
251
|
+
align-items: center;
|
|
252
|
+
background: rgba(#000, 0.035);
|
|
253
|
+
border: 1px solid rgba(#000, 0.05);
|
|
254
|
+
border-radius: 4px;
|
|
255
|
+
padding: 0 2px 0 4px;
|
|
256
|
+
flex-shrink: 0;
|
|
257
|
+
|
|
258
|
+
span {margin-top: -1px;line-height: 1;}
|
|
259
|
+
.w-button .w-icon:before {font-size: 0.8em;line-height: 0;}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
&__input {
|
|
263
|
+
min-width: 0;
|
|
264
|
+
flex: 1 1 0;
|
|
265
|
+
color: inherit;
|
|
266
|
+
border: none;
|
|
267
|
+
background-color: transparent;
|
|
268
|
+
line-height: 18px;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
&__placeholder {
|
|
272
|
+
position: absolute;
|
|
273
|
+
color: #ccc;
|
|
274
|
+
pointer-events: none;
|
|
275
|
+
line-height: 18px;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
&__menu {
|
|
279
|
+
position: absolute;
|
|
280
|
+
inset: 100% -1px auto;
|
|
281
|
+
max-height: clamp(20px, 400px, 80vh);
|
|
282
|
+
margin-top: -1px;
|
|
283
|
+
margin-left: 0;
|
|
284
|
+
background-color: #fff;
|
|
285
|
+
border: 1px solid rgba(#000, 0.2);
|
|
286
|
+
border-top: none;
|
|
287
|
+
border-bottom-left-radius: inherit;
|
|
288
|
+
border-bottom-right-radius: inherit;
|
|
289
|
+
overflow: auto;
|
|
290
|
+
z-index: 10;
|
|
291
|
+
|
|
292
|
+
li {
|
|
293
|
+
list-style-type: none;
|
|
294
|
+
margin: 0;
|
|
295
|
+
padding: 4px 8px;
|
|
296
|
+
border-left: 2px solid transparent;
|
|
297
|
+
|
|
298
|
+
&:hover {background-color: rgba($primary, 0.1);}
|
|
299
|
+
|
|
300
|
+
&.highlighted {
|
|
301
|
+
background-color: rgba($primary, 0.15);
|
|
302
|
+
border-left-color: rgba($primary, 0.75);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
li.w-autocomplete__no-match--default:hover {background-color: transparent;}
|
|
309
|
+
</style>
|
|
@@ -103,6 +103,8 @@ export default {
|
|
|
103
103
|
padding: (2 * $base-increment) (3 * $base-increment);
|
|
104
104
|
font-size: 1.3em;
|
|
105
105
|
border-bottom: $border;
|
|
106
|
+
border-top-left-radius: inherit;
|
|
107
|
+
border-top-right-radius: inherit;
|
|
106
108
|
|
|
107
109
|
&--has-toolbar {padding: 0;border-bottom: none;}
|
|
108
110
|
}
|
|
@@ -20,11 +20,12 @@ component(
|
|
|
20
20
|
|
|
21
21
|
//- Input wrapper.
|
|
22
22
|
.w-input__input-wrap(:class="inputWrapClasses")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
slot(name="icon-left" :input-id="`w-input--${_.uid}`")
|
|
24
|
+
w-icon.w-input__icon.w-input__icon--inner-left(
|
|
25
|
+
v-if="innerIconLeft"
|
|
26
|
+
tag="label"
|
|
27
|
+
:for="`w-input--${_.uid}`"
|
|
28
|
+
@click="$emit('click:inner-icon-left', $event)") {{ innerIconLeft }}
|
|
28
29
|
//- All types of input except file.
|
|
29
30
|
input.w-input__input(
|
|
30
31
|
v-if="type !== 'file'"
|
|
@@ -78,14 +79,15 @@ component(
|
|
|
78
79
|
template(v-if="labelPosition === 'inside' && showLabelInside")
|
|
79
80
|
label.w-input__label.w-input__label--inside.w-form-el-shakable(
|
|
80
81
|
v-if="$slots.default || label"
|
|
81
|
-
:for="`w-input--${_.uid}`"
|
|
82
82
|
:class="labelClasses")
|
|
83
83
|
slot {{ label }}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
|
|
85
|
+
slot(name="icon-right" :input-id="`w-input--${_.uid}`")
|
|
86
|
+
w-icon.w-input__icon.w-input__icon--inner-right(
|
|
87
|
+
v-if="innerIconRight"
|
|
88
|
+
tag="label"
|
|
89
|
+
:for="`w-input--${_.uid}`"
|
|
90
|
+
@click="$emit('click:inner-icon-right', $event)") {{ innerIconRight }}
|
|
89
91
|
|
|
90
92
|
w-progress.fill-width(
|
|
91
93
|
v-if="hasLoading || (showProgress && (uploadInProgress || uploadComplete))"
|
|
@@ -160,8 +160,8 @@ export default {
|
|
|
160
160
|
'w-list__item-label--focused': item._focused,
|
|
161
161
|
'w-list__item-label--hoverable': this.hover,
|
|
162
162
|
'w-list__item-label--selectable': this.isSelectable,
|
|
163
|
-
[item.
|
|
164
|
-
[this.SelectionColor]: item._selected && !item.
|
|
163
|
+
[item[this.itemColorKey]]: !!item[this.itemColorKey],
|
|
164
|
+
[this.SelectionColor]: item._selected && !item[this.itemColorKey] && this.SelectionColor,
|
|
165
165
|
[item[this.itemClassKey] || this.itemClass]: item[this.itemClassKey] || this.itemClass
|
|
166
166
|
}
|
|
167
167
|
},
|