Skip to content

Commit

Permalink
feat(radio): update validity state on isRequired change
Browse files Browse the repository at this point in the history
  • Loading branch information
dpellier committed Nov 28, 2024
1 parent f787a42 commit 8a74238
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -138,27 +138,24 @@ function FormNative(): ReactElement {
name="quantity"
/>

{/*/!* OKish no validity method but required is managed by browser directly *!/*/}
{/*<div>*/}
{/* <OdsRadio*/}
{/* // isRequired={ true }*/}
{/* inputId="radio1"*/}
{/* name="radio"*/}
{/* isChecked*/}
{/* value="radio-1"*/}
{/* ref={ radioRef }*/}

{/* />*/}
{/* <label htmlFor="radio1">Radio 1</label>*/}

{/* <OdsRadio*/}
{/* // isRequired={ true }*/}
{/* inputId="radio2"*/}
{/* name="radio"*/}
{/* value="radio-2"*/}
{/* />*/}
{/* <label htmlFor="radio2">Radio 2</label>*/}
{/*</div>*/}
{/* OKish no validity method but required is managed by browser directly */}
<div>
<OdsRadio
isRequired={ areAllRequired }
inputId="radio1"
name="radio"
value="radio-1"
/>
<label htmlFor="radio1">Radio 1</label>

<OdsRadio
isRequired={ areAllRequired }
inputId="radio2"
name="radio"
value="radio-2"
/>
<label htmlFor="radio2">Radio 2</label>
</div>

{/*/!* KO required does not return valid: false on empty select *!/*/}
{/*/!* KO reset return empty string instead of null *!/*/}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import type { SpecPage } from '@stencil/core/testing';
import { newSpecPage } from '@stencil/core/testing';
import { OdsCheckbox } from '../../src';

// @ts-ignore for test purposes
global.MutationObserver = jest.fn(() => ({
disconnect: jest.fn(),
observe: jest.fn(),
}));

describe('ods-checkbox rendering', () => {
let page: SpecPage;
let root: HTMLElement | undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Element, Event, type EventEmitter, type FunctionalComponent, Host, Method, Prop, State, h } from '@stencil/core';
import { Component, Event, type EventEmitter, type FunctionalComponent, Host, Method, Prop, State, h } from '@stencil/core';
import { submitFormOnEnter } from '../../../../../utils/dom';
import { type OdsRadioChangeEventDetail } from '../../interfaces/events';

Expand All @@ -10,8 +10,7 @@ import { type OdsRadioChangeEventDetail } from '../../interfaces/events';
})
export class OdsRadio {
private inputEl?: HTMLInputElement;

@Element() el!: HTMLElement;
private observer?: MutationObserver;

@State() private isInvalid: boolean = false;

Expand Down Expand Up @@ -61,6 +60,12 @@ export class OdsRadio {
return this.inputEl?.validity;
}

@Method()
public async reportValidity(): Promise<boolean | undefined> {
this.isInvalid = !this.inputEl?.validity.valid;
return this.inputEl?.reportValidity();
}

@Method()
public async reset(): Promise<void> {
this.getOdsRadiosGroupByName().forEach((radio) => {
Expand All @@ -83,12 +88,6 @@ export class OdsRadio {
});
}

@Method()
public async reportValidity(): Promise<boolean | undefined> {
this.isInvalid = !this.inputEl?.validity.valid;
return this.inputEl?.reportValidity();
}

@Method()
public async select(): Promise<void> {
this.inputEl?.click();
Expand All @@ -99,6 +98,29 @@ export class OdsRadio {
return this.inputEl?.willValidate;
}

componentWillLoad(): void {
this.observer = new MutationObserver((mutations: MutationRecord[]) => {
for (const mutation of mutations) {
if (mutation.attributeName === 'required') {
this.isInvalid = !this.inputEl?.validity.valid;
}
}
});
}

componentDidLoad(): void {
if (this.inputEl) {
this.observer?.observe(this.inputEl, {
attributeFilter: ['required'],
attributeOldValue: false,
});
}
}

disconnectedCallback(): void {
this.observer?.disconnect();
}

async formResetCallback(): Promise<void> {
await this.reset();
}
Expand All @@ -112,13 +134,18 @@ export class OdsRadio {
});
}

private getOdsRadiosGroupByName(): NodeListOf<Element> {
private getOdsRadiosGroupByName(): NodeListOf<Element & OdsRadio> {
return document.querySelectorAll(`ods-radio[name="${this.name}"]`);
}

private onBlur(): void {
this.isInvalid = !this.inputEl?.validity.valid;
this.odsBlur.emit();

this.getOdsRadiosGroupByName().forEach((radio: OdsRadio) => {
// This will enforce error state to be updated on all radio when blurring
radio.checkValidity();
});
}

private onInput(event: Event): void {
Expand All @@ -141,7 +168,8 @@ export class OdsRadio {

render(): FunctionalComponent {
return (
<Host class="ods-radio"
<Host
class="ods-radio"
disabled={ this.isDisabled }>
<input
aria-label={ this.ariaLabel }
Expand Down
15 changes: 14 additions & 1 deletion packages/ods/src/components/radio/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
<form id="radio-form" target="_self">
<ods-input name="ods-input" value="On Vous Heberge ?" clearable></ods-input>
<div>
<ods-radio input-id="huey-form" name="name" value="huey"></ods-radio>
<ods-radio input-id="huey-form" name="name" value="huey" id="form-radio1"></ods-radio>
<label for="huey-form">Huey</label>
</div>

Expand All @@ -98,17 +98,30 @@
<input type="text" name="natif-input">
<button id="form-reset-button" type="reset">Reset</button>
<button id="form-submit-button" type="submit">Submit</button>
<button id="form-toggle-is-required" type="button">Toggle is-required</button>
</form>

<script>
const form = document.getElementById('radio-form');
const resetFormButton = document.getElementById('form-reset-button');
const submitFormButton = document.getElementById('form-submit-button');
const toggleIsRequiredButton = document.getElementById('form-toggle-is-required');
const radio1 = document.querySelector('#form-radio1')

submitFormButton.addEventListener('click', () => {
const formData = new FormData(form);
console.log('formData', formData);
});

toggleIsRequiredButton.addEventListener('click', async() => {
if (radio1.hasAttribute('is-required')) {
radio1.removeAttribute('is-required');
} else {
radio1.setAttribute('is-required', 'true');
}
console.log((await radio1.getValidity()).valid);
console.log(await radio1.checkValidity());
});
</script>

<p>Font / Alignment</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import type { SpecPage } from '@stencil/core/testing';
import { newSpecPage } from '@stencil/core/testing';
import { OdsRadio } from '../../src';

// @ts-ignore for test purposes
global.MutationObserver = jest.fn(() => ({
disconnect: jest.fn(),
observe: jest.fn(),
}));

describe('ods-radio rendering', () => {
let page: SpecPage;
let root: HTMLElement | undefined;
Expand Down
20 changes: 20 additions & 0 deletions packages/ods/src/components/radio/tests/validity/ods-radio.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,24 @@ describe('ods-radio validity', () => {
expect(formValidity).toBe(true);
});
});

describe('watchers', () => {
describe('is-required', () => {
it('should update validity when is-required change', async() => {
await setup('<ods-radio name="validity-test" is-required></ods-radio>');

expect(await el.callMethod('checkValidity')).toBe(false);

await el.removeAttribute('is-required');
await page.waitForChanges();

expect(await el.callMethod('checkValidity')).toBe(true);

await el.setAttribute('is-required', 'true');
await page.waitForChanges();

expect(await el.callMethod('checkValidity')).toBe(false);
});
});
});
});

0 comments on commit 8a74238

Please sign in to comment.