wave-ui 2.48.0 → 3.0.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 +1630 -1419
- package/dist/wave-ui.umd.js +1 -1
- package/package.json +4 -3
- package/src/wave-ui/components/index.js +1 -0
- package/src/wave-ui/components/w-accordion.vue +8 -2
- package/src/wave-ui/components/w-alert.vue +10 -4
- package/src/wave-ui/components/w-app.vue +2 -107
- package/src/wave-ui/components/w-badge.vue +7 -3
- package/src/wave-ui/components/w-button/button.vue +6 -2
- package/src/wave-ui/components/w-card.vue +14 -4
- package/src/wave-ui/components/w-checkbox.vue +15 -8
- package/src/wave-ui/components/w-confirm.vue +5 -1
- package/src/wave-ui/components/w-date-picker.vue +6 -0
- package/src/wave-ui/components/w-dialog.vue +9 -3
- package/src/wave-ui/components/w-divider.vue +9 -3
- package/src/wave-ui/components/w-drawer.vue +9 -3
- package/src/wave-ui/components/w-input.vue +4 -2
- package/src/wave-ui/components/w-menu.vue +11 -4
- package/src/wave-ui/components/w-notification-manager.vue +18 -30
- package/src/wave-ui/components/w-notification.vue +7 -1
- package/src/wave-ui/components/w-progress.vue +2 -2
- package/src/wave-ui/components/w-radio.vue +8 -2
- package/src/wave-ui/components/w-rating.vue +11 -3
- package/src/wave-ui/components/w-scrollbar.vue +24 -0
- package/src/wave-ui/components/w-select.vue +9 -5
- package/src/wave-ui/components/w-slider.vue +13 -7
- package/src/wave-ui/components/w-steps.vue +14 -4
- package/src/wave-ui/components/w-switch.vue +14 -14
- package/src/wave-ui/components/w-table.vue +25 -11
- package/src/wave-ui/components/w-tabs/index.vue +8 -2
- package/src/wave-ui/components/w-tag.vue +15 -10
- package/src/wave-ui/components/w-textarea.vue +6 -2
- package/src/wave-ui/components/w-timeline.vue +18 -5
- package/src/wave-ui/components/w-toolbar.vue +8 -2
- package/src/wave-ui/components/w-tooltip.vue +10 -4
- package/src/wave-ui/components/w-tree.vue +47 -15
- package/src/wave-ui/core.js +117 -90
- package/src/wave-ui/scss/_base.scss +53 -2
- package/src/wave-ui/scss/_colors.scss +41 -17
- package/src/wave-ui/scss/_layout.scss +5 -12
- package/src/wave-ui/scss/_mixins.scss +24 -0
- package/src/wave-ui/scss/_variables.scss +100 -11
- package/src/wave-ui/utils/colors.js +60 -3
- package/src/wave-ui/utils/config.js +35 -11
- package/src/wave-ui/utils/dynamic-css.js +92 -30
- package/src/wave-ui/utils/notification-manager.js +39 -8
|
@@ -25,7 +25,6 @@ export default {
|
|
|
25
25
|
modelValue: { type: [Boolean, Number], default: -1 },
|
|
26
26
|
color: { type: String },
|
|
27
27
|
bgColor: { type: String },
|
|
28
|
-
dark: { type: Boolean },
|
|
29
28
|
shadow: { type: Boolean },
|
|
30
29
|
tile: { type: Boolean },
|
|
31
30
|
round: { type: Boolean },
|
|
@@ -38,7 +37,9 @@ export default {
|
|
|
38
37
|
lg: { type: Boolean },
|
|
39
38
|
xl: { type: Boolean },
|
|
40
39
|
width: { type: [Number, String] },
|
|
41
|
-
height: { type: [Number, String] }
|
|
40
|
+
height: { type: [Number, String] },
|
|
41
|
+
dark: { type: Boolean },
|
|
42
|
+
light: { type: Boolean }
|
|
42
43
|
},
|
|
43
44
|
|
|
44
45
|
emits: ['input', 'update:modelValue'],
|
|
@@ -58,7 +59,8 @@ export default {
|
|
|
58
59
|
[this.color]: this.color,
|
|
59
60
|
[`${this.bgColor}--bg`]: this.bgColor,
|
|
60
61
|
[`size--${this.presetSize}`]: true,
|
|
61
|
-
'w-tag--dark': this.dark
|
|
62
|
+
'w-tag--dark': this.dark,
|
|
63
|
+
'w-tag--light': this.light,
|
|
62
64
|
'w-tag--clickable': this.modelValue !== -1,
|
|
63
65
|
'w-tag--outline': this.outline,
|
|
64
66
|
'w-tag--no-border': this.noBorder || this.shadow,
|
|
@@ -85,14 +87,16 @@ export default {
|
|
|
85
87
|
justify-content: center;
|
|
86
88
|
vertical-align: middle;
|
|
87
89
|
border-radius: $border-radius;
|
|
88
|
-
border: 1px solid rgba(
|
|
89
|
-
background-color: rgba(
|
|
90
|
+
border: 1px solid rgba(var(--w-contrast-bg-color-rgb), 0.08);
|
|
91
|
+
background-color: rgba(var(--w-base-bg-color-rgb), 0.85);
|
|
90
92
|
padding-left: 2 * $base-increment;
|
|
91
93
|
padding-right: 2 * $base-increment;
|
|
92
94
|
cursor: default;
|
|
93
95
|
user-select: none;
|
|
94
96
|
|
|
95
|
-
|
|
97
|
+
@include themeable;
|
|
98
|
+
|
|
99
|
+
&--dark {color: rgba(var(--w-base-bg-color-rgb), 0.95);}
|
|
96
100
|
&--outline {background-color: transparent;border-color: currentColor;}
|
|
97
101
|
&--no-border {border-color: transparent;}
|
|
98
102
|
&--round {border-radius: 99em;}
|
|
@@ -103,6 +107,7 @@ export default {
|
|
|
103
107
|
$font-size: round(0.7 * $base-font-size);
|
|
104
108
|
font-size: $font-size;
|
|
105
109
|
line-height: $font-size + 2px;
|
|
110
|
+
padding: round(0.25 * $base-increment) $base-increment;
|
|
106
111
|
}
|
|
107
112
|
&.size--sm {
|
|
108
113
|
$font-size: round(0.82 * $base-font-size);
|
|
@@ -150,7 +155,7 @@ export default {
|
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
&:hover {
|
|
153
|
-
.w-tag__closable {background-color: rgba(
|
|
158
|
+
.w-tag__closable {background-color: rgba(var(--w-contrast-bg-color-rgb), 0.1);}
|
|
154
159
|
}
|
|
155
160
|
|
|
156
161
|
// Overlay to mark the focus and active state.
|
|
@@ -171,19 +176,19 @@ export default {
|
|
|
171
176
|
|
|
172
177
|
// Hover state.
|
|
173
178
|
&:hover:before {background-color: currentColor;opacity: 0.06;}
|
|
174
|
-
&--dark:hover:before {background-color: rgba(
|
|
179
|
+
&--dark:hover:before {background-color: rgba(var(--w-base-bg-color-rgb), 0.12);opacity: 1;}
|
|
175
180
|
&--outline:hover:before,
|
|
176
181
|
&--text:hover:before {background-color: currentColor;opacity: 0.12;}
|
|
177
182
|
|
|
178
183
|
// Focus state.
|
|
179
184
|
&:focus:before {background-color: currentColor;opacity: 0.2;}
|
|
180
|
-
&--dark:focus:before {background-color: rgba(
|
|
185
|
+
&--dark:focus:before {background-color: rgba(var(--w-base-bg-color-rgb), 0.12);}
|
|
181
186
|
&--outline:focus:before,
|
|
182
187
|
&--text:focus:before {background-color: currentColor;opacity: 0.12;}
|
|
183
188
|
|
|
184
189
|
// Active state.
|
|
185
190
|
&:active:before {background-color: currentColor;opacity: 0.2;}
|
|
186
|
-
&--dark:active:before {background-color: rgba(
|
|
191
|
+
&--dark:active:before {background-color: rgba(var(--w-base-bg-color-rgb), 0.2);}
|
|
187
192
|
&--outline:active:before,
|
|
188
193
|
&--text:active:before {background-color: currentColor;opacity: 0.2;}
|
|
189
194
|
}
|
|
@@ -83,14 +83,15 @@ export default {
|
|
|
83
83
|
color: { type: String, default: 'primary' },
|
|
84
84
|
bgColor: { type: String },
|
|
85
85
|
labelColor: { type: String, default: 'primary' },
|
|
86
|
-
dark: { type: Boolean },
|
|
87
86
|
outline: { type: Boolean },
|
|
88
87
|
shadow: { type: Boolean },
|
|
89
88
|
noAutogrow: { type: Boolean },
|
|
90
89
|
resizable: { type: Boolean }, // Toggle the HTML built-in bottom right corner resize handle.
|
|
91
90
|
tile: { type: Boolean },
|
|
92
91
|
rows: { type: [Number, String], default: 3 },
|
|
93
|
-
cols: { type: [Number, String] }
|
|
92
|
+
cols: { type: [Number, String] },
|
|
93
|
+
dark: { type: Boolean },
|
|
94
|
+
light: { type: Boolean }
|
|
94
95
|
// Props from mixin: name, disabled, readonly, required, tabindex, validators.
|
|
95
96
|
// Computed from mixin: inputName, isDisabled & isReadonly.
|
|
96
97
|
},
|
|
@@ -132,6 +133,7 @@ export default {
|
|
|
132
133
|
[`w-textarea--${this.hasValue ? 'filled' : 'empty'}`]: true,
|
|
133
134
|
'w-textarea--focused': this.isFocused && !this.isReadonly,
|
|
134
135
|
'w-textarea--dark': this.dark,
|
|
136
|
+
'w-textarea--light': this.light,
|
|
135
137
|
'w-textarea--resizable': this.resizable,
|
|
136
138
|
'w-textarea--floating-label': this.hasLabel && this.labelPosition === 'inside' && !this.staticLabel,
|
|
137
139
|
'w-textarea--no-padding': !this.outline && !this.bgColor && !this.shadow,
|
|
@@ -228,6 +230,8 @@ $inactive-color: #777;
|
|
|
228
230
|
flex-wrap: wrap;
|
|
229
231
|
font-size: $base-font-size;
|
|
230
232
|
|
|
233
|
+
@include themeable;
|
|
234
|
+
|
|
231
235
|
// textarea wrapper.
|
|
232
236
|
// ------------------------------------------------------
|
|
233
237
|
&__textarea-wrap {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
|
-
ul.w-timeline
|
|
2
|
+
ul.w-timeline(:class="classes")
|
|
3
3
|
li.w-timeline-item(v-for="(item, i) in items" :key="i")
|
|
4
4
|
component.w-timeline-item__bullet(
|
|
5
5
|
:is="item[itemIconKey] || icon ? 'w-icon' : 'div'"
|
|
@@ -23,16 +23,29 @@ export default {
|
|
|
23
23
|
itemTitleKey: { type: String, default: 'title' },
|
|
24
24
|
itemContentKey: { type: String, default: 'content' },
|
|
25
25
|
itemIconKey: { type: String, default: 'icon' },
|
|
26
|
-
itemColorKey: { type: String, default: 'color' }
|
|
26
|
+
itemColorKey: { type: String, default: 'color' },
|
|
27
|
+
dark: { type: Boolean },
|
|
28
|
+
light: { type: Boolean }
|
|
27
29
|
},
|
|
28
30
|
|
|
29
|
-
emits: []
|
|
31
|
+
emits: [],
|
|
32
|
+
|
|
33
|
+
computed: {
|
|
34
|
+
classes () {
|
|
35
|
+
return {
|
|
36
|
+
'w-timeline--dark': this.dark,
|
|
37
|
+
'w-timeline--light': this.light
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
30
41
|
}
|
|
31
42
|
</script>
|
|
32
43
|
|
|
33
44
|
<style lang="scss">
|
|
34
45
|
.w-timeline {
|
|
35
46
|
margin-left: $base-increment;
|
|
47
|
+
|
|
48
|
+
@include themeable;
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
.w-timeline-item {
|
|
@@ -48,7 +61,7 @@ export default {
|
|
|
48
61
|
position: absolute;
|
|
49
62
|
top: 2px;
|
|
50
63
|
left: 0;
|
|
51
|
-
background-color:
|
|
64
|
+
background-color: $timeline-bullet-color;
|
|
52
65
|
border-radius: 1em;
|
|
53
66
|
border: 1px solid currentColor;
|
|
54
67
|
width: $base-font-size;
|
|
@@ -66,7 +79,7 @@ export default {
|
|
|
66
79
|
top: 2px;
|
|
67
80
|
bottom: -2px;
|
|
68
81
|
left: -1px;
|
|
69
|
-
border-left: 2px solid
|
|
82
|
+
border-left: 2px solid $timeline-bg-color;
|
|
70
83
|
}
|
|
71
84
|
}
|
|
72
85
|
</style>
|
|
@@ -19,7 +19,9 @@ export default {
|
|
|
19
19
|
width: { type: [Number, String], default: null },
|
|
20
20
|
height: { type: [Number, String], default: null },
|
|
21
21
|
noBorder: { type: Boolean },
|
|
22
|
-
shadow: { type: Boolean }
|
|
22
|
+
shadow: { type: Boolean },
|
|
23
|
+
dark: { type: Boolean },
|
|
24
|
+
light: { type: Boolean }
|
|
23
25
|
},
|
|
24
26
|
|
|
25
27
|
emits: [],
|
|
@@ -41,6 +43,8 @@ export default {
|
|
|
41
43
|
return {
|
|
42
44
|
[this.color]: !!this.color,
|
|
43
45
|
[`${this.bgColor}--bg`]: !!this.bgColor,
|
|
46
|
+
'w-toolbar--dark': this.dark,
|
|
47
|
+
'w-toolbar--light': this.light,
|
|
44
48
|
'w-toolbar--absolute': !!this.absolute,
|
|
45
49
|
'w-toolbar--fixed': !!this.fixed,
|
|
46
50
|
[`w-toolbar--${this.bottom ? 'bottom' : 'top'}`]: !this.vertical,
|
|
@@ -66,9 +70,11 @@ export default {
|
|
|
66
70
|
flex: 1 1 auto;
|
|
67
71
|
align-items: center;
|
|
68
72
|
padding: (2 * $base-increment) (3 * $base-increment);
|
|
69
|
-
background-color:
|
|
73
|
+
background-color: $toolbar-bg-color;
|
|
70
74
|
z-index: 10;
|
|
71
75
|
|
|
76
|
+
@include themeable;
|
|
77
|
+
|
|
72
78
|
&--absolute, &--fixed {top: 0;left: 0;right: 0;}
|
|
73
79
|
&--absolute {position: absolute;}
|
|
74
80
|
&--fixed {position: fixed;}
|
|
@@ -32,7 +32,9 @@ export default {
|
|
|
32
32
|
transition: { type: String },
|
|
33
33
|
tooltipClass: { type: [String, Object, Array] },
|
|
34
34
|
persistent: { type: Boolean },
|
|
35
|
-
delay: { type: Number }
|
|
35
|
+
delay: { type: Number },
|
|
36
|
+
dark: { type: Boolean },
|
|
37
|
+
light: { type: Boolean }
|
|
36
38
|
// Other props in the detachable mixin:
|
|
37
39
|
// detachTo, appendTo, fixed, top, bottom, left, right, alignTop, alignBottom, alignLeft,
|
|
38
40
|
// alignRight, noPosition, zIndex, activator.
|
|
@@ -78,6 +80,8 @@ export default {
|
|
|
78
80
|
...this.tooltipClasses,
|
|
79
81
|
[`w-tooltip--${this.position}`]: !this.noPosition,
|
|
80
82
|
[`w-tooltip--align-${this.alignment}`]: !this.noPosition && this.alignment,
|
|
83
|
+
'w-tooltip--dark': this.dark,
|
|
84
|
+
'w-tooltip--light': this.light,
|
|
81
85
|
'w-tooltip--tile': this.tile,
|
|
82
86
|
'w-tooltip--round': this.round,
|
|
83
87
|
'w-tooltip--shadow': this.shadow,
|
|
@@ -190,7 +194,7 @@ export default {
|
|
|
190
194
|
position: absolute;
|
|
191
195
|
padding: $base-increment round(1.5 * $base-increment);
|
|
192
196
|
border-radius: $border-radius;
|
|
193
|
-
border: 1px solid
|
|
197
|
+
border: 1px solid $tooltip-border-color;
|
|
194
198
|
background-color: $tooltip-bg-color;
|
|
195
199
|
pointer-events: none;
|
|
196
200
|
color: $tooltip-color;
|
|
@@ -199,6 +203,8 @@ export default {
|
|
|
199
203
|
width: max-content; // Not supported in IE11. :/
|
|
200
204
|
z-index: 100;
|
|
201
205
|
|
|
206
|
+
@include themeable;
|
|
207
|
+
|
|
202
208
|
&--fixed {position: fixed;z-index: 1000;}
|
|
203
209
|
|
|
204
210
|
&--tile {border-radius: 0;}
|
|
@@ -218,12 +224,12 @@ export default {
|
|
|
218
224
|
|
|
219
225
|
// Tooltip without border.
|
|
220
226
|
&--no-border {
|
|
221
|
-
@include triangle(
|
|
227
|
+
@include triangle($tooltip-bg-color, '.w-tooltip', 7px, 0);
|
|
222
228
|
}
|
|
223
229
|
|
|
224
230
|
// Tooltip with border.
|
|
225
231
|
&:not(&--no-border) {
|
|
226
|
-
@include triangle(
|
|
232
|
+
@include triangle($tooltip-bg-color, '.w-tooltip', 7px);
|
|
227
233
|
}
|
|
228
234
|
}
|
|
229
235
|
|
|
@@ -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
|
|
25
|
+
slot(name="item" :item="item.originalItem" :depth="depth" :open="item.open")
|
|
21
26
|
w-icon(v-if="itemIcon(item)" class="w-tree__item-icon" :color="item.originalItem[itemIconColorKey] || iconColor") {{ itemIcon(item) }}
|
|
22
|
-
span
|
|
27
|
+
span(v-html="item.label")
|
|
23
28
|
span.ml1(v-if="counts && (item.children || item.branch)").
|
|
24
29
|
({{ 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
|
@update:model-value="$emit('update:model-value', $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 {
|
|
@@ -74,7 +78,10 @@ export default {
|
|
|
74
78
|
counts: { type: Boolean },
|
|
75
79
|
itemIconKey: { type: String, default: 'icon' }, // Support a different icon per item.
|
|
76
80
|
iconColor: { type: String }, // Applies a color on all the label item icons.
|
|
77
|
-
itemIconColorKey: { type: String, default: 'iconColor' } // Applies a specific color on each 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.
|
|
78
85
|
},
|
|
79
86
|
|
|
80
87
|
emits: ['update:model-value', 'before-open', 'open', 'before-close', 'close', 'click', 'select'],
|
|
@@ -100,6 +107,12 @@ export default {
|
|
|
100
107
|
updateCurrentDepthTree (items, oldItems = []) {
|
|
101
108
|
this.currentDepthItems = []
|
|
102
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
|
+
|
|
103
116
|
items.forEach((item, i) => {
|
|
104
117
|
this.currentDepthItems.push({
|
|
105
118
|
originalItem: item, // Store the original item to return it on event emits.
|
|
@@ -107,8 +120,10 @@ export default {
|
|
|
107
120
|
label: item.label,
|
|
108
121
|
children: !!item.children, // The children tree remains available in originalItem.
|
|
109
122
|
branch: item.branch,
|
|
123
|
+
route: item[this.itemRouteKey],
|
|
124
|
+
disabled: item[this.itemDisabledKey],
|
|
110
125
|
depth: this.depth,
|
|
111
|
-
open: !!(oldItems[i]?.open || this.expandAll)
|
|
126
|
+
open: !!(oldItems[i]?.open || this.expandAll || item[this.itemOpenKey])
|
|
112
127
|
})
|
|
113
128
|
})
|
|
114
129
|
},
|
|
@@ -135,6 +150,9 @@ export default {
|
|
|
135
150
|
},
|
|
136
151
|
|
|
137
152
|
onLabelClick (item, e) {
|
|
153
|
+
const route = item[this.itemRouteKey]
|
|
154
|
+
if (route && this.$router && !this.hasExternalLink(item)) e.preventDefault()
|
|
155
|
+
|
|
138
156
|
this.$emit('click', { item: item.originalItem, depth: this.depth, e })
|
|
139
157
|
if (item.children || (item.branch && !this.unexpandableEmpty)) this.expandDepth(item)
|
|
140
158
|
|
|
@@ -219,9 +237,14 @@ export default {
|
|
|
219
237
|
)
|
|
220
238
|
},
|
|
221
239
|
|
|
240
|
+
hasExternalLink (item) {
|
|
241
|
+
return /^(https?:)?\/\/|mailto:|tel:/.test(item[this.itemRouteKey])
|
|
242
|
+
},
|
|
243
|
+
|
|
222
244
|
itemClasses (item) {
|
|
223
245
|
return {
|
|
224
246
|
[item.children || item.branch ? 'w-tree__item--branch' : 'w-tree__item--leaf']: true,
|
|
247
|
+
'w-tree__item--disabled': item[this.itemDisabledKey],
|
|
225
248
|
'w-tree__item--empty': item.branch && !item.children,
|
|
226
249
|
'w-tree__item--unexpandable': item.branch && !item.children && this.unexpandableEmpty
|
|
227
250
|
}
|
|
@@ -264,6 +287,7 @@ $expand-icon-size: 20px;
|
|
|
264
287
|
position: relative;
|
|
265
288
|
display: inline-flex;
|
|
266
289
|
align-items: center;
|
|
290
|
+
user-select: none;
|
|
267
291
|
|
|
268
292
|
&:before {
|
|
269
293
|
content: '';
|
|
@@ -274,16 +298,24 @@ $expand-icon-size: 20px;
|
|
|
274
298
|
right: - $base-increment - 2px;
|
|
275
299
|
border-radius: $border-radius;
|
|
276
300
|
}
|
|
301
|
+
&:hover:before {background-color: rgba($primary, 0.05);}
|
|
277
302
|
&:focus:before {background-color: rgba($primary, 0.1);}
|
|
278
303
|
}
|
|
279
304
|
&__item--leaf &__item-label:before {
|
|
280
305
|
left: - $base-increment;
|
|
281
306
|
right: - $base-increment;
|
|
282
307
|
}
|
|
308
|
+
&__item--disabled &__item-label {opacity: 0.5;}
|
|
309
|
+
&__item--disabled &__item-label:before {display: none;}
|
|
283
310
|
|
|
284
311
|
&__item-expand {margin-right: 2px;}
|
|
285
312
|
|
|
286
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
|
+
}
|
|
287
319
|
&__item--unexpandable > &__item-label {
|
|
288
320
|
margin-left: $expand-icon-size + 2px;
|
|
289
321
|
cursor: auto;
|
package/src/wave-ui/core.js
CHANGED
|
@@ -1,52 +1,73 @@
|
|
|
1
1
|
import { reactive, inject } from 'vue'
|
|
2
|
-
import
|
|
3
|
-
import NotificationManager from './utils/notification-manager'
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
import { mergeConfig } from './utils/config'
|
|
3
|
+
import { injectNotifManagerInDOM, NotificationManager } from './utils/notification-manager'
|
|
4
|
+
import { colorPalette, generateColorShades, flattenColors } from './utils/colors'
|
|
5
|
+
import { injectColorsCSSInDOM, injectCSSInDOM } from './utils/dynamic-css'
|
|
6
|
+
import './scss/index.scss'
|
|
7
|
+
|
|
8
|
+
let mounted = false
|
|
9
|
+
const detectOSDarkMode = $waveui => {
|
|
10
|
+
const matchMedia = window.matchMedia('(prefers-color-scheme: dark)')
|
|
11
|
+
$waveui.preferredTheme = matchMedia.matches ? 'dark' : 'light'
|
|
12
|
+
$waveui.switchTheme($waveui.preferredTheme)
|
|
13
|
+
|
|
14
|
+
matchMedia.addEventListener('change', event => {
|
|
15
|
+
$waveui.preferredTheme = event.matches ? 'dark' : 'light'
|
|
16
|
+
$waveui.switchTheme($waveui.preferredTheme)
|
|
17
|
+
})
|
|
11
18
|
}
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Inject presets into a Vue component props defaults before its registration into the app.
|
|
22
|
+
*
|
|
23
|
+
* @param {Object} component the Vue component to inject presets into.
|
|
24
|
+
* @param {Object} presets the presets to inject. E.g. `{ bgColor: 'green' }`.
|
|
25
|
+
*/
|
|
26
|
+
const injectPresets = (component, presets) => {
|
|
27
|
+
for (const preset in presets) {
|
|
28
|
+
component.props[preset].default = presets[preset]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
18
31
|
|
|
19
32
|
export default class WaveUI {
|
|
20
|
-
static
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
static #registered = false
|
|
34
|
+
|
|
35
|
+
// Exposed as a global object and also `app.provide`d.
|
|
36
|
+
// Accessible from this.$waveui, or inject('$waveui').
|
|
37
|
+
$waveui = {
|
|
38
|
+
breakpoint: {
|
|
39
|
+
name: '',
|
|
40
|
+
xs: false,
|
|
41
|
+
sm: false,
|
|
42
|
+
md: false,
|
|
43
|
+
lg: false,
|
|
44
|
+
xl: false,
|
|
45
|
+
width: null
|
|
46
|
+
},
|
|
47
|
+
config: {},
|
|
48
|
+
colors: {}, // Object of pairs of color-name => color hex.
|
|
49
|
+
preferredTheme: null, // The user OS preferred theme (light or dark).
|
|
50
|
+
theme: null, // The current theme (light or dark).
|
|
51
|
+
_notificationManager: null,
|
|
52
|
+
|
|
53
|
+
// Callable from this.$waveui.
|
|
54
|
+
notify (...args) {
|
|
55
|
+
this._notificationManager.notify(...args)
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Callable from this.$waveui.
|
|
59
|
+
switchTheme (theme) {
|
|
60
|
+
this.theme = theme
|
|
61
|
+
document.documentElement.setAttribute('data-theme', theme)
|
|
62
|
+
document.head.querySelector('#wave-ui-colors')?.remove?.()
|
|
63
|
+
const themeColors = this.config.colors[this.theme]
|
|
64
|
+
injectColorsCSSInDOM(themeColors)
|
|
65
|
+
this.colors = flattenColors(themeColors, colorPalette)
|
|
66
|
+
}
|
|
32
67
|
}
|
|
33
68
|
|
|
34
|
-
// A public object containing pairs of color-name => color hex.
|
|
35
|
-
// Accessible from anywhere via `this.$waveui.colors`.
|
|
36
|
-
// These colors generate the CSS in `w-app` on mounted.
|
|
37
|
-
colors = colors.reduce((obj, color) => {
|
|
38
|
-
obj[color.label] = color.color
|
|
39
|
-
color.shades.forEach(shade => (obj[shade.label] = shade.color))
|
|
40
|
-
return obj
|
|
41
|
-
}, { ...config.colors, black: '#000', white: '#fff', transparent: 'transparent', inherit: 'inherit' })
|
|
42
|
-
|
|
43
|
-
config = {} // Store and expose the config in the $waveui object.
|
|
44
|
-
|
|
45
69
|
static install (app, options = {}) {
|
|
46
70
|
// Register directives.
|
|
47
|
-
// for (const id in directives) {
|
|
48
|
-
// if (directives[id]) app.directive(id, directives[id])
|
|
49
|
-
// }
|
|
50
71
|
app.directive('focus', {
|
|
51
72
|
// Wait for the next tick to focus the newly mounted element.
|
|
52
73
|
mounted: el => setTimeout(() => el.focus(), 0)
|
|
@@ -62,71 +83,77 @@ export default class WaveUI {
|
|
|
62
83
|
|
|
63
84
|
// Register a-la-carte components from the given list.
|
|
64
85
|
const { components = {} } = options || {}
|
|
65
|
-
for (
|
|
86
|
+
for (const id in components) {
|
|
66
87
|
const component = components[id]
|
|
88
|
+
// If presets are defined for this component inject them into the props defaults.
|
|
89
|
+
if (options.presets?.[component.name]) injectPresets(component, options.presets[component.name])
|
|
67
90
|
app.component(component.name, component)
|
|
68
91
|
}
|
|
69
92
|
|
|
70
93
|
// Register mixins.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
94
|
+
app.mixin({
|
|
95
|
+
// Add a mixin to capture the first mounted hook, trigger the Wave UI init then unregister the mixin straight away.
|
|
96
|
+
beforeMount () {
|
|
97
|
+
if (!mounted) {
|
|
98
|
+
mounted = true
|
|
99
|
+
const $waveui = inject('$waveui')
|
|
100
|
+
const { config } = $waveui
|
|
101
|
+
|
|
102
|
+
// Add the .w-app class where defined by user or at the root.
|
|
103
|
+
const wApp = document.querySelector(config.on) || document.body
|
|
104
|
+
wApp.classList.add('w-app')
|
|
105
|
+
|
|
106
|
+
let themeColors = config.colors[config.theme]
|
|
107
|
+
if (config.theme === 'auto') {
|
|
108
|
+
detectOSDarkMode($waveui)
|
|
109
|
+
themeColors = config.colors[$waveui.preferredTheme]
|
|
110
|
+
$waveui.colors = flattenColors(themeColors, colorPalette)
|
|
111
|
+
}
|
|
112
|
+
injectColorsCSSInDOM(themeColors)
|
|
113
|
+
injectCSSInDOM($waveui)
|
|
114
|
+
injectNotifManagerInDOM(wApp, components, $waveui)
|
|
115
|
+
|
|
116
|
+
// This mixin must only run once, we can delete it.
|
|
117
|
+
app._context.mixins.find(mixin => mixin.mounted && delete mixin.mounted)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
})
|
|
75
121
|
|
|
76
|
-
WaveUI
|
|
122
|
+
new WaveUI(app, options)
|
|
123
|
+
WaveUI.#registered = true
|
|
77
124
|
}
|
|
78
125
|
|
|
79
|
-
// Singleton.
|
|
80
126
|
constructor (app, options = {}) {
|
|
81
|
-
if (WaveUI
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
notificationManager = reactive(new NotificationManager())
|
|
86
|
-
|
|
87
|
-
// Merge user options into the default config.
|
|
88
|
-
mergeConfig(options)
|
|
89
|
-
|
|
90
|
-
// Add color shades for each custom color given in options.
|
|
91
|
-
if (config.css.colorShades) {
|
|
92
|
-
config.colorShades = {}
|
|
127
|
+
if (WaveUI.#registered) {
|
|
128
|
+
console.warn('Wave UI is already instantiated.')
|
|
129
|
+
return
|
|
130
|
+
}
|
|
93
131
|
|
|
94
|
-
|
|
95
|
-
color = { label: color, color: config.colors[color].replace('#', '') }
|
|
96
|
-
const col = color.color
|
|
97
|
-
if (col.length === 3) color.color = col[0] + '' + col[0] + col[1] + col[1] + col[2] + col[2]
|
|
132
|
+
this.$waveui._notificationManager = new NotificationManager()
|
|
98
133
|
|
|
99
|
-
|
|
134
|
+
if (!options.theme) options.theme = 'light'
|
|
135
|
+
// Move colors inside a theme if there are option.colors without theme.
|
|
136
|
+
// E.g. colors: { primary, ... } & not colors: { light { primary, ... }, dark: { primary, ... } })
|
|
137
|
+
const colors = { ...options.colors }
|
|
138
|
+
if (!options.colors?.light) options.colors.light = colors
|
|
139
|
+
if (!options.colors?.dark) options.colors.dark = colors
|
|
140
|
+
// Cleanup anything else than themes in config.colors.
|
|
141
|
+
options.colors = { light: options.colors.light, dark: options.colors.dark }
|
|
100
142
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.colors[`${color.label}-light${i}`] = lighterColor
|
|
105
|
-
this.colors[`${color.label}-dark${i}`] = darkerColor
|
|
143
|
+
// Merge user options into the default config.
|
|
144
|
+
let { components, ...config } = options
|
|
145
|
+
config = this.$waveui.config = mergeConfig(config)
|
|
106
146
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
config.colorShades[`${color.label}-dark${i}`] = darkerColor
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
147
|
+
// Generates color shades for each color of each theme and store in the config.colors object.
|
|
148
|
+
if (config.css.colorShades) generateColorShades(config)
|
|
113
149
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
150
|
+
// Make Wave UI reactive and expose the single instance in the app.
|
|
151
|
+
const $waveui = reactive(this.$waveui)
|
|
152
|
+
app.config.globalProperties.$waveui = $waveui
|
|
153
|
+
app.provide('$waveui', $waveui)
|
|
117
154
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
app.provide('$waveui', WaveUI.instance)
|
|
155
|
+
if (config.theme !== 'auto') {
|
|
156
|
+
this.$waveui.colors = flattenColors(config.colors[config.theme], colorPalette)
|
|
121
157
|
}
|
|
122
158
|
}
|
|
123
|
-
|
|
124
|
-
notify (...args) {
|
|
125
|
-
notificationManager.notify(...args)
|
|
126
|
-
}
|
|
127
159
|
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Returns the WaveUI instance. Equivalent to using `$waveui` inside templates.
|
|
131
|
-
*/
|
|
132
|
-
export const useWaveUI = () => inject('$waveui')
|