wave-ui 1.69.0 → 1.70.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 +390 -69
- package/dist/wave-ui.umd.js +1 -1
- package/package.json +13 -13
- package/src/wave-ui/components/index.js +1 -0
- package/src/wave-ui/components/transitions/w-transition-expand.vue +2 -4
- package/src/wave-ui/components/w-accordion.vue +1 -4
- package/src/wave-ui/components/w-alert.vue +1 -4
- package/src/wave-ui/components/w-autocomplete.vue +404 -0
- package/src/wave-ui/components/w-badge.vue +1 -0
- package/src/wave-ui/components/w-button/button.vue +3 -8
- package/src/wave-ui/components/w-card.vue +2 -0
- package/src/wave-ui/components/w-drawer.vue +2 -8
- package/src/wave-ui/components/w-icon.vue +1 -1
- package/src/wave-ui/components/w-image.vue +2 -8
- package/src/wave-ui/components/w-input.vue +17 -16
- package/src/wave-ui/components/w-list.vue +1 -4
- package/src/wave-ui/components/w-notification-manager.vue +3 -3
- package/src/wave-ui/components/w-progress.vue +2 -8
- package/src/wave-ui/components/w-rating.vue +1 -4
- package/src/wave-ui/components/w-select.vue +66 -34
- package/src/wave-ui/components/w-slider.vue +6 -5
- package/src/wave-ui/components/w-spinner.vue +2 -0
- package/src/wave-ui/components/w-switch.vue +1 -1
- package/src/wave-ui/components/w-table.vue +220 -210
- package/src/wave-ui/components/w-tabs/index.vue +1 -4
- package/src/wave-ui/components/w-tag.vue +1 -4
- package/src/wave-ui/components/w-textarea.vue +0 -1
- package/src/wave-ui/components/w-timeline.vue +1 -0
- package/src/wave-ui/components/w-tree.vue +7 -7
- package/src/wave-ui/scss/_variables.scss +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.70.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,38 +50,38 @@
|
|
|
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.23.
|
|
54
|
-
"@babel/eslint-parser": "^7.
|
|
53
|
+
"@babel/core": "^7.23.9",
|
|
54
|
+
"@babel/eslint-parser": "^7.23.10",
|
|
55
55
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
56
|
-
"@faker-js/faker": "^8.
|
|
56
|
+
"@faker-js/faker": "^8.4.1",
|
|
57
57
|
"@mdi/font": "^6.9.96",
|
|
58
58
|
"@vitejs/plugin-vue": "^1.10.2",
|
|
59
|
-
"autoprefixer": "^10.4.
|
|
59
|
+
"autoprefixer": "^10.4.17",
|
|
60
60
|
"axios": "^0.21.4",
|
|
61
|
-
"core-js": "^3.
|
|
61
|
+
"core-js": "^3.36.0",
|
|
62
62
|
"eslint": "^7.32.0",
|
|
63
63
|
"eslint-config-standard": "^16.0.3",
|
|
64
|
-
"eslint-plugin-import": "^2.29.
|
|
64
|
+
"eslint-plugin-import": "^2.29.1",
|
|
65
65
|
"eslint-plugin-node": "^11.1.0",
|
|
66
66
|
"eslint-plugin-promise": "^5.2.0",
|
|
67
67
|
"eslint-plugin-vue": "^7.20.0",
|
|
68
68
|
"font-awesome": "^4.7.0",
|
|
69
69
|
"ghspa": "^1.0.0",
|
|
70
|
-
"gsap": "^3.12.
|
|
70
|
+
"gsap": "^3.12.5",
|
|
71
71
|
"ionicons": "^4.6.3",
|
|
72
72
|
"material-design-icons": "^3.0.1",
|
|
73
|
-
"postcss": "^8.4.
|
|
73
|
+
"postcss": "^8.4.35",
|
|
74
74
|
"pug": "^3.0.2",
|
|
75
75
|
"rollup-plugin-delete": "^2.0.0",
|
|
76
|
-
"sass": "^1.
|
|
76
|
+
"sass": "^1.71.0",
|
|
77
77
|
"simple-syntax-highlighter": "^1.6.2",
|
|
78
78
|
"splitpanes": "^2.4.1",
|
|
79
|
-
"vite": "^2.9.
|
|
79
|
+
"vite": "^2.9.17",
|
|
80
80
|
"vite-plugin-vue2": "^1.9.3",
|
|
81
|
-
"vue": "^2.7.
|
|
81
|
+
"vue": "^2.7.16",
|
|
82
82
|
"vue-router": "^3.6.5",
|
|
83
83
|
"vue-svg-loader": "^0.16.0",
|
|
84
|
-
"vue-template-compiler": "^2.7.
|
|
84
|
+
"vue-template-compiler": "^2.7.16",
|
|
85
85
|
"vueperslides": "^2.16.0",
|
|
86
86
|
"vuex": "^3.6.2"
|
|
87
87
|
}
|
|
@@ -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'
|
|
@@ -162,19 +162,17 @@ export default {
|
|
|
162
162
|
// Keep any original inline styles to restore them after transition.
|
|
163
163
|
this.el.originalStyles = el.style.cssText
|
|
164
164
|
},
|
|
165
|
-
show (el
|
|
165
|
+
show (el) {
|
|
166
166
|
this.saveComputedStyles(el)
|
|
167
167
|
this.applyHideStyles(el)
|
|
168
168
|
|
|
169
169
|
setTimeout(() => this.applyShowStyles(el), 20)
|
|
170
|
-
setTimeout(done, this.duration)
|
|
171
170
|
},
|
|
172
171
|
beforeHide (el) {
|
|
173
172
|
this.applyShowStyles(el)
|
|
174
173
|
},
|
|
175
|
-
hide (el
|
|
174
|
+
hide (el) {
|
|
176
175
|
setTimeout(() => this.applyHideStyles(el), 20)
|
|
177
|
-
setTimeout(done, this.duration)
|
|
178
176
|
},
|
|
179
177
|
saveComputedStyles (el) {
|
|
180
178
|
const computedStyles = window.getComputedStyle(el, null)
|
|
@@ -156,10 +156,7 @@ export default {
|
|
|
156
156
|
// ------------------------------------------------------
|
|
157
157
|
&:before, &:after {
|
|
158
158
|
position: absolute;
|
|
159
|
-
|
|
160
|
-
bottom: 0;
|
|
161
|
-
left: 0;
|
|
162
|
-
right: 0;
|
|
159
|
+
inset: 0;
|
|
163
160
|
background-color: currentColor;
|
|
164
161
|
pointer-events: none;
|
|
165
162
|
}
|
|
@@ -0,0 +1,404 @@
|
|
|
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
|
+
slot(name="selection" :item="item" :unselect="i => unselectItem(i)")
|
|
6
|
+
span(v-html="item[itemLabelKey]")
|
|
7
|
+
w-button(@click.stop="unselectItem(i)" icon="wi-cross" xs text color="currentColor")
|
|
8
|
+
.w-autocomplete__placeholder(
|
|
9
|
+
v-if="!selection.length && !keywords && placeholder"
|
|
10
|
+
v-html="placeholder")
|
|
11
|
+
input.w-autocomplete__input(
|
|
12
|
+
ref="input"
|
|
13
|
+
:value="keywords"
|
|
14
|
+
v-on="inputEventListeners")
|
|
15
|
+
w-transition-slide-fade
|
|
16
|
+
ul.w-autocomplete__menu(
|
|
17
|
+
v-if="menuOpen"
|
|
18
|
+
ref="menu"
|
|
19
|
+
@mousedown="menuIsBeingClicked = true"
|
|
20
|
+
@mouseup="setEndOfMenuClick"
|
|
21
|
+
@touchstart="menuIsBeingClicked = true"
|
|
22
|
+
@touchend="setEndOfMenuClick")
|
|
23
|
+
li(
|
|
24
|
+
v-for="(item, i) in filteredItems"
|
|
25
|
+
:key="i"
|
|
26
|
+
@click.stop="selectItem(item), $emit('item-click', item)"
|
|
27
|
+
:class="{ highlighted: highlightedItem === item.uid }")
|
|
28
|
+
slot(name="item" :item="item" :highlighted="highlightedItem === item.uid")
|
|
29
|
+
span(v-html="item[itemLabelKey]")
|
|
30
|
+
li.w-autocomplete__no-match(
|
|
31
|
+
v-if="!filteredItems.length"
|
|
32
|
+
:class="{ 'w-autocomplete__no-match--default': !$slots.noMatch }")
|
|
33
|
+
slot(name="no-match")
|
|
34
|
+
.caption(v-html="noMatch !== undefined ? noMatch : 'No match.'")
|
|
35
|
+
li.w-autocomplete__extra-item(
|
|
36
|
+
v-if="$slots['extra-item']"
|
|
37
|
+
@click="selectExtraItem"
|
|
38
|
+
:class="{ highlighted: highlightedItem === 'extra-item' }")
|
|
39
|
+
slot(name="extra-item")
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<script>
|
|
43
|
+
export default {
|
|
44
|
+
name: 'w-autocomplete',
|
|
45
|
+
|
|
46
|
+
props: {
|
|
47
|
+
items: { type: Array, required: true },
|
|
48
|
+
value: { type: [String, Number, Array] }, // String or Number if single selections, Array if multiple.
|
|
49
|
+
placeholder: { type: String },
|
|
50
|
+
openOnKeydown: { type: Boolean }, // By default the menu is always open for selection.
|
|
51
|
+
multiple: { type: Boolean },
|
|
52
|
+
// When multiple is on, prevents duplicate items selections by default, unless this is set to true.
|
|
53
|
+
allowDuplicates: { type: Boolean },
|
|
54
|
+
noMatch: { type: String },
|
|
55
|
+
// Contains the unique selection value for each item.
|
|
56
|
+
// Can be a numeric ID, a slug, etc. (outside of Wave UI)
|
|
57
|
+
itemValueKey: { type: String, default: 'value' },
|
|
58
|
+
// Contains the string to display for each item.
|
|
59
|
+
itemLabelKey: { type: String, default: 'label' },
|
|
60
|
+
// Contains the string to search keywords into for each item.
|
|
61
|
+
// This can for instance be an aggregation of multiple fields (outside of Wave UI).
|
|
62
|
+
itemSearchableKey: { type: String, default: 'searchable' }
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// item-select is also from keyboard, 'item-click' may be useful for mouseenter mouseleave events.
|
|
66
|
+
emits: ['update:modelValue', 'input', 'focus', 'blur', 'keydown', 'item-click', 'item-select', 'extra-item-select'],
|
|
67
|
+
|
|
68
|
+
data: () => ({
|
|
69
|
+
keywords: '',
|
|
70
|
+
selection: [],
|
|
71
|
+
menuOpen: false,
|
|
72
|
+
highlightedItem: null,
|
|
73
|
+
// The focus-blur events occur more times than it should emit to the outside due to the menu
|
|
74
|
+
// item clicking. So keep the focus on as long as the user is interacting with the autocomplete.
|
|
75
|
+
menuIsBeingClicked: false
|
|
76
|
+
}),
|
|
77
|
+
|
|
78
|
+
computed: {
|
|
79
|
+
// Keep the autocomplete matching as fast as possible by caching optimized search strings.
|
|
80
|
+
normalizedKeywords () {
|
|
81
|
+
return this.normalize(this.keywords)
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// Keep the autocomplete matching as fast as possible by caching optimized search strings.
|
|
85
|
+
// An array of optimized strings.
|
|
86
|
+
normalizedSelection () {
|
|
87
|
+
return this.selection.map(item => this.normalize(item?.searchable))
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
// Keep the autocomplete matching as fast as possible by caching optimized search strings.
|
|
91
|
+
optimizedItemsForSearch () {
|
|
92
|
+
return this.items.map((item, i) => ({
|
|
93
|
+
...item,
|
|
94
|
+
uid: i,
|
|
95
|
+
searchable: this.normalize(item[this.itemSearchableKey] || '')
|
|
96
|
+
}))
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
filteredItems () {
|
|
100
|
+
let items = this.optimizedItemsForSearch // Array of objects.
|
|
101
|
+
const selection = this.normalizedSelection.join(',') // Optimized string of coma separated words.
|
|
102
|
+
const isItemNotSelected = item => !selection.includes(item.searchable)
|
|
103
|
+
|
|
104
|
+
if (this.keywords) {
|
|
105
|
+
items = items.filter(item => {
|
|
106
|
+
if (!item.searchable.includes(this.normalizedKeywords)) return false
|
|
107
|
+
else if (this.multiple && !this.allowDuplicates) return isItemNotSelected(item)
|
|
108
|
+
else return true
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
else if (this.multiple && !this.allowDuplicates) items = items.filter(isItemNotSelected)
|
|
113
|
+
|
|
114
|
+
return items
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
highlightedItemIndex () {
|
|
118
|
+
if (this.highlightedItem === null) return -1
|
|
119
|
+
else if (this.highlightedItem === 'extra-item') return this.filteredItems.length
|
|
120
|
+
return this.filteredItems.findIndex(item => item.uid === this.highlightedItem)
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
inputEventListeners () {
|
|
124
|
+
return {
|
|
125
|
+
...this.$attrs,
|
|
126
|
+
input: e => {
|
|
127
|
+
this.keywords = e.target.value
|
|
128
|
+
},
|
|
129
|
+
focus: e => {
|
|
130
|
+
if (this.menuIsBeingClicked) return
|
|
131
|
+
this.onFocus(e)
|
|
132
|
+
this.$emit('focus', e)
|
|
133
|
+
},
|
|
134
|
+
blur: e => {
|
|
135
|
+
if (!this.menuIsBeingClicked) this.$emit('blur', e)
|
|
136
|
+
},
|
|
137
|
+
keydown: e => {
|
|
138
|
+
this.onKeydown(e)
|
|
139
|
+
this.$emit('keydown', e)
|
|
140
|
+
},
|
|
141
|
+
drop: this.onDrop,
|
|
142
|
+
compositionstart: this.onCompositionStart,
|
|
143
|
+
compositionupdate: this.onCompositionUpdate
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
classes () {
|
|
148
|
+
return {
|
|
149
|
+
'w-autocomplete--open': this.menuOpen,
|
|
150
|
+
'w-autocomplete--filled': this.selection.length,
|
|
151
|
+
'w-autocomplete--has-keywords': this.keywords,
|
|
152
|
+
'w-autocomplete--empty': !this.selection.length && !this.keywords
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
methods: {
|
|
158
|
+
// Replace all the accents and non-latin characters with equivalent letters. E.g. é -> e.
|
|
159
|
+
normalize (string) {
|
|
160
|
+
return string.toLowerCase().normalize('NFKD').replace(/\p{Diacritic}/gu, '').replace(/œ/g, 'oe')
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// Selection can be made from click or enter key.
|
|
164
|
+
selectItem (item) {
|
|
165
|
+
if (!this.multiple) this.selection = []
|
|
166
|
+
this.selection.push(item)
|
|
167
|
+
this.highlightedItem = item.uid
|
|
168
|
+
this.keywords = ''
|
|
169
|
+
const emitPayload = this.multiple ? this.selection.map(item => item[this.itemValueKey]) : item[this.itemValueKey]
|
|
170
|
+
// Unlike input, item-select is only emitted when selecting (not unselecting).
|
|
171
|
+
this.$emit('item-select', item)
|
|
172
|
+
this.$emit('update:modelValue', emitPayload)
|
|
173
|
+
this.$emit('input', emitPayload)
|
|
174
|
+
this.$refs.input.focus()
|
|
175
|
+
if (!this.multiple) this.closeMenu()
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
unselectItem (i) {
|
|
179
|
+
this.selection.splice(i ?? this.selection.length - 1, 1)
|
|
180
|
+
this.highlightedItem = null
|
|
181
|
+
this.$emit('update:modelValue', null)
|
|
182
|
+
this.$emit('input', null)
|
|
183
|
+
this.$refs.input.focus()
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
selectExtraItem () {
|
|
187
|
+
this.keywords = ''
|
|
188
|
+
this.$emit('extra-item-select')
|
|
189
|
+
this.closeMenu()
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
setEndOfMenuClick () {
|
|
193
|
+
setTimeout(() => (this.menuIsBeingClicked = false), 100)
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
onClick () {
|
|
197
|
+
if (!this.openOnKeydown) this.openMenu()
|
|
198
|
+
this.$refs.input.focus()
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
onFocus () {
|
|
202
|
+
if (!this.openOnKeydown) this.openMenu()
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
// Can be triggered by a click outside the autocomplete, or by a tab key.
|
|
206
|
+
// It should not be simply triggered by input blur, because when we click a menu item it will
|
|
207
|
+
// blur the input for a few ms before we refocus it.
|
|
208
|
+
// onBlur () {
|
|
209
|
+
// this.closeMenu()
|
|
210
|
+
// },
|
|
211
|
+
|
|
212
|
+
onKeydown (e) {
|
|
213
|
+
const itemsCount = this.filteredItems.length + (this.$slots['extra-item'] ? 1 : 0)
|
|
214
|
+
// `e.key.length === 1`: is all the keyboard keys that generate a character.
|
|
215
|
+
if (!this.openOnKeydown || ((this.keywords || e.key.length === 1) && !this.menuOpen)) this.openMenu()
|
|
216
|
+
|
|
217
|
+
// Tab key.
|
|
218
|
+
if (e.keyCode === 9) this.closeMenu()
|
|
219
|
+
|
|
220
|
+
// Delete key.
|
|
221
|
+
else if (e.keyCode === 8 && (!this.keywords || (!e.target.selectionStart && !e.target.selectionEnd))) {
|
|
222
|
+
this.unselectItem()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Enter key.
|
|
226
|
+
else if (e.keyCode === 13) {
|
|
227
|
+
e.preventDefault() // Prevent form submissions.
|
|
228
|
+
if (this.highlightedItem === 'extra-item') this.selectExtraItem()
|
|
229
|
+
else if (this.highlightedItemIndex >= 0) this.selectItem(this.filteredItems[this.highlightedItemIndex])
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Up & down arrow keys.
|
|
233
|
+
else if ([38, 40].includes(e.keyCode)) {
|
|
234
|
+
e.preventDefault() // Prevent moving the cursor to the left of the text while selecting item.
|
|
235
|
+
let index = this.highlightedItemIndex
|
|
236
|
+
if (index === -1) index = e.keyCode === 38 ? itemsCount - 1 : 0
|
|
237
|
+
else index = (index + (e.keyCode === 38 ? -1 : 1) + itemsCount) % itemsCount // Never out of range.
|
|
238
|
+
|
|
239
|
+
if (this.$slots['extra-item'] && index === itemsCount - 1) this.highlightedItem = 'extra-item'
|
|
240
|
+
else this.highlightedItem = this.filteredItems[index]?.uid || 0
|
|
241
|
+
|
|
242
|
+
// Scroll the container if highlighted item is not in view.
|
|
243
|
+
const menuEl = this.$refs.menu
|
|
244
|
+
if (menuEl) {
|
|
245
|
+
if (this.$slots['extra-item'] && index === itemsCount - 1) menuEl.scrollTop = menuEl.scrollHeight
|
|
246
|
+
else {
|
|
247
|
+
const { offsetHeight: itemElHeight, offsetTop: itemElTop } = menuEl.childNodes[index] || {}
|
|
248
|
+
if (menuEl.scrollTop + menuEl.offsetHeight - itemElHeight < itemElTop) {
|
|
249
|
+
menuEl.scrollTop = itemElTop - menuEl.offsetHeight + itemElHeight
|
|
250
|
+
}
|
|
251
|
+
else if (menuEl.scrollTop > itemElTop) menuEl.scrollTop = itemElTop
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// `e.key.length === 1`: allow all control keys but no character creation.
|
|
257
|
+
else if (!this.multiple && this.selection.length && (e.key.length === 1)) e.preventDefault()
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
// On drag & drop of a text in the input field, don't paste if single selection and already selected.
|
|
261
|
+
onDrop (e) {
|
|
262
|
+
if (!this.multiple && this.selection.length) e.preventDefault()
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
// When starting a sequence of keys that produces a character.
|
|
266
|
+
onCompositionStart (e) {
|
|
267
|
+
// e.preventDefault() does not work. https://stackoverflow.com/a/77556830/2012407
|
|
268
|
+
if (!this.multiple && this.selection.length) e.target.setAttribute('readonly', true)
|
|
269
|
+
},
|
|
270
|
+
onCompositionUpdate (e) {
|
|
271
|
+
if (!this.multiple && this.selection.length) setTimeout(() => e.target.removeAttribute('readonly'), 200)
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
openMenu () {
|
|
275
|
+
if (this.menuOpen) return
|
|
276
|
+
this.menuOpen = true
|
|
277
|
+
document.addEventListener('click', this.onDocumentClick)
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
closeMenu () {
|
|
281
|
+
this.menuOpen = false
|
|
282
|
+
document.removeEventListener('click', this.onDocumentClick)
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
onDocumentClick (e) {
|
|
286
|
+
if (!this.$el.contains(e.target) && !this.$el.isSameNode(e.target)) this.closeMenu()
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
created () {
|
|
291
|
+
if (this.value) {
|
|
292
|
+
const arrayOfValues = Array.isArray(this.value) ? this.value : [this.value]
|
|
293
|
+
arrayOfValues.forEach(value => {
|
|
294
|
+
this.selection.push(this.optimizedItemsForSearch.find(item => item[this.itemValueKey] === +value))
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
beforeDestroy () {
|
|
300
|
+
document.removeEventListener('click', this.onDocumentClick)
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
watch: {
|
|
304
|
+
value (value) {
|
|
305
|
+
this.selection = []
|
|
306
|
+
if (value) {
|
|
307
|
+
const arrayOfValues = Array.isArray(value) ? value : [value]
|
|
308
|
+
arrayOfValues.forEach(value => {
|
|
309
|
+
this.selection.push(this.optimizedItemsForSearch.find(item => item[this.itemValueKey] === +value))
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
</script>
|
|
316
|
+
|
|
317
|
+
<style lang="scss">
|
|
318
|
+
.w-autocomplete {
|
|
319
|
+
display: flex;
|
|
320
|
+
flex-wrap: wrap;
|
|
321
|
+
gap: 4px;
|
|
322
|
+
position: relative;
|
|
323
|
+
border-radius: $border-radius;
|
|
324
|
+
border: $border;
|
|
325
|
+
padding: 2px 4px;
|
|
326
|
+
user-select: none;
|
|
327
|
+
|
|
328
|
+
&--open {
|
|
329
|
+
border-bottom-left-radius: 0;
|
|
330
|
+
border-bottom-right-radius: 0;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
&__selection {
|
|
334
|
+
display: flex;
|
|
335
|
+
align-items: center;
|
|
336
|
+
background: rgba(var(--w-contrast-bg-color-rgb), 0.035);
|
|
337
|
+
border: 1px solid rgba(var(--w-contrast-bg-color-rgb), 0.05);
|
|
338
|
+
border-radius: $border-radius;
|
|
339
|
+
padding: 0 2px 0 4px;
|
|
340
|
+
flex-shrink: 0;
|
|
341
|
+
|
|
342
|
+
span {margin-top: -1px;line-height: 1;}
|
|
343
|
+
.w-button .w-icon:before {font-size: 0.8em;line-height: 0;}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
&__input {
|
|
347
|
+
min-width: 0;
|
|
348
|
+
flex: 1 1 0;
|
|
349
|
+
color: inherit;
|
|
350
|
+
border: none;
|
|
351
|
+
background-color: transparent;
|
|
352
|
+
line-height: 18px;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
&__placeholder {
|
|
356
|
+
color: rgba(var(--w-base-color-rgb), 0.5);
|
|
357
|
+
pointer-events: none;
|
|
358
|
+
line-height: 18px;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
&__menu {
|
|
362
|
+
position: absolute;
|
|
363
|
+
inset: 100% -1px auto;
|
|
364
|
+
max-height: clamp(20px, 400px, 80vh);
|
|
365
|
+
margin-top: -1px;
|
|
366
|
+
margin-left: 0;
|
|
367
|
+
background-color: $base-bg-color;
|
|
368
|
+
border: 1px solid rgba(var(--w-contrast-bg-color-rgb), 0.2);
|
|
369
|
+
border-top: none;
|
|
370
|
+
border-bottom-left-radius: $border-radius;
|
|
371
|
+
border-bottom-right-radius: $border-radius;
|
|
372
|
+
overflow: auto;
|
|
373
|
+
z-index: 10;
|
|
374
|
+
|
|
375
|
+
li {
|
|
376
|
+
position: relative;
|
|
377
|
+
list-style-type: none;
|
|
378
|
+
margin: 0;
|
|
379
|
+
padding: 4px 8px;
|
|
380
|
+
|
|
381
|
+
&:hover {background-color: rgba($primary, 0.1);}
|
|
382
|
+
|
|
383
|
+
&:before, &:after {
|
|
384
|
+
content: '';
|
|
385
|
+
position: absolute;
|
|
386
|
+
inset: 0;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
&.highlighted:before {
|
|
390
|
+
border-left: 2px solid transparent;
|
|
391
|
+
border-left-color: $primary;
|
|
392
|
+
opacity: 0.3;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
&.highlighted:after {
|
|
396
|
+
background-color: $primary;
|
|
397
|
+
opacity: 0.1;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
li.w-autocomplete__no-match--default:hover {background-color: transparent;}
|
|
404
|
+
</style>
|
|
@@ -199,6 +199,7 @@ $spinner-size: 40;
|
|
|
199
199
|
aspect-ratio: 1;
|
|
200
200
|
border-radius: 99em;
|
|
201
201
|
padding: 0;
|
|
202
|
+
min-width: 0; // Safari ratio fix (e.g. losing ratio if height is set and side padding are added).
|
|
202
203
|
}
|
|
203
204
|
&--tile {border-radius: initial;}
|
|
204
205
|
&--shadow {box-shadow: $box-shadow;}
|
|
@@ -235,10 +236,7 @@ $spinner-size: 40;
|
|
|
235
236
|
&:before {
|
|
236
237
|
content: '';
|
|
237
238
|
position: absolute;
|
|
238
|
-
|
|
239
|
-
left: 0;
|
|
240
|
-
right: 0;
|
|
241
|
-
bottom: 0;
|
|
239
|
+
inset: 0;
|
|
242
240
|
opacity: 0;
|
|
243
241
|
background-color: #000;
|
|
244
242
|
border-radius: inherit;
|
|
@@ -301,10 +299,7 @@ $spinner-size: 40;
|
|
|
301
299
|
|
|
302
300
|
&__loader {
|
|
303
301
|
position: absolute;
|
|
304
|
-
|
|
305
|
-
bottom: 0;
|
|
306
|
-
left: 0;
|
|
307
|
-
right: 0;
|
|
302
|
+
inset: 0;
|
|
308
303
|
display: flex;
|
|
309
304
|
align-items: center;
|
|
310
305
|
justify-content: center;
|
|
@@ -97,6 +97,8 @@ export default {
|
|
|
97
97
|
padding: (2 * $base-increment) (3 * $base-increment);
|
|
98
98
|
font-size: 1.3em;
|
|
99
99
|
border-bottom: $border;
|
|
100
|
+
border-top-left-radius: inherit;
|
|
101
|
+
border-top-right-radius: inherit;
|
|
100
102
|
|
|
101
103
|
&--has-toolbar {padding: 0;border-bottom: none;}
|
|
102
104
|
}
|
|
@@ -199,10 +199,7 @@ export default {
|
|
|
199
199
|
|
|
200
200
|
&--absolute {
|
|
201
201
|
position: absolute;
|
|
202
|
-
|
|
203
|
-
left: 0;
|
|
204
|
-
bottom: 0;
|
|
205
|
-
right: 0;
|
|
202
|
+
inset: 0;
|
|
206
203
|
overflow: hidden;
|
|
207
204
|
}
|
|
208
205
|
|
|
@@ -218,10 +215,7 @@ export default {
|
|
|
218
215
|
|
|
219
216
|
.w-overlay {
|
|
220
217
|
position: absolute;
|
|
221
|
-
|
|
222
|
-
bottom: 0;
|
|
223
|
-
left: 0;
|
|
224
|
-
right: 0;
|
|
218
|
+
inset: 0;
|
|
225
219
|
z-index: 2;
|
|
226
220
|
}
|
|
227
221
|
.w-drawer {position: absolute;}
|
|
@@ -111,7 +111,7 @@ export default {
|
|
|
111
111
|
line-height: 1;
|
|
112
112
|
font-size: 1.2em;
|
|
113
113
|
width: 1em;
|
|
114
|
-
// The aspect ratio will not work if the content
|
|
114
|
+
// The aspect ratio will not work if the content overflows (needs overflow hidden, but we don't want that in the library).
|
|
115
115
|
height: 1em;
|
|
116
116
|
|
|
117
117
|
&.size--xs {font-size: round(0.85 * $base-font-size);}
|
|
@@ -196,19 +196,13 @@ export default {
|
|
|
196
196
|
background-repeat: no-repeat;
|
|
197
197
|
background-size: cover;
|
|
198
198
|
position: absolute;
|
|
199
|
-
|
|
200
|
-
left: 0;
|
|
201
|
-
right: 0;
|
|
202
|
-
bottom: 0;
|
|
199
|
+
inset: 0;
|
|
203
200
|
|
|
204
201
|
&--contain {background-size: contain;}
|
|
205
202
|
|
|
206
203
|
&__loader, &__content {
|
|
207
204
|
position: absolute;
|
|
208
|
-
|
|
209
|
-
left: 0;
|
|
210
|
-
right: 0;
|
|
211
|
-
bottom: 0;
|
|
205
|
+
inset: 0;
|
|
212
206
|
display: flex;
|
|
213
207
|
justify-content: center;
|
|
214
208
|
align-items: center;
|
|
@@ -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'"
|
|
@@ -75,17 +76,17 @@ component(
|
|
|
75
76
|
span.filename(:key="`${i}b`") {{ file.base }}
|
|
76
77
|
| {{ file.extension ? `.${file.extension}` : '' }}
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
label.w-input__label.w-input__label--inside.w-form-el-shakable(
|
|
80
|
+
v-if="labelPosition === 'inside' && showLabelInside && ($slots.default || label)"
|
|
81
|
+
:class="labelClasses")
|
|
82
|
+
slot {{ label }}
|
|
83
|
+
|
|
84
|
+
slot(name="icon-right" :input-id="`w-input--${_uid}`")
|
|
85
|
+
w-icon.w-input__icon.w-input__icon--inner-right(
|
|
86
|
+
v-if="innerIconRight"
|
|
87
|
+
tag="label"
|
|
81
88
|
:for="`w-input--${_uid}`"
|
|
82
|
-
|
|
83
|
-
slot {{ label }}
|
|
84
|
-
w-icon.w-input__icon.w-input__icon--inner-right(
|
|
85
|
-
v-if="innerIconRight"
|
|
86
|
-
tag="label"
|
|
87
|
-
:for="`w-input--${_uid}`"
|
|
88
|
-
@click="$emit('click:inner-icon-right', $event)") {{ innerIconRight }}
|
|
89
|
+
@click="$emit('click:inner-icon-right', $event)") {{ innerIconRight }}
|
|
89
90
|
|
|
90
91
|
w-progress.fill-width(
|
|
91
92
|
v-if="hasLoading || (showProgress && (uploadInProgress || uploadComplete))"
|
|
@@ -229,7 +230,7 @@ export default {
|
|
|
229
230
|
overallFilesProgress () {
|
|
230
231
|
const progress = +this.inputFiles.reduce((total, file) => total + file.progress, 0)
|
|
231
232
|
const total = progress / this.inputFiles.length
|
|
232
|
-
this.$emit('update:overallProgress', this.inputFiles.length ? total :
|
|
233
|
+
this.$emit('update:overallProgress', this.inputFiles.length ? total : 0)
|
|
233
234
|
|
|
234
235
|
return total
|
|
235
236
|
},
|