Skip to content

Commit

Permalink
feat(block, block-section): Add setFocus method (#7208)
Browse files Browse the repository at this point in the history
**Related Issue:** #6944

## Summary

- Adds `setFocus` methods to `block` and `block-section` components.
- Implements `LoadableComponent` on`block` and `block-section`
components.
- Focuses first tabbable element after being loaded.
- Add tests
  • Loading branch information
driskull authored Jun 28, 2023
1 parent a9c0bce commit 35d4bbb
Showing 4 changed files with 134 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -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`<calcite-block-section text="text" toggle-display="switch" open>
<div>some content</div>
</calcite-block-section>`,
{
shadowFocusTargetSelector: `.${CSS.toggle}`
}
);
});

describe("focuses toggle button", () => {
focusable(
html`<calcite-block-section text="text" toggle-display="button" open>
<div>some content</div>
</calcite-block-section>`,
{
shadowFocusTargetSelector: `.${CSS.toggle}`
}
);
});
});

describe("toggle-display = 'switch'", () => {
describe("accessible", () => {
accessible(html`
Original file line number Diff line number Diff line change
@@ -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<void> {
await componentLoaded(this);
focusFirstTabbable(this.el);
}

// --------------------------------------------------------------------------
//
// Private Properties
@@ -146,15 +169,20 @@ export class BlockSection implements LocalizedComponent, T9nComponent {
connectMessages(this);
}

async componentWillLoad(): Promise<void> {
await setUpMessages(this);
setUpLoadableComponent(this);
}

componentDidLoad(): void {
setComponentLoaded(this);
}

disconnectedCallback(): void {
disconnectLocalized(this);
disconnectMessages(this);
}

async componentWillLoad(): Promise<void> {
await setUpMessages(this);
}

// --------------------------------------------------------------------------
//
// Render Methods
37 changes: 36 additions & 1 deletion packages/calcite-components/src/components/block/block.e2e.ts
Original file line number Diff line number Diff line change
@@ -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`<calcite-block heading="Heading" description="summary" collapsible open>
<calcite-block-section text="input block-section" open>
<calcite-input
icon="form-field"
placeholder="This is an input field... enter something here"
></calcite-input>
</calcite-block-section>
</calcite-block>`,
{
shadowFocusTargetSelector: `.${CSS.toggle}`
}
);
});

const blockSectionClass = "my-block-section";
describe("focuses block section", () => {
focusable(
html`<calcite-block heading="Heading" description="summary" open>
<calcite-block-section class="${blockSectionClass}" text="input block-section" open>
<calcite-input
icon="form-field"
placeholder="This is an input field... enter something here"
></calcite-input>
</calcite-block-section>
</calcite-block>`,
{
focusTargetSelector: `.${blockSectionClass}`
}
);
});
});

describe("disabled", () => {
disabled(html`<calcite-block heading="heading" description="description" collapsible></calcite-block>`);
});
45 changes: 39 additions & 6 deletions packages/calcite-components/src/components/block/block.tsx
Original file line number Diff line number Diff line change
@@ -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<void> {
await componentLoaded(this);
focusFirstTabbable(this.el);
}

// --------------------------------------------------------------------------
//
// Private Properties
@@ -160,12 +188,17 @@ export class Block
disconnectConditionalSlotComponent(this);
}

componentDidRender(): void {
updateHostInteraction(this);
}

async componentWillLoad(): Promise<void> {
await setUpMessages(this);
setUpLoadableComponent(this);
}

componentDidLoad(): void {
setComponentLoaded(this);
}

componentDidRender(): void {
updateHostInteraction(this);
}

// --------------------------------------------------------------------------

0 comments on commit 35d4bbb

Please sign in to comment.