wave-ui 1.61.0 → 1.63.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 +649 -511
- package/dist/wave-ui.umd.js +1 -1
- package/package.json +1 -1
- package/src/wave-ui/components/index.js +1 -0
- package/src/wave-ui/components/transitions/w-transition-expand.vue +26 -15
- package/src/wave-ui/components/w-confirm.vue +4 -1
- package/src/wave-ui/components/w-list.vue +5 -2
- package/src/wave-ui/components/w-notification-manager.vue +2 -3
- package/src/wave-ui/components/w-scrollbar.vue +24 -0
- package/src/wave-ui/components/w-select.vue +9 -4
- package/src/wave-ui/components/w-switch.vue +1 -1
- package/src/wave-ui/components/w-table.vue +93 -8
- package/src/wave-ui/components/w-tree.vue +56 -19
- package/src/wave-ui/core.js +45 -69
- package/src/wave-ui/scss/_layout.scss +1 -1
- package/src/wave-ui/utils/colors.js +56 -2
- package/src/wave-ui/utils/config.js +17 -8
- package/src/wave-ui/utils/dynamic-css.js +2 -2
- package/src/wave-ui/utils/notification-manager.js +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.63.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",
|
|
@@ -28,6 +28,7 @@ export { default as WProgress } from './w-progress.vue'
|
|
|
28
28
|
export { default as WRadio } from './w-radio.vue'
|
|
29
29
|
export { default as WRadios } from './w-radios.vue'
|
|
30
30
|
export { default as WRating } from './w-rating.vue'
|
|
31
|
+
export { default as WScrollbar } from './w-scrollbar.vue'
|
|
31
32
|
export { default as WSelect } from './w-select.vue'
|
|
32
33
|
export { default as WSlider } from './w-slider.vue'
|
|
33
34
|
export { default as WSpinner } from './w-spinner.vue'
|
|
@@ -27,6 +27,7 @@ export default {
|
|
|
27
27
|
|
|
28
28
|
data: () => ({
|
|
29
29
|
el: {
|
|
30
|
+
savedState: false,
|
|
30
31
|
originalStyles: '',
|
|
31
32
|
width: 0,
|
|
32
33
|
height: 0,
|
|
@@ -59,7 +60,7 @@ export default {
|
|
|
59
60
|
beforeAppear (el) {
|
|
60
61
|
// Only save original state once before a 'clean' transition start.
|
|
61
62
|
// Not when clicking very fast and mixing states order.
|
|
62
|
-
if (this.cleanTransitionCycle) this.
|
|
63
|
+
if (this.cleanTransitionCycle) this.saveOriginalInlineStyles(el)
|
|
63
64
|
this.cleanTransitionCycle = false
|
|
64
65
|
},
|
|
65
66
|
appear (el, done) {
|
|
@@ -76,7 +77,7 @@ export default {
|
|
|
76
77
|
beforeEnter (el) {
|
|
77
78
|
// Only save original state once before a 'clean' transition start.
|
|
78
79
|
// Not when clicking very fast and mixing states order.
|
|
79
|
-
if (this.cleanTransitionCycle) this.
|
|
80
|
+
if (this.cleanTransitionCycle) this.saveOriginalInlineStyles(el)
|
|
80
81
|
this.cleanTransitionCycle = false
|
|
81
82
|
},
|
|
82
83
|
enter (el, done) {
|
|
@@ -91,6 +92,9 @@ export default {
|
|
|
91
92
|
this.cleanTransitionCycle = false
|
|
92
93
|
},
|
|
93
94
|
beforeLeave (el) {
|
|
95
|
+
// When starting with an open item.
|
|
96
|
+
if (!this.el.savedState) this.saveComputedStyles(el)
|
|
97
|
+
|
|
94
98
|
this.beforeHide(el)
|
|
95
99
|
this.cleanTransitionCycle = false
|
|
96
100
|
},
|
|
@@ -102,6 +106,9 @@ export default {
|
|
|
102
106
|
afterLeave (el) {
|
|
103
107
|
this.applyOriginalStyles(el)
|
|
104
108
|
this.cleanTransitionCycle = true
|
|
109
|
+
// Reset for recomputing the next time we start the transition from an open state.
|
|
110
|
+
// In case there might be some changed styles from last closing.
|
|
111
|
+
this.el.savedState = false
|
|
105
112
|
},
|
|
106
113
|
|
|
107
114
|
applyHideStyles (el) {
|
|
@@ -151,11 +158,25 @@ export default {
|
|
|
151
158
|
applyOriginalStyles (el) {
|
|
152
159
|
el.style.cssText = this.el.originalStyles
|
|
153
160
|
},
|
|
154
|
-
|
|
155
|
-
// Keep
|
|
161
|
+
saveOriginalInlineStyles (el) {
|
|
162
|
+
// Keep any original inline styles to restore them after transition.
|
|
156
163
|
this.el.originalStyles = el.style.cssText
|
|
157
164
|
},
|
|
158
165
|
show (el, done) {
|
|
166
|
+
this.saveComputedStyles(el)
|
|
167
|
+
this.applyHideStyles(el)
|
|
168
|
+
|
|
169
|
+
setTimeout(() => this.applyShowStyles(el), 20)
|
|
170
|
+
setTimeout(done, this.duration)
|
|
171
|
+
},
|
|
172
|
+
beforeHide (el) {
|
|
173
|
+
this.applyShowStyles(el)
|
|
174
|
+
},
|
|
175
|
+
hide (el, done) {
|
|
176
|
+
setTimeout(() => this.applyHideStyles(el), 20)
|
|
177
|
+
setTimeout(done, this.duration)
|
|
178
|
+
},
|
|
179
|
+
saveComputedStyles (el) {
|
|
159
180
|
const computedStyles = window.getComputedStyle(el, null)
|
|
160
181
|
|
|
161
182
|
// Save the width & height then set them to 0 as the animation starting point.
|
|
@@ -177,17 +198,7 @@ export default {
|
|
|
177
198
|
this.el.borderTopWidth = computedStyles.getPropertyValue('borderTopWidth')
|
|
178
199
|
this.el.borderBottomWidth = computedStyles.getPropertyValue('borderBottomWidth')
|
|
179
200
|
}
|
|
180
|
-
this.
|
|
181
|
-
|
|
182
|
-
setTimeout(() => this.applyShowStyles(el), 20)
|
|
183
|
-
setTimeout(done, this.duration)
|
|
184
|
-
},
|
|
185
|
-
beforeHide (el) {
|
|
186
|
-
this.applyShowStyles(el)
|
|
187
|
-
},
|
|
188
|
-
hide (el, done) {
|
|
189
|
-
setTimeout(() => this.applyHideStyles(el), 20)
|
|
190
|
-
setTimeout(done, this.duration)
|
|
201
|
+
this.el.savedState = true
|
|
191
202
|
}
|
|
192
203
|
}
|
|
193
204
|
}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
.w-confirm
|
|
3
3
|
w-menu(v-model="showPopup" v-bind="wMenuProps")
|
|
4
4
|
template(#activator="{ on }")
|
|
5
|
-
w-button.w-confirm__button(
|
|
5
|
+
w-button.w-confirm__button(
|
|
6
|
+
v-on="{ ...$listeners, ...(disablePrompt ? {} : on) }"
|
|
7
|
+
v-bind="buttonProps")
|
|
6
8
|
slot
|
|
7
9
|
w-flex(:column="!inline" align-center)
|
|
8
10
|
div
|
|
@@ -32,6 +34,7 @@ export default {
|
|
|
32
34
|
bgColor: { type: String },
|
|
33
35
|
color: { type: String },
|
|
34
36
|
icon: { type: String },
|
|
37
|
+
disablePrompt: { type: Boolean }, // If true, the confirm button acts like a simple w-button.
|
|
35
38
|
mainButton: { type: Object }, // Allow passing down an object of props to the w-button component.
|
|
36
39
|
question: { type: String, default: 'Are you sure?' },
|
|
37
40
|
|
|
@@ -302,7 +302,7 @@ export default {
|
|
|
302
302
|
// eslint-disable-next-line vue/custom-event-name-casing
|
|
303
303
|
else if (e.keyCode === 27) this.$emit('keydown:escape')
|
|
304
304
|
// On arrow keys press, navigate to prev/next item.
|
|
305
|
-
else if (this.arrowsNavigation) {
|
|
305
|
+
else if (this.arrowsNavigation && [38, 40].includes(e.keyCode)) {
|
|
306
306
|
e.preventDefault()
|
|
307
307
|
if (e.keyCode === 38) this.focusPrevNextItem(li._index, false)
|
|
308
308
|
if (e.keyCode === 40) this.focusPrevNextItem(li._index, true)
|
|
@@ -407,7 +407,10 @@ export default {
|
|
|
407
407
|
if (!this.isMultipleSelect) this.listItems.forEach(item => (item._selected = false))
|
|
408
408
|
|
|
409
409
|
this.checkSelection(selection) // Create an array with the selected values.
|
|
410
|
-
.forEach(val =>
|
|
410
|
+
.forEach(val => {
|
|
411
|
+
const foundItem = this.listItems.find(item => item._value === val)
|
|
412
|
+
if (foundItem) foundItem._selected = true
|
|
413
|
+
})
|
|
411
414
|
}
|
|
412
415
|
},
|
|
413
416
|
|
|
@@ -63,12 +63,12 @@ export default {
|
|
|
63
63
|
.w-notification-manager {
|
|
64
64
|
position: fixed;
|
|
65
65
|
top: 0;
|
|
66
|
-
|
|
66
|
+
max-height: 100%;
|
|
67
67
|
right: 0;
|
|
68
68
|
z-index: 1000;
|
|
69
|
-
pointer-events: none;
|
|
70
69
|
width: 280px;
|
|
71
70
|
overflow-x: hidden;
|
|
71
|
+
overflow-y: auto;
|
|
72
72
|
|
|
73
73
|
&--left {right: auto;left: 0;}
|
|
74
74
|
|
|
@@ -79,7 +79,6 @@ export default {
|
|
|
79
79
|
right: 0;
|
|
80
80
|
margin: 8px;
|
|
81
81
|
flex-grow: 1;
|
|
82
|
-
pointer-events: all;
|
|
83
82
|
}
|
|
84
83
|
}
|
|
85
84
|
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
.w-scrollbar
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script>
|
|
6
|
+
export default {
|
|
7
|
+
name: 'w-scrollbar',
|
|
8
|
+
props: {
|
|
9
|
+
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
emits: [],
|
|
13
|
+
|
|
14
|
+
data: () => ({
|
|
15
|
+
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<style lang="scss">
|
|
21
|
+
.w-scrollbar {
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
</style>
|
|
@@ -60,7 +60,7 @@ component(
|
|
|
60
60
|
v-for="(val, i) in (inputValue.length ? inputValue : [{}])"
|
|
61
61
|
:key="i"
|
|
62
62
|
type="hidden"
|
|
63
|
-
:value="val.value
|
|
63
|
+
:value="val.value === undefined ? '' : val.value.toString()"
|
|
64
64
|
:name="inputName + (multiple ? '[]' : '')")
|
|
65
65
|
template(v-if="labelPosition === 'inside' && showLabelInside")
|
|
66
66
|
label.w-select__label.w-select__label--inside.w-form-el-shakable(
|
|
@@ -299,7 +299,7 @@ export default {
|
|
|
299
299
|
// Also accept objects if returnObject is true.
|
|
300
300
|
// In any case, always end up with an array.
|
|
301
301
|
checkSelection (items) {
|
|
302
|
-
items = Array.isArray(items) ? items : (items ? [items] : [])
|
|
302
|
+
items = Array.isArray(items) ? items : (items !== undefined ? [items] : [])
|
|
303
303
|
// `selectItems` items always have a value.
|
|
304
304
|
const allValues = this.selectItems.map(item => item.value)
|
|
305
305
|
|
|
@@ -424,8 +424,10 @@ export default {
|
|
|
424
424
|
&__selection {
|
|
425
425
|
width: 100%;
|
|
426
426
|
height: 100%;
|
|
427
|
-
|
|
427
|
+
min-height: inherit;
|
|
428
|
+
font: inherit;
|
|
428
429
|
color: inherit;
|
|
430
|
+
text-align: inherit;
|
|
429
431
|
background: none;
|
|
430
432
|
border: none;
|
|
431
433
|
outline: none;
|
|
@@ -522,12 +524,15 @@ export default {
|
|
|
522
524
|
position: absolute;
|
|
523
525
|
top: 50%;
|
|
524
526
|
left: 0;
|
|
525
|
-
|
|
527
|
+
right: 0;
|
|
528
|
+
// Use margin instead of padding as the scale transformation below decreases the real padding
|
|
526
529
|
// size and misaligns the label.
|
|
527
530
|
margin-left: 2 * $base-increment;
|
|
528
531
|
transform: translateY(-50%);
|
|
529
532
|
pointer-events: none;
|
|
530
533
|
|
|
534
|
+
.w-select--inner-icon-right & {padding-right: 22px;}
|
|
535
|
+
|
|
531
536
|
.w-select--no-padding & {
|
|
532
537
|
left: 0;
|
|
533
538
|
margin-left: 0;
|
|
@@ -38,7 +38,7 @@ component(
|
|
|
38
38
|
v-if="loading"
|
|
39
39
|
circle
|
|
40
40
|
color="inherit"
|
|
41
|
-
v-bind="typeof loading === 'number' ? {
|
|
41
|
+
v-bind="typeof loading === 'number' ? { value: loading } : {}")
|
|
42
42
|
slot(v-else name="thumb")
|
|
43
43
|
template(v-if="hasLabel && !labelOnLeft")
|
|
44
44
|
label.w-switch__label.w-switch__label--right.w-form-el-shakable(
|
|
@@ -128,12 +128,34 @@
|
|
|
128
128
|
slot(name="extra-row")
|
|
129
129
|
|
|
130
130
|
//- Table footer.
|
|
131
|
-
tfoot.w-table__footer(v-if="$slots.footer || $slots['footer-row']")
|
|
131
|
+
tfoot.w-table__footer(v-if="$slots.footer || $slots['footer-row'] || pagination")
|
|
132
132
|
slot(v-if="$slots['footer-row']" name="footer-row")
|
|
133
|
-
tr.w-table__row(v-else)
|
|
133
|
+
tr.w-table__row(v-else-if="$slots.footer")
|
|
134
134
|
td.w-table__cell(:colspan="headers.length")
|
|
135
135
|
slot(name="footer")
|
|
136
|
-
|
|
136
|
+
tr.w-table__row.w-table__pagination-wrap(v-if="pagination && paginationConfig")
|
|
137
|
+
td.w-table__cell(:colspan="headers.length")
|
|
138
|
+
.w-table__pagination
|
|
139
|
+
w-select.pagination-number.pagination-number--items-per-page(
|
|
140
|
+
v-if="paginationConfig.itemsPerPageOptions"
|
|
141
|
+
v-model="paginationConfig.itemsPerPage"
|
|
142
|
+
:items="paginationConfig.itemsPerPageOptions"
|
|
143
|
+
label-position="left"
|
|
144
|
+
label="Items per page"
|
|
145
|
+
label-color="inherit")
|
|
146
|
+
span.pagination-number.pagination-number--results.
|
|
147
|
+
{{ paginationConfig.start }}-{{ paginationConfig.end }} of {{ paginationConfig.total }}
|
|
148
|
+
.pagination-arrows
|
|
149
|
+
w-button.pagination-arrow.pagination-arrow--prev(
|
|
150
|
+
@click="paginationConfig.page--"
|
|
151
|
+
icon="wi-chevron-left"
|
|
152
|
+
text
|
|
153
|
+
lg)
|
|
154
|
+
w-button.pagination-arrow.pagination-arrow--next(
|
|
155
|
+
@click="paginationConfig.page++"
|
|
156
|
+
icon="wi-chevron-right"
|
|
157
|
+
text
|
|
158
|
+
lg)
|
|
137
159
|
</template>
|
|
138
160
|
|
|
139
161
|
<script>
|
|
@@ -189,13 +211,28 @@ export default {
|
|
|
189
211
|
|
|
190
212
|
forceSelection: { type: Boolean },
|
|
191
213
|
|
|
192
|
-
// Useful to select or expand a row, and even after a filter, the same row will stay selected or
|
|
214
|
+
// Useful to select or expand a row, and even after a filter, the same row will stay selected or expanded.
|
|
193
215
|
uidKey: { type: String, default: 'id' },
|
|
194
216
|
|
|
195
217
|
filter: { type: Function },
|
|
196
218
|
sortFunction: { type: Function },
|
|
197
219
|
mobileBreakpoint: { type: Number, default: 0 },
|
|
198
|
-
resizableColumns: { type: Boolean }
|
|
220
|
+
resizableColumns: { type: Boolean },
|
|
221
|
+
|
|
222
|
+
pagination: {
|
|
223
|
+
type: [Boolean, Object, String],
|
|
224
|
+
validator: object => {
|
|
225
|
+
if (!object) return true // Accept any falsy value.
|
|
226
|
+
else if (typeof object === 'object' && (!object.itemsPerPage || (object.page && isNaN(object.page)))) {
|
|
227
|
+
consoleError(
|
|
228
|
+
'Wrong pagination config received in the w-table\'s `pagination` prop (received: `' + JSON.stringify(object) + '`). ' +
|
|
229
|
+
'\nExpected object: { itemsPerPage: Integer, page: Integer } or { itemsPerPage: Integer, start: Integer }.'
|
|
230
|
+
)
|
|
231
|
+
return false
|
|
232
|
+
}
|
|
233
|
+
return true
|
|
234
|
+
}
|
|
235
|
+
}
|
|
199
236
|
},
|
|
200
237
|
|
|
201
238
|
emits: [
|
|
@@ -222,7 +259,8 @@ export default {
|
|
|
222
259
|
nextColWidth: null,
|
|
223
260
|
columnEl: null,
|
|
224
261
|
nextColumnEl: null
|
|
225
|
-
}
|
|
262
|
+
},
|
|
263
|
+
paginationConfig: {}
|
|
226
264
|
}),
|
|
227
265
|
|
|
228
266
|
computed: {
|
|
@@ -490,6 +528,20 @@ export default {
|
|
|
490
528
|
this.colResizing.colWidth = null
|
|
491
529
|
this.colResizing.nextColWidth = null
|
|
492
530
|
}, 0)
|
|
531
|
+
},
|
|
532
|
+
|
|
533
|
+
updatePaginationConfig () {
|
|
534
|
+
const itemsPerPage = this.pagination?.itemsPerPage || 10
|
|
535
|
+
const total = this.pagination?.total || this.items.length
|
|
536
|
+
const page = this.pagination?.page || 1
|
|
537
|
+
this.paginationConfig = {
|
|
538
|
+
itemsPerPage,
|
|
539
|
+
itemsPerPageOptions: this.pagination?.itemsPerPageOptions || [{ label: '10', value: 10 }, { label: '100', value: 100 }, { label: 'All', value: 0 }],
|
|
540
|
+
page,
|
|
541
|
+
start: this.pagination?.start || 1,
|
|
542
|
+
end: total >= (itemsPerPage * page) ? (itemsPerPage * page) : (total % (itemsPerPage * page)),
|
|
543
|
+
total
|
|
544
|
+
}
|
|
493
545
|
}
|
|
494
546
|
},
|
|
495
547
|
|
|
@@ -499,6 +551,8 @@ export default {
|
|
|
499
551
|
|
|
500
552
|
if ((this.expandedRows || []).length) this.expandedRowsInternal = this.expandedRows
|
|
501
553
|
if ((this.selectedRows || []).length) this.selectedRowsInternal = this.selectedRows
|
|
554
|
+
|
|
555
|
+
if (this.pagination) this.updatePaginationConfig()
|
|
502
556
|
},
|
|
503
557
|
|
|
504
558
|
watch: {
|
|
@@ -739,14 +793,45 @@ $tr-border-top: 1px;
|
|
|
739
793
|
border-bottom: $border;
|
|
740
794
|
}
|
|
741
795
|
}
|
|
796
|
+
|
|
797
|
+
&__pagination {
|
|
798
|
+
display: flex;
|
|
799
|
+
align-items: center;
|
|
800
|
+
justify-content: flex-end;
|
|
801
|
+
padding-top: $base-increment;
|
|
802
|
+
padding-bottom: $base-increment;
|
|
803
|
+
|
|
804
|
+
.pagination-number--items-per-page {
|
|
805
|
+
margin-right: 6 * $base-increment;
|
|
806
|
+
flex-grow: 0;
|
|
807
|
+
text-align: right;
|
|
808
|
+
}
|
|
809
|
+
.pagination-number--of {
|
|
810
|
+
margin-left: $base-increment;
|
|
811
|
+
margin-right: $base-increment;
|
|
812
|
+
}
|
|
813
|
+
.w-select__selection {max-width: 60px;}
|
|
814
|
+
|
|
815
|
+
.pagination-arrows {
|
|
816
|
+
margin-left: 6 * $base-increment;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
742
819
|
}
|
|
743
820
|
|
|
744
821
|
// Mobile layout.
|
|
745
822
|
.w-table--mobile {
|
|
823
|
+
display: flex;
|
|
824
|
+
|
|
746
825
|
thead {display: none;}
|
|
747
|
-
|
|
826
|
+
tbody {
|
|
827
|
+
display: flex;
|
|
828
|
+
flex-direction: column;
|
|
829
|
+
flex-grow: 1;
|
|
830
|
+
}
|
|
748
831
|
tr {
|
|
749
|
-
display:
|
|
832
|
+
display: flex;
|
|
833
|
+
flex-direction: column;
|
|
834
|
+
flex-grow: 1;
|
|
750
835
|
padding-top: $base-increment;
|
|
751
836
|
padding-bottom: $base-increment;
|
|
752
837
|
}
|
|
@@ -4,22 +4,27 @@ ul.w-tree(:class="classes")
|
|
|
4
4
|
v-for="(item, i) in currentDepthItems"
|
|
5
5
|
:key="i"
|
|
6
6
|
:class="itemClasses(item)")
|
|
7
|
-
.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
//- The keys `route` & `disabled` are always present in any currentDepthItems.
|
|
8
|
+
component.w-tree__item-label(
|
|
9
|
+
:is="!disabled && !item.disabled && item.route ? (!$router || hasExternalLink(item) ? 'a' : 'router-link') : 'div'"
|
|
10
|
+
v-bind="item.route && { [!$router || hasExternalLink(item) ? 'href' : 'to']: item.route }"
|
|
11
|
+
@click="!disabled && !item.disabled && onLabelClick(item, $event)"
|
|
12
|
+
@keydown="!disabled && !item.disabled && onLabelKeydown(item, $event)"
|
|
13
|
+
:tabindex="!disabled && !item.disabled && (item.children || item.branch || selectable) && !(unexpandableEmpty && !item.children) ? 0 : null")
|
|
14
|
+
//- @click.stop to not follow link if item is a link.
|
|
11
15
|
w-button.w-tree__item-expand(
|
|
12
16
|
v-if="(item.children || item.branch) && ((expandOpenIcon && item.open) || expandIcon) && !(unexpandableEmpty && !item.children)"
|
|
17
|
+
@click.stop="!disabled && !item.disabled && onLabelClick(item, $event)"
|
|
13
18
|
color="inherit"
|
|
14
19
|
:icon="(item.open && expandOpenIcon) || expandIcon"
|
|
15
20
|
:icon-props="{ rotate90a: !item.open }"
|
|
16
21
|
:tabindex="-1"
|
|
17
|
-
:disabled="disabled"
|
|
22
|
+
:disabled="disabled || item.disabled"
|
|
18
23
|
text
|
|
19
24
|
sm)
|
|
20
|
-
slot(name="item
|
|
21
|
-
w-icon(v-if="itemIcon(item)" class="w-tree__item-icon") {{ itemIcon(item) }}
|
|
22
|
-
span
|
|
25
|
+
slot(name="item" :item="item.originalItem" :depth="depth" :open="item.open")
|
|
26
|
+
w-icon(v-if="itemIcon(item)" class="w-tree__item-icon" :color="item.originalItem[itemIconColorKey] || iconColor") {{ itemIcon(item) }}
|
|
27
|
+
span(v-html="item.label")
|
|
23
28
|
span.ml1(v-if="counts && (item.children || item.branch)").
|
|
24
29
|
({{ item.originalItem.children ? item.originalItem.children.length : 0 }})
|
|
25
30
|
component(
|
|
@@ -39,16 +44,15 @@ ul.w-tree(:class="classes")
|
|
|
39
44
|
@click="$emit('click', $event)"
|
|
40
45
|
@select="$emit('select', $event)"
|
|
41
46
|
@input="$emit('input', $event)")
|
|
42
|
-
template(#item
|
|
43
|
-
slot(name="item
|
|
47
|
+
template(#item="{ item, depth, open }")
|
|
48
|
+
slot(name="item" :item="item" :depth="depth" :open="open")
|
|
44
49
|
</template>
|
|
45
50
|
|
|
46
51
|
<script>
|
|
52
|
+
import { consoleWarn } from '../utils/console'
|
|
47
53
|
/**
|
|
48
|
-
* @todo
|
|
49
|
-
* -
|
|
50
|
-
* - icon per item
|
|
51
|
-
* - left border?
|
|
54
|
+
* @todo:
|
|
55
|
+
* - option to add a left border.
|
|
52
56
|
**/
|
|
53
57
|
|
|
54
58
|
export default {
|
|
@@ -70,8 +74,14 @@ export default {
|
|
|
70
74
|
noTransition: { type: Boolean },
|
|
71
75
|
selectable: { type: Boolean },
|
|
72
76
|
// By default it only reacts to items count change (added or deleted items) not property of items change.
|
|
73
|
-
|
|
74
|
-
counts: { type: Boolean }
|
|
77
|
+
deepReactivity: { type: Boolean },
|
|
78
|
+
counts: { type: Boolean },
|
|
79
|
+
itemIconKey: { type: String, default: 'icon' }, // Support a different icon per item.
|
|
80
|
+
iconColor: { type: String }, // Applies a color on all the label item icons.
|
|
81
|
+
itemIconColorKey: { type: String, default: 'iconColor' }, // Applies a specific color on each label item icons.
|
|
82
|
+
itemRouteKey: { type: String, default: 'route' }, // Uses a router link if the item has the `route` key.
|
|
83
|
+
itemDisabledKey: { type: String, default: 'disabled' }, // Disables the item click and selection.
|
|
84
|
+
itemOpenKey: { type: String, default: 'open' } // Open the item by default.
|
|
75
85
|
},
|
|
76
86
|
|
|
77
87
|
emits: ['input', 'before-open', 'open', 'before-close', 'close', 'click', 'select'],
|
|
@@ -97,6 +107,12 @@ export default {
|
|
|
97
107
|
updateCurrentDepthTree (items, oldItems = []) {
|
|
98
108
|
this.currentDepthItems = []
|
|
99
109
|
|
|
110
|
+
if (!Array.isArray(items) && typeof items !== 'object') {
|
|
111
|
+
return consoleWarn(`[w-tree] the tree items must be of type array or object, ${typeof items} received.`, items)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!Array.isArray(items)) items = [items]
|
|
115
|
+
|
|
100
116
|
items.forEach((item, i) => {
|
|
101
117
|
this.currentDepthItems.push({
|
|
102
118
|
originalItem: item, // Store the original item to return it on event emits.
|
|
@@ -104,8 +120,10 @@ export default {
|
|
|
104
120
|
label: item.label,
|
|
105
121
|
children: !!item.children, // The children tree remains available in originalItem.
|
|
106
122
|
branch: item.branch,
|
|
123
|
+
route: item[this.itemRouteKey],
|
|
124
|
+
disabled: item[this.itemDisabledKey],
|
|
107
125
|
depth: this.depth,
|
|
108
|
-
open: oldItems[i]?.open ||
|
|
126
|
+
open: !!(oldItems[i]?.open || this.expandAll || item[this.itemOpenKey])
|
|
109
127
|
})
|
|
110
128
|
})
|
|
111
129
|
},
|
|
@@ -132,6 +150,9 @@ export default {
|
|
|
132
150
|
},
|
|
133
151
|
|
|
134
152
|
onLabelClick (item, e) {
|
|
153
|
+
const route = item[this.itemRouteKey]
|
|
154
|
+
if (route && this.$router && !this.hasExternalLink(item)) e.preventDefault()
|
|
155
|
+
|
|
135
156
|
this.$emit('click', { item: item.originalItem, depth: this.depth, e })
|
|
136
157
|
if (item.children || (item.branch && !this.unexpandableEmpty)) this.expandDepth(item)
|
|
137
158
|
|
|
@@ -183,6 +204,7 @@ export default {
|
|
|
183
204
|
* @param {String} selector any valid DOM selector to match the siblings.
|
|
184
205
|
*/
|
|
185
206
|
getPreviousSibling (node, selector) {
|
|
207
|
+
// eslint-disable-next-line no-unmodified-loop-condition
|
|
186
208
|
while (selector && (node = node.previousElementSibling)) {
|
|
187
209
|
if (node.matches(selector)) return node
|
|
188
210
|
}
|
|
@@ -196,6 +218,7 @@ export default {
|
|
|
196
218
|
* @param {String} selector any valid DOM selector to match the siblings.
|
|
197
219
|
*/
|
|
198
220
|
getNextSibling (node, selector) {
|
|
221
|
+
// eslint-disable-next-line no-unmodified-loop-condition
|
|
199
222
|
while (selector && (node = node.nextElementSibling)) {
|
|
200
223
|
if (node.matches(selector)) return node
|
|
201
224
|
}
|
|
@@ -208,15 +231,20 @@ export default {
|
|
|
208
231
|
|
|
209
232
|
itemIcon (item) {
|
|
210
233
|
return (
|
|
211
|
-
item.originalItem.
|
|
234
|
+
item.originalItem[this.itemIconKey] ||
|
|
212
235
|
(!item.children && !item.branch && this.leafIcon) ||
|
|
213
236
|
((item.children || item.branch) && ((item.open && this.branchOpenIcon) || this.branchIcon))
|
|
214
237
|
)
|
|
215
238
|
},
|
|
216
239
|
|
|
240
|
+
hasExternalLink (item) {
|
|
241
|
+
return /^(https?:)?\/\/|mailto:|tel:/.test(item[this.itemRouteKey])
|
|
242
|
+
},
|
|
243
|
+
|
|
217
244
|
itemClasses (item) {
|
|
218
245
|
return {
|
|
219
246
|
[item.children || item.branch ? 'w-tree__item--branch' : 'w-tree__item--leaf']: true,
|
|
247
|
+
'w-tree__item--disabled': item[this.itemDisabledKey],
|
|
220
248
|
'w-tree__item--empty': item.branch && !item.children,
|
|
221
249
|
'w-tree__item--unexpandable': item.branch && !item.children && this.unexpandableEmpty
|
|
222
250
|
}
|
|
@@ -230,7 +258,7 @@ export default {
|
|
|
230
258
|
// The open property of each item has to be retained from this.currentDepthItems in order to stay
|
|
231
259
|
// in the same state after DOM repaint.
|
|
232
260
|
items => this.updateCurrentDepthTree(items, this.currentDepthItems),
|
|
233
|
-
{ deep: !!this.
|
|
261
|
+
{ deep: !!this.deepReactivity } // Deep watching is more resource consuming. Only enable on user demand.
|
|
234
262
|
)
|
|
235
263
|
},
|
|
236
264
|
|
|
@@ -259,6 +287,7 @@ $expand-icon-size: 20px;
|
|
|
259
287
|
position: relative;
|
|
260
288
|
display: inline-flex;
|
|
261
289
|
align-items: center;
|
|
290
|
+
user-select: none;
|
|
262
291
|
|
|
263
292
|
&:before {
|
|
264
293
|
content: '';
|
|
@@ -269,16 +298,24 @@ $expand-icon-size: 20px;
|
|
|
269
298
|
right: - $base-increment - 2px;
|
|
270
299
|
border-radius: $border-radius;
|
|
271
300
|
}
|
|
301
|
+
&:hover:before {background-color: rgba($primary, 0.05);}
|
|
272
302
|
&:focus:before {background-color: rgba($primary, 0.1);}
|
|
273
303
|
}
|
|
274
304
|
&__item--leaf &__item-label:before {
|
|
275
305
|
left: - $base-increment;
|
|
276
306
|
right: - $base-increment;
|
|
277
307
|
}
|
|
308
|
+
&__item--disabled &__item-label {opacity: 0.5;}
|
|
309
|
+
&__item--disabled &__item-label:before {display: none;}
|
|
278
310
|
|
|
279
311
|
&__item-expand {margin-right: 2px;}
|
|
280
312
|
|
|
281
313
|
&__item--branch > &__item-label {cursor: pointer;}
|
|
314
|
+
&__item--disabled > &__item-label {
|
|
315
|
+
color: $disabled-color;
|
|
316
|
+
cursor: not-allowed;
|
|
317
|
+
-webkit-tap-highlight-color: transparent;
|
|
318
|
+
}
|
|
282
319
|
&__item--unexpandable > &__item-label {
|
|
283
320
|
margin-left: $expand-icon-size + 2px;
|
|
284
321
|
cursor: auto;
|