From 159a746ce3f0ef3e665da4af3b4607e77460e901 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Wed, 21 Jun 2023 16:37:40 -0700 Subject: [PATCH] feat(block, block-section): Add setFocus method. #6944 --- .../block-section/block-section.e2e.ts | 26 ++++++++++- .../block-section/block-section.tsx | 40 ++++++++++++++--- .../src/components/block/block.e2e.ts | 37 ++++++++++++++- .../src/components/block/block.tsx | 45 ++++++++++++++++--- 4 files changed, 134 insertions(+), 14 deletions(-) diff --git a/packages/calcite-components/src/components/block-section/block-section.e2e.ts b/packages/calcite-components/src/components/block-section/block-section.e2e.ts index 0ac8504d178..5fd77a25a1a 100644 --- a/packages/calcite-components/src/components/block-section/block-section.e2e.ts +++ b/packages/calcite-components/src/components/block-section/block-section.e2e.ts @@ -1,5 +1,5 @@ import { CSS, TEXT } from "./resources"; -import { accessible, defaults, hidden, reflects, renders, t9n } from "../../tests/commonTests"; +import { accessible, defaults, focusable, hidden, reflects, renders, t9n } from "../../tests/commonTests"; import { E2EPage, newE2EPage } from "@stencil/core/testing"; import { html } from "../../../support/formatting"; @@ -38,6 +38,30 @@ describe("calcite-block-section", () => { t9n("calcite-block-section"); }); + describe("setFocus", () => { + describe("focuses toggle switch", () => { + focusable( + html` +
some content
+
`, + { + shadowFocusTargetSelector: `.${CSS.toggle}` + } + ); + }); + + describe("focuses toggle button", () => { + focusable( + html` +
some content
+
`, + { + shadowFocusTargetSelector: `.${CSS.toggle}` + } + ); + }); + }); + describe("toggle-display = 'switch'", () => { describe("accessible", () => { accessible(html` diff --git a/packages/calcite-components/src/components/block-section/block-section.tsx b/packages/calcite-components/src/components/block-section/block-section.tsx index c8e6b930aef..a7da5984fc1 100644 --- a/packages/calcite-components/src/components/block-section/block-section.tsx +++ b/packages/calcite-components/src/components/block-section/block-section.tsx @@ -5,13 +5,14 @@ import { EventEmitter, h, Host, + Method, Prop, State, VNode, Watch } from "@stencil/core"; -import { getElementDir, toAriaBoolean } from "../../utils/dom"; +import { focusFirstTabbable, getElementDir, toAriaBoolean } from "../../utils/dom"; import { guid } from "../../utils/guid"; import { isActivationKey } from "../../utils/key"; import { connectLocalized, disconnectLocalized, LocalizedComponent } from "../../utils/locale"; @@ -26,6 +27,12 @@ import { Status } from "../interfaces"; import { BlockSectionMessages } from "./assets/block-section/t9n"; import { BlockSectionToggleDisplay } from "./interfaces"; import { CSS, ICONS } from "./resources"; +import { + componentLoaded, + LoadableComponent, + setComponentLoaded, + setUpLoadableComponent +} from "../../utils/loadable"; /** * @slot - A slot for adding custom content. @@ -36,7 +43,7 @@ import { CSS, ICONS } from "./resources"; shadow: true, assetsDirs: ["assets"] }) -export class BlockSection implements LocalizedComponent, T9nComponent { +export class BlockSection implements LocalizedComponent, T9nComponent, LoadableComponent { // -------------------------------------------------------------------------- // // Properties @@ -86,6 +93,22 @@ export class BlockSection implements LocalizedComponent, T9nComponent { /* wired up by t9n util */ } + //-------------------------------------------------------------------------- + // + // Public Methods + // + //-------------------------------------------------------------------------- + + /** + * Sets focus on the component's first tabbable element. + * + */ + @Method() + async setFocus(): Promise { + await componentLoaded(this); + focusFirstTabbable(this.el); + } + // -------------------------------------------------------------------------- // // Private Properties @@ -146,15 +169,20 @@ export class BlockSection implements LocalizedComponent, T9nComponent { connectMessages(this); } + async componentWillLoad(): Promise { + await setUpMessages(this); + setUpLoadableComponent(this); + } + + componentDidLoad(): void { + setComponentLoaded(this); + } + disconnectedCallback(): void { disconnectLocalized(this); disconnectMessages(this); } - async componentWillLoad(): Promise { - await setUpMessages(this); - } - // -------------------------------------------------------------------------- // // Render Methods diff --git a/packages/calcite-components/src/components/block/block.e2e.ts b/packages/calcite-components/src/components/block/block.e2e.ts index dd4934e0b42..440671a95db 100644 --- a/packages/calcite-components/src/components/block/block.e2e.ts +++ b/packages/calcite-components/src/components/block/block.e2e.ts @@ -1,6 +1,6 @@ import { newE2EPage } from "@stencil/core/testing"; import { CSS, SLOTS, TEXT } from "./resources"; -import { accessible, defaults, disabled, hidden, renders, slots, t9n } from "../../tests/commonTests"; +import { accessible, defaults, disabled, focusable, hidden, renders, slots, t9n } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; describe("calcite-block", () => { @@ -43,6 +43,41 @@ describe("calcite-block", () => { `); }); + describe("setFocus", () => { + describe("focuses block heading toggle", () => { + focusable( + html` + + + + `, + { + shadowFocusTargetSelector: `.${CSS.toggle}` + } + ); + }); + + const blockSectionClass = "my-block-section"; + describe("focuses block section", () => { + focusable( + html` + + + + `, + { + focusTargetSelector: `.${blockSectionClass}` + } + ); + }); + }); + it("can be disabled", () => disabled(html``)); diff --git a/packages/calcite-components/src/components/block/block.tsx b/packages/calcite-components/src/components/block/block.tsx index 36f7b5de257..005a9b1038f 100644 --- a/packages/calcite-components/src/components/block/block.tsx +++ b/packages/calcite-components/src/components/block/block.tsx @@ -5,6 +5,7 @@ import { EventEmitter, h, Host, + Method, Prop, State, VNode, @@ -15,7 +16,7 @@ import { connectConditionalSlotComponent, disconnectConditionalSlotComponent } from "../../utils/conditionalSlot"; -import { getSlotted, toAriaBoolean } from "../../utils/dom"; +import { focusFirstTabbable, getSlotted, toAriaBoolean } from "../../utils/dom"; import { guid } from "../../utils/guid"; import { connectInteractive, @@ -35,6 +36,12 @@ import { Heading, HeadingLevel } from "../functional/Heading"; import { Status } from "../interfaces"; import { BlockMessages } from "./assets/block/t9n"; import { CSS, ICONS, SLOTS } from "./resources"; +import { + componentLoaded, + LoadableComponent, + setComponentLoaded, + setUpLoadableComponent +} from "../../utils/loadable"; /** * @slot - A slot for adding custom content. @@ -49,7 +56,12 @@ import { CSS, ICONS, SLOTS } from "./resources"; assetsDirs: ["assets"] }) export class Block - implements ConditionalSlotComponent, InteractiveComponent, LocalizedComponent, T9nComponent + implements + ConditionalSlotComponent, + InteractiveComponent, + LocalizedComponent, + T9nComponent, + LoadableComponent { // -------------------------------------------------------------------------- // @@ -121,6 +133,22 @@ export class Block /* wired up by t9n util */ } + //-------------------------------------------------------------------------- + // + // Public Methods + // + //-------------------------------------------------------------------------- + + /** + * Sets focus on the component's first tabbable element. + * + */ + @Method() + async setFocus(): Promise { + await componentLoaded(this); + focusFirstTabbable(this.el); + } + // -------------------------------------------------------------------------- // // Private Properties @@ -160,12 +188,17 @@ export class Block disconnectConditionalSlotComponent(this); } - componentDidRender(): void { - updateHostInteraction(this); - } - async componentWillLoad(): Promise { await setUpMessages(this); + setUpLoadableComponent(this); + } + + componentDidLoad(): void { + setComponentLoaded(this); + } + + componentDidRender(): void { + updateHostInteraction(this); } // --------------------------------------------------------------------------