Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(segmented-control): refresh items when added dynamically #7567

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -172,100 +172,92 @@ describe("calcite-segmented-control", () => {
});

describe("keyboard navigation", () => {
it("selects item with left and arrow keys", async () => {
const page = await newE2EPage();
await page.setContent(
`<calcite-segmented-control>
<calcite-segmented-control-item value="1" checked>one</calcite-segmented-control-item>
<calcite-segmented-control-item value="2">two</calcite-segmented-control-item>
<calcite-segmented-control-item value="3">three</calcite-segmented-control-item>
</calcite-segmented-control>`
);
async function assertArrowSelection(page: E2EPage): Promise<void> {
const element = await page.find("calcite-segmented-control");
const spy = await element.spyOnEvent("calciteSegmentedControlChange");

const firstElement = await element.find("calcite-segmented-control-item[checked]");
await firstElement.click();
await element.press("ArrowRight");
await page.waitForChanges();

let selected = await element.find("calcite-segmented-control-item[checked]");
let value = await selected.getProperty("value");
expect(value).toBe("2");

await element.press("ArrowRight");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("3");

await element.press("ArrowRight");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("1");

await element.press("ArrowLeft");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("3");

await element.press("ArrowLeft");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("2");

await element.press("ArrowLeft");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("1");

await tabIntoFirstElement();
await cycleThroughItemsAndAssertValue("left-right");
expect(spy).toHaveReceivedEventTimes(6);
});

it("selects item with up and down keys", async () => {
await tabIntoFirstElement();
await cycleThroughItemsAndAssertValue("up-down");
expect(spy).toHaveReceivedEventTimes(12);

async function tabIntoFirstElement(): Promise<void> {
const firstElement = await element.find("calcite-segmented-control-item[checked]");
await firstElement.click();

await page.keyboard.down("Shift");
await page.keyboard.press("Tab");
await page.keyboard.up("Shift");

await page.keyboard.press("Tab");
}

async function cycleThroughItemsAndAssertValue(keys: "left-right" | "up-down"): Promise<void> {
const [moveBeforeArrowKey, moveAfterArrowKey] =
keys === "left-right" ? ["ArrowLeft", "ArrowRight"] : ["ArrowUp", "ArrowDown"];

await element.press(moveAfterArrowKey);
await page.waitForChanges();

let selected = await element.find("calcite-segmented-control-item[checked]");
let value = await selected.getProperty("value");
expect(value).toBe("2");

await element.press(moveAfterArrowKey);
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("3");

await element.press(moveAfterArrowKey);
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("1");

await element.press(moveBeforeArrowKey);
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("3");

await element.press(moveBeforeArrowKey);
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("2");

await element.press(moveBeforeArrowKey);
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("1");
}
}

it("selects item with left-right/up-down arrow keys", async () => {
const page = await newE2EPage();
await page.setContent(
`<calcite-segmented-control>
html`<calcite-segmented-control>
<calcite-segmented-control-item value="1" checked>one</calcite-segmented-control-item>
<calcite-segmented-control-item value="2">two</calcite-segmented-control-item>
<calcite-segmented-control-item value="3">three</calcite-segmented-control-item>
</calcite-segmented-control>`
);
const element = await page.find("calcite-segmented-control");
const spy = await element.spyOnEvent("calciteSegmentedControlChange");

const firstElement = await element.find("calcite-segmented-control-item[checked]");
await firstElement.click();
await element.press("ArrowDown");
let selected = await element.find("calcite-segmented-control-item[checked]");
let value = await selected.getProperty("value");
expect(value).toBe("2");

await element.press("ArrowDown");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("3");

await element.press("ArrowDown");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("1");

await element.press("ArrowUp");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("3");

await element.press("ArrowUp");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("2");

await element.press("ArrowUp");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("1");
await assertArrowSelection(page);
});

expect(spy).toHaveReceivedEventTimes(6);
it("selects item with left-right/up-down arrow keys after adding items programmatically", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-segmented-control></calcite-segmented-control>`);

await page.$eval("calcite-segmented-control", (segmentedControl: HTMLCalciteSegmentedControlElement) => {
segmentedControl.innerHTML = `
<calcite-segmented-control-item value="1" checked>one</calcite-segmented-control-item>
<calcite-segmented-control-item value="2">two</calcite-segmented-control-item>
<calcite-segmented-control-item value="3">three</calcite-segmented-control-item>`;
});

await assertArrowSelection(page);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
setUpLoadableComponent,
} from "../../utils/loadable";
import { Appearance, Layout, Scale, Width } from "../interfaces";
import { createObserver } from "../../utils/observers";

/**
* @slot - A slot for adding `calcite-segmented-control-item`s.
Expand Down Expand Up @@ -134,15 +135,7 @@ export class SegmentedControl

componentWillLoad(): void {
setUpLoadableComponent(this);

const items = this.getItems();
const lastChecked = items.filter((item) => item.checked).pop();

if (lastChecked) {
this.selectItem(lastChecked);
} else if (items[0]) {
items[0].tabIndex = 0;
}
this.setUpItems();
}

componentDidLoad(): void {
Expand All @@ -154,12 +147,14 @@ export class SegmentedControl
connectInteractive(this);
connectLabel(this);
connectForm(this);
this.mutationObserver?.observe(this.el, { childList: true });
}

disconnectedCallback(): void {
disconnectInteractive(this);
disconnectLabel(this);
disconnectForm(this);
this.mutationObserver?.unobserve(this.el);
}

componentDidRender(): void {
Expand Down Expand Up @@ -287,6 +282,8 @@ export class SegmentedControl

defaultValue: SegmentedControl["value"];

private mutationObserver = createObserver("mutation", () => this.setUpItems());

//--------------------------------------------------------------------------
//
// Private Methods
Expand Down Expand Up @@ -332,4 +329,15 @@ export class SegmentedControl
match.focus();
}
}

private setUpItems(): void {
const items = this.getItems();
const lastChecked = items.filter((item) => item.checked).pop();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could use array.find as well right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, but maybe once Safari 17 is out. I need to find the last checked item and Safari added support for findLast in v15.4. 🧭🥲


if (lastChecked) {
this.selectItem(lastChecked);
} else if (items[0]) {
items[0].tabIndex = 0;
}
}
}