wave-ui 4.1.0 → 4.2.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/types/types/$waveui.d.ts +2 -2
- package/dist/wave-ui.cjs.js +3 -3
- package/dist/wave-ui.esm.js +898 -798
- package/dist/wave-ui.umd.js +3 -3
- package/package.json +1 -1
- package/src/wave-ui/components/w-accordion/item.vue +1 -0
- package/src/wave-ui/components/w-button/index.vue +1 -0
- package/src/wave-ui/components/w-checkboxes.vue +1 -0
- package/src/wave-ui/components/w-list.vue +1 -0
- package/src/wave-ui/components/w-menu.vue +1 -1
- package/src/wave-ui/components/w-radios.vue +1 -0
- package/src/wave-ui/components/w-rating.vue +1 -0
- package/src/wave-ui/components/w-scrollable.vue +1 -1
- package/src/wave-ui/components/w-tabs/index.vue +1 -0
- package/src/wave-ui/components/w-tag.vue +1 -0
- package/src/wave-ui/components/w-tooltip.vue +1 -1
- package/src/wave-ui/components/w-tree.vue +1 -0
- package/src/wave-ui/core.js +100 -5
- package/src/wave-ui/mixins/detachable.js +41 -2
- package/src/wave-ui/mixins/focusable.js +12 -3
- package/src/wave-ui/utils/dynamic-css.js +3 -3
- package/src/wave-ui/utils/focus.js +52 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-ui",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.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",
|
|
@@ -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"
|
|
@@ -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' },
|
|
@@ -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"
|
package/src/wave-ui/core.js
CHANGED
|
@@ -2,10 +2,10 @@ import { reactive, inject } from 'vue'
|
|
|
2
2
|
import { mergeConfig } from './utils/config'
|
|
3
3
|
import { consoleWarn } from './utils/console'
|
|
4
4
|
import { colorPalette, generateColorShades, flattenColors } from './utils/colors'
|
|
5
|
-
import { injectColorsCSSInDOM, injectCSSInDOM } from './utils/dynamic-css'
|
|
5
|
+
import { injectColorsCSSInDOM, injectCSSInDOM, generatePaletteVariables, generateColors } 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
|
+
import { scheduleFocus, registerVFocus, unregisterVFocus } from './utils/focus'
|
|
9
9
|
import './scss/index.scss'
|
|
10
10
|
|
|
11
11
|
let mounted = false
|
|
@@ -88,9 +88,12 @@ export default class WaveUI {
|
|
|
88
88
|
* @param {string} theme - The theme to switch to.
|
|
89
89
|
*/
|
|
90
90
|
switchTheme (theme) {
|
|
91
|
+
// Only remove the current colors stylesheet when the theme actually changes.
|
|
92
|
+
// This prevents a blink when switchTheme is called on first mount with the same theme
|
|
93
|
+
// that was already set during SSR (via getSSRStyles + useHead) or in the constructor.
|
|
94
|
+
if (this.theme !== theme) document.head.querySelector('#wave-ui-colors')?.remove?.()
|
|
91
95
|
this.theme = theme
|
|
92
96
|
document.documentElement.setAttribute('data-theme', theme)
|
|
93
|
-
document.head.querySelector('#wave-ui-colors')?.remove?.()
|
|
94
97
|
const themeColors = this.config.colors[this.theme]
|
|
95
98
|
injectColorsCSSInDOM(themeColors, colorPalette, this.config.css.colorShadeCssVariables)
|
|
96
99
|
this.colors = flattenColors(themeColors, colorPalette)
|
|
@@ -107,13 +110,99 @@ export default class WaveUI {
|
|
|
107
110
|
wApp.className = 'w-app' // First reset the classes.
|
|
108
111
|
if (classes.length && classes[0]) wApp.classList.add(...classes)
|
|
109
112
|
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Returns the CSS strings for both Wave UI stylesheets so they can be injected
|
|
117
|
+
* server-side (e.g. via Nuxt's useHead) to prevent FOUC.
|
|
118
|
+
* The returned strings map to the `#wave-ui-palette` and `#wave-ui-colors` <style> tags.
|
|
119
|
+
*
|
|
120
|
+
* When called without a `theme` argument (recommended), `colors` contains both themes'
|
|
121
|
+
* custom color variables each scoped to `[data-theme="light"]` / `[data-theme="dark"]`.
|
|
122
|
+
* Combined with `WaveUI.getThemeInitScript()` injected as a blocking <script> in <head>,
|
|
123
|
+
* this guarantees the correct colors are present at first paint — no flash of wrong theme.
|
|
124
|
+
*
|
|
125
|
+
* When called with an explicit `theme`, returns only that theme's variables on `:root`
|
|
126
|
+
* (legacy / single-theme use cases).
|
|
127
|
+
*
|
|
128
|
+
* @param {string} [theme] - Explicit theme ('light'|'dark'). Omit for dual-scoped output.
|
|
129
|
+
* @returns {{ theme: string, palette: string, colors: string }}
|
|
130
|
+
*/
|
|
131
|
+
getSSRStyles (theme) {
|
|
132
|
+
const palette = generatePaletteVariables(colorPalette)
|
|
133
|
+
const { colorShadeCssVariables } = this.config.css
|
|
134
|
+
|
|
135
|
+
// No theme specified → emit both themes scoped to [data-theme="X"] so a blocking init
|
|
136
|
+
// script only needs to set the attribute — no color flash is possible.
|
|
137
|
+
if (!theme) {
|
|
138
|
+
return {
|
|
139
|
+
theme: this.theme || this.config?.theme || 'light',
|
|
140
|
+
palette,
|
|
141
|
+
colors:
|
|
142
|
+
generateColors(this.config.colors.light, colorShadeCssVariables, '[data-theme="light"]') +
|
|
143
|
+
generateColors(this.config.colors.dark, colorShadeCssVariables, '[data-theme="dark"]')
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
theme,
|
|
149
|
+
palette,
|
|
150
|
+
colors: generateColors(this.config.colors[theme], colorShadeCssVariables)
|
|
151
|
+
}
|
|
110
152
|
}
|
|
111
153
|
}
|
|
112
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Resolves the initial theme from localStorage or OS preference.
|
|
157
|
+
* Use this as the `theme` install option so Wave UI is initialized with the correct
|
|
158
|
+
* theme from the very first render — no flash, no manual localStorage reading needed.
|
|
159
|
+
*
|
|
160
|
+
* app.use(WaveUI, { theme: WaveUI.resolveInitialTheme() })
|
|
161
|
+
*
|
|
162
|
+
* On the server (SSR) where localStorage/window are unavailable, always returns 'light'
|
|
163
|
+
* as the safe fallback — pair with `getSSRStyles()` + `getThemeInitScript()` to handle
|
|
164
|
+
* the server → client handoff without FOUC.
|
|
165
|
+
*
|
|
166
|
+
* @param {string} [storageKey='waveui-theme'] - The localStorage key to read.
|
|
167
|
+
* @returns {'light'|'dark'}
|
|
168
|
+
*/
|
|
169
|
+
static resolveInitialTheme (storageKey = 'waveui-theme') {
|
|
170
|
+
if (typeof window === 'undefined') return 'light'
|
|
171
|
+
try {
|
|
172
|
+
const stored = localStorage.getItem(storageKey)
|
|
173
|
+
if (stored === 'light' || stored === 'dark') return stored
|
|
174
|
+
}
|
|
175
|
+
catch { }
|
|
176
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Returns a minified inline script that sets `data-theme` on `<html>` synchronously,
|
|
181
|
+
* before any CSS is parsed or rendered — eliminating theme FOUC entirely.
|
|
182
|
+
*
|
|
183
|
+
* Inject it as a blocking (no async/defer) <script> in <head> before any CSS:
|
|
184
|
+
*
|
|
185
|
+
* Nuxt — nuxt.config.ts:
|
|
186
|
+
* app: { head: { script: [{ innerHTML: WaveUI.getThemeInitScript() }] } }
|
|
187
|
+
*
|
|
188
|
+
* Plain HTML:
|
|
189
|
+
* <script><%= WaveUI.getThemeInitScript() %></script>
|
|
190
|
+
*
|
|
191
|
+
* @param {string} [storageKey='waveui-theme'] - Must match the key used when saving the theme.
|
|
192
|
+
* @returns {string} Minified inline script string (no <script> tags).
|
|
193
|
+
*/
|
|
194
|
+
static getThemeInitScript (storageKey = 'waveui-theme') {
|
|
195
|
+
const key = JSON.stringify(storageKey)
|
|
196
|
+
return `(function(){try{var t=localStorage.getItem(${key});if(t==='dark'||t==='light'){document.documentElement.setAttribute('data-theme',t);return}}catch(e){}if(window.matchMedia('(prefers-color-scheme:dark)').matches)document.documentElement.setAttribute('data-theme','dark')})()`
|
|
197
|
+
}
|
|
198
|
+
|
|
113
199
|
static install (app, options = {}) {
|
|
114
200
|
// Register directives.
|
|
115
201
|
app.directive('focus', {
|
|
116
|
-
mounted: (el
|
|
202
|
+
mounted: (el) => {
|
|
203
|
+
if (!registerVFocus(el)) scheduleFocus(el)
|
|
204
|
+
},
|
|
205
|
+
unmounted: (el) => unregisterVFocus(el)
|
|
117
206
|
})
|
|
118
207
|
app.directive('scroll', {
|
|
119
208
|
mounted: (el, binding) => {
|
|
@@ -156,7 +245,12 @@ export default class WaveUI {
|
|
|
156
245
|
}
|
|
157
246
|
|
|
158
247
|
if (config.theme === 'auto') detectOSDarkMode($waveui) // Also switches the theme.
|
|
159
|
-
else
|
|
248
|
+
else {
|
|
249
|
+
// Respect any data-theme already set on <html> (e.g. by getThemeInitScript) so a
|
|
250
|
+
// blocking init script is enough — no need to pass `theme` to app.use(WaveUI, ...).
|
|
251
|
+
const docTheme = document.documentElement.getAttribute('data-theme')
|
|
252
|
+
$waveui.switchTheme(docTheme === 'light' || docTheme === 'dark' ? docTheme : config.theme)
|
|
253
|
+
}
|
|
160
254
|
|
|
161
255
|
injectCSSInDOM($waveui)
|
|
162
256
|
injectNotifManagerInDOM(app)
|
|
@@ -200,6 +294,7 @@ export default class WaveUI {
|
|
|
200
294
|
app.provide('$waveui', $waveui)
|
|
201
295
|
|
|
202
296
|
if (config.theme !== 'auto') {
|
|
297
|
+
this.$waveui.theme = config.theme
|
|
203
298
|
this.$waveui.colors = flattenColors(config.colors[config.theme], colorPalette)
|
|
204
299
|
}
|
|
205
300
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { consoleWarn } from '../utils/console'
|
|
12
|
+
import { callFocus } from '../utils/focus'
|
|
12
13
|
|
|
13
14
|
// Minimum space (px) from the viewport edge when flipping / nudging.
|
|
14
15
|
const VIEWPORT_MARGIN = 4
|
|
@@ -65,6 +66,10 @@ export default {
|
|
|
65
66
|
// Components use this to bind visibility:hidden, so the element is never visible at the
|
|
66
67
|
// wrong position before its coordinates are calculated.
|
|
67
68
|
detachableReady: false,
|
|
69
|
+
// Transition @after-enter has run for the current open cycle.
|
|
70
|
+
_contentEntered: false,
|
|
71
|
+
// v-focus entries waiting for detachableReady + _contentEntered.
|
|
72
|
+
_autofocusTargets: [],
|
|
68
73
|
// The Vue Teleport target. Stored as data (not computed) so it is resolved lazily at
|
|
69
74
|
// open()-time — after the DOM is committed — rather than during VNode creation where
|
|
70
75
|
// document.querySelector() may return null for elements that are part of the same render batch.
|
|
@@ -171,6 +176,35 @@ export default {
|
|
|
171
176
|
onAfterLeave () {
|
|
172
177
|
this.detachableReady = false
|
|
173
178
|
this.detachableEl = null
|
|
179
|
+
this._contentEntered = false
|
|
180
|
+
this._autofocusTargets = []
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
onDetachableAfterEnter () {
|
|
184
|
+
this._contentEntered = true
|
|
185
|
+
this._maybeFlushAutofocus()
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
registerAutofocus (entry) {
|
|
189
|
+
if (!this._autofocusTargets) this._autofocusTargets = []
|
|
190
|
+
this._autofocusTargets.push(entry)
|
|
191
|
+
this._maybeFlushAutofocus()
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
unregisterAutofocus (el) {
|
|
195
|
+
if (!this._autofocusTargets?.length) return
|
|
196
|
+
this._autofocusTargets = this._autofocusTargets.filter(entry => entry.el !== el)
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
_maybeFlushAutofocus () {
|
|
200
|
+
if (this.detachableReady && this._contentEntered) this.flushAutofocus()
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
flushAutofocus () {
|
|
204
|
+
const targets = this._autofocusTargets || []
|
|
205
|
+
this._autofocusTargets = []
|
|
206
|
+
if (!targets.length) return
|
|
207
|
+
this.$nextTick(() => targets.forEach(({ el }) => callFocus(el)))
|
|
174
208
|
},
|
|
175
209
|
|
|
176
210
|
unbindActivatorDocEvents () {
|
|
@@ -253,6 +287,7 @@ export default {
|
|
|
253
287
|
|
|
254
288
|
// Hide before entering the DOM; handles rapid re-opens where detachableReady is still true.
|
|
255
289
|
this.detachableReady = false
|
|
290
|
+
this._contentEntered = false
|
|
256
291
|
|
|
257
292
|
// Resolve the teleport target here, at open()-time, so the DOM is fully committed and
|
|
258
293
|
// detachableDefaultRoot() (for nested menus/tooltips) returns the correct element.
|
|
@@ -274,8 +309,11 @@ export default {
|
|
|
274
309
|
this.activatorWidth = this.activatorEl.offsetWidth
|
|
275
310
|
}
|
|
276
311
|
|
|
277
|
-
if (!this.noPosition) this.computeDetachableCoords()
|
|
278
|
-
else
|
|
312
|
+
if (!this.noPosition) await this.computeDetachableCoords()
|
|
313
|
+
else {
|
|
314
|
+
this.detachableReady = true
|
|
315
|
+
this._maybeFlushAutofocus()
|
|
316
|
+
}
|
|
279
317
|
|
|
280
318
|
// In `getActivatorCoordinates` accessing the menu computed styles takes a few ms (less than 10ms),
|
|
281
319
|
// if we don't postpone the Menu apparition it will start transition from a visible menu and
|
|
@@ -412,6 +450,7 @@ export default {
|
|
|
412
450
|
// Always await $nextTick so coordinates, placement class, and visibility are flushed atomically.
|
|
413
451
|
this.detachableReady = true
|
|
414
452
|
await this.$nextTick()
|
|
453
|
+
this._maybeFlushAutofocus()
|
|
415
454
|
|
|
416
455
|
// Guard against the component being unmounted while awaiting.
|
|
417
456
|
if (!this.detachableEl) return
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { guardFocusable, focusElement } from '../utils/focus'
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
|
+
focusable: true,
|
|
5
|
+
|
|
4
6
|
methods: {
|
|
5
7
|
focus () {
|
|
6
8
|
if (!guardFocusable(this)) return
|
|
7
9
|
const refName = this.$options.focusTargetRef || 'input'
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const tryFocus = () => {
|
|
11
|
+
const target = this.$refs[refName]
|
|
12
|
+
if (target) focusElement(target)
|
|
13
|
+
return !!target
|
|
14
|
+
}
|
|
15
|
+
if (tryFocus()) return
|
|
16
|
+
this.$nextTick(() => {
|
|
17
|
+
if (tryFocus()) return
|
|
18
|
+
this.$nextTick(() => tryFocus())
|
|
19
|
+
})
|
|
11
20
|
}
|
|
12
21
|
}
|
|
13
22
|
}
|
|
@@ -9,7 +9,7 @@ const cssVars = {
|
|
|
9
9
|
let breakpointsDef = { keys: [], values: [] }
|
|
10
10
|
let currentBreakpoint = null
|
|
11
11
|
|
|
12
|
-
const generatePaletteVariables = colorPalette => {
|
|
12
|
+
export const generatePaletteVariables = colorPalette => {
|
|
13
13
|
let cssVariablesString = ''
|
|
14
14
|
|
|
15
15
|
colorPalette.forEach(({ label, color, shades = [] }) => {
|
|
@@ -26,7 +26,7 @@ const generatePaletteVariables = colorPalette => {
|
|
|
26
26
|
// :root {[color1-variable], [color2-variable]}
|
|
27
27
|
// .color1--bg {background-color: [color1-variable]}
|
|
28
28
|
// .color1 {color: [color1-variable]}
|
|
29
|
-
const generateColors = (themeColors, generateShadeCssVariables) => {
|
|
29
|
+
export const generateColors = (themeColors, generateShadeCssVariables, scope = ':root') => {
|
|
30
30
|
let styles = ''
|
|
31
31
|
let cssVariablesString = ''
|
|
32
32
|
|
|
@@ -62,7 +62,7 @@ const generateColors = (themeColors, generateShadeCssVariables) => {
|
|
|
62
62
|
for (const colorName in shades) cssVariablesString += `--w-${colorName}-color: ${shades[colorName]};`
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
return
|
|
65
|
+
return `${scope}{${cssVariablesString}}${styles}`
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// Generate the layout grid. E.g. xs1, xs2, ..., xl12.
|
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
import { nextTick } from 'vue'
|
|
2
2
|
|
|
3
|
+
const DETACHABLE_COMPONENTS = new Set(['w-menu', 'w-tooltip'])
|
|
4
|
+
|
|
5
|
+
/** Walk Vue 3 logical parent instances from a directive host element (`__vueParentComponent`). */
|
|
6
|
+
function walkParentInstances (el, predicate) {
|
|
7
|
+
let instance = el?.__vueParentComponent
|
|
8
|
+
while (instance) {
|
|
9
|
+
if (predicate(instance)) return instance
|
|
10
|
+
instance = instance.parent
|
|
11
|
+
}
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function componentName (instance) {
|
|
16
|
+
return instance?.type?.name ?? instance?.proxy?.$options?.name
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isFocusableInstance (instance) {
|
|
20
|
+
return !!(instance?.type?.focusable || instance?.proxy?.$options?.focusable)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isDetachableInstance (instance) {
|
|
24
|
+
return DETACHABLE_COMPONENTS.has(componentName(instance))
|
|
25
|
+
}
|
|
26
|
+
|
|
3
27
|
export function focusElement (el) {
|
|
4
28
|
el?.focus?.()
|
|
5
29
|
}
|
|
@@ -9,23 +33,20 @@ export function guardFocusable (vm) {
|
|
|
9
33
|
return true
|
|
10
34
|
}
|
|
11
35
|
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (typeof focusFn === 'function') return focusFn
|
|
22
|
-
instance = instance.parent
|
|
23
|
-
}
|
|
24
|
-
return null
|
|
36
|
+
/** Nearest ancestor component with `focusable: true` (e.g. w-input inside w-form-element). */
|
|
37
|
+
export function resolveFocusableInstance (el) {
|
|
38
|
+
return walkParentInstances(el, isFocusableInstance)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** w-menu / w-tooltip host for deferred v-focus while floating content opens. */
|
|
42
|
+
export function findDetachableInstance (el) {
|
|
43
|
+
const instance = walkParentInstances(el, isDetachableInstance)
|
|
44
|
+
return instance?.proxy ?? null
|
|
25
45
|
}
|
|
26
46
|
|
|
27
|
-
export function callFocus (
|
|
28
|
-
const
|
|
47
|
+
export function callFocus (el) {
|
|
48
|
+
const instance = resolveFocusableInstance(el)
|
|
49
|
+
const focusFn = instance?.exposed?.focus ?? instance?.proxy?.focus
|
|
29
50
|
if (typeof focusFn === 'function') {
|
|
30
51
|
focusFn()
|
|
31
52
|
return
|
|
@@ -34,6 +55,20 @@ export function callFocus (vnode, el) {
|
|
|
34
55
|
}
|
|
35
56
|
|
|
36
57
|
/** Schedule focus after mount so template refs exist on the host component. */
|
|
37
|
-
export function scheduleFocus (
|
|
38
|
-
nextTick(() => callFocus(
|
|
58
|
+
export function scheduleFocus (el) {
|
|
59
|
+
nextTick(() => callFocus(el))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Register v-focus on a detachable host; returns false when not inside w-menu/w-tooltip. */
|
|
63
|
+
export function registerVFocus (el) {
|
|
64
|
+
const detachable = findDetachableInstance(el)
|
|
65
|
+
if (!detachable) return false
|
|
66
|
+
el.__waveUiDetachable = detachable
|
|
67
|
+
detachable.registerAutofocus({ el })
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function unregisterVFocus (el) {
|
|
72
|
+
el.__waveUiDetachable?.unregisterAutofocus(el)
|
|
73
|
+
delete el.__waveUiDetachable
|
|
39
74
|
}
|