From 3ae0902bb4372482c2dfd308c60847f8acc15704 Mon Sep 17 00:00:00 2001 From: Marco D'Auria <101181211+dauriamarco@users.noreply.github.com> Date: Wed, 8 Nov 2023 17:10:14 +0100 Subject: [PATCH] fix(sbb-radio-button-group): radio group state update (#2138) --- src/components.d.ts | 8 +++++ src/components/sbb-checkbox/readme.md | 7 ++-- .../sbb-checkbox/sbb-checkbox.events.ts | 1 + src/components/sbb-checkbox/sbb-checkbox.tsx | 10 ++++++ .../sbb-radio-button-group.e2e.ts | 32 +++++++++++++++++-- .../sbb-radio-button-group.tsx | 14 +++++--- src/components/sbb-radio-button/readme.md | 7 ++-- .../sbb-radio-button.events.ts | 1 + .../sbb-radio-button/sbb-radio-button.tsx | 10 ++++++ .../sbb-selection-panel.spec.ts | 2 +- .../sbb-selection-panel.tsx | 12 +++++-- 11 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/components.d.ts b/src/components.d.ts index 30ddd1a038..776c1ac6fe 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -3178,6 +3178,10 @@ declare namespace LocalJSX { * @deprecated only used for React. Will probably be removed once React 19 is available. */ "onDidChange"?: (event: SbbCheckboxCustomEvent) => void; + /** + * Internal event that emits when the input element is loaded. + */ + "onSbb-checkbox-loaded"?: (event: SbbCheckboxCustomEvent) => void; "onState-change"?: (event: SbbCheckboxCustomEvent) => void; /** * Whether the checkbox is required. @@ -4176,6 +4180,10 @@ declare namespace LocalJSX { * Whether the radio button is disabled. */ "disabled"?: boolean; + /** + * Internal event that emits when the input element is loaded. + */ + "onSbb-radio-button-loaded"?: (event: SbbRadioButtonCustomEvent) => void; /** * Internal event that emits whenever the state of the radio option in relation to the parent selection panel changes. */ diff --git a/src/components/sbb-checkbox/readme.md b/src/components/sbb-checkbox/readme.md index 14eea3f97f..99f48a87a0 100644 --- a/src/components/sbb-checkbox/readme.md +++ b/src/components/sbb-checkbox/readme.md @@ -85,9 +85,10 @@ If you don't want the label to appear next to the checkbox, you can use `aria-la ## Events -| Event | Description | Type | -| ----------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| `didChange` | **[DEPRECATED]** only used for React. Will probably be removed once React 19 is available.

| `CustomEvent` | +| Event | Description | Type | +| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------- | +| `didChange` | **[DEPRECATED]** only used for React. Will probably be removed once React 19 is available.

| `CustomEvent` | +| `sbb-checkbox-loaded` | Internal event that emits when the input element is loaded. | `CustomEvent` | ## Slots diff --git a/src/components/sbb-checkbox/sbb-checkbox.events.ts b/src/components/sbb-checkbox/sbb-checkbox.events.ts index 9e6e78d0c7..2b5485d0a9 100644 --- a/src/components/sbb-checkbox/sbb-checkbox.events.ts +++ b/src/components/sbb-checkbox/sbb-checkbox.events.ts @@ -4,5 +4,6 @@ */ export default { didChange: 'didChange', + sbbCheckboxLoaded: 'sbb-checkbox-loaded', stateChange: 'state-change', }; diff --git a/src/components/sbb-checkbox/sbb-checkbox.tsx b/src/components/sbb-checkbox/sbb-checkbox.tsx index 0e4f006536..c390c69d19 100644 --- a/src/components/sbb-checkbox/sbb-checkbox.tsx +++ b/src/components/sbb-checkbox/sbb-checkbox.tsx @@ -115,6 +115,15 @@ export class SbbCheckbox implements ComponentInterface { }) public stateChange: EventEmitter; + /** + * Internal event that emits when the input element is loaded. + */ + @Event({ + bubbles: true, + eventName: 'sbb-checkbox-loaded', + }) + public sbbCheckboxLoaded: EventEmitter; + @Watch('checked') public handleCheckedChange(currentValue: boolean, previousValue: boolean): void { if (this._isSelectionPanelInput && currentValue !== previousValue) { @@ -168,6 +177,7 @@ export class SbbCheckbox implements ComponentInterface { !this._element.closest('sbb-selection-panel [slot="content"]'); this._handlerRepository.connect(); this._setupInitialStateAndAttributeObserver(); + this._isSelectionPanelInput && this.sbbCheckboxLoaded.emit(); } public componentDidLoad(): void { diff --git a/src/components/sbb-radio-button-group/sbb-radio-button-group.e2e.ts b/src/components/sbb-radio-button-group/sbb-radio-button-group.e2e.ts index 56422ca207..ed0d56a939 100644 --- a/src/components/sbb-radio-button-group/sbb-radio-button-group.e2e.ts +++ b/src/components/sbb-radio-button-group/sbb-radio-button-group.e2e.ts @@ -70,7 +70,7 @@ describe('sbb-radio-button-group', () => { 'sbb-radio-button-group > sbb-radio-button#sbb-radio-3', ); - await element.setProperty('disabled', true); + element.setProperty('disabled', true); await page.waitForChanges(); await disabledRadio.click(); @@ -82,7 +82,7 @@ describe('sbb-radio-button-group', () => { await page.waitForChanges(); expect(secondRadio).not.toHaveAttribute('checked'); - await element.setProperty('disabled', false); + element.setProperty('disabled', false); await page.waitForChanges(); await disabledRadio.click(); @@ -146,5 +146,33 @@ describe('sbb-radio-button-group', () => { expect(firstRadio).toHaveAttribute('checked'); }); + + it('sets the value correctly on slot change', async () => { + const firstRadio = await page.find('sbb-radio-button-group > sbb-radio-button#sbb-radio-1'); + + expect(firstRadio).toHaveAttribute('checked'); + expect(await element.getProperty('value')).toBe('Value one'); + + await page.evaluate(() => { + const newRadios = ['New radio one', 'New radio two']; + const radioGroup = document.querySelector('sbb-radio-button-group'); + + // Remove all the radio buttons + while (radioGroup.firstChild) { + radioGroup.removeChild(radioGroup.firstChild); + } + + // Add two new radios + newRadios.forEach(async (radio, i) => { + const newRadio = document.createElement('SBB-RADIO-BUTTON') as HTMLSbbRadioButtonElement; + newRadio.innerText = radio; + newRadio.value = radio; + newRadio.checked = i === 0; + radioGroup.appendChild(newRadio); + }); + }); + + expect(await element.getProperty('value')).toBe('New radio one'); + }); }); }); diff --git a/src/components/sbb-radio-button-group/sbb-radio-button-group.tsx b/src/components/sbb-radio-button-group/sbb-radio-button-group.tsx index 44686dbc8d..f88128ae30 100644 --- a/src/components/sbb-radio-button-group/sbb-radio-button-group.tsx +++ b/src/components/sbb-radio-button-group/sbb-radio-button-group.tsx @@ -78,6 +78,7 @@ export class SbbRadioButtonGroup implements ComponentInterface { @Element() private _element!: HTMLElement; private _hasSelectionPanel: boolean; + private _componentLoaded = false; @Watch('value') public valueChanged(value: any | undefined): void { @@ -155,6 +156,11 @@ export class SbbRadioButtonGroup implements ComponentInterface { this._hasSelectionPanel = !!this._element.querySelector('sbb-selection-panel'); toggleDatasetEntry(this._element, 'hasSelectionPanel', this._hasSelectionPanel); this._handlerRepository.connect(); + this._updateRadios(this.value); + } + + public componentDidLoad(): void { + this._componentLoaded = true; } public disconnectedCallback(): void { @@ -164,7 +170,7 @@ export class SbbRadioButtonGroup implements ComponentInterface { @Listen('state-change', { passive: true }) public onRadioButtonSelect(event: CustomEvent): void { event.stopPropagation(); - if (event.detail.type !== 'checked') { + if (event.detail.type !== 'checked' || !this._componentLoaded) { return; } @@ -185,11 +191,11 @@ export class SbbRadioButtonGroup implements ComponentInterface { this.didChange.emit({ value }); } - private _updateRadios(): void { - const value = this.value ?? this._radioButtons.find((radio) => radio.checked)?.value; + private _updateRadios(initValue?: string): void { + this.value = initValue ?? this._radioButtons.find((radio) => radio.checked)?.value; for (const radio of this._radioButtons) { - radio.checked = radio.value === value; + radio.checked = radio.value === this.value; radio.size = this.size; radio.allowEmptySelection = this.allowEmptySelection; diff --git a/src/components/sbb-radio-button/readme.md b/src/components/sbb-radio-button/readme.md index 777e329f96..4e01d0c054 100644 --- a/src/components/sbb-radio-button/readme.md +++ b/src/components/sbb-radio-button/readme.md @@ -56,9 +56,10 @@ The component has two different sizes, which can be changed using the `size` pro ## Events -| Event | Description | Type | -| -------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -| `state-change` | Internal event that emits whenever the state of the radio option in relation to the parent selection panel changes. | `CustomEvent` | +| Event | Description | Type | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | +| `sbb-radio-button-loaded` | Internal event that emits when the input element is loaded. | `CustomEvent` | +| `state-change` | Internal event that emits whenever the state of the radio option in relation to the parent selection panel changes. | `CustomEvent` | ## Methods diff --git a/src/components/sbb-radio-button/sbb-radio-button.events.ts b/src/components/sbb-radio-button/sbb-radio-button.events.ts index 4120dce5be..1c945bf471 100644 --- a/src/components/sbb-radio-button/sbb-radio-button.events.ts +++ b/src/components/sbb-radio-button/sbb-radio-button.events.ts @@ -3,5 +3,6 @@ * See stencil.config.ts in the root directory. */ export default { + sbbRadioButtonLoaded: 'sbb-radio-button-loaded', stateChange: 'state-change', }; diff --git a/src/components/sbb-radio-button/sbb-radio-button.tsx b/src/components/sbb-radio-button/sbb-radio-button.tsx index a703505959..8f97400f84 100644 --- a/src/components/sbb-radio-button/sbb-radio-button.tsx +++ b/src/components/sbb-radio-button/sbb-radio-button.tsx @@ -120,6 +120,15 @@ export class SbbRadioButton implements ComponentInterface { }) public stateChange: EventEmitter; + /** + * Internal event that emits when the input element is loaded. + */ + @Event({ + bubbles: true, + eventName: 'sbb-radio-button-loaded', + }) + public sbbRadioButtonLoaded: EventEmitter; + @Watch('checked') public handleCheckedChange(currentValue: boolean, previousValue: boolean): void { if (currentValue !== previousValue) { @@ -169,6 +178,7 @@ export class SbbRadioButton implements ComponentInterface { !!this._selectionPanelElement && !this._element.closest('sbb-selection-panel [slot="content"]'); this._setupInitialStateAndAttributeObserver(); + this._isSelectionPanelInput && this.sbbRadioButtonLoaded.emit(); } public componentDidLoad(): void { diff --git a/src/components/sbb-selection-panel/sbb-selection-panel.spec.ts b/src/components/sbb-selection-panel/sbb-selection-panel.spec.ts index cd80c11855..08bb5b1eb9 100644 --- a/src/components/sbb-selection-panel/sbb-selection-panel.spec.ts +++ b/src/components/sbb-selection-panel/sbb-selection-panel.spec.ts @@ -25,7 +25,7 @@ describe('sbb-selection-panel', () => { }); expect(root).toEqualHtml(` - +
diff --git a/src/components/sbb-selection-panel/sbb-selection-panel.tsx b/src/components/sbb-selection-panel/sbb-selection-panel.tsx index 6d29116b89..948ccc12e7 100644 --- a/src/components/sbb-selection-panel/sbb-selection-panel.tsx +++ b/src/components/sbb-selection-panel/sbb-selection-panel.tsx @@ -119,6 +119,7 @@ export class SbbSelectionPanel implements ComponentInterface { ); private _contentElement: HTMLElement; + private _componentLoaded = false; private get _input(): HTMLSbbCheckboxElement | HTMLSbbRadioButtonElement { return this._element.querySelector('sbb-checkbox, sbb-radio-button') as @@ -128,7 +129,7 @@ export class SbbSelectionPanel implements ComponentInterface { @Listen('state-change', { passive: true }) public onInputChange(event: CustomEvent): void { - if (!this._state) { + if (!this._state || !this._componentLoaded) { return; } @@ -157,15 +158,20 @@ export class SbbSelectionPanel implements ComponentInterface { } public connectedCallback(): void { - this._updateSelectionPanel(); this._handlerRepository.connect(); } + public componentDidLoad(): void { + this._componentLoaded = true; + } + public disconnectedCallback(): void { this._handlerRepository.disconnect(); } - private _updateSelectionPanel(): void { + @Listen('sbb-checkbox-loaded') + @Listen('sbb-radio-button-loaded') + public updateSelectionPanel(): void { this._checked = this._input?.checked; this._state = this._checked || this.forceOpen ? 'opened' : 'closed'; this._disabled = this._input?.disabled;