Skip to content

Commit 42c5cc6

Browse files
committed
feat(use-focus): adds focusWithin flag to useFocus hook
This adds an option to add onfocusin and onfocusout event handlers to the focus hook.
1 parent e713eba commit 42c5cc6

File tree

2 files changed

+55
-3
lines changed

2 files changed

+55
-3
lines changed

packages/floating-ui-svelte/src/hooks/use-focus.svelte.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { isMac, isSafari } from "../internal/environment.js";
1111
import { isTypeableElement } from "../internal/is-typable-element.js";
1212
import type { OpenChangeReason } from "../types.js";
1313
import type { FloatingContext } from "./use-floating.svelte.js";
14+
import type { HTMLAttributes } from 'svelte/elements';
1415

1516
interface UseFocusOptions {
1617
/**
@@ -25,6 +26,12 @@ interface UseFocusOptions {
2526
* @default true
2627
*/
2728
visibleOnly?: boolean;
29+
/**
30+
* Whether the open state should change when focusing within the trigger element.
31+
*
32+
* @default false
33+
*/
34+
focusWithin?: boolean;
2835
}
2936

3037
function useFocus(context: FloatingContext, options: UseFocusOptions = {}) {
@@ -35,7 +42,7 @@ function useFocus(context: FloatingContext, options: UseFocusOptions = {}) {
3542
elements: { reference, floating },
3643
} = $derived(context);
3744

38-
const { enabled = true, visibleOnly = true } = $derived(options);
45+
const { enabled = true, visibleOnly = true, focusWithin = false } = $derived(options);
3946

4047
let blockFocus = false;
4148
let timeout = -1;
@@ -101,7 +108,7 @@ function useFocus(context: FloatingContext, options: UseFocusOptions = {}) {
101108
if (!enabled) {
102109
return {};
103110
}
104-
return {
111+
const handlers: HTMLAttributes<HTMLElement>= {
105112
onpointerdown: (event: PointerEvent) => {
106113
if (isVirtualPointerEvent(event)) return;
107114
keyboardModality = false;
@@ -173,7 +180,14 @@ function useFocus(context: FloatingContext, options: UseFocusOptions = {}) {
173180
onOpenChange(false, event, "focus");
174181
});
175182
},
176-
};
183+
}
184+
185+
if (focusWithin) {
186+
handlers.onfocusin = handlers.onfocus;
187+
handlers.onfocusout = handlers.onblur;
188+
}
189+
190+
return handlers;
177191
},
178192
};
179193
}

packages/floating-ui-svelte/test/hooks/use-focus.ts

+38
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { fireEvent, render, screen } from "@testing-library/svelte";
22
import { userEvent } from "@testing-library/user-event";
33
import { describe, expect, it, vi } from "vitest";
44
import App from "./wrapper-components/use-focus.svelte";
5+
import { useFloating, useId, useFocus } from '../../src/index.js';
6+
import { withRunes } from '../internal/with-runes.svelte.js';
57

68
/**
79
* This test suite is skipped because `useFocus` heavily depends on the environment and JSDom can't deliver that environment (nor can happy-dom)
@@ -101,4 +103,40 @@ describe.skip("useFocus", () => {
101103
expect(screen.queryByTestId("floating")).toBeInTheDocument();
102104
});
103105
});
106+
107+
104108
});
109+
110+
describe("focusWithin", () => {
111+
function createElements(): { reference: HTMLElement; floating: HTMLElement } {
112+
const reference = document.createElement("div");
113+
const floating = document.createElement("div");
114+
reference.id = useId();
115+
floating.id = useId();
116+
return { reference, floating };
117+
}
118+
119+
it("Should not output onfocusin or onfocusout event handlers", withRunes( () => {
120+
expect.assertions(2);
121+
122+
const elements = createElements();
123+
const floating = useFloating({ elements });
124+
const focus = useFocus(floating.context);
125+
const handlers = focus.reference;
126+
127+
expect(handlers).not.toHaveProperty("onfocusin");
128+
expect(handlers).not.toHaveProperty("onfocusout");
129+
}))
130+
131+
it("Should output onfocusin or onfocusout event handlers", withRunes( () => {
132+
expect.assertions(2);
133+
134+
const elements = createElements();
135+
const floating = useFloating({ elements });
136+
const focus = useFocus(floating.context, { focusWithin: true });
137+
const handlers = focus.reference;
138+
139+
expect(handlers).toHaveProperty("onfocusin");
140+
expect(handlers).toHaveProperty("onfocusout");
141+
}))
142+
})

0 commit comments

Comments
 (0)