Skip to content

Commit cd3bcf7

Browse files
committed
Simplify handling of multiple selectors
1 parent 427f2a4 commit cd3bcf7

File tree

4 files changed

+22
-43
lines changed

4 files changed

+22
-43
lines changed

src/Components/Web.JS/src/Rendering/FocusOnNavigate.ts

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@ import { domFunctions } from '../DomWrapper';
55
import { EnhancedNavigationStartEvent, JSEventRegistry } from '../Services/JSEventRegistry';
66

77
const customElementName = 'blazor-focus-on-navigate';
8-
const focusOnNavigateRegistrations: FocusOnNavigateRegistration[] = [];
9-
8+
let currentFocusOnNavigateElement: FocusOnNavigateElement | null = null;
109
let allowFocusOnEnhancedLoad = false;
1110

1211
export function enableFocusOnNavigate(jsEventRegistry: JSEventRegistry) {
13-
customElements.define(customElementName, FocusOnNavigate);
12+
customElements.define(customElementName, FocusOnNavigateElement);
1413
jsEventRegistry.addEventListener('enhancednavigationstart', onEnhancedNavigationStart);
1514
jsEventRegistry.addEventListener('enhancedload', onEnhancedLoad);
1615
document.addEventListener('focusin', onFocusIn);
1716
document.addEventListener('focusout', onFocusOut);
1817

19-
// Focus the element on the initial page load
18+
// Focus the element on the initial page load.
2019
if (document.readyState === 'loading') {
20+
// There may be streaming updates on the initial page load, so we want to allow
21+
// those to add elements matching the active selector.
2122
allowFocusOnEnhancedLoad = true;
2223
document.addEventListener('DOMContentLoaded', afterInitialPageLoad);
2324
} else {
@@ -78,53 +79,21 @@ function tryApplyFocus(forceMoveFocus: boolean) {
7879

7980
lastFocusedElement = null;
8081

81-
const selector = findActiveSelector();
82+
const selector = currentFocusOnNavigateElement?.getAttribute('selector');
8283
if (selector) {
8384
lastFocusedElement = domFunctions.focusBySelector(selector);
8485
}
8586
}
8687

87-
function findActiveSelector(): string | null {
88-
// It's unlikely that there will be more than one <blazor-focus-on-navigate> registered
89-
// at a time. But if there is, we'll prefer the one most recently added to the DOM,
90-
// keeping a stack of all previous registrations to fall back on if the current one
91-
// gets removed.
92-
let registration: FocusOnNavigateRegistration | undefined;
93-
while ((registration = focusOnNavigateRegistrations.at(-1)) !== undefined) {
94-
if (registration.isConnected) {
95-
return registration.selector;
96-
}
97-
98-
focusOnNavigateRegistrations.pop();
99-
}
100-
101-
return null;
102-
}
103-
104-
type FocusOnNavigateRegistration = {
105-
isConnected: boolean;
106-
selector: string | null;
107-
}
108-
109-
class FocusOnNavigate extends HTMLElement {
110-
static observedAttributes = ['selector'];
111-
112-
private readonly _registration: FocusOnNavigateRegistration = {
113-
isConnected: true,
114-
selector: null,
115-
};
116-
88+
class FocusOnNavigateElement extends HTMLElement {
11789
connectedCallback() {
118-
focusOnNavigateRegistrations.push(this._registration);
119-
}
120-
121-
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
122-
if (name === 'selector') {
123-
this._registration.selector = newValue;
124-
}
90+
// eslint-disable-next-line @typescript-eslint/no-this-alias
91+
currentFocusOnNavigateElement = this;
12592
}
12693

12794
disconnectedCallback() {
128-
this._registration.isConnected = false;
95+
if (currentFocusOnNavigateElement === this) {
96+
currentFocusOnNavigateElement = null;
97+
}
12998
}
13099
}

src/Components/Web/src/Routing/FocusOnNavigate.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
5858
{
5959
if (AssignedRenderMode is not null)
6060
{
61+
// When interactivity is enabled, functionality is handled via JS interop.
62+
// We don't need to render anything to the page in that case.
63+
// In non-interactive scenarios, a custom element is rendered so that
64+
// JS logic can find it and focus the element matching the specified
65+
// selector.
6166
return;
6267
}
6368

src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
<NotFound>There's nothing here</NotFound>
1717
</Router>
1818
<script>
19+
// This script must come before blazor.web.js to test that
20+
// the framework does the right then when an element is already focused.
1921
const elementToFocus = document.querySelector('[data-focus-on-load]');
2022
if (elementToFocus) {
2123
elementToFocus.focus();

src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/FocusOnNavigate/StreamRendered.razor

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@page "/focus-on-navigate/stream"
22
@using System.Threading.Channels
3+
@using System.Diagnostics
34
@attribute [StreamRendering]
45

56
<h1>Stream rendered element to focus</h1>
@@ -37,6 +38,8 @@
3738
case RemoveElementMessage removeElement:
3839
_elements.RemoveAt(removeElement.index);
3940
break;
41+
default:
42+
throw new UnreachableException($"Unknown message '{message}'");
4043
}
4144

4245
StateHasChanged();

0 commit comments

Comments
 (0)