Skip to content

Commit

Permalink
feat(switch): add validity state
Browse files Browse the repository at this point in the history
  • Loading branch information
aesteves60 authored and dpellier committed Nov 28, 2024
1 parent 44183f4 commit 22b8652
Show file tree
Hide file tree
Showing 9 changed files with 508 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class OdsRadio {
@Event() odsChange!: EventEmitter<OdsRadioChangeEventDetail>;
@Event() odsClear!: EventEmitter<void>;
@Event() odsFocus!: EventEmitter<void>;
@Event() odsInvalid!: EventEmitter<boolean>;
@Event() odsReset!: EventEmitter<void>;

@Method()
Expand Down Expand Up @@ -164,6 +165,7 @@ 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 Down
2 changes: 1 addition & 1 deletion packages/ods/src/components/switch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"lint:scss": "stylelint 'src/components/**/*.scss'",
"lint:ts": "eslint '{src,tests}/**/*.{js,ts,tsx}'",
"start": "stencil build --dev --watch --serve",
"test:e2e": "stencil test --e2e --config stencil.config.ts",
"test:e2e": "stencil test --e2e --runInBand --config stencil.config.ts",
"test:e2e:ci": "echo \"FIXME e2e suites randomly fails on CI when run amongst other components e2e suites\"",
"test:e2e:switch": "tsc --noEmit && stencil test --e2e --ci --runInBand --config stencil.config.ts",
"test:spec": "stencil test --spec --config stencil.config.ts --coverage",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { type OdsRadio } from '../../../../radio/src';
tag: 'ods-switch-item',
})
export class OdsSwitchItem {
private isInvalid: boolean = false;
private observer?: MutationObserver;
private odsRadio?: OdsRadio;
private odsRadio?: OdsRadio & HTMLElement;

@State() private inputId: string = '';
@State() private isDisabled: boolean = false;
Expand All @@ -23,12 +24,25 @@ export class OdsSwitchItem {

@Event() odsBlur!: EventEmitter<void>;
@Event() odsFocus!: EventEmitter<void>;
@Event() odsInvalid!: EventEmitter<boolean>;

@Method()
public async checkValidity(): Promise<boolean | undefined> {
const validity = await this.odsRadio?.getValidity();
this.isInvalid = !validity?.valid;
return this.odsRadio?.checkValidity();
}

@Method()
public async clear(): Promise<void> {
return this.odsRadio?.clear();
}

@Method()
public async getValidationMessage(): Promise<string | undefined> {
return this.odsRadio?.getValidationMessage();
}

@Method()
public async getValidity(): Promise<ValidityState | undefined> {
return this.odsRadio?.getValidity();
Expand All @@ -39,6 +53,18 @@ export class OdsSwitchItem {
return this.odsRadio?.reset();
}

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

@Method()
public async willValidate(): Promise<boolean | undefined> {
return this.odsRadio?.willValidate();
}

componentWillLoad(): void {
this.observer = new MutationObserver((mutations: MutationRecord[]) => {
for (const mutation of mutations) {
Expand Down Expand Up @@ -76,9 +102,20 @@ export class OdsSwitchItem {
}
}

private async onBlur(): Promise<void> {
this.isInvalid = !(await this.odsRadio?.getValidity())?.valid;
this.odsInvalid.emit(this.isInvalid);
this.odsBlur.emit();
}

private onOdsInvalid(event: CustomEvent<boolean>): void {
this.isInvalid = event.detail;
}

render(): FunctionalComponent {
return (
<Host class="ods-switch-item">
<Host class="ods-switch-item"
disabled={ this.isDisabled }>
<ods-radio
ariaLabel={ this.ariaLabel }
ariaLabelledby={ this.ariaLabelledby }
Expand All @@ -88,9 +125,10 @@ export class OdsSwitchItem {
isRequired={ this.isRequired }
inputId={ this.inputId }
onOdsClear={ (event: CustomEvent<void>) => event.stopPropagation() }
onOdsInvalid={ (event: CustomEvent<boolean>) => this.onOdsInvalid(event) }
onOdsReset={ (event: CustomEvent<void>) => event.stopPropagation() }
name={ this.el.getAttribute('name') ?? '' }
ref={ (el?: HTMLElement) => this.odsRadio = el as unknown as OdsRadio }
ref={ (el?: HTMLElement) => this.odsRadio = el as unknown as OdsRadio & HTMLElement }
value={ this.value }>
</ods-radio>

Expand All @@ -101,7 +139,7 @@ export class OdsSwitchItem {
}}
htmlFor={ this.inputId }
tabindex={ !this.isDisabled ? 0 : -1 }
onBlur={ () => this.odsBlur.emit() }
onBlur={ (): Promise<void> => this.onBlur() }
onFocus={ () => this.odsFocus.emit() }
onKeyDown={ (e: KeyboardEvent) => this.handleKeyDown(e) }
onKeyUp={ (event: KeyboardEvent) => this.handleKeyUp(event) }>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ $ods-switch-height-md: 32px;
:host(.ods-switch--md) {
height: $ods-switch-height-md;
}

:host(.ods-switch--error) {
border: 1px solid var(--ods-color-form-element-border-critical);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { OdsSwitchChangeEventDetail } from '../../interfaces/events';
import { Component, Element, Event, type EventEmitter, type FunctionalComponent, Host, Method, Prop, Watch, h } from '@stencil/core';
import { Component, Element, Event, type EventEmitter, type FunctionalComponent, Host, Listen, Method, Prop, State, Watch, h } from '@stencil/core';
import { ODS_SWITCH_SIZE, type OdsSwitchSize } from '../../constant/switch-size';
import { clearItems, propagateInputId, propagateIsDisabled, propagateIsRequired, propagateName, resetItems } from '../../controller/ods-switch';

Expand All @@ -11,6 +11,8 @@ import { clearItems, propagateInputId, propagateIsDisabled, propagateIsRequired,
export class OdsSwitch {
@Element() el!: HTMLElement;

@State() private isInvalid: boolean = false;

@Prop({ reflect: true }) public isDisabled?: boolean = false;
@Prop({ reflect: true }) public isRequired?: boolean = false;
@Prop({ reflect: true }) public name!: string;
Expand All @@ -22,6 +24,11 @@ export class OdsSwitch {
@Event() odsFocus!: EventEmitter<CustomEvent<void>>;
@Event() odsReset!: EventEmitter<void>;

@Listen('odsInvalid')
onOdsInvalid(event: CustomEvent<boolean>): void {
this.isInvalid = event.detail;
}

@Method()
public async clear(): Promise<void> {
this.odsClear.emit();
Expand Down Expand Up @@ -59,7 +66,11 @@ export class OdsSwitch {

render(): FunctionalComponent {
return (
<Host class={ `ods-switch ods-switch--${this.size}` }>
<Host class={{
[`ods-switch ods-switch--${this.size}`]: true,
'ods-switch--error': this.isInvalid,
}}
disabled={ this.isDisabled }>
<slot onSlotchange={ () => this.init() }></slot>
</Host>
);
Expand Down
24 changes: 22 additions & 2 deletions packages/ods/src/components/switch/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,26 +82,46 @@

<p>Form</p>
<form id="radio-form" target="_self">
<ods-switch size="md" name="switch-radio-form">
<ods-switch-item value="1">form 1</ods-switch-item>
<ods-switch id="switch-form" size="md" name="switch-radio-form" is-required>
<ods-switch-item value="1" id="switch-radio-form-item-1">form 1</ods-switch-item>
<ods-switch-item value="2">form 2</ods-switch-item>
<ods-switch-item value="3">form 3</ods-switch-item>
</ods-switch>

<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-get-validity-button">getValidity</button>
<button id="form-toggle-required-button">toggle isRequired</button>

</form>

<script>
const form = document.getElementById('radio-form');
const resetFormButton = document.getElementById('form-reset-button');
const submitFormButton = document.getElementById('form-submit-button');
const getValidityFormButton = document.getElementById('form-get-validity-button');
const toggleRequiredFormButton = document.getElementById('form-toggle-required-button');

const switchItem = document.getElementById('switch-radio-form-item-1');
const odsSwitch = document.getElementById('switch-form');

getValidityFormButton.addEventListener('click', async() => {
console.log(await switchItem.getValidity())
});

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

toggleRequiredFormButton.addEventListener('click', () => {
if (odsSwitch.hasAttribute('is-required')) {
odsSwitch.removeAttribute('is-required')
} else {
odsSwitch.setAttribute('is-required', '')
}
});
</script>
</body>
</html>
Loading

0 comments on commit 22b8652

Please sign in to comment.