From 40928a9b9bbed8d63400ecdb50260865de036c17 Mon Sep 17 00:00:00 2001 From: Vicki League Date: Wed, 25 Jun 2025 16:20:38 -0400 Subject: [PATCH] autofocus improvements --- .../src/input/autofocus.directive.ts | 8 ++--- libs/components/src/input/autofocus.mdx | 21 ++++++++++++ .../components/src/input/autofocus.stories.ts | 34 +++++++++++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 libs/components/src/input/autofocus.mdx create mode 100644 libs/components/src/input/autofocus.stories.ts diff --git a/libs/components/src/input/autofocus.directive.ts b/libs/components/src/input/autofocus.directive.ts index 2e3f99c91c0..4a5ce2806a3 100644 --- a/libs/components/src/input/autofocus.directive.ts +++ b/libs/components/src/input/autofocus.directive.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { AfterContentChecked, - computed, + booleanAttribute, Directive, ElementRef, input, @@ -29,9 +29,7 @@ import { FocusableElement } from "../shared/focusable-element"; selector: "[appAutofocus], [bitAutofocus]", }) export class AutofocusDirective implements AfterContentChecked { - appAutofocus = input(); - - private autofocus = computed(() => this.appAutofocus() === "" || this.appAutofocus() === true); + readonly appAutofocus = input(undefined, { transform: booleanAttribute }); // Track if we have already focused the element. private focused = false; @@ -52,7 +50,7 @@ export class AutofocusDirective implements AfterContentChecked { */ ngAfterContentChecked() { // We only want to focus the element on initial render and it's not a mobile browser - if (this.focused || !this.autofocus() || Utils.isMobileBrowser) { + if (this.focused || !this.appAutofocus() || Utils.isMobileBrowser) { return; } diff --git a/libs/components/src/input/autofocus.mdx b/libs/components/src/input/autofocus.mdx new file mode 100644 index 00000000000..b65aaff0c0a --- /dev/null +++ b/libs/components/src/input/autofocus.mdx @@ -0,0 +1,21 @@ +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; + +import * as stories from "./autofocus.stories"; + + + +```ts +import { AutofocusDirective } from "@bitwarden/components"; +``` + + +<Description /> + +<Primary /> +<Controls /> + +## Accessibility + +The autofocus directive has accessibility implications, because it will steal focus from wherever it +would naturally be placed on page load. Please consider whether or not the user truly needs the +element truly needs to be manually focused on their behalf. diff --git a/libs/components/src/input/autofocus.stories.ts b/libs/components/src/input/autofocus.stories.ts new file mode 100644 index 00000000000..b49a4898ee9 --- /dev/null +++ b/libs/components/src/input/autofocus.stories.ts @@ -0,0 +1,34 @@ +import { Component } from "@angular/core"; +import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; + +import { FormFieldModule } from "../form-field"; + +import { AutofocusDirective } from "./autofocus.directive"; + +@Component({ + selector: "autofocus-example", + imports: [FormFieldModule, AutofocusDirective], + template: ` <bit-form-field> + <bit-label>Email</bit-label> + <input bitInput formControlName="email" appAutofocus /> + </bit-form-field>`, +}) +class AutofocusExampleComponent {} + +export default { + title: "Component Library/Form/Autofocus Directive", + component: AutofocusDirective, + decorators: [ + moduleMetadata({ + imports: [AutofocusExampleComponent], + }), + ], +} as Meta; + +type Story = StoryObj<AutofocusExampleComponent>; + +export const AutofocusField: Story = { + render: (args) => ({ + template: `<autofocus-example></autofocus-example>`, + }), +};