Skip to content

Commit ee2acde

Browse files
zombieJclaude
andcommitted
feat: add ignored element support for focus lock
Add ability to mark elements as ignored during focus lock, allowing temporary focus on elements outside the locked area. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 595856a commit ee2acde

File tree

1 file changed

+28
-1
lines changed

1 file changed

+28
-1
lines changed

src/Dom/focus.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,20 @@ export function triggerFocus(
102102
// ======================================================
103103
let lastFocusElement: HTMLElement | null = null;
104104
let focusElements: HTMLElement[] = [];
105+
const ignoredElementMap = new Map<HTMLElement, HTMLElement | null>();
105106

106107
function getLastElement() {
107108
return focusElements[focusElements.length - 1];
108109
}
109110

111+
function isIgnoredElement(element: HTMLElement | null): boolean {
112+
if (!element) return false;
113+
const ignoredEle = ignoredElementMap.get(getLastElement());
114+
return (
115+
!!ignoredEle && (ignoredEle === element || ignoredEle.contains(element))
116+
);
117+
}
118+
110119
function hasFocus(element: HTMLElement) {
111120
const { activeElement } = document;
112121
return element === activeElement || element.contains(activeElement);
@@ -116,6 +125,11 @@ function syncFocus() {
116125
const lastElement = getLastElement();
117126
const { activeElement } = document;
118127

128+
// If current focus is on an ignored element, don't force it back
129+
if (isIgnoredElement(activeElement as HTMLElement)) {
130+
return;
131+
}
132+
119133
if (lastElement && !hasFocus(lastElement)) {
120134
const focusableList = getFocusNodeList(lastElement);
121135

@@ -166,6 +180,7 @@ export function lockFocus(element: HTMLElement): VoidFunction {
166180
return () => {
167181
lastFocusElement = null;
168182
focusElements = focusElements.filter(ele => ele !== element);
183+
ignoredElementMap.delete(element);
169184
if (focusElements.length === 0) {
170185
window.removeEventListener('focusin', syncFocus);
171186
window.removeEventListener('keydown', onWindowKeyDown, true);
@@ -177,11 +192,12 @@ export function lockFocus(element: HTMLElement): VoidFunction {
177192
* Lock focus within an element.
178193
* When locked, focus will be restricted to focusable elements within the specified element.
179194
* If multiple elements are locked, only the last locked element will be effective.
195+
* @returns A function to mark an element as ignored, which will temporarily allow focus on that element even if it's outside the locked area.
180196
*/
181197
export function useLockFocus(
182198
lock: boolean,
183199
getElement: () => HTMLElement | null,
184-
) {
200+
): [ignoreElement: (ele: HTMLElement) => void] {
185201
useEffect(() => {
186202
if (lock) {
187203
const element = getElement();
@@ -190,4 +206,15 @@ export function useLockFocus(
190206
}
191207
}
192208
}, [lock]);
209+
210+
const ignoreElement = (ele: HTMLElement) => {
211+
const element = getElement();
212+
if (element && ele) {
213+
// Set the ignored element for current lock element
214+
// Only one element can be ignored at a time for this lock
215+
ignoredElementMap.set(element, ele);
216+
}
217+
};
218+
219+
return [ignoreElement];
193220
}

0 commit comments

Comments
 (0)