Skip to content

Commit

Permalink
fix(validityState): emit odsInvalid in internal isInvalid state chang…
Browse files Browse the repository at this point in the history
…e & update doc examples
  • Loading branch information
dpellier committed Nov 28, 2024
1 parent 0ea95cc commit e6d1801
Show file tree
Hide file tree
Showing 47 changed files with 448 additions and 258 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Event, type EventEmitter, type FunctionalComponent, Host, Method, Prop, State, h } from '@stencil/core';
import { Component, Event, type EventEmitter, type FunctionalComponent, Host, Method, Prop, State, Watch, h } from '@stencil/core';
import { submitFormOnEnter } from '../../../../../utils/dom';
import { type OdsCheckboxChangeEventDetail } from '../../interfaces/event';

Expand All @@ -12,7 +12,7 @@ export class OdsCheckbox {
private inputEl?: HTMLInputElement;
private observer?: MutationObserver;

@State() private isInvalid: boolean = false;
@State() private isInvalid: boolean | undefined;

@Prop({ reflect: true }) public ariaLabel: HTMLElement['ariaLabel'] = null;
@Prop({ reflect: true }) public ariaLabelledby?: string;
Expand All @@ -28,6 +28,7 @@ export class OdsCheckbox {
@Event() odsChange!: EventEmitter<OdsCheckboxChangeEventDetail>;
@Event() odsClear!: EventEmitter<void>;
@Event() odsFocus!: EventEmitter<void>;
@Event() odsInvalid!: EventEmitter<boolean>;
@Event() odsReset!: EventEmitter<void>;

@Method()
Expand Down Expand Up @@ -97,6 +98,11 @@ export class OdsCheckbox {
return this.inputEl?.willValidate;
}

@Watch('isInvalid')
onIsInvalidChange(): void {
this.odsInvalid.emit(this.isInvalid);
}

componentWillLoad(): void {
this.observer = new MutationObserver((mutations: MutationRecord[]) => {
for (const mutation of mutations) {
Expand Down Expand Up @@ -178,7 +184,7 @@ export class OdsCheckbox {
aria-labelledby={ this.ariaLabelledby }
class={{
'ods-checkbox__checkbox': true,
'ods-checkbox__checkbox--error': this.isInvalid,
'ods-checkbox__checkbox--error': !!this.isInvalid,
}}
checked={ this.isChecked }
disabled={ this.isDisabled }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,23 @@ describe('ods-checkbox validity', () => {

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

it('should triggers an odsInvalid event when is-required change', async() => {
await setup('<ods-checkbox></ods-checkbox>');
const odsInvalidSpy = await page.spyOnEvent('odsInvalid');

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

expect(odsInvalidSpy).toHaveReceivedEventTimes(1);
expect(odsInvalidSpy).toHaveReceivedEventDetail(true);

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

expect(odsInvalidSpy).toHaveReceivedEventTimes(2);
expect(odsInvalidSpy).toHaveReceivedEventDetail(false);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class OdsDatepicker {

@AttachInternals() internals!: ElementInternals;

@State() private isInvalid: boolean = false;
@State() private isInvalid: boolean | undefined;

@Prop({ reflect: true }) public ariaLabel: HTMLElement['ariaLabel'] = null;
@Prop({ reflect: true }) public ariaLabelledby?: string;
Expand All @@ -74,6 +74,7 @@ export class OdsDatepicker {
@Event() odsChange!: EventEmitter<OdsDatepickerChangeEventDetail>;
@Event() odsClear!: EventEmitter<void>;
@Event() odsFocus!: EventEmitter<void>;
@Event() odsInvalid!: EventEmitter<boolean>;
@Event() odsReset!: EventEmitter<void>;

@Listen('invalid')
Expand Down Expand Up @@ -167,6 +168,11 @@ export class OdsDatepicker {
this.datepickerInstance?.setOptions({ format: this.format });
}

@Watch('isInvalid')
onIsInvalidChange(): void {
this.odsInvalid.emit(this.isInvalid);
}

@Watch('locale')
onLocaleChange(): void {
this.datepickerInstance?.setOptions({ language: this.locale });
Expand Down Expand Up @@ -330,7 +336,7 @@ export class OdsDatepicker {
class={{
'ods-datepicker__input': true,
'ods-datepicker__input--clearable': hasClearableAction,
'ods-datepicker__input--error': this.hasError || this.isInvalid,
'ods-datepicker__input--error': this.hasError || !!this.isInvalid,
'ods-datepicker__input--loading': this.isLoading,
}}
disabled={ this.isDisabled }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,23 @@ describe('ods-datepicker validity', () => {

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

it('should triggers an odsInvalid event when is-required change', async() => {
await setup('<ods-datepicker></ods-datepicker>');
const odsInvalidSpy = await page.spyOnEvent('odsInvalid');

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

expect(odsInvalidSpy).toHaveReceivedEventTimes(1);
expect(odsInvalidSpy).toHaveReceivedEventDetail(true);

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

expect(odsInvalidSpy).toHaveReceivedEventTimes(2);
expect(odsInvalidSpy).toHaveReceivedEventDetail(false);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class OdsInput {

@AttachInternals() private internals!: ElementInternals;

@State() private isInvalid: boolean = false;
@State() private isInvalid: boolean | undefined;
@State() private isPassword = false;

@Prop({ reflect: true }) public ariaLabel: HTMLElement['ariaLabel'] = null;
Expand Down Expand Up @@ -278,7 +278,7 @@ export class OdsInput {
class={{
'ods-input__input': true,
'ods-input__input--clearable': hasClearableIcon,
'ods-input__input--error': this.hasError || this.isInvalid,
'ods-input__input--error': this.hasError || !!this.isInvalid,
'ods-input__input--loading': this.isLoading,
'ods-input__input--search': hasSearchIcon,
'ods-input__input--toggle-mask': hasToggleMaskIcon,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Event, type EventEmitter, type FunctionalComponent, Host, Method, Prop, State, h } from '@stencil/core';
import { Component, Event, type EventEmitter, type FunctionalComponent, Host, Method, Prop, State, Watch, h } from '@stencil/core';
import { submitFormOnEnter } from '../../../../../utils/dom';
import { type OdsRadioChangeEventDetail } from '../../interfaces/events';

Expand All @@ -12,7 +12,7 @@ export class OdsRadio {
private inputEl?: HTMLInputElement;
private observer?: MutationObserver;

@State() private isInvalid: boolean = false;
@State() private isInvalid: boolean | undefined;

@Prop({ reflect: true }) public ariaLabel: HTMLElement['ariaLabel'] = null;
@Prop({ reflect: true }) public ariaLabelledby?: string;
Expand Down Expand Up @@ -102,6 +102,11 @@ export class OdsRadio {
return this.inputEl?.willValidate;
}

@Watch('isInvalid')
onIsInvalidChange(): void {
this.odsInvalid.emit(this.isInvalid);
}

componentWillLoad(): void {
this.observer = new MutationObserver((mutations: MutationRecord[]) => {
for (const mutation of mutations) {
Expand Down Expand Up @@ -159,7 +164,6 @@ export class OdsRadio {

// Enforce the state here as we may still be in pristine state (if the form is submitted before any changes occurs)
this.isInvalid = true;
this.odsInvalid.emit(this.isInvalid);
}

render(): FunctionalComponent {
Expand All @@ -172,7 +176,7 @@ export class OdsRadio {
aria-labelledby={ this.ariaLabelledby }
class={{
'ods-radio__radio': true,
'ods-radio__radio--error': this.isInvalid,
'ods-radio__radio--error': !!this.isInvalid,
}}
checked={ this.isChecked }
disabled={ this.isDisabled }
Expand Down
17 changes: 17 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 @@ -238,6 +238,23 @@ describe('ods-radio validity', () => {

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

it('should triggers an odsInvalid event when is-required change', async() => {
await setup('<ods-radio name="validity-test"></ods-radio>');
const odsInvalidSpy = await page.spyOnEvent('odsInvalid');

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

expect(odsInvalidSpy).toHaveReceivedEventTimes(1);
expect(odsInvalidSpy).toHaveReceivedEventDetail(true);

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

expect(odsInvalidSpy).toHaveReceivedEventTimes(2);
expect(odsInvalidSpy).toHaveReceivedEventDetail(false);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class OdsRange {
@State() private dualValue?: number;
@State() private currentValue?: number;
@State() private isDualRange: boolean = false;
@State() private isInvalid: boolean = false;
@State() private isInvalid: boolean | undefined;

@Element() el!: HTMLElement;

Expand All @@ -47,6 +47,7 @@ export class OdsRange {
@Event() odsChange!: EventEmitter<OdsRangeChangeEventDetail>;
@Event() odsClear!: EventEmitter<void>;
@Event() odsFocus!: EventEmitter<void>;
@Event() odsInvalid!: EventEmitter<boolean>;
@Event() odsReset!: EventEmitter<void>;

@Listen('invalid')
Expand Down Expand Up @@ -115,6 +116,11 @@ export class OdsRange {
return this.internals.willValidate;
}

@Watch('isInvalid')
onIsInvalidChange(): void {
this.odsInvalid.emit(this.isInvalid);
}

@Watch('min')
@Watch('max')
onMinOrMaxChange(): void {
Expand Down Expand Up @@ -204,6 +210,14 @@ export class OdsRange {
await this.reset();
}

private emitOdsChange(): void {
this.odsChange.emit({
name: this.name,
validity: this.internals.validity,
value: this.value,
});
}

private fillInputs(currentValue: number | null, dualValue?: number | null): number | [number, number]| [null, null] | undefined {
let value: number | [number, number] | [null, null] | undefined;

Expand All @@ -218,12 +232,12 @@ export class OdsRange {
return value;
}

private emitOdsChange(): void {
this.odsChange.emit({
name: this.name,
validity: this.internals.validity,
value: this.value,
});
private hideTooltip(): void {
this.tooltip?.hide();
}

private hideTooltipDual(): void {
this.tooltipDual?.hide();
}

private onBlur(): void {
Expand Down Expand Up @@ -253,16 +267,10 @@ export class OdsRange {
}
}

private hideTooltip(): void {
this.tooltip?.hide();
}

private hideTooltipDual(): void {
this.tooltipDual?.hide();
}

private showTooltip(): void {
this.tooltip?.show();
private setInputElDualValue(step : number): void {
if (this.inputEl && this.inputElDual) {
this.inputElDual.valueAsNumber = (this.inputEl?.valueAsNumber ?? 0) + step;
}
}

private setInputElValue(step : number): void {
Expand All @@ -271,10 +279,8 @@ export class OdsRange {
}
}

private setInputElDualValue(step : number): void {
if (this.inputEl && this.inputElDual) {
this.inputElDual.valueAsNumber = (this.inputEl?.valueAsNumber ?? 0) + step;
}
private showTooltip(): void {
this.tooltip?.show();
}

private showTooltipDual(): void {
Expand All @@ -300,7 +306,7 @@ export class OdsRange {
<input
class={{
'ods-range__range': true,
'ods-range__range--error': this.hasError || this.isInvalid,
'ods-range__range--error': this.hasError || !!this.isInvalid,
}}
aria-valuemax={ this.max }
aria-valuemin={ this.min }
Expand Down Expand Up @@ -350,7 +356,7 @@ export class OdsRange {
<input
class={{
'ods-range__range-dual': true,
'ods-range__range-dual--error': this.hasError || this.isInvalid,
'ods-range__range-dual--error': this.hasError || !!this.isInvalid,
}}
aria-valuemax={ this.max }
aria-valuemin={ this.min }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class OdsSelect {

@AttachInternals() private internals!: ElementInternals;

@State() isInvalid: boolean = false;
@State() isInvalid: boolean | undefined;

@Prop({ reflect: true }) public allowMultiple: boolean = false;
@Prop({ reflect: true }) public ariaLabel: HTMLElement['ariaLabel'] = null;
Expand All @@ -54,6 +54,7 @@ export class OdsSelect {
@Event() odsChange!: EventEmitter<OdsSelectChangeEventDetail>;
@Event() odsClear!: EventEmitter<void>;
@Event() odsFocus!: EventEmitter<void>;
@Event() odsInvalid!: EventEmitter<boolean>;
@Event() odsReset!: EventEmitter<void>;

@Listen('invalid')
Expand Down Expand Up @@ -146,6 +147,11 @@ export class OdsSelect {
}
}

@Watch('isInvalid')
onIsInvalidChange(): void {
this.odsInvalid.emit(this.isInvalid);
}

@Watch('isReadonly')
onIsReadonlyChange(newValue: boolean): void {
if (newValue) {
Expand Down Expand Up @@ -396,7 +402,7 @@ export class OdsSelect {
'ods-select': true,
'ods-select--disabled': this.isDisabled,
'ods-select--dropdown-width-auto': this.dropdownWidth === 'auto',
'ods-select--error': this.hasError || this.isInvalid,
'ods-select--error': this.hasError || !!this.isInvalid,
[`ods-select--border-rounded-${this.borderRounded}`]: true,
}}
disabled={ this.isDisabled }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,23 @@ describe('ods-select validity', () => {

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

it('should triggers an odsInvalid event when is-required change', async() => {
await setup('<ods-select><option value="1">1</option></ods-select>');
const odsInvalidSpy = await page.spyOnEvent('odsInvalid');

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

expect(odsInvalidSpy).toHaveReceivedEventTimes(1);
expect(odsInvalidSpy).toHaveReceivedEventDetail(true);

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

expect(odsInvalidSpy).toHaveReceivedEventTimes(2);
expect(odsInvalidSpy).toHaveReceivedEventDetail(false);
});
});
});
});
Loading

0 comments on commit e6d1801

Please sign in to comment.