Skip to content

Commit 403a4ce

Browse files
committed
more
1 parent e659564 commit 403a4ce

File tree

11 files changed

+131
-37
lines changed

11 files changed

+131
-37
lines changed

biome.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"rules": {
1010
"style": {
1111
"useConst": "off",
12-
"useImportType": "off"
12+
"useImportType": "off",
13+
"noNonNullAssertion": "off"
1314
}
1415
}
1516
}

packages/floating-ui-svelte/src/components/floating-focus-manager/floating-focus-manager.svelte

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@
156156
import { box } from "../../internal/box.svelte.js";
157157
import { reactiveActiveElement } from "../../internal/active-element.svelte.js";
158158
import { sleep } from "../../internal/sleep.js";
159+
import { handleGuardFocus } from "../../internal/handle-guard-focus.js";
159160
160161
let {
161162
context,
@@ -433,14 +434,14 @@
433434
() => beforeGuardRef,
434435
() => {
435436
if (!portalContext) return;
436-
portalContext.beforeInsideRef = beforeGuardRef;
437+
portalContext.beforeInsideGuard = beforeGuardRef;
437438
}
438439
);
439440
watch.pre(
440441
() => afterGuardRef,
441442
() => {
442443
if (!portalContext) return;
443-
portalContext.afterInsideRef = afterGuardRef;
444+
portalContext.afterInsideGuard = afterGuardRef;
444445
}
445446
);
446447
@@ -482,8 +483,8 @@
482483
endDismissButtonRef.current,
483484
beforeGuardRef,
484485
afterGuardRef,
485-
portalContext?.beforeOutsideRef,
486-
portalContext?.afterOutsideRef,
486+
portalContext?.beforeOutsideGuard,
487+
portalContext?.afterOutsideGuard,
487488
order.includes("reference") || isUntrappedTypeableCombobox
488489
? context.domReference
489490
: null,
@@ -811,7 +812,7 @@
811812
nextTabbable?.focus();
812813
});
813814
} else {
814-
portalContext.beforeOutsideRef?.focus();
815+
handleGuardFocus(portalContext.beforeOutsideGuard);
815816
}
816817
}
817818
}} />
@@ -850,7 +851,7 @@ will have a dismiss button.
850851
prevTabbable?.focus();
851852
});
852853
} else {
853-
portalContext.afterOutsideRef?.focus();
854+
handleGuardFocus(portalContext.afterOutsideGuard);
854855
}
855856
}
856857
}} />

packages/floating-ui-svelte/src/components/floating-portal/floating-portal.svelte

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
<script lang="ts">
4343
import Portal from "./portal.svelte";
4444
import { sleep } from "../../internal/sleep.js";
45+
import { handleGuardFocus } from "../../internal/handle-guard-focus.js";
4546
4647
let {
4748
children,
@@ -56,10 +57,10 @@
5657
});
5758
5859
let focusManagerState = $state.raw<FocusManagerState>(null);
59-
let beforeOutsideRef = $state<HTMLSpanElement | null>(null);
60-
let afterOutsideRef = $state<HTMLSpanElement | null>(null);
61-
let beforeInsideRef = $state<HTMLSpanElement | null>(null);
62-
let afterInsideRef = $state<HTMLSpanElement | null>(null);
60+
let beforeOutsideGuard = $state<HTMLSpanElement | null>(null);
61+
let afterOutsideGuard = $state<HTMLSpanElement | null>(null);
62+
let beforeInsideGuard = $state<HTMLSpanElement | null>(null);
63+
let afterInsideGuard = $state<HTMLSpanElement | null>(null);
6364
6465
const modal = $derived(focusManagerState?.modal);
6566
const open = $derived(focusManagerState?.open);
@@ -110,23 +111,23 @@
110111
get preserveTabOrder() {
111112
return preserveTabOrder;
112113
},
113-
get beforeInsideRef() {
114-
return beforeInsideRef;
114+
get beforeInsideGuard() {
115+
return beforeInsideGuard;
115116
},
116-
set beforeInsideRef(value: HTMLSpanElement | null) {
117-
beforeInsideRef = value;
117+
set beforeInsideGuard(value: HTMLSpanElement | null) {
118+
beforeInsideGuard = value;
118119
},
119-
get beforeOutsideRef() {
120-
return beforeOutsideRef;
120+
get beforeOutsideGuard() {
121+
return beforeOutsideGuard;
121122
},
122-
get afterInsideRef() {
123-
return afterInsideRef;
123+
get afterInsideGuard() {
124+
return afterInsideGuard;
124125
},
125-
set afterInsideRef(value: HTMLSpanElement | null) {
126-
afterInsideRef = value;
126+
set afterInsideGuard(value: HTMLSpanElement | null) {
127+
afterInsideGuard = value;
127128
},
128-
get afterOutsideRef() {
129-
return afterOutsideRef;
129+
get afterOutsideGuard() {
130+
return afterOutsideGuard;
130131
},
131132
get portalNode() {
132133
return portalNode.current;
@@ -141,10 +142,10 @@
141142
<FocusGuard
142143
type="outside"
143144
data-name="before"
144-
bind:ref={() => beforeOutsideRef, (v) => (beforeOutsideRef = v)}
145+
bind:ref={() => beforeOutsideGuard, (v) => (beforeOutsideGuard = v)}
145146
onfocus={(event) => {
146147
if (isOutsideEvent(event, portalNode.current)) {
147-
beforeInsideRef?.focus();
148+
handleGuardFocus(beforeInsideGuard);
148149
} else {
149150
sleep().then(() => {
150151
const prevTabbable =
@@ -168,10 +169,10 @@
168169
<FocusGuard
169170
type="outside"
170171
data-name="after"
171-
bind:ref={() => afterOutsideRef, (v) => (afterOutsideRef = v)}
172+
bind:ref={() => afterOutsideGuard, (v) => (afterOutsideGuard = v)}
172173
onfocus={(event) => {
173174
if (isOutsideEvent(event, portalNode.current)) {
174-
afterInsideRef?.focus();
175+
handleGuardFocus(afterInsideGuard);
175176
} else {
176177
sleep().then(() => {
177178
const nextTabbable =

packages/floating-ui-svelte/src/components/floating-portal/hooks.svelte.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ const PortalContext = new Context<{
1717
preserveTabOrder: boolean;
1818
portalNode: HTMLElement | null;
1919
setFocusManagerState: (state: FocusManagerState) => void;
20-
beforeInsideRef: HTMLSpanElement | null;
21-
afterInsideRef: HTMLSpanElement | null;
22-
beforeOutsideRef: HTMLSpanElement | null;
23-
afterOutsideRef: HTMLSpanElement | null;
20+
beforeInsideGuard: HTMLSpanElement | null;
21+
afterInsideGuard: HTMLSpanElement | null;
22+
beforeOutsideGuard: HTMLSpanElement | null;
23+
afterOutsideGuard: HTMLSpanElement | null;
2424
} | null>("PortalContext");
2525

2626
const attr = createAttribute("portal");
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { sleep } from "./sleep.js";
2+
3+
/**
4+
* Guards have an `aria-hidden` attribute on them, as they should not be visible to screen readers,
5+
* however, they are used as an intermediary to focus the real target. When they receive focus in
6+
* chromium browsers, an error is logged informing that an `aria-hidden` element should not be
7+
* focused.
8+
*
9+
* This function handles the focus of the guard element first by momentary removing the
10+
* `aria-hidden` attribute, focusing the guard (which will cause something else to focus), and then
11+
* restoring the attribute.
12+
*/
13+
14+
function handleGuardFocus(guard: HTMLElement | null) {
15+
if (!guard) return;
16+
const ariaHidden = guard.getAttribute("aria-hidden");
17+
guard.removeAttribute("aria-hidden");
18+
guard.focus();
19+
sleep().then(() => {
20+
if (ariaHidden === null) {
21+
guard.setAttribute("aria-hidden", "");
22+
} else {
23+
guard.setAttribute("aria-hidden", ariaHidden);
24+
}
25+
});
26+
}
27+
28+
export { handleGuardFocus };

packages/floating-ui-svelte/test/visual/components/drawer/drawer.svelte

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
import { box } from "../../../../src/internal/box.svelte.js";
1414
import FloatingPortal from "../../../../src/components/floating-portal/floating-portal.svelte";
1515
import FloatingOverlay from "../../../../src/components/floating-overlay.svelte";
16+
import type { ReferenceSnippetProps } from "../../types.js";
17+
import { MediaQuery } from "svelte/reactivity";
1618
1719
let {
1820
reference,
1921
content,
2022
}: {
21-
reference: Snippet<
22-
[reference: Boxed<Element | null>, props: Record<string, unknown>]
23-
>;
23+
reference: Snippet<ReferenceSnippetProps>;
2424
content: Snippet<
2525
[{ labelId: string; descriptionId: string; close: () => void }]
2626
>;
@@ -30,7 +30,8 @@
3030
3131
const ref = box<Element | null>(null);
3232
33-
const isLargeScreen = true;
33+
const isLargeScreen = new MediaQuery("min-width: 1400px");
34+
3435
const f = useFloating({
3536
reference: () => ref.current,
3637
onReferenceChange: (v) => {
@@ -47,7 +48,7 @@
4748
const labelId = `${id}-label`;
4849
const descriptionId = `${id}-description`;
4950
50-
const modal = !isLargeScreen;
51+
const modal = $derived(!isLargeScreen);
5152
5253
const ints = useInteractions([
5354
useClick(f.context),
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script lang="ts">
2+
import type { Placement } from "@floating-ui/utils";
3+
4+
let open = $state(false);
5+
let search = $state("");
6+
let selectedEmoji = $state<string | null>(null);
7+
let activeIndex = $state<number | null>(null);
8+
let placement = $state<Placement | null>(null);
9+
10+
let arrowEl: HTMLElement = null!;
11+
</script>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<script lang="ts">
2+
import type { HTMLButtonAttributes } from "svelte/elements";
3+
import { useId, type WithRef } from "../../../../src/index.js";
4+
import c from "clsx";
5+
6+
let {
7+
id = useId(),
8+
name,
9+
children,
10+
active,
11+
selected,
12+
ref = $bindable(null),
13+
...rest
14+
}: HTMLButtonAttributes & {
15+
name: string;
16+
active: boolean;
17+
selected: boolean;
18+
} & Partial<WithRef<HTMLButtonElement>> = $props();
19+
</script>
20+
21+
<button
22+
{...rest}
23+
bind:this={ref}
24+
{id}
25+
role="option"
26+
class={c(
27+
"rounded text-3xl text-center cursor-default select-none aspect-square",
28+
{
29+
"bg-cyan-100": selected && !active,
30+
"bg-cyan-200": active,
31+
"opacity-40": name === "orange",
32+
}
33+
)}
34+
aria-selected={selected}
35+
disabled={name === "orange"}
36+
aria-label={name}
37+
tabindex={-1}
38+
data-active={active ? "" : undefined}>
39+
{@render children?.()}
40+
</button>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
import Main from "../../components/drawer/main.svelte";
3+
</script>
4+
5+
<Main />
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { WritableBox } from "../../src/internal/box.svelte.js";
2+
3+
export type ReferenceSnippetProps = [
4+
WritableBox<Element | null>,
5+
Record<string, unknown>,
6+
];

0 commit comments

Comments
 (0)