wave-ui 4.0.2 → 4.1.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/dist/wave-ui.cjs.js +3 -3
- package/dist/wave-ui.css +1 -1
- package/dist/wave-ui.esm.js +1355 -1145
- package/dist/wave-ui.umd.js +3 -3
- package/package.json +1 -1
- package/src/wave-ui/components/w-accordion/item.vue +8 -1
- package/src/wave-ui/components/w-autocomplete.vue +3 -1
- package/src/wave-ui/components/w-button/index.vue +12 -2
- package/src/wave-ui/components/w-checkbox.vue +3 -1
- package/src/wave-ui/components/w-checkboxes.vue +10 -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 +11 -1
- package/src/wave-ui/components/w-menu.vue +8 -1
- 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 +10 -0
- package/src/wave-ui/components/w-rating.vue +8 -0
- package/src/wave-ui/components/w-scrollable.vue +1 -1
- 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 +17 -1
- package/src/wave-ui/components/w-tag.vue +10 -1
- package/src/wave-ui/components/w-textarea.vue +4 -1
- package/src/wave-ui/components/w-tooltip.vue +1 -1
- package/src/wave-ui/components/w-tree.vue +9 -0
- package/src/wave-ui/core.js +5 -2
- package/src/wave-ui/mixins/detachable.js +41 -2
- package/src/wave-ui/mixins/focusable.js +22 -0
- package/src/wave-ui/scss/_base.scss +4 -0
- package/src/wave-ui/utils/focus.js +74 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-ui",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
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,12 @@ 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
|
+
focusable: true,
|
|
98
|
+
expose: ['focus'],
|
|
97
99
|
mixins: [RippleMixin],
|
|
98
100
|
|
|
99
101
|
setup () {
|
|
@@ -161,6 +163,11 @@ export default {
|
|
|
161
163
|
},
|
|
162
164
|
|
|
163
165
|
methods: {
|
|
166
|
+
focus () {
|
|
167
|
+
if (this.accordionItem._disabled) return
|
|
168
|
+
focusElement(this.$el.querySelector('.w-accordion__item-title'))
|
|
169
|
+
},
|
|
170
|
+
|
|
164
171
|
onAccordionTitlePointerDown (e) {
|
|
165
172
|
if (this.accordionItem._disabled) return
|
|
166
173
|
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,12 @@ 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
|
+
focusable: true,
|
|
20
|
+
expose: ['focus'],
|
|
18
21
|
inheritAttrs: false, // The attrs are only bound to the button-partial, not the root.
|
|
19
22
|
|
|
20
23
|
props: {
|
|
@@ -66,6 +69,13 @@ export default {
|
|
|
66
69
|
const { tooltip, tooltipProps = {}, ...props } = this.$props
|
|
67
70
|
return { ...props, ...this.$attrs }
|
|
68
71
|
}
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
methods: {
|
|
75
|
+
focus () {
|
|
76
|
+
if (this.disabled) return
|
|
77
|
+
focusElement(this.$refs.button?.$el)
|
|
78
|
+
}
|
|
69
79
|
}
|
|
70
80
|
}
|
|
71
81
|
</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,12 @@ 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
|
+
focusable: true,
|
|
41
|
+
expose: ['focus'],
|
|
38
42
|
mixins: [FormElementMixin],
|
|
39
43
|
|
|
40
44
|
setup () {
|
|
@@ -104,6 +108,12 @@ export default {
|
|
|
104
108
|
},
|
|
105
109
|
|
|
106
110
|
methods: {
|
|
111
|
+
focus () {
|
|
112
|
+
if (!guardFocusable(this)) return
|
|
113
|
+
const items = [].concat(this.$refs.item || []).filter(Boolean)
|
|
114
|
+
items.find(c => !c.isDisabled && !c.isReadonly)?.focus?.()
|
|
115
|
+
},
|
|
116
|
+
|
|
107
117
|
reset () {
|
|
108
118
|
this.checkboxItems.forEach(item => (item._isChecked = null))
|
|
109
119
|
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,12 @@ 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
|
+
focusable: true,
|
|
53
|
+
expose: ['focus'],
|
|
52
54
|
mixins: [RippleMixin],
|
|
53
55
|
|
|
54
56
|
setup () {
|
|
@@ -138,6 +140,14 @@ export default {
|
|
|
138
140
|
},
|
|
139
141
|
|
|
140
142
|
methods: {
|
|
143
|
+
focus () {
|
|
144
|
+
const selected = this.listItems.find(li => li._selected && !li.disabled)
|
|
145
|
+
const target = selected || this.listItems.find(li => !li.disabled)
|
|
146
|
+
if (!target) return
|
|
147
|
+
if (this.listId) focusElement(document.getElementById(`${this.listId}_item-${target._index + 1}`))
|
|
148
|
+
else focusElement(this.$el.querySelector('[role="option"][tabindex="0"]'))
|
|
149
|
+
},
|
|
150
|
+
|
|
141
151
|
// If object, get the item value, if none, get the item label, if none get the id.
|
|
142
152
|
// If simple value, return as is.
|
|
143
153
|
getItemValue (item) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
slot(name="activator")
|
|
3
3
|
slot(v-if="!$slots.activator")
|
|
4
4
|
teleport(v-if="detachableDomReady" :to="teleportTarget" :disabled="!teleportTarget")
|
|
5
|
-
transition(:name="transitionName" appear @after-leave="onAfterLeave")
|
|
5
|
+
transition(:name="transitionName" appear @after-enter="onDetachableAfterEnter" @after-leave="onAfterLeave")
|
|
6
6
|
.w-menu(
|
|
7
7
|
v-if="custom && detachableVisible"
|
|
8
8
|
ref="detachable"
|
|
@@ -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,12 @@ 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
|
+
focusable: true,
|
|
40
|
+
expose: ['focus'],
|
|
37
41
|
mixins: [FormElementMixin],
|
|
38
42
|
|
|
39
43
|
setup () {
|
|
@@ -97,6 +101,12 @@ export default {
|
|
|
97
101
|
},
|
|
98
102
|
|
|
99
103
|
methods: {
|
|
104
|
+
focus () {
|
|
105
|
+
if (!guardFocusable(this)) return
|
|
106
|
+
const items = [].concat(this.$refs.item || []).filter(Boolean)
|
|
107
|
+
items.find(c => !c.isDisabled && !c.isReadonly)?.focus?.()
|
|
108
|
+
},
|
|
109
|
+
|
|
100
110
|
onInput (item) {
|
|
101
111
|
this.inputValue = true
|
|
102
112
|
this.$emit('update:modelValue', item.value)
|
|
@@ -30,9 +30,12 @@ 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
|
+
focusable: true,
|
|
38
|
+
expose: ['focus'],
|
|
36
39
|
mixins: [FormElementMixin],
|
|
37
40
|
|
|
38
41
|
setup () {
|
|
@@ -104,6 +107,11 @@ export default {
|
|
|
104
107
|
},
|
|
105
108
|
|
|
106
109
|
methods: {
|
|
110
|
+
focus () {
|
|
111
|
+
if (!guardFocusable(this)) return
|
|
112
|
+
focusElement(this.$el.querySelector('.w-rating__button:not([disabled])'))
|
|
113
|
+
},
|
|
114
|
+
|
|
107
115
|
onButtonClick (i) {
|
|
108
116
|
this.rating = i
|
|
109
117
|
this.$emit('update:modelValue', this.rating)
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch, useId, useAttrs } from 'vue'
|
|
44
44
|
import { objectifyClasses } from '../utils/index'
|
|
45
45
|
|
|
46
|
-
defineOptions({ name: 'WScrollable' })
|
|
46
|
+
defineOptions({ name: 'WScrollable', focusable: true })
|
|
47
47
|
|
|
48
48
|
const props = defineProps({
|
|
49
49
|
color: { type: String, default: 'primary' },
|
|
@@ -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,12 @@ 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
|
+
focusable: true,
|
|
84
|
+
expose: ['focus'],
|
|
83
85
|
mixins: [RippleMixin],
|
|
84
86
|
|
|
85
87
|
setup () {
|
|
@@ -183,6 +185,14 @@ export default {
|
|
|
183
185
|
},
|
|
184
186
|
|
|
185
187
|
methods: {
|
|
188
|
+
focus () {
|
|
189
|
+
const bar = this.$refs['tabs-bar']
|
|
190
|
+
if (!bar) return
|
|
191
|
+
const tab = bar.querySelector('[role="tab"][aria-selected="true"]:not([tabindex="-1"])')
|
|
192
|
+
|| bar.querySelector('[role="tab"][tabindex="0"]')
|
|
193
|
+
focusElement(tab)
|
|
194
|
+
},
|
|
195
|
+
|
|
186
196
|
resolveTabUid (value) {
|
|
187
197
|
if (!this.tabs.length) return null
|
|
188
198
|
if (value === undefined || value === null || value === '') return this.tabs[0]._uid
|
|
@@ -455,6 +465,12 @@ export default {
|
|
|
455
465
|
margin-right: -1px;
|
|
456
466
|
}
|
|
457
467
|
.w-tabs--card &--active {border-bottom-color: transparent;}
|
|
468
|
+
.w-tabs--pill-slider & {
|
|
469
|
+
padding: calc(var(--w-base-increment) * 1) calc(var(--w-base-increment) * 2);
|
|
470
|
+
margin: calc(var(--w-base-increment) * 1) calc(var(--w-base-increment) * 1);
|
|
471
|
+
border-radius: 99em;
|
|
472
|
+
font-size: round(nearest, calc(1.1 * var(--w-base-font-size)), 1px);
|
|
473
|
+
}
|
|
458
474
|
|
|
459
475
|
&--disabled {
|
|
460
476
|
cursor: not-allowed;
|
|
@@ -19,10 +19,12 @@ 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
|
+
focusable: true,
|
|
27
|
+
expose: ['focus'],
|
|
26
28
|
mixins: [RippleMixin],
|
|
27
29
|
|
|
28
30
|
props: {
|
|
@@ -48,6 +50,13 @@ export default {
|
|
|
48
50
|
|
|
49
51
|
emits: ['input', 'update:modelValue'],
|
|
50
52
|
|
|
53
|
+
methods: {
|
|
54
|
+
focus () {
|
|
55
|
+
if (this.modelValue === -1) return
|
|
56
|
+
focusElement(this.$el)
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
51
60
|
computed: {
|
|
52
61
|
presetSize () {
|
|
53
62
|
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 () {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
slot(name="activator")
|
|
3
3
|
slot(v-if="!$slots.activator")
|
|
4
4
|
teleport(v-if="detachableDomReady" :to="teleportTarget" :disabled="!teleportTarget")
|
|
5
|
-
transition(:name="transitionName" appear @after-leave="onAfterLeave")
|
|
5
|
+
transition(:name="transitionName" appear @after-enter="onDetachableAfterEnter" @after-leave="onAfterLeave")
|
|
6
6
|
.w-tooltip(
|
|
7
7
|
v-if="detachableVisible"
|
|
8
8
|
ref="detachable"
|
|
@@ -68,8 +68,12 @@ 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
|
+
focusable: true,
|
|
76
|
+
expose: ['focus'],
|
|
73
77
|
|
|
74
78
|
mixins: [RippleMixin],
|
|
75
79
|
|
|
@@ -122,6 +126,11 @@ export default {
|
|
|
122
126
|
},
|
|
123
127
|
|
|
124
128
|
methods: {
|
|
129
|
+
focus () {
|
|
130
|
+
if (this.disabled) return
|
|
131
|
+
focusElement(this.$el.querySelector('.w-tree__item-label[tabindex="0"]'))
|
|
132
|
+
},
|
|
133
|
+
|
|
125
134
|
// From data watcher, retain the oldItems open state.
|
|
126
135
|
updateCurrentDepthTree (items, oldItems = []) {
|
|
127
136
|
this.currentDepthItems = []
|