diff --git a/packages/calcite-components/src/components/segmented-control/segmented-control.e2e.ts b/packages/calcite-components/src/components/segmented-control/segmented-control.e2e.ts index d158d8a703f..014e30572f7 100644 --- a/packages/calcite-components/src/components/segmented-control/segmented-control.e2e.ts +++ b/packages/calcite-components/src/components/segmented-control/segmented-control.e2e.ts @@ -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, @@ -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 { + 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` - + + one + two + three + `); - 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` + one + two + `; + await page.waitForChanges(); + + segmentedControlItems = await page.findAll("calcite-segmented-control-item"); + expect(segmentedControlItems).toHaveLength(2); + await inheritsProps(segmentedControlItems); }); describe("setFocus()", () => { diff --git a/packages/calcite-components/src/components/segmented-control/segmented-control.tsx b/packages/calcite-components/src/components/segmented-control/segmented-control.tsx index fb52158ddac..fb96cb4a527 100644 --- a/packages/calcite-components/src/components/segmented-control/segmented-control.tsx +++ b/packages/calcite-components/src/components/segmented-control/segmented-control.tsx @@ -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, @@ -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"; @@ -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)); } @@ -110,7 +109,7 @@ export class SegmentedControl @Prop({ mutable: true }) selectedItem: HTMLCalciteSegmentedControlItemElement; @Watch("selectedItem") - protected handleSelectedItemChange( + handleSelectedItemChange( newItem: T, oldItem: T, ): void { @@ -118,7 +117,7 @@ export class SegmentedControl if (newItem === oldItem) { return; } - const items = this.getItems(); + const { items } = this; const match = items.filter((item) => item === newItem).pop(); if (match) { @@ -169,7 +168,6 @@ export class SegmentedControl componentWillLoad(): void { setUpLoadableComponent(this); - this.setUpItems(); } componentDidLoad(): void { @@ -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 { @@ -204,7 +198,7 @@ export class SegmentedControl class={CSS.itemWrapper} > - + @@ -227,7 +221,7 @@ export class SegmentedControl // //-------------------------------------------------------------------------- - protected handleClick = (event: MouseEvent): void => { + private handleClick = (event: MouseEvent): void => { if (this.disabled) { return; } @@ -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) { @@ -268,7 +262,7 @@ export class SegmentedControl } } - const items = this.getItems(); + const { items } = this; let selectedIndex = -1; items.forEach((item, index) => { @@ -321,7 +315,7 @@ export class SegmentedControl async setFocus(): Promise { await componentFocusable(this); - (this.selectedItem || this.getItems()[0])?.focus(); + (this.selectedItem || this.items[0])?.focus(); } //-------------------------------------------------------------------------- @@ -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; @@ -350,18 +350,32 @@ 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 { @@ -369,7 +383,7 @@ export class SegmentedControl return; } - const items = this.getItems(); + const { items } = this; let match: HTMLCalciteSegmentedControlItemElement = null; items.forEach((item) => { @@ -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; - } - } }