@@ -6,10 +6,15 @@ import {
6
6
shallowRef ,
7
7
unref ,
8
8
} from '@vue/runtime-core'
9
- import { nextSibling , previousSibling } from '../renderer/nodeOpts'
9
+ import {
10
+ getLastLeaf ,
11
+ previousDeepSibling ,
12
+ nextDeepSibling ,
13
+ } from '../renderer/nodeOpts'
10
14
import { DOMElement , DOMNode , isDOMElement } from '../renderer/dom'
11
15
import { checkCurrentInstance , getElementFromInstance , noop } from '../utils'
12
16
import { Focusable , FocusId } from './types'
17
+ import { indentHTML } from '../utils/indentHTML'
13
18
14
19
export interface FocusManager {
15
20
activeElement : ShallowRef < Focusable | null | undefined >
@@ -26,7 +31,7 @@ export interface FocusManager {
26
31
_changeFocusableId ( newId : FocusId , oldId : FocusId ) : void
27
32
28
33
/**
29
- * Focus a Focusable or remove the current focus by passing `null`.
34
+ * Focus a Focusable or remove the current focus by passing `null`. Returns the currently Focusable or null otherwise.
30
35
*
31
36
* @param id - id of the focusable
32
37
*/
@@ -92,62 +97,63 @@ export function createFocusManager(
92
97
}
93
98
}
94
99
95
- const focusNext : FocusManager [ 'focusNext' ] = ( ) => {
96
- // the rootNode cannot be focused, so it's safe to start from there
97
- const startNode : DOMNode =
98
- getElementFromInstance ( activeElement . value ?. _i ) || rootNode
99
- let node : DOMNode | null = startNode
100
- // startNode === rootNode ? rootNode.childNodes[0] : nextSibling(startNode)
100
+ const focusPrevious : FocusManager [ 'focusPrevious' ] = ( ) => {
101
+ const lastNode = getLastLeaf ( rootNode )
102
+ const activeNode = getElementFromInstance ( activeElement . value ?. _i )
103
+ let startNode = activeNode
104
+ let cursor : DOMNode | null | undefined = activeNode
101
105
102
106
do {
103
- node = nextDeepSibling ( node , true )
104
- // we reached the end of the tree and we didn't start at the root node
105
- // so let's try going from the root
106
- if ( ! node && startNode !== rootNode && cyclic ) {
107
- node = nextDeepSibling ( rootNode , true ) !
107
+ // get the previous deep node or the very last node
108
+ cursor = ( cursor && previousDeepSibling ( cursor ) ) || lastNode
109
+
110
+ // cursor cannot be empty but startNode can
111
+ if ( cursor === startNode ) {
112
+ break
108
113
}
114
+ // it is safe to set the starter node to the lastNode now
115
+ // so we can detect full loops
116
+ startNode = startNode || lastNode
109
117
} while (
110
- node &&
111
- // we did a full loop
112
- node !== startNode &&
118
+ cursor &&
113
119
// we skip any non focusable
114
- ( ! node . focusable ||
120
+ ( ! cursor . focusable ||
115
121
// or disabled element
116
- node . focusable . disabled . value )
122
+ cursor . focusable . disabled . value )
117
123
)
118
124
119
- if ( node && node . focusable ) {
120
- return focus ( unref ( node . focusable . id ) )
125
+ if ( cursor . focusable ) {
126
+ return focus ( unref ( cursor . focusable . id ) )
121
127
}
122
128
}
123
129
124
- const focusPrevious : FocusManager [ 'focusPrevious' ] = ( ) => {
125
- // the rootNode cannot be focused, so it's safe to start from there
126
- const lastNode = getLastNode ( rootNode )
127
- const startNode : DOMNode =
128
- getElementFromInstance ( activeElement . value ?. _i ) || lastNode
129
- let node : DOMNode | null = startNode
130
- // startNode === rootNode ? rootNode.childNodes[0] : nextSibling(startNode)
130
+ const focusNext : FocusManager [ 'focusNext' ] = ( ) => {
131
+ const firstNode = rootNode
132
+ const activeNode = getElementFromInstance ( activeElement . value ?. _i )
133
+ let startNode = activeNode
134
+ let cursor : DOMNode | null | undefined = activeNode
131
135
132
136
do {
133
- node = nextDeepSibling ( node , false )
134
- // we reached the rootNode of the tree and we didn't start at the root node
135
- // so let's try going from the root
136
- if ( ! node && startNode !== lastNode && cyclic ) {
137
- node = nextDeepSibling ( lastNode , false ) !
137
+ // get the next deep node or the very first node (the root)
138
+ cursor = ( cursor && nextDeepSibling ( cursor ) ) || firstNode
139
+
140
+ // cursor cannot be empty but startNode can
141
+ if ( cursor === startNode ) {
142
+ break
138
143
}
144
+ // it is safe to set the starter node to the lastNode now
145
+ // so we can detect full loops
146
+ startNode = startNode || firstNode
139
147
} while (
140
- node &&
141
- // we did a full loop
142
- node !== lastNode &&
148
+ cursor &&
143
149
// we skip any non focusable
144
- ( ! node . focusable ||
150
+ ( ! cursor . focusable ||
145
151
// or disabled element
146
- node . focusable . disabled . value )
152
+ cursor . focusable . disabled . value )
147
153
)
148
154
149
- if ( node && node . focusable ) {
150
- return focus ( unref ( node . focusable . id ) )
155
+ if ( cursor . focusable ) {
156
+ return focus ( unref ( cursor . focusable . id ) )
151
157
}
152
158
}
153
159
@@ -159,6 +165,7 @@ export function createFocusManager(
159
165
const _add : FocusManager [ '_add' ] = ( focusable ) => {
160
166
focusableMap . set ( unref ( focusable . id ) , focusable )
161
167
const el = getElementFromInstance ( focusable . _i )
168
+ // TODO: can el be an array? If so, should we error and allow only single element root?
162
169
if ( ! el ) {
163
170
throw new Error ( 'NO VNODE wat' )
164
171
}
@@ -168,6 +175,12 @@ export function createFocusManager(
168
175
const id = unref ( focusable . id )
169
176
const existingFocusable = focusableMap . get ( id )
170
177
if ( existingFocusable ) {
178
+ // remove the cyclic referenc
179
+ const el = getElementFromInstance ( focusable . _i )
180
+ if ( el ) {
181
+ el . focusable = null
182
+ }
183
+
171
184
focusableMap . delete ( id )
172
185
// if the focusable being removed is focused, remove focus
173
186
if ( activeElement . value === existingFocusable ) {
@@ -199,41 +212,3 @@ export function createFocusManager(
199
212
trapFocus,
200
213
}
201
214
}
202
-
203
- /**
204
- * Gets the next deep sibling. A DFS tree search. Returns `null` if we reached the root.
205
- *
206
- * @param node - node to start at
207
- */
208
- function nextDeepSibling ( node : DOMNode , forward : boolean ) : DOMNode | null {
209
- // check if the node has children
210
- if ( forward && isDOMElement ( node ) && node . childNodes . length > 0 ) {
211
- return node . childNodes [ 0 ]
212
- } else {
213
- // get the next sibling based on the parent
214
- let nextNode = ( forward ? nextSibling : previousSibling ) ( node )
215
- if ( nextNode ) return nextNode
216
- // no next sibling, find the closest parent next sibling
217
- nextNode = node . parentNode
218
- while ( nextNode ) {
219
- const sibling = ( forward ? nextSibling : previousSibling ) ( nextNode )
220
- if ( sibling ) {
221
- return sibling
222
- }
223
- // try again with the parent
224
- nextNode = nextNode . parentNode
225
- }
226
-
227
- // we reached the root
228
- return null
229
- }
230
- }
231
-
232
- function getLastNode ( node : DOMElement ) : DOMNode {
233
- let cursor : DOMNode = node
234
- while ( isDOMElement ( cursor ) && cursor . childNodes . length > 0 ) {
235
- cursor = cursor . childNodes [ cursor . childNodes . length - 1 ]
236
- }
237
-
238
- return cursor
239
- }
0 commit comments