wave-ui 1.65.2 → 1.66.1
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": "wave-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.66.1",
|
|
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",
|
|
@@ -2,56 +2,79 @@
|
|
|
2
2
|
.w-tabs(:class="tabsClasses")
|
|
3
3
|
.w-tabs__bar(ref="tabs-bar" :class="tabsBarClasses")
|
|
4
4
|
.w-tabs__bar-item(
|
|
5
|
-
v-for="(
|
|
5
|
+
v-for="(tab, i) in tabs"
|
|
6
6
|
:key="i"
|
|
7
|
-
:class="barItemClasses(
|
|
8
|
-
@click="!
|
|
9
|
-
@focus="$emit('focus', getOriginalItem(
|
|
10
|
-
:tabindex="!
|
|
11
|
-
@keypress.enter="!
|
|
12
|
-
:aria-selected="
|
|
7
|
+
:class="barItemClasses(tab)"
|
|
8
|
+
@click="!tab._disabled && tab._uid !== activeTabUid && openTab(tab._uid)"
|
|
9
|
+
@focus="$emit('focus', getOriginalItem(tab))"
|
|
10
|
+
:tabindex="!tab._disabled && 0"
|
|
11
|
+
@keypress.enter="!tab._disabled && openTab(tab._uid)"
|
|
12
|
+
:aria-selected="tab._uid === activeTabUid ? 'true' : 'false'"
|
|
13
13
|
role="tab")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
slot(
|
|
15
|
+
v-if="$scopedSlots[`item-title.${tab.id || i + 1}`]"
|
|
16
|
+
:name="`item-title.${tab.id || i + 1}`"
|
|
17
|
+
:item="getOriginalItem(tab)"
|
|
18
|
+
:index="i + 1"
|
|
19
|
+
:active="tab._uid === activeTabUid")
|
|
20
|
+
slot(
|
|
21
|
+
v-else
|
|
22
|
+
name="item-title"
|
|
23
|
+
:item="getOriginalItem(tab)"
|
|
24
|
+
:index="i + 1"
|
|
25
|
+
:active="tab._uid === activeTabUid")
|
|
26
|
+
div(v-html="tab[itemTitleKey]")
|
|
27
27
|
.w-tabs__bar-extra(v-if="$scopedSlots['tabs-bar-extra']")
|
|
28
28
|
slot(name="tabs-bar-extra")
|
|
29
29
|
.w-tabs__slider(v-if="!noSlider && !card" :class="sliderColor" :style="sliderStyles")
|
|
30
30
|
|
|
31
|
-
.w-tabs__content-wrap(v-if="
|
|
31
|
+
transition-group.w-tabs__content-wrap(v-if="keepInDom" :name="transitionName" tag="div")
|
|
32
|
+
tab-content(
|
|
33
|
+
v-for="(tab, i) in tabs"
|
|
34
|
+
:key="tab._uid"
|
|
35
|
+
:item="tab"
|
|
36
|
+
v-show="tab._uid === activeTab._uid"
|
|
37
|
+
:class="contentClass")
|
|
38
|
+
slot(
|
|
39
|
+
v-if="$scopedSlots[`item-content.${tab._index + 1}`]"
|
|
40
|
+
:name="`item-content.${tab._index + 1}`"
|
|
41
|
+
:item="getOriginalItem(tab)"
|
|
42
|
+
:index="tab._index + 1"
|
|
43
|
+
:active="tab._index === activeTab._index")
|
|
44
|
+
slot(
|
|
45
|
+
v-else
|
|
46
|
+
name="item-content"
|
|
47
|
+
:item="getOriginalItem(tab)"
|
|
48
|
+
:index="tab._index + 1"
|
|
49
|
+
:active="tab._index === activeTab._index")
|
|
50
|
+
div(v-if="tab[itemContentKey]" v-html="tab[itemContentKey]")
|
|
51
|
+
.w-tabs__content-wrap(v-else)
|
|
32
52
|
transition(:name="transitionName" :mode="transitionMode")
|
|
33
|
-
keep-alive
|
|
53
|
+
keep-alive(:exclude="keepAlive ? '' : 'tab-content'")
|
|
34
54
|
//- Keep-alive only works with components, not with DOM nodes.
|
|
35
|
-
tab-content(:
|
|
36
|
-
|
|
37
|
-
v-if="
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
tab-content(:key="activeTabUid" :item="activeTab" :class="contentClass")
|
|
56
|
+
template(#default="{ item }")
|
|
57
|
+
template(v-if="item")
|
|
58
|
+
slot(
|
|
59
|
+
v-if="$scopedSlots[`item-content.${item._index + 1}`]"
|
|
60
|
+
:name="`item-content.${item._index + 1}`"
|
|
61
|
+
:item="getOriginalItem(item)"
|
|
62
|
+
:index="item._index + 1"
|
|
63
|
+
:active="item._uid === activeTabUid")
|
|
64
|
+
slot(
|
|
65
|
+
v-else
|
|
66
|
+
name="item-content"
|
|
67
|
+
:item="getOriginalItem(item)"
|
|
68
|
+
:index="item._index + 1"
|
|
69
|
+
:active="item._uid === activeTabUid")
|
|
70
|
+
div(v-if="item[itemContentKey]" v-html="item[itemContentKey]")
|
|
49
71
|
</template>
|
|
50
72
|
|
|
51
73
|
<script>
|
|
52
|
-
import Vue from 'vue'
|
|
53
74
|
import TabContent from './tab-content.vue'
|
|
54
75
|
|
|
76
|
+
let uid = 0
|
|
77
|
+
|
|
55
78
|
export default {
|
|
56
79
|
name: 'w-tabs',
|
|
57
80
|
|
|
@@ -60,6 +83,7 @@ export default {
|
|
|
60
83
|
color: { type: String },
|
|
61
84
|
bgColor: { type: String },
|
|
62
85
|
items: { type: [Array, Number] },
|
|
86
|
+
itemIdKey: { type: String, default: 'id' },
|
|
63
87
|
itemTitleKey: { type: String, default: 'title' },
|
|
64
88
|
itemContentKey: { type: String, default: 'content' },
|
|
65
89
|
titleClass: { type: String },
|
|
@@ -72,7 +96,9 @@ export default {
|
|
|
72
96
|
fillBar: { type: Boolean },
|
|
73
97
|
center: { type: Boolean },
|
|
74
98
|
right: { type: Boolean },
|
|
75
|
-
card: { type: Boolean }
|
|
99
|
+
card: { type: Boolean },
|
|
100
|
+
keepAlive: { type: Boolean, default: true },
|
|
101
|
+
keepInDom: { type: Boolean, default: false }
|
|
76
102
|
},
|
|
77
103
|
|
|
78
104
|
components: { TabContent },
|
|
@@ -80,7 +106,9 @@ export default {
|
|
|
80
106
|
emits: ['input', 'update:modelValue', 'focus'],
|
|
81
107
|
|
|
82
108
|
data: () => ({
|
|
109
|
+
tabs: [],
|
|
83
110
|
activeTabEl: null,
|
|
111
|
+
activeTabUid: null,
|
|
84
112
|
activeTabIndex: 0,
|
|
85
113
|
prevTabIndex: -1, // To detect transition direction.
|
|
86
114
|
slider: {
|
|
@@ -104,19 +132,13 @@ export default {
|
|
|
104
132
|
return this.activeTab._index < this.prevTabIndex ? 'right' : 'left'
|
|
105
133
|
},
|
|
106
134
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// eslint-disable-next-line new-cap
|
|
111
|
-
return items.map((item, _index) => new Vue.observable({
|
|
112
|
-
...item,
|
|
113
|
-
_index,
|
|
114
|
-
_disabled: !!item.disabled
|
|
115
|
-
}))
|
|
135
|
+
activeTab () {
|
|
136
|
+
return this.tabsByUid[this.activeTabUid] || this.tabs[0] || {}
|
|
116
137
|
},
|
|
117
138
|
|
|
118
|
-
|
|
119
|
-
|
|
139
|
+
// An object indexing the tabs by their uid.
|
|
140
|
+
tabsByUid () {
|
|
141
|
+
return this.tabs.reduce((obj, tab) => ((obj[tab._uid] = tab) && obj), {})
|
|
120
142
|
},
|
|
121
143
|
|
|
122
144
|
tabsClasses () {
|
|
@@ -145,6 +167,54 @@ export default {
|
|
|
145
167
|
},
|
|
146
168
|
|
|
147
169
|
methods: {
|
|
170
|
+
// Adding a tab in the list.
|
|
171
|
+
addTab (item) {
|
|
172
|
+
// If there is no unique ID provided, inject one in each tab.
|
|
173
|
+
// This will cause a single other update from watching the tabs items and stop there.
|
|
174
|
+
if (!(item[this.itemIdKey] ?? item._uid ?? false)) item._uid = +`${this._uid}${++uid}`
|
|
175
|
+
|
|
176
|
+
this.tabs.push({
|
|
177
|
+
_uid: item[this.itemIdKey] ?? item._uid,
|
|
178
|
+
_index: this.tabs.length,
|
|
179
|
+
...item,
|
|
180
|
+
_disabled: !!item.disabled
|
|
181
|
+
})
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
refreshTabs () {
|
|
185
|
+
let items = this.items
|
|
186
|
+
if (typeof items === 'number') items = Array(items).fill().map((_, i) => this.tabs[i] || {})
|
|
187
|
+
|
|
188
|
+
this.tabs = items.map((item, _index) => {
|
|
189
|
+
// If there is no unique ID provided, inject one in each tab.
|
|
190
|
+
// This will cause a single other update from watching the tabs items and stop there.
|
|
191
|
+
if (!(item[this.itemIdKey] ?? item._uid ?? false)) item._uid = +`${this._uid}${++uid}`
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
...item,
|
|
195
|
+
_uid: item[this.itemIdKey] ?? item._uid,
|
|
196
|
+
_index,
|
|
197
|
+
_disabled: !!item.disabled
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
reopenTheActiveTab () {
|
|
203
|
+
// If there is only 1 tab left open it.
|
|
204
|
+
if (this.tabs.length === 1) return this.openTab(this.tabs[0]._uid)
|
|
205
|
+
|
|
206
|
+
// First try to find the same uid in remaining tabs.
|
|
207
|
+
let uid = this.tabsByUid[this.activeTabUid]?._uid
|
|
208
|
+
|
|
209
|
+
// If not found, try to open the tab with the same index.
|
|
210
|
+
if (!uid) uid = this.tabs[this.activeTabIndex]?._uid
|
|
211
|
+
|
|
212
|
+
// If not found (no tab to the right), try to open the next tab to the left.
|
|
213
|
+
if (!uid) uid = this.tabs[Math.max(this.activeTabIndex - 1, this.tabs.length - 1)]?._uid
|
|
214
|
+
|
|
215
|
+
if (uid) this.openTab(uid)
|
|
216
|
+
},
|
|
217
|
+
|
|
148
218
|
onResize () {
|
|
149
219
|
this.updateSlider(false)
|
|
150
220
|
},
|
|
@@ -161,11 +231,14 @@ export default {
|
|
|
161
231
|
}
|
|
162
232
|
},
|
|
163
233
|
|
|
164
|
-
|
|
234
|
+
// Switching tabs.
|
|
235
|
+
openTab (uid) {
|
|
165
236
|
this.prevTabIndex = this.activeTabIndex // To resolve the transition direction.
|
|
166
|
-
|
|
167
|
-
this
|
|
168
|
-
this
|
|
237
|
+
const tab = this.tabsByUid[uid]
|
|
238
|
+
this.activeTabIndex = tab._index
|
|
239
|
+
this.activeTabUid = tab._uid
|
|
240
|
+
this.$emit('update:modelValue', tab._index)
|
|
241
|
+
this.$emit('input', tab._index)
|
|
169
242
|
|
|
170
243
|
if (!this.noSlider) this.$nextTick(this.updateSlider)
|
|
171
244
|
},
|
|
@@ -173,7 +246,8 @@ export default {
|
|
|
173
246
|
// Updates the slider position.
|
|
174
247
|
updateSlider (domLookup = true) {
|
|
175
248
|
if (domLookup) {
|
|
176
|
-
|
|
249
|
+
const ref = this.$refs['tabs-bar']
|
|
250
|
+
this.activeTabEl = ref && ref.querySelector('.w-tabs__bar-item--active')
|
|
177
251
|
}
|
|
178
252
|
|
|
179
253
|
if (!this.fillBar && this.activeTabEl) {
|
|
@@ -185,34 +259,42 @@ export default {
|
|
|
185
259
|
this.slider.width = `${width}px`
|
|
186
260
|
}
|
|
187
261
|
else {
|
|
188
|
-
this.slider.left = `${this.activeTab._index * 100 / this.
|
|
189
|
-
this.slider.width = `${100 / this.
|
|
262
|
+
this.slider.left = `${this.activeTab._index * 100 / this.tabs.length}%`
|
|
263
|
+
this.slider.width = `${100 / this.tabs.length}%`
|
|
190
264
|
}
|
|
191
265
|
},
|
|
192
266
|
|
|
193
267
|
updateActiveTab (index) {
|
|
194
268
|
if (typeof index === 'string') index = ~~index
|
|
195
269
|
else if (isNaN(index) || index < 0) index = 0
|
|
196
|
-
this.activeTabIndex = index
|
|
197
270
|
|
|
198
|
-
//
|
|
199
|
-
this
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
271
|
+
// Only open the tab if it is found.
|
|
272
|
+
if (this.tabs[index]?._uid) {
|
|
273
|
+
this.openTab(this.tabs[index]?._uid)
|
|
274
|
+
|
|
275
|
+
// Scroll the new active tab item title into view if needed.
|
|
276
|
+
this.$nextTick(() => {
|
|
277
|
+
const ref = this.$refs['tabs-bar']
|
|
278
|
+
this.activeTabEl = ref && ref.querySelector(`.w-tabs__bar-item:nth-child(${index + 1})`)
|
|
279
|
+
if (this.activeTabEl) {
|
|
280
|
+
this.activeTabEl.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' })
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
}
|
|
206
284
|
},
|
|
207
285
|
|
|
208
286
|
// Return the original item (so there is no `_index`, etc.).
|
|
209
287
|
getOriginalItem (item) {
|
|
210
|
-
return this.items[item._index]
|
|
288
|
+
return this.items[item._index] || {}
|
|
211
289
|
}
|
|
212
290
|
},
|
|
213
291
|
|
|
214
292
|
beforeMount () {
|
|
215
|
-
this.
|
|
293
|
+
this.tabs = [] // Reset for hot-reloading.
|
|
294
|
+
const items = typeof this.items === 'number' ? Array(this.items).fill().map(Object) : this.items
|
|
295
|
+
items.forEach(this.addTab)
|
|
296
|
+
|
|
297
|
+
if (this.value ?? false) this.updateActiveTab(this.value)
|
|
216
298
|
|
|
217
299
|
this.$nextTick(() => {
|
|
218
300
|
this.updateSlider()
|
|
@@ -229,13 +311,17 @@ export default {
|
|
|
229
311
|
|
|
230
312
|
watch: {
|
|
231
313
|
value (index) {
|
|
232
|
-
this.updateActiveTab(index)
|
|
314
|
+
if (index !== this.activeTabIndex) this.updateActiveTab(index)
|
|
233
315
|
},
|
|
234
|
-
items
|
|
235
|
-
|
|
236
|
-
|
|
316
|
+
items: {
|
|
317
|
+
handler () {
|
|
318
|
+
this.refreshTabs()
|
|
237
319
|
|
|
238
|
-
|
|
320
|
+
if (this.tabs.length) this.reopenTheActiveTab()
|
|
321
|
+
|
|
322
|
+
if (!this.noSlider) this.$nextTick(this.updateSlider)
|
|
323
|
+
},
|
|
324
|
+
deep: true
|
|
239
325
|
},
|
|
240
326
|
fillBar () {
|
|
241
327
|
if (!this.noSlider) this.$nextTick(this.updateSlider)
|
|
@@ -374,8 +460,10 @@ export default {
|
|
|
374
460
|
.w-tabs-slide-left-leave-active,
|
|
375
461
|
.w-tabs-slide-right-leave-active {
|
|
376
462
|
position: absolute;
|
|
463
|
+
top: 0;
|
|
377
464
|
left: 0;
|
|
378
465
|
right: 0;
|
|
466
|
+
overflow: hidden;
|
|
379
467
|
}
|
|
380
468
|
|
|
381
469
|
.w-tabs-slide-left-enter-active {animation: w-tabs-slide-left-enter $transition-duration + 0.15s;}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
2
|
.w-tabs__content
|
|
3
|
-
slot
|
|
3
|
+
slot(:item="item")
|
|
4
4
|
</template>
|
|
5
5
|
|
|
6
6
|
<script>
|
|
7
7
|
// Keep-alive only works with components, not with DOM nodes.
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
name: 'tab-content', // Keep-alive include/exclude mechanism is component-name-based.
|
|
11
|
+
|
|
12
|
+
props: { item: Object }
|
|
13
|
+
}
|
|
8
14
|
</script>
|