wave-ui 4.0.2 → 4.1.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 +3 -3
- package/dist/wave-ui.css +1 -1
- package/dist/wave-ui.esm.js +1289 -1151
- package/dist/wave-ui.umd.js +3 -3
- package/package.json +1 -1
- package/src/wave-ui/components/w-accordion/item.vue +7 -1
- package/src/wave-ui/components/w-autocomplete.vue +3 -1
- package/src/wave-ui/components/w-button/index.vue +11 -2
- package/src/wave-ui/components/w-checkbox.vue +3 -1
- package/src/wave-ui/components/w-checkboxes.vue +9 -0
- package/src/wave-ui/components/w-dialog.vue +6 -0
- package/src/wave-ui/components/w-drawer.vue +15 -2
- package/src/wave-ui/components/w-input.vue +3 -1
- package/src/wave-ui/components/w-list.vue +10 -1
- package/src/wave-ui/components/w-menu.vue +7 -0
- package/src/wave-ui/components/w-overlay.vue +44 -8
- package/src/wave-ui/components/w-radio.vue +3 -1
- package/src/wave-ui/components/w-radios.vue +9 -0
- package/src/wave-ui/components/w-rating.vue +7 -0
- package/src/wave-ui/components/w-select.vue +10 -1
- package/src/wave-ui/components/w-slider.vue +4 -1
- package/src/wave-ui/components/w-switch.vue +3 -1
- package/src/wave-ui/components/w-tabs/index.vue +16 -1
- package/src/wave-ui/components/w-tag.vue +9 -1
- package/src/wave-ui/components/w-textarea.vue +4 -1
- package/src/wave-ui/components/w-tree.vue +8 -0
- package/src/wave-ui/core.js +2 -2
- package/src/wave-ui/mixins/focusable.js +13 -0
- package/src/wave-ui/scss/_base.scss +4 -0
- package/src/wave-ui/utils/focus.js +39 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-ui",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "A UI framework for Vue.js 3 (and 2) with only the bright side. :sunny:",
|
|
5
5
|
"author": "Antoni Andre <antoniandre.web@gmail.com>",
|
|
6
6
|
"homepage": "https://antoniandre.github.io/wave-ui",
|
|
@@ -90,10 +90,11 @@ import { useId } from 'vue'
|
|
|
90
90
|
import RippleMixin from '../../mixins/ripple'
|
|
91
91
|
import { isRippleEnabled } from '../../utils/ripple'
|
|
92
92
|
import AccordionContent from './accordion-content.vue'
|
|
93
|
+
import { focusElement } from '../../utils/focus'
|
|
93
94
|
|
|
94
95
|
export default {
|
|
95
96
|
name: 'w-accordion-item',
|
|
96
|
-
|
|
97
|
+
expose: ['focus'],
|
|
97
98
|
mixins: [RippleMixin],
|
|
98
99
|
|
|
99
100
|
setup () {
|
|
@@ -161,6 +162,11 @@ export default {
|
|
|
161
162
|
},
|
|
162
163
|
|
|
163
164
|
methods: {
|
|
165
|
+
focus () {
|
|
166
|
+
if (this.accordionItem._disabled) return
|
|
167
|
+
focusElement(this.$el.querySelector('.w-accordion__item-title'))
|
|
168
|
+
},
|
|
169
|
+
|
|
164
170
|
onAccordionTitlePointerDown (e) {
|
|
165
171
|
if (this.accordionItem._disabled) return
|
|
166
172
|
if (e.target.closest?.('.w-accordion__expand-icon')) return
|
|
@@ -113,10 +113,12 @@ component(
|
|
|
113
113
|
<script>
|
|
114
114
|
import { computed } from 'vue'
|
|
115
115
|
import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
|
|
116
|
+
import FocusableMixin from '../mixins/focusable'
|
|
116
117
|
|
|
117
118
|
export default {
|
|
118
119
|
name: 'w-autocomplete',
|
|
119
|
-
|
|
120
|
+
expose: ['focus'],
|
|
121
|
+
mixins: [FormElementMixin, FocusableMixin],
|
|
120
122
|
inheritAttrs: false,
|
|
121
123
|
|
|
122
124
|
setup () {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
2
|
component(v-if="tooltip" is="w-tooltip" v-bind="tooltipProps")
|
|
3
|
-
button-partial(v-bind="buttonProps")
|
|
3
|
+
button-partial(ref="button" v-bind="buttonProps")
|
|
4
4
|
slot
|
|
5
5
|
template(#tooltip)
|
|
6
6
|
div(v-html="tooltip")
|
|
7
|
-
button-partial(v-else v-bind="buttonProps")
|
|
7
|
+
button-partial(v-else ref="button" v-bind="buttonProps")
|
|
8
8
|
slot
|
|
9
9
|
template(#loading)
|
|
10
10
|
slot(name="loading")
|
|
@@ -12,9 +12,11 @@ button-partial(v-else v-bind="buttonProps")
|
|
|
12
12
|
|
|
13
13
|
<script>
|
|
14
14
|
import ButtonPartial from './button.vue'
|
|
15
|
+
import { focusElement } from '../../utils/focus'
|
|
15
16
|
|
|
16
17
|
export default {
|
|
17
18
|
name: 'w-button',
|
|
19
|
+
expose: ['focus'],
|
|
18
20
|
inheritAttrs: false, // The attrs are only bound to the button-partial, not the root.
|
|
19
21
|
|
|
20
22
|
props: {
|
|
@@ -66,6 +68,13 @@ export default {
|
|
|
66
68
|
const { tooltip, tooltipProps = {}, ...props } = this.$props
|
|
67
69
|
return { ...props, ...this.$attrs }
|
|
68
70
|
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
methods: {
|
|
74
|
+
focus () {
|
|
75
|
+
if (this.disabled) return
|
|
76
|
+
focusElement(this.$refs.button?.$el)
|
|
77
|
+
}
|
|
69
78
|
}
|
|
70
79
|
}
|
|
71
80
|
</script>
|
|
@@ -50,10 +50,12 @@ component(
|
|
|
50
50
|
|
|
51
51
|
<script>
|
|
52
52
|
import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
|
|
53
|
+
import FocusableMixin from '../mixins/focusable'
|
|
53
54
|
|
|
54
55
|
export default {
|
|
55
56
|
name: 'w-checkbox',
|
|
56
|
-
|
|
57
|
+
expose: ['focus'],
|
|
58
|
+
mixins: [FormElementMixin, FocusableMixin],
|
|
57
59
|
|
|
58
60
|
setup () {
|
|
59
61
|
return useWaveUiFormIds()
|
|
@@ -9,6 +9,7 @@ component(
|
|
|
9
9
|
:wrap="inline"
|
|
10
10
|
:class="classes")
|
|
11
11
|
w-checkbox(
|
|
12
|
+
ref="item"
|
|
12
13
|
v-for="(item, i) in checkboxItems"
|
|
13
14
|
:key="i"
|
|
14
15
|
:model-value="item._isChecked"
|
|
@@ -32,9 +33,11 @@ component(
|
|
|
32
33
|
<script>
|
|
33
34
|
import { reactive } from 'vue'
|
|
34
35
|
import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
|
|
36
|
+
import { guardFocusable } from '../utils/focus'
|
|
35
37
|
|
|
36
38
|
export default {
|
|
37
39
|
name: 'w-checkboxes',
|
|
40
|
+
expose: ['focus'],
|
|
38
41
|
mixins: [FormElementMixin],
|
|
39
42
|
|
|
40
43
|
setup () {
|
|
@@ -104,6 +107,12 @@ export default {
|
|
|
104
107
|
},
|
|
105
108
|
|
|
106
109
|
methods: {
|
|
110
|
+
focus () {
|
|
111
|
+
if (!guardFocusable(this)) return
|
|
112
|
+
const items = [].concat(this.$refs.item || []).filter(Boolean)
|
|
113
|
+
items.find(c => !c.isDisabled && !c.isReadonly)?.focus?.()
|
|
114
|
+
},
|
|
115
|
+
|
|
107
116
|
reset () {
|
|
108
117
|
this.checkboxItems.forEach(item => (item._isChecked = null))
|
|
109
118
|
this.$emit('update:modelValue', [])
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
2
|
w-overlay.w-dialog(
|
|
3
|
+
ref="overlay"
|
|
3
4
|
:model-value="showWrapper"
|
|
4
5
|
:persistent="persistent"
|
|
5
6
|
:persistent-no-animation="persistentNoAnimation"
|
|
@@ -32,6 +33,7 @@ import { objectifyClasses } from '../utils/index'
|
|
|
32
33
|
|
|
33
34
|
export default {
|
|
34
35
|
name: 'w-dialog',
|
|
36
|
+
expose: ['focus'],
|
|
35
37
|
|
|
36
38
|
props: {
|
|
37
39
|
modelValue: { default: true },
|
|
@@ -103,6 +105,10 @@ export default {
|
|
|
103
105
|
},
|
|
104
106
|
|
|
105
107
|
methods: {
|
|
108
|
+
focus () {
|
|
109
|
+
this.$refs.overlay?.focus?.()
|
|
110
|
+
},
|
|
111
|
+
|
|
106
112
|
onOutsideClick () {
|
|
107
113
|
if (!this.persistent) {
|
|
108
114
|
this.showContent = false
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
.w-drawer-wrap__pushable
|
|
6
6
|
w-overlay(
|
|
7
7
|
v-if="!noOverlay"
|
|
8
|
+
ref="overlay"
|
|
8
9
|
v-model="showDrawer"
|
|
9
10
|
@click="onOutsideClick"
|
|
10
11
|
:persistent="persistent"
|
|
@@ -21,12 +22,15 @@
|
|
|
21
22
|
ref="drawer"
|
|
22
23
|
:is="tag || 'aside'"
|
|
23
24
|
:class="drawerClasses"
|
|
24
|
-
:style="styles"
|
|
25
|
+
:style="styles"
|
|
26
|
+
:tabindex="noOverlay ? 0 : null"
|
|
27
|
+
@keydown.escape.stop="noOverlay && !persistent && onOutsideClick()")
|
|
25
28
|
slot
|
|
26
29
|
//- Other cases.
|
|
27
30
|
template(v-else)
|
|
28
31
|
w-overlay(
|
|
29
32
|
v-if="!noOverlay"
|
|
33
|
+
ref="overlay"
|
|
30
34
|
v-model="showDrawer"
|
|
31
35
|
@click="onOutsideClick"
|
|
32
36
|
:persistent="persistent"
|
|
@@ -43,11 +47,14 @@
|
|
|
43
47
|
ref="drawer"
|
|
44
48
|
:is="tag || 'aside'"
|
|
45
49
|
:class="drawerClasses"
|
|
46
|
-
:style="styles"
|
|
50
|
+
:style="styles"
|
|
51
|
+
:tabindex="noOverlay ? 0 : null"
|
|
52
|
+
@keydown.escape.stop="noOverlay && !persistent && onOutsideClick()")
|
|
47
53
|
slot
|
|
48
54
|
</template>
|
|
49
55
|
|
|
50
56
|
<script>
|
|
57
|
+
import { focusElement } from '../utils/focus'
|
|
51
58
|
// The complexity in this component is on close:
|
|
52
59
|
// we must keep the wrapper in the DOM until the drawer transition is finished.
|
|
53
60
|
// Then emit the modelValue update that will trigger the removal of the wrapper from the DOM.
|
|
@@ -56,6 +63,7 @@ const oppositeSides = { left: 'right', right: 'left', top: 'down', bottom: 'up'
|
|
|
56
63
|
|
|
57
64
|
export default {
|
|
58
65
|
name: 'w-drawer',
|
|
66
|
+
expose: ['focus'],
|
|
59
67
|
|
|
60
68
|
props: {
|
|
61
69
|
modelValue: { default: true },
|
|
@@ -164,6 +172,11 @@ export default {
|
|
|
164
172
|
},
|
|
165
173
|
|
|
166
174
|
methods: {
|
|
175
|
+
focus () {
|
|
176
|
+
if (this.$refs.overlay) this.$refs.overlay.focus()
|
|
177
|
+
else focusElement(this.$refs.drawer?.$el || this.$refs.drawer)
|
|
178
|
+
},
|
|
179
|
+
|
|
167
180
|
onBeforeClose () {
|
|
168
181
|
this.$emit('before-close')
|
|
169
182
|
},
|
|
@@ -129,11 +129,13 @@ component(
|
|
|
129
129
|
**/
|
|
130
130
|
|
|
131
131
|
import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
|
|
132
|
+
import FocusableMixin from '../mixins/focusable'
|
|
132
133
|
import { reactive } from 'vue'
|
|
133
134
|
|
|
134
135
|
export default {
|
|
135
136
|
name: 'w-input',
|
|
136
|
-
|
|
137
|
+
expose: ['focus'],
|
|
138
|
+
mixins: [FormElementMixin, FocusableMixin],
|
|
137
139
|
inheritAttrs: false, // The attrs should only be added to the input not the wrapper.
|
|
138
140
|
|
|
139
141
|
setup () {
|
|
@@ -45,10 +45,11 @@ ul.w-list(:class="classes")
|
|
|
45
45
|
<script>
|
|
46
46
|
import { useId } from 'vue'
|
|
47
47
|
import RippleMixin from '../mixins/ripple'
|
|
48
|
+
import { focusElement } from '../utils/focus'
|
|
48
49
|
|
|
49
50
|
export default {
|
|
50
51
|
name: 'w-list',
|
|
51
|
-
|
|
52
|
+
expose: ['focus'],
|
|
52
53
|
mixins: [RippleMixin],
|
|
53
54
|
|
|
54
55
|
setup () {
|
|
@@ -138,6 +139,14 @@ export default {
|
|
|
138
139
|
},
|
|
139
140
|
|
|
140
141
|
methods: {
|
|
142
|
+
focus () {
|
|
143
|
+
const selected = this.listItems.find(li => li._selected && !li.disabled)
|
|
144
|
+
const target = selected || this.listItems.find(li => !li.disabled)
|
|
145
|
+
if (!target) return
|
|
146
|
+
if (this.listId) focusElement(document.getElementById(`${this.listId}_item-${target._index + 1}`))
|
|
147
|
+
else focusElement(this.$el.querySelector('[role="option"][tabindex="0"]'))
|
|
148
|
+
},
|
|
149
|
+
|
|
141
150
|
// If object, get the item value, if none, get the item label, if none get the id.
|
|
142
151
|
// If simple value, return as is.
|
|
143
152
|
getItemValue (item) {
|
|
@@ -48,9 +48,11 @@ teleport(v-if="detachableDomReady" :to="teleportTarget" :disabled="!teleportTarg
|
|
|
48
48
|
<script>
|
|
49
49
|
import { objectifyClasses } from '../utils/index'
|
|
50
50
|
import DetachableMixin from '../mixins/detachable'
|
|
51
|
+
import { focusElement } from '../utils/focus'
|
|
51
52
|
|
|
52
53
|
export default {
|
|
53
54
|
name: 'w-menu',
|
|
55
|
+
expose: ['focus'],
|
|
54
56
|
mixins: [DetachableMixin],
|
|
55
57
|
inheritAttrs: false, // The attrs are only bound to the button-partial, not the root.
|
|
56
58
|
|
|
@@ -215,6 +217,11 @@ export default {
|
|
|
215
217
|
},
|
|
216
218
|
|
|
217
219
|
methods: {
|
|
220
|
+
focus () {
|
|
221
|
+
if (this.overlay && this.$refs.overlay) this.$refs.overlay.focus()
|
|
222
|
+
else focusElement(this.activatorEl)
|
|
223
|
+
},
|
|
224
|
+
|
|
218
225
|
/**
|
|
219
226
|
* Other methods in the `detachable` mixin:
|
|
220
227
|
* - `open`
|
|
@@ -5,17 +5,18 @@ transition(name="fade" appear @after-leave="onClose")
|
|
|
5
5
|
v-show="showOverlay"
|
|
6
6
|
ref="overlay"
|
|
7
7
|
:style="(modelValue && styles) || null"
|
|
8
|
-
@keydown.escape.stop="onClick"
|
|
9
8
|
@click="onClick"
|
|
10
|
-
v-focus
|
|
11
9
|
tabindex="0"
|
|
12
10
|
:class="classes")
|
|
13
11
|
slot
|
|
14
12
|
</template>
|
|
15
13
|
|
|
16
14
|
<script>
|
|
15
|
+
import { focusElement } from '../utils/focus'
|
|
16
|
+
|
|
17
17
|
export default {
|
|
18
18
|
name: 'w-overlay',
|
|
19
|
+
expose: ['focus'],
|
|
19
20
|
|
|
20
21
|
props: {
|
|
21
22
|
modelValue: {},
|
|
@@ -39,7 +40,8 @@ export default {
|
|
|
39
40
|
|
|
40
41
|
data: () => ({
|
|
41
42
|
persistentAnimate: false,
|
|
42
|
-
showOverlay: false
|
|
43
|
+
showOverlay: false,
|
|
44
|
+
documentEscapeBound: false
|
|
43
45
|
}),
|
|
44
46
|
|
|
45
47
|
computed: {
|
|
@@ -61,11 +63,29 @@ export default {
|
|
|
61
63
|
},
|
|
62
64
|
|
|
63
65
|
methods: {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
focus () {
|
|
67
|
+
focusElement(this.$refs.overlay)
|
|
68
|
+
},
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
bindDocumentEscape () {
|
|
71
|
+
if (this.documentEscapeBound) return
|
|
72
|
+
document.addEventListener('keydown', this.onDocumentEscape)
|
|
73
|
+
this.documentEscapeBound = true
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
unbindDocumentEscape () {
|
|
77
|
+
if (!this.documentEscapeBound) return
|
|
78
|
+
document.removeEventListener('keydown', this.onDocumentEscape)
|
|
79
|
+
this.documentEscapeBound = false
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
onDocumentEscape (e) {
|
|
83
|
+
if (e.key !== 'Escape') return
|
|
84
|
+
if (!this.showOverlay || !this.modelValue) return
|
|
85
|
+
this.dismiss(e)
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
dismiss (e) {
|
|
69
89
|
if (this.persistent && !this.persistentNoAnimation) {
|
|
70
90
|
this.persistentAnimate = true
|
|
71
91
|
setTimeout(() => (this.persistentAnimate = false), 150) // Must match CSS animation duration.
|
|
@@ -78,9 +98,16 @@ export default {
|
|
|
78
98
|
this.$emit('click', e)
|
|
79
99
|
},
|
|
80
100
|
|
|
101
|
+
onClick (e) {
|
|
102
|
+
// Don't react to a click inside the content (event bubbling).
|
|
103
|
+
if (!e.target.classList.contains('w-overlay')) return
|
|
104
|
+
this.dismiss(e)
|
|
105
|
+
},
|
|
106
|
+
|
|
81
107
|
// Wait until the end of the closing transition (v-show) to completely unmount (v-if).
|
|
82
108
|
// The onClose method is called twice from the transition: once for the v-show, and once for the v-if.
|
|
83
109
|
onClose () {
|
|
110
|
+
this.unbindDocumentEscape()
|
|
84
111
|
this.$emit('update:modelValue', false)
|
|
85
112
|
this.$emit('input', false)
|
|
86
113
|
if (!this.modelValue) this.$emit('close') // Only emit once.
|
|
@@ -89,11 +116,20 @@ export default {
|
|
|
89
116
|
|
|
90
117
|
created () {
|
|
91
118
|
this.showOverlay = this.modelValue
|
|
119
|
+
if (this.modelValue) this.bindDocumentEscape()
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
unmounted () {
|
|
123
|
+
this.unbindDocumentEscape()
|
|
92
124
|
},
|
|
93
125
|
|
|
94
126
|
watch: {
|
|
95
127
|
modelValue (bool) {
|
|
96
|
-
if (bool)
|
|
128
|
+
if (bool) {
|
|
129
|
+
this.showOverlay = true
|
|
130
|
+
this.bindDocumentEscape()
|
|
131
|
+
}
|
|
132
|
+
else this.unbindDocumentEscape()
|
|
97
133
|
}
|
|
98
134
|
}
|
|
99
135
|
}
|
|
@@ -48,10 +48,12 @@ component(
|
|
|
48
48
|
|
|
49
49
|
<script>
|
|
50
50
|
import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
|
|
51
|
+
import FocusableMixin from '../mixins/focusable'
|
|
51
52
|
|
|
52
53
|
export default {
|
|
53
54
|
name: 'w-radio',
|
|
54
|
-
|
|
55
|
+
expose: ['focus'],
|
|
56
|
+
mixins: [FormElementMixin, FocusableMixin],
|
|
55
57
|
|
|
56
58
|
setup () {
|
|
57
59
|
return useWaveUiFormIds()
|
|
@@ -9,6 +9,7 @@ component(
|
|
|
9
9
|
:wrap="inline"
|
|
10
10
|
:class="classes")
|
|
11
11
|
w-radio(
|
|
12
|
+
ref="item"
|
|
12
13
|
v-for="(item, i) in radioItems"
|
|
13
14
|
:key="i"
|
|
14
15
|
:model-value="item.value === modelValue"
|
|
@@ -31,9 +32,11 @@ component(
|
|
|
31
32
|
|
|
32
33
|
<script>
|
|
33
34
|
import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
|
|
35
|
+
import { guardFocusable } from '../utils/focus'
|
|
34
36
|
|
|
35
37
|
export default {
|
|
36
38
|
name: 'w-radios',
|
|
39
|
+
expose: ['focus'],
|
|
37
40
|
mixins: [FormElementMixin],
|
|
38
41
|
|
|
39
42
|
setup () {
|
|
@@ -97,6 +100,12 @@ export default {
|
|
|
97
100
|
},
|
|
98
101
|
|
|
99
102
|
methods: {
|
|
103
|
+
focus () {
|
|
104
|
+
if (!guardFocusable(this)) return
|
|
105
|
+
const items = [].concat(this.$refs.item || []).filter(Boolean)
|
|
106
|
+
items.find(c => !c.isDisabled && !c.isReadonly)?.focus?.()
|
|
107
|
+
},
|
|
108
|
+
|
|
100
109
|
onInput (item) {
|
|
101
110
|
this.inputValue = true
|
|
102
111
|
this.$emit('update:modelValue', item.value)
|
|
@@ -30,9 +30,11 @@ component(
|
|
|
30
30
|
|
|
31
31
|
<script>
|
|
32
32
|
import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
|
|
33
|
+
import { guardFocusable, focusElement } from '../utils/focus'
|
|
33
34
|
|
|
34
35
|
export default {
|
|
35
36
|
name: 'w-rating',
|
|
37
|
+
expose: ['focus'],
|
|
36
38
|
mixins: [FormElementMixin],
|
|
37
39
|
|
|
38
40
|
setup () {
|
|
@@ -104,6 +106,11 @@ export default {
|
|
|
104
106
|
},
|
|
105
107
|
|
|
106
108
|
methods: {
|
|
109
|
+
focus () {
|
|
110
|
+
if (!guardFocusable(this)) return
|
|
111
|
+
focusElement(this.$el.querySelector('.w-rating__button:not([disabled])'))
|
|
112
|
+
},
|
|
113
|
+
|
|
107
114
|
onButtonClick (i) {
|
|
108
115
|
this.rating = i
|
|
109
116
|
this.$emit('update:modelValue', this.rating)
|
|
@@ -110,10 +110,13 @@ component(
|
|
|
110
110
|
|
|
111
111
|
import { computed } from 'vue'
|
|
112
112
|
import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
|
|
113
|
+
import FocusableMixin from '../mixins/focusable'
|
|
114
|
+
import { guardFocusable, focusElement } from '../utils/focus'
|
|
113
115
|
|
|
114
116
|
export default {
|
|
115
117
|
name: 'w-select',
|
|
116
|
-
|
|
118
|
+
expose: ['focus'],
|
|
119
|
+
mixins: [FormElementMixin, FocusableMixin],
|
|
117
120
|
|
|
118
121
|
setup (props) {
|
|
119
122
|
const { waveUiUseId } = useWaveUiFormIds()
|
|
@@ -259,6 +262,12 @@ export default {
|
|
|
259
262
|
},
|
|
260
263
|
|
|
261
264
|
methods: {
|
|
265
|
+
focus () {
|
|
266
|
+
if (!guardFocusable(this)) return
|
|
267
|
+
focusElement(this.$refs['selection-input'])
|
|
268
|
+
if (!this.showMenu) this.openMenu()
|
|
269
|
+
},
|
|
270
|
+
|
|
262
271
|
onFocus (e) {
|
|
263
272
|
if (this.isFocused) return
|
|
264
273
|
|
|
@@ -79,10 +79,13 @@ component(
|
|
|
79
79
|
|
|
80
80
|
<script>
|
|
81
81
|
import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
|
|
82
|
+
import FocusableMixin from '../mixins/focusable'
|
|
82
83
|
|
|
83
84
|
export default {
|
|
84
85
|
name: 'w-slider',
|
|
85
|
-
|
|
86
|
+
expose: ['focus'],
|
|
87
|
+
focusTargetRef: 'thumb',
|
|
88
|
+
mixins: [FormElementMixin, FocusableMixin],
|
|
86
89
|
|
|
87
90
|
setup () {
|
|
88
91
|
return useWaveUiFormIds()
|
|
@@ -55,10 +55,12 @@ component(
|
|
|
55
55
|
|
|
56
56
|
<script>
|
|
57
57
|
import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
|
|
58
|
+
import FocusableMixin from '../mixins/focusable'
|
|
58
59
|
|
|
59
60
|
export default {
|
|
60
61
|
name: 'w-switch',
|
|
61
|
-
|
|
62
|
+
expose: ['focus'],
|
|
63
|
+
mixins: [FormElementMixin, FocusableMixin],
|
|
62
64
|
inheritAttrs: false, // The attrs should only be added to the input not the wrapper.
|
|
63
65
|
|
|
64
66
|
setup () {
|
|
@@ -76,10 +76,11 @@ import { useId } from 'vue'
|
|
|
76
76
|
import { objectifyClasses } from '../../utils/index'
|
|
77
77
|
import RippleMixin from '../../mixins/ripple'
|
|
78
78
|
import TabContent from './tab-content.vue'
|
|
79
|
+
import { focusElement } from '../../utils/focus'
|
|
79
80
|
|
|
80
81
|
export default {
|
|
81
82
|
name: 'w-tabs',
|
|
82
|
-
|
|
83
|
+
expose: ['focus'],
|
|
83
84
|
mixins: [RippleMixin],
|
|
84
85
|
|
|
85
86
|
setup () {
|
|
@@ -183,6 +184,14 @@ export default {
|
|
|
183
184
|
},
|
|
184
185
|
|
|
185
186
|
methods: {
|
|
187
|
+
focus () {
|
|
188
|
+
const bar = this.$refs['tabs-bar']
|
|
189
|
+
if (!bar) return
|
|
190
|
+
const tab = bar.querySelector('[role="tab"][aria-selected="true"]:not([tabindex="-1"])')
|
|
191
|
+
|| bar.querySelector('[role="tab"][tabindex="0"]')
|
|
192
|
+
focusElement(tab)
|
|
193
|
+
},
|
|
194
|
+
|
|
186
195
|
resolveTabUid (value) {
|
|
187
196
|
if (!this.tabs.length) return null
|
|
188
197
|
if (value === undefined || value === null || value === '') return this.tabs[0]._uid
|
|
@@ -455,6 +464,12 @@ export default {
|
|
|
455
464
|
margin-right: -1px;
|
|
456
465
|
}
|
|
457
466
|
.w-tabs--card &--active {border-bottom-color: transparent;}
|
|
467
|
+
.w-tabs--pill-slider & {
|
|
468
|
+
padding: calc(var(--w-base-increment) * 1) calc(var(--w-base-increment) * 2);
|
|
469
|
+
margin: calc(var(--w-base-increment) * 1) calc(var(--w-base-increment) * 1);
|
|
470
|
+
border-radius: 99em;
|
|
471
|
+
font-size: round(nearest, calc(1.1 * var(--w-base-font-size)), 1px);
|
|
472
|
+
}
|
|
458
473
|
|
|
459
474
|
&--disabled {
|
|
460
475
|
cursor: not-allowed;
|
|
@@ -19,10 +19,11 @@ span.w-tag(
|
|
|
19
19
|
|
|
20
20
|
<script>
|
|
21
21
|
import RippleMixin from '../mixins/ripple'
|
|
22
|
+
import { focusElement } from '../utils/focus'
|
|
22
23
|
|
|
23
24
|
export default {
|
|
24
25
|
name: 'w-tag',
|
|
25
|
-
|
|
26
|
+
expose: ['focus'],
|
|
26
27
|
mixins: [RippleMixin],
|
|
27
28
|
|
|
28
29
|
props: {
|
|
@@ -48,6 +49,13 @@ export default {
|
|
|
48
49
|
|
|
49
50
|
emits: ['input', 'update:modelValue'],
|
|
50
51
|
|
|
52
|
+
methods: {
|
|
53
|
+
focus () {
|
|
54
|
+
if (this.modelValue === -1) return
|
|
55
|
+
focusElement(this.$el)
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
51
59
|
computed: {
|
|
52
60
|
presetSize () {
|
|
53
61
|
return (
|
|
@@ -68,10 +68,13 @@ component(
|
|
|
68
68
|
**/
|
|
69
69
|
|
|
70
70
|
import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
|
|
71
|
+
import FocusableMixin from '../mixins/focusable'
|
|
71
72
|
|
|
72
73
|
export default {
|
|
73
74
|
name: 'w-textarea',
|
|
74
|
-
|
|
75
|
+
expose: ['focus'],
|
|
76
|
+
focusTargetRef: 'textarea',
|
|
77
|
+
mixins: [FormElementMixin, FocusableMixin],
|
|
75
78
|
inheritAttrs: false, // The attrs should only be added to the textarea not the wrapper.
|
|
76
79
|
|
|
77
80
|
setup () {
|
|
@@ -68,8 +68,11 @@ import { consoleWarn } from '../utils/console'
|
|
|
68
68
|
* - option to add a left border.
|
|
69
69
|
**/
|
|
70
70
|
|
|
71
|
+
import { focusElement } from '../utils/focus'
|
|
72
|
+
|
|
71
73
|
export default {
|
|
72
74
|
name: 'w-tree',
|
|
75
|
+
expose: ['focus'],
|
|
73
76
|
|
|
74
77
|
mixins: [RippleMixin],
|
|
75
78
|
|
|
@@ -122,6 +125,11 @@ export default {
|
|
|
122
125
|
},
|
|
123
126
|
|
|
124
127
|
methods: {
|
|
128
|
+
focus () {
|
|
129
|
+
if (this.disabled) return
|
|
130
|
+
focusElement(this.$el.querySelector('.w-tree__item-label[tabindex="0"]'))
|
|
131
|
+
},
|
|
132
|
+
|
|
125
133
|
// From data watcher, retain the oldItems open state.
|
|
126
134
|
updateCurrentDepthTree (items, oldItems = []) {
|
|
127
135
|
this.currentDepthItems = []
|
package/src/wave-ui/core.js
CHANGED
|
@@ -5,6 +5,7 @@ import { colorPalette, generateColorShades, flattenColors } from './utils/colors
|
|
|
5
5
|
import { injectColorsCSSInDOM, injectCSSInDOM } from './utils/dynamic-css'
|
|
6
6
|
import { injectNotifManagerInDOM, NotificationManager } from './utils/notification-manager'
|
|
7
7
|
import { waveRippleDirective } from './utils/wave-ripple-directive'
|
|
8
|
+
import { scheduleFocus } from './utils/focus'
|
|
8
9
|
import './scss/index.scss'
|
|
9
10
|
|
|
10
11
|
let mounted = false
|
|
@@ -112,8 +113,7 @@ export default class WaveUI {
|
|
|
112
113
|
static install (app, options = {}) {
|
|
113
114
|
// Register directives.
|
|
114
115
|
app.directive('focus', {
|
|
115
|
-
|
|
116
|
-
mounted: el => setTimeout(() => el.focus(), 0)
|
|
116
|
+
mounted: (el, binding, vnode) => scheduleFocus(vnode, el)
|
|
117
117
|
})
|
|
118
118
|
app.directive('scroll', {
|
|
119
119
|
mounted: (el, binding) => {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { guardFocusable, focusElement } from '../utils/focus'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
methods: {
|
|
5
|
+
focus () {
|
|
6
|
+
if (!guardFocusable(this)) return
|
|
7
|
+
const refName = this.$options.focusTargetRef || 'input'
|
|
8
|
+
const target = this.$refs[refName]
|
|
9
|
+
if (target) focusElement(target)
|
|
10
|
+
else this.$nextTick(() => focusElement(this.$refs[refName]))
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|