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.
Files changed (32) hide show
  1. package/dist/wave-ui.cjs.js +1 -1
  2. package/dist/wave-ui.css +1 -1
  3. package/dist/wave-ui.es.js +390 -69
  4. package/dist/wave-ui.umd.js +1 -1
  5. package/package.json +13 -13
  6. package/src/wave-ui/components/index.js +1 -0
  7. package/src/wave-ui/components/transitions/w-transition-expand.vue +2 -4
  8. package/src/wave-ui/components/w-accordion.vue +1 -4
  9. package/src/wave-ui/components/w-alert.vue +1 -4
  10. package/src/wave-ui/components/w-autocomplete.vue +404 -0
  11. package/src/wave-ui/components/w-badge.vue +1 -0
  12. package/src/wave-ui/components/w-button/button.vue +3 -8
  13. package/src/wave-ui/components/w-card.vue +2 -0
  14. package/src/wave-ui/components/w-drawer.vue +2 -8
  15. package/src/wave-ui/components/w-icon.vue +1 -1
  16. package/src/wave-ui/components/w-image.vue +2 -8
  17. package/src/wave-ui/components/w-input.vue +17 -16
  18. package/src/wave-ui/components/w-list.vue +1 -4
  19. package/src/wave-ui/components/w-notification-manager.vue +3 -3
  20. package/src/wave-ui/components/w-progress.vue +2 -8
  21. package/src/wave-ui/components/w-rating.vue +1 -4
  22. package/src/wave-ui/components/w-select.vue +66 -34
  23. package/src/wave-ui/components/w-slider.vue +6 -5
  24. package/src/wave-ui/components/w-spinner.vue +2 -0
  25. package/src/wave-ui/components/w-switch.vue +1 -1
  26. package/src/wave-ui/components/w-table.vue +220 -210
  27. package/src/wave-ui/components/w-tabs/index.vue +1 -4
  28. package/src/wave-ui/components/w-tag.vue +1 -4
  29. package/src/wave-ui/components/w-textarea.vue +0 -1
  30. package/src/wave-ui/components/w-timeline.vue +1 -0
  31. package/src/wave-ui/components/w-tree.vue +7 -7
  32. 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.69.0",
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.2",
54
- "@babel/eslint-parser": "^7.22.15",
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.2.0",
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.16",
59
+ "autoprefixer": "^10.4.17",
60
60
  "axios": "^0.21.4",
61
- "core-js": "^3.33.2",
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.0",
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.2",
70
+ "gsap": "^3.12.5",
71
71
  "ionicons": "^4.6.3",
72
72
  "material-design-icons": "^3.0.1",
73
- "postcss": "^8.4.31",
73
+ "postcss": "^8.4.35",
74
74
  "pug": "^3.0.2",
75
75
  "rollup-plugin-delete": "^2.0.0",
76
- "sass": "^1.69.5",
76
+ "sass": "^1.71.0",
77
77
  "simple-syntax-highlighter": "^1.6.2",
78
78
  "splitpanes": "^2.4.1",
79
- "vite": "^2.9.16",
79
+ "vite": "^2.9.17",
80
80
  "vite-plugin-vue2": "^1.9.3",
81
- "vue": "^2.7.15",
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.15",
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, done) {
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, done) {
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)
@@ -207,10 +207,7 @@ export default {
207
207
  &:before {
208
208
  content: '';
209
209
  position: absolute;
210
- top: 0;
211
- left: 0;
212
- right: 0;
213
- bottom: 0;
210
+ inset: 0;
214
211
  background-color: currentColor;
215
212
  opacity: 0;
216
213
  transition: $fast-transition-duration;
@@ -156,10 +156,7 @@ export default {
156
156
  // ------------------------------------------------------
157
157
  &:before, &:after {
158
158
  position: absolute;
159
- top: 0;
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>
@@ -113,6 +113,7 @@ export default {
113
113
 
114
114
  &--round {
115
115
  aspect-ratio: 1;
116
+ min-width: 0; // Safari ratio fix (e.g. losing ratio if height is set and side padding are added).
116
117
  padding: 0;
117
118
  }
118
119
 
@@ -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
- top: 0;
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
- top: 0;
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
- top: 0;
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
- top: 0;
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 is the content overflows.
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
- top: 0;
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
- top: 0;
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
- w-icon.w-input__icon.w-input__icon--inner-left(
24
- v-if="innerIconLeft"
25
- tag="label"
26
- :for="`w-input--${_uid}`"
27
- @click="$emit('click:inner-icon-left', $event)") {{ innerIconLeft }}
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
- template(v-if="labelPosition === 'inside' && showLabelInside")
79
- label.w-input__label.w-input__label--inside.w-form-el-shakable(
80
- v-if="$slots.default || label"
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
- :class="labelClasses")
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 : undefined)
233
+ this.$emit('update:overallProgress', this.inputFiles.length ? total : 0)
233
234
 
234
235
  return total
235
236
  },