Skip to content

Commit

Permalink
fix(segmented-control): honor appearance, layout and scale when items…
Browse files Browse the repository at this point in the history
… are added after initialization (#10368)

**Related Issue:** #9955

## Summary

- Use slotchange event to get items and update them when necessary
- No need for the mutation observer
- add e2e test
  • Loading branch information
driskull authored Sep 25, 2024
1 parent 7b03745 commit 98177f0
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { E2EPage, newE2EPage } from "@stencil/core/testing";
import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing";
import { html } from "../../../support/formatting";
import {
defaults,
Expand Down Expand Up @@ -380,17 +380,39 @@ describe("calcite-segmented-control", () => {
});

it("inheritable props: `appearance`, `layout`, and `scale` modified on the parent get passed to items", async () => {
async function inheritsProps(segmentedControlItems: E2EElement[]): Promise<void> {
for (const item of segmentedControlItems) {
expect(await item.getProperty("appearance")).toBe("outline");
expect(await item.getProperty("layout")).toBe("vertical");
expect(await item.getProperty("scale")).toBe("l");
}
}

const page = await newE2EPage();
await page.setContent(html`
<calcite-segmented-control appearance="outline" layout="vertical" scale="l"></calcite-segmented-control>
<calcite-segmented-control appearance="outline" layout="vertical" scale="l">
<calcite-segmented-control-item id="child-1" value="1">one</calcite-segmented-control-item>
<calcite-segmented-control-item id="child-2" value="2">two</calcite-segmented-control-item>
<calcite-segmented-control-item id="child-3" value="3">three</calcite-segmented-control-item>
</calcite-segmented-control>
`);
const segmentedControlItems = await page.findAll("calcite-segmented-control-item");
await page.waitForChanges();

for (const item of segmentedControlItems) {
expect(await item.getProperty("appearance")).toBe("outline");
expect(await item.getProperty("layout")).toBe("vertical");
expect(await item.getProperty("scale")).toBe("l");
}
const segmentedControl = await page.find("calcite-segmented-control");

let segmentedControlItems = await page.findAll("calcite-segmented-control-item");
expect(segmentedControlItems).toHaveLength(3);
await inheritsProps(segmentedControlItems);

segmentedControl.innerHTML = html`
<calcite-segmented-control-item id="child-4" value="4">one</calcite-segmented-control-item>
<calcite-segmented-control-item id="child-5" value="5">two</calcite-segmented-control-item>
`;
await page.waitForChanges();

segmentedControlItems = await page.findAll("calcite-segmented-control-item");
expect(segmentedControlItems).toHaveLength(2);
await inheritsProps(segmentedControlItems);
});

describe("setFocus()", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
VNode,
Watch,
} from "@stencil/core";
import { getElementDir, toAriaBoolean } from "../../utils/dom";
import { getElementDir, slotChangeGetAssignedElements, toAriaBoolean } from "../../utils/dom";
import {
afterConnectDefaultValueSet,
connectForm,
Expand All @@ -33,7 +33,6 @@ import {
setUpLoadableComponent,
} from "../../utils/loadable";
import { Appearance, Layout, Scale, Status, Width } from "../interfaces";
import { createObserver } from "../../utils/observers";
import { Validation } from "../functional/Validation";
import { IconNameOrString } from "../icon/interfaces";
import { isBrowser } from "../../utils/browser";
Expand Down Expand Up @@ -98,7 +97,7 @@ export class SegmentedControl

@Watch("value")
valueHandler(value: string): void {
const items = this.getItems();
const { items } = this;
items.forEach((item) => (item.checked = item.value === value));
}

Expand All @@ -110,15 +109,15 @@ export class SegmentedControl
@Prop({ mutable: true }) selectedItem: HTMLCalciteSegmentedControlItemElement;

@Watch("selectedItem")
protected handleSelectedItemChange<T extends HTMLCalciteSegmentedControlItemElement>(
handleSelectedItemChange<T extends HTMLCalciteSegmentedControlItemElement>(
newItem: T,
oldItem: T,
): void {
this.value = newItem?.value;
if (newItem === oldItem) {
return;
}
const items = this.getItems();
const { items } = this;
const match = items.filter((item) => item === newItem).pop();

if (match) {
Expand Down Expand Up @@ -169,7 +168,6 @@ export class SegmentedControl

componentWillLoad(): void {
setUpLoadableComponent(this);
this.setUpItems();
}

componentDidLoad(): void {
Expand All @@ -180,15 +178,11 @@ export class SegmentedControl
connectedCallback(): void {
connectLabel(this);
connectForm(this);
this.mutationObserver?.observe(this.el, { childList: true });

this.handleItemPropChange();
}

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

componentDidRender(): void {
Expand All @@ -204,7 +198,7 @@ export class SegmentedControl
class={CSS.itemWrapper}
>
<InteractiveContainer disabled={this.disabled}>
<slot />
<slot onSlotchange={this.handleDefaultSlotChange} />
<HiddenFormInputSlot component={this} />
</InteractiveContainer>
</div>
Expand All @@ -227,7 +221,7 @@ export class SegmentedControl
//
//--------------------------------------------------------------------------

protected handleClick = (event: MouseEvent): void => {
private handleClick = (event: MouseEvent): void => {
if (this.disabled) {
return;
}
Expand All @@ -238,7 +232,7 @@ export class SegmentedControl
};

@Listen("calciteInternalSegmentedControlItemChange")
protected handleSelected(event: Event): void {
handleSelected(event: Event): void {
event.preventDefault();
const el = event.target as HTMLCalciteSegmentedControlItemElement;
if (el.checked) {
Expand Down Expand Up @@ -268,7 +262,7 @@ export class SegmentedControl
}
}

const items = this.getItems();
const { items } = this;
let selectedIndex = -1;

items.forEach((item, index) => {
Expand Down Expand Up @@ -321,7 +315,7 @@ export class SegmentedControl
async setFocus(): Promise<void> {
await componentFocusable(this);

(this.selectedItem || this.getItems()[0])?.focus();
(this.selectedItem || this.items[0])?.focus();
}

//--------------------------------------------------------------------------
Expand All @@ -332,16 +326,22 @@ export class SegmentedControl

@Element() el: HTMLCalciteSegmentedControlElement;

private items: HTMLCalciteSegmentedControlItemElement[] = [];

labelEl: HTMLCalciteLabelElement;

formEl: HTMLFormElement;

defaultValue: SegmentedControl["value"];

private mutationObserver = createObserver("mutation", () => this.setUpItems());
//--------------------------------------------------------------------------
//
// Private Methods
//
//--------------------------------------------------------------------------

private handleItemPropChange(): void {
const items = this.getItems();
const { items } = this;

items.forEach((item) => {
item.appearance = this.appearance;
Expand All @@ -350,26 +350,40 @@ export class SegmentedControl
});
}

//--------------------------------------------------------------------------
//
// Private Methods
//
//--------------------------------------------------------------------------
private handleSelectedItem(): void {
const { items } = this;

onLabelClick(): void {
this.setFocus();
const lastChecked = items.filter((item) => item.checked).pop();

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

private getItems(): HTMLCalciteSegmentedControlItemElement[] {
return Array.from(this.el.querySelectorAll("calcite-segmented-control-item"));
private handleDefaultSlotChange = (event: Event): void => {
const items = slotChangeGetAssignedElements(event).filter(
(el): el is HTMLCalciteSegmentedControlItemElement =>
el.matches("calcite-segmented-control-item"),
);

this.items = items;

this.handleSelectedItem();
this.handleItemPropChange();
};

onLabelClick(): void {
this.setFocus();
}

private selectItem(selected: HTMLCalciteSegmentedControlItemElement, emit = false): void {
if (selected === this.selectedItem) {
return;
}

const items = this.getItems();
const { items } = this;
let match: HTMLCalciteSegmentedControlItemElement = null;

items.forEach((item) => {
Expand All @@ -395,15 +409,4 @@ export class SegmentedControl
match.focus();
}
}

private setUpItems(): void {
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;
}
}
}

0 comments on commit 98177f0

Please sign in to comment.