Skip to content

Commit 884a50b

Browse files
KonstantinAlexeevichSoshin Konstantin
andauthored
Add FocusAsync overload with preventScroll parameter #24852 (#28686)
* Add FocusAsync overload with preventScroll parameter #24852 * Add E2E Test for preventScroll parameter #24852 Co-authored-by: Soshin Konstantin <[email protected]>
1 parent e79d740 commit 884a50b

File tree

5 files changed

+76
-10
lines changed

5 files changed

+76
-10
lines changed

src/Components/Web.JS/src/DomWrapper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ export const domFunctions = {
44
focus,
55
};
66

7-
function focus(element: HTMLElement): void {
7+
function focus(element: HTMLElement, preventScroll: boolean): void {
88
if (element instanceof HTMLElement) {
9-
element.focus();
9+
element.focus({ preventScroll });
1010
} else {
1111
throw new Error('Unable to focus an invalid element.');
1212
}

src/Components/Web/src/ElementReferenceExtensions.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,21 @@ public static class ElementReferenceExtensions
1717
/// </summary>
1818
/// <param name="elementReference">A reference to the element to focus.</param>
1919
/// <returns>The <see cref="ValueTask"/> representing the asynchronous focus operation.</returns>
20-
public static ValueTask FocusAsync(this ElementReference elementReference)
20+
public static ValueTask FocusAsync(this ElementReference elementReference) => elementReference.FocusAsync(preventScroll: false);
21+
22+
/// <summary>
23+
/// Gives focus to an element given its <see cref="ElementReference"/>.
24+
/// </summary>
25+
/// <param name="elementReference">A reference to the element to focus.</param>
26+
/// <param name="preventScroll">
27+
/// <para>
28+
/// A <see cref="bool" /> value indicating whether or not the browser should scroll the document to bring the newly-focused element into view.
29+
/// A value of false for preventScroll (the default) means that the browser will scroll the element into view after focusing it.
30+
/// If preventScroll is set to true, no scrolling will occur.
31+
/// </para>
32+
/// </param>
33+
/// <returns>The <see cref="ValueTask"/> representing the asynchronous focus operation.</returns>
34+
public static ValueTask FocusAsync(this ElementReference elementReference, bool preventScroll)
2135
{
2236
var jsRuntime = elementReference.GetJSRuntime();
2337

@@ -26,7 +40,7 @@ public static ValueTask FocusAsync(this ElementReference elementReference)
2640
throw new InvalidOperationException("No JavaScript runtime found.");
2741
}
2842

29-
return jsRuntime.InvokeVoidAsync(DomWrapperInterop.Focus, elementReference);
43+
return jsRuntime.InvokeVoidAsync(DomWrapperInterop.Focus, elementReference, preventScroll);
3044
}
3145

3246
internal static IJSRuntime GetJSRuntime(this ElementReference elementReference)

src/Components/Web/src/PublicAPI.Shipped.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#nullable enable
1+
#nullable enable
22
Microsoft.AspNetCore.Components.BindInputElementAttribute
33
Microsoft.AspNetCore.Components.BindInputElementAttribute.BindInputElementAttribute(string? type, string? suffix, string? valueAttribute, string? changeAttribute, bool isInvariantCulture, string? format) -> void
44
Microsoft.AspNetCore.Components.BindInputElementAttribute.ChangeAttribute.get -> string?
@@ -382,6 +382,7 @@ override Microsoft.AspNetCore.Components.Routing.NavLink.BuildRenderTree(Microso
382382
override Microsoft.AspNetCore.Components.Routing.NavLink.OnInitialized() -> void
383383
override Microsoft.AspNetCore.Components.Routing.NavLink.OnParametersSet() -> void
384384
static Microsoft.AspNetCore.Components.ElementReferenceExtensions.FocusAsync(this Microsoft.AspNetCore.Components.ElementReference elementReference) -> System.Threading.Tasks.ValueTask
385+
static Microsoft.AspNetCore.Components.ElementReferenceExtensions.FocusAsync(this Microsoft.AspNetCore.Components.ElementReference elementReference, bool preventScroll) -> System.Threading.Tasks.ValueTask
385386
static Microsoft.AspNetCore.Components.Forms.BrowserFileExtensions.RequestImageFileAsync(this Microsoft.AspNetCore.Components.Forms.IBrowserFile! browserFile, string! format, int maxWith, int maxHeight) -> System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Components.Forms.IBrowserFile!>
386387
static Microsoft.AspNetCore.Components.Forms.EditContextFieldClassExtensions.FieldCssClass(this Microsoft.AspNetCore.Components.Forms.EditContext! editContext, in Microsoft.AspNetCore.Components.Forms.FieldIdentifier fieldIdentifier) -> string!
387388
static Microsoft.AspNetCore.Components.Forms.EditContextFieldClassExtensions.FieldCssClass<TField>(this Microsoft.AspNetCore.Components.Forms.EditContext! editContext, System.Linq.Expressions.Expression<System.Func<TField>!>! accessor) -> string!

src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,12 @@ public void CanUseJsInteropToReferenceElements()
408408
[Fact]
409409
public void CanUseFocusExtensionToFocusElement()
410410
{
411+
Browser.Manage().Window.Size = new System.Drawing.Size(100, 300);
411412
var appElement = Browser.MountTestComponent<ElementFocusComponent>();
413+
414+
// y scroll position before click
415+
var pageYOffsetBefore = getPageYOffset();
416+
412417
var buttonElement = appElement.FindElement(By.Id("focus-button"));
413418

414419
// Make sure the input element isn't focused when the test begins; we don't want
@@ -421,8 +426,51 @@ public void CanUseFocusExtensionToFocusElement()
421426
// Verify that the input element is focused
422427
Browser.Equal("focus-input", getFocusedElementId);
423428

429+
// y scroll position ater click
430+
var pageYOffsetAfter = getPageYOffset();
431+
432+
// Verify that scroll became
433+
Assert.True(pageYOffsetAfter > pageYOffsetBefore);
434+
435+
// A local helper that gets the ID of the focused element.
436+
string getFocusedElementId() => Browser.SwitchTo().ActiveElement().GetAttribute("id");
437+
438+
// A local helper that gets window.PageYOffset
439+
long getPageYOffset() => (long)((IJavaScriptExecutor)Browser).ExecuteScript("return window.pageYOffset");
440+
}
441+
442+
[Fact]
443+
public void CanUseFocusExtensionToFocusElementPreventScroll()
444+
{
445+
Browser.Manage().Window.Size = new System.Drawing.Size(100, 300);
446+
var appElement = Browser.MountTestComponent<ElementFocusComponent>();
447+
448+
// y scroll position before click
449+
var pageYOffsetBefore = getPageYOffset();
450+
451+
var buttonElement = appElement.FindElement(By.Id("focus-button-prevented"));
452+
453+
// Make sure the input element isn't focused when the test begins; we don't want
454+
// the test to pass just because the input started as the focused element
455+
Browser.NotEqual("focus-input", getFocusedElementId);
456+
457+
// Click the button whose callback focuses the input element
458+
buttonElement.Click();
459+
460+
// Verify that the input element is focused
461+
Browser.Equal("focus-input", getFocusedElementId);
462+
463+
// y scroll position ater click
464+
var pageYOffsetAfter = getPageYOffset();
465+
466+
// Verify that not scrolled
467+
Assert.Equal(pageYOffsetAfter, pageYOffsetBefore);
468+
424469
// A local helper that gets the ID of the focused element.
425470
string getFocusedElementId() => Browser.SwitchTo().ActiveElement().GetAttribute("id");
471+
472+
// A local helper that gets window.PageYOffset
473+
long getPageYOffset() => (long)((IJavaScriptExecutor)Browser).ExecuteScript("return window.pageYOffset");
426474
}
427475

428476
[Fact]
Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
@using Microsoft.JSInterop
1+
@using Microsoft.JSInterop
22

3-
<button id="focus-button" @onclick="FocusInput">Click to focus!</button>
4-
<input id="focus-input" @ref="inputReference" />
3+
<button id="focus-button" @onclick="@(() => FocusInput(false))">Click to focus!</button>
4+
<hr />
5+
<button id="focus-button-prevented" @onclick="@(() => FocusInput(true))">Click to focus with preventScroll!</button>
6+
<hr />
7+
<input id="focus-input" @ref="inputReference" style="margin-top: 10000px" />
58

69
@code {
710
private ElementReference inputReference;
811

9-
private async Task FocusInput()
12+
private async Task FocusInput(bool preventScroll)
1013
{
11-
await inputReference.FocusAsync();
14+
await inputReference.FocusAsync(preventScroll);
1215
}
1316
}

0 commit comments

Comments
 (0)