diff --git a/packages/examples/react-webpack/src/app/App.tsx b/packages/examples/react-webpack/src/app/App.tsx
index 0185d535a1..fcb87c49f9 100644
--- a/packages/examples/react-webpack/src/app/App.tsx
+++ b/packages/examples/react-webpack/src/app/App.tsx
@@ -1,15 +1,15 @@
import React, { type ReactElement } from 'react';
-import { Gallery } from './components/gallery/Gallery';
+// import { Gallery } from './components/gallery/Gallery';
// import { FormFormik } from './components/formFormik/FormFormik';
-// import { FormNative } from './components/formNative/FormNative';
import styles from './app.scss';
+import { FormNative } from './components/formNative/FormNative';
function App(): ReactElement {
return (
-
+ {/* */}
{/**/}
- {/**/}
+
);
}
diff --git a/packages/examples/react-webpack/src/app/components/formNative/FormNative.tsx b/packages/examples/react-webpack/src/app/components/formNative/FormNative.tsx
index 5a448dd9bb..48e02011d2 100644
--- a/packages/examples/react-webpack/src/app/components/formNative/FormNative.tsx
+++ b/packages/examples/react-webpack/src/app/components/formNative/FormNative.tsx
@@ -4,7 +4,7 @@ import React, { type ReactElement, useRef, useState } from 'react';
import styles from './formNative.scss';
function FormNative(): ReactElement {
- // const checkboxRef = useRef(null);
+ const checkboxRef = useRef(null);
const datepickerRef = useRef(null);
const formRef = useRef(null);
const inputNumberRef = useRef(null);
@@ -12,7 +12,7 @@ function FormNative(): ReactElement {
const passwordRef = useRef(null);
const phoneNumberRef = useRef(null);
const quantityRef = useRef(null);
- // const radioRef = useRef(null);
+ const radioRef = useRef(null);
const selectRef = useRef(null);
const switchRef = useRef(null);
const textareaRef = useRef(null);
@@ -48,21 +48,21 @@ function FormNative(): ReactElement {
// await validateField(selectRef.current);
// await validateField(switchRef.current);
// await validateField(textareaRef.current);
- await validateField(timepickerRef.current);
+ // await validateField(timepickerRef.current);
const formData = new FormData(formRef.current!);
- console.log(formData)
+ console.log(formData);
return false;
}
- async function validateField(element: any) {
+ async function validateField(element: any): Promise {
if (!element) {
return;
}
const validity = await element.getValidity();
-// console.log(validity)
+ // console.log(validity)
if (validity !== undefined) {
setError({
...error,
@@ -71,6 +71,18 @@ function FormNative(): ReactElement {
}
}
+ function onChange(event: CustomEvent) {
+ console.log('onChange', event);
+ }
+
+ function onReset(event: CustomEvent): void {
+ console.log('onReset', event);
+ }
+
+ function onClear(event: CustomEvent): void {
+ console.log('onClear', event);
+ }
+
return (
@@ -113,21 +126,20 @@ function FormNative(): ReactElement {
defaultValue={ 23 }
hasError={ error.inputNumber }
isClearable={ true }
- isRequired={ true }
+ // isRequired={ true }
name="inputNumber"
- onOdsChange={ () => validateField(inputNumberRef.current) }
ref={ inputNumberRef }
type={ ODS_INPUT_TYPE.number }
/>
{/* OK */}
validateField(inputTextRef.current) }
ref={ inputTextRef }
type={ ODS_INPUT_TYPE.text }
/>
@@ -139,30 +151,27 @@ function FormNative(): ReactElement {
isClearable={ true }
isRequired={ true }
name="password"
- onOdsChange={ () => validateField(passwordRef.current) }
ref={ passwordRef }
/>
{/* KO default value not in formData on immediate submit */}
{/* KO style width different */}
validateField(phoneNumberRef.current) }
ref={ phoneNumberRef }
/>
{/* KO reset does not reset formData if no default value */}
validateField(quantityRef.current) }
ref={ quantityRef }
/>
@@ -172,7 +181,10 @@ function FormNative(): ReactElement {
// isRequired={ true }
inputId="radio1"
name="radio"
+ isChecked
value="radio-1"
+ ref={ radioRef }
+
/>
@@ -192,6 +204,9 @@ function FormNative(): ReactElement {
hasError={ error.select }
isRequired={ true }
name="select"
+ onOdsChange={ (event) => onChange(event) }
+ onOdsReset={ (event) => onReset(event) }
+ onOdsClear={ (event) => onClear(event) }
ref={ selectRef }
>
@@ -202,14 +217,18 @@ function FormNative(): ReactElement {
+
+
{/* KO isRequired => no way to get validity */}
{/* KO should have an hasError status */}
+ {/* Two event change on reset & clear */}
@@ -229,18 +248,16 @@ function FormNative(): ReactElement {
hasError={ error.textarea }
isRequired={ true }
name="textarea"
- onOdsChange={ () => validateField(textareaRef.current) }
ref={ textareaRef }
/>
{/* KO value not in formData when no default */}
{/* KO value in formData not updated when default */}
validateField(timepickerRef.current) }
ref={ timepickerRef }
/>
diff --git a/packages/ods/src/components/checkbox/src/components/ods-checkbox/ods-checkbox.tsx b/packages/ods/src/components/checkbox/src/components/ods-checkbox/ods-checkbox.tsx
index fb6ca4c880..105a66d4d4 100644
--- a/packages/ods/src/components/checkbox/src/components/ods-checkbox/ods-checkbox.tsx
+++ b/packages/ods/src/components/checkbox/src/components/ods-checkbox/ods-checkbox.tsx
@@ -34,6 +34,7 @@ export class OdsCheckbox {
}
this.odsClear.emit();
hasChange && this.onInput();
+ this.inputEl?.focus();
}
@Method()
diff --git a/packages/ods/src/components/input/src/components/ods-input/ods-input.tsx b/packages/ods/src/components/input/src/components/ods-input/ods-input.tsx
index dbde72b44a..7bb36c4c10 100644
--- a/packages/ods/src/components/input/src/components/ods-input/ods-input.tsx
+++ b/packages/ods/src/components/input/src/components/ods-input/ods-input.tsx
@@ -51,9 +51,9 @@ export class OdsInput {
@Method()
async clear(): Promise {
+ this.odsClear.emit();
this.value = null;
this.inputEl?.focus();
- this.odsClear.emit();
}
@Method()
@@ -69,8 +69,8 @@ export class OdsInput {
@Method()
async reset(): Promise {
- this.value = this.defaultValue ?? null;
this.odsReset.emit();
+ this.value = this.defaultValue ?? null;
}
@Watch('isMasked')
diff --git a/packages/ods/src/components/quantity/src/components/ods-quantity/ods-quantity.tsx b/packages/ods/src/components/quantity/src/components/ods-quantity/ods-quantity.tsx
index 1ae588f848..b5a8da5217 100644
--- a/packages/ods/src/components/quantity/src/components/ods-quantity/ods-quantity.tsx
+++ b/packages/ods/src/components/quantity/src/components/ods-quantity/ods-quantity.tsx
@@ -81,9 +81,9 @@ export class OdsQuantity {
private onOdsChange(event: OdsInputValueChangeEvent): void {
if (event.detail.value === null) {
this.value = null;
- return;
+ } else {
+ this.value = Number(event.detail.value) ?? null;
}
- this.value = Number(event.detail.value) ?? null;
setFormValue(this.internals, this.value);
}
diff --git a/packages/ods/src/components/quantity/src/interfaces/events.ts b/packages/ods/src/components/quantity/src/interfaces/events.ts
index 9728881238..acea2d3f17 100644
--- a/packages/ods/src/components/quantity/src/interfaces/events.ts
+++ b/packages/ods/src/components/quantity/src/interfaces/events.ts
@@ -2,7 +2,7 @@ interface OdsQuantityEventValueChangeDetail {
name: string;
previousValue?: number;
validity?: ValidityState;
- value: number;
+ value: number | null;
}
type OdsQuantityEventValueChange = CustomEvent;
diff --git a/packages/ods/src/components/quantity/tests/behaviour/ods-quantity.e2e.ts b/packages/ods/src/components/quantity/tests/behaviour/ods-quantity.e2e.ts
index 40c2139123..6bfea06c6a 100644
--- a/packages/ods/src/components/quantity/tests/behaviour/ods-quantity.e2e.ts
+++ b/packages/ods/src/components/quantity/tests/behaviour/ods-quantity.e2e.ts
@@ -222,7 +222,7 @@ describe('ods-quantity behaviour', () => {
await submitButton.click();
await page.waitForNetworkIdle();
const url = new URL(page.url());
- expect(url.searchParams.get('ods-quantity')).toBe('0');
+ expect(url.searchParams.get('ods-quantity')).toBe('');
});
});
});
diff --git a/packages/ods/src/components/radio/src/components/ods-radio/ods-radio.tsx b/packages/ods/src/components/radio/src/components/ods-radio/ods-radio.tsx
index 40dfe3f15b..7b93f0e045 100644
--- a/packages/ods/src/components/radio/src/components/ods-radio/ods-radio.tsx
+++ b/packages/ods/src/components/radio/src/components/ods-radio/ods-radio.tsx
@@ -33,6 +33,13 @@ export class OdsRadio {
this.inputEl.checked = false;
}
this.odsClear.emit();
+ this.emitChange({
+ checked: false,
+ name: this.name,
+ validity: this.inputEl?.validity,
+ value: this.value ?? null,
+ });
+ this.inputEl?.focus();
}
@Method()
@@ -47,13 +54,19 @@ export class OdsRadio {
if (!inputRadio) {
return;
}
- if (radio.getAttribute('is-checked') === '') {
+ if (radio.getAttribute('is-checked') !== null) {
inputRadio.checked = true;
} else {
inputRadio.checked = false;
}
});
this.odsReset.emit();
+ this.emitChange({
+ checked: this.isChecked,
+ name: this.name,
+ validity: this.inputEl?.validity,
+ value: this.value ?? null,
+ });
}
@Method()
@@ -65,8 +78,8 @@ export class OdsRadio {
return document.querySelectorAll(`ods-radio[name="${this.name}"]`);
}
- private onInput(event: InputEvent): void {
- this.odsChange.emit({
+ private onInput(event: Event): void {
+ this.emitChange({
checked: (event.target as HTMLInputElement)?.checked,
name: this.name,
validity: this.inputEl?.validity,
@@ -74,6 +87,15 @@ export class OdsRadio {
});
}
+ private emitChange(detail: OdsRadioValueChangeEventDetail): void {
+ this.odsChange.emit({
+ checked: detail.checked,
+ name: detail.name,
+ validity: detail.validity,
+ value: detail.value,
+ });
+ }
+
render(): FunctionalComponent {
return (
diff --git a/packages/ods/src/components/radio/tests/behaviour/ods-radio.e2e.ts b/packages/ods/src/components/radio/tests/behaviour/ods-radio.e2e.ts
index d938cf2a60..c5e58cdddd 100644
--- a/packages/ods/src/components/radio/tests/behaviour/ods-radio.e2e.ts
+++ b/packages/ods/src/components/radio/tests/behaviour/ods-radio.e2e.ts
@@ -26,27 +26,43 @@ describe('ods-radio behaviour', () => {
describe('Methods', () => {
describe('method:clear', () => {
it('should receive odsClear event', async() => {
- await setup('');
+ await setup('');
const odsClearSpy = await page.spyOnEvent('odsClear');
+ const odsChangeSpy = await page.spyOnEvent('odsChange');
expect(await isInputRadioChecked(el)).toBe(true);
await el.callMethod('clear');
await page.waitForChanges();
expect(await isInputRadioChecked(el)).toBe(false);
expect(odsClearSpy).toHaveReceivedEventTimes(1);
+ expect(odsChangeSpy).toHaveReceivedEventTimes(1);
+ expect(odsChangeSpy).toHaveReceivedEventDetail({
+ checked: false,
+ name: 'ods-radio',
+ validity: {},
+ value: 'value',
+ });
});
});
describe('method:reset', () => {
it('should receive odsReset event', async() => {
- await setup('');
+ await setup('');
const odsResetSpy = await page.spyOnEvent('odsReset');
+ const odsChangeSpy = await page.spyOnEvent('odsChange');
expect(await isInputRadioChecked(el)).toBe(true);
await el.callMethod('reset');
await page.waitForChanges();
expect(await isInputRadioChecked(el)).toBe(true);
expect(odsResetSpy).toHaveReceivedEventTimes(1);
+ expect(odsChangeSpy).toHaveReceivedEventTimes(1);
+ expect(odsChangeSpy).toHaveReceivedEventDetail({
+ checked: true,
+ name: 'ods-radio',
+ validity: {},
+ value: 'value',
+ });
});
it('should checked the radio with is-checked after reset', async() => {
@@ -55,6 +71,8 @@ describe('ods-radio behaviour', () => {
`);
const radios = await page.findAll('ods-radio');
const odsResetSpy = await page.spyOnEvent('odsReset');
+ const odsChangeSpy = await page.spyOnEvent('odsChange');
+
expect(await isInputRadioChecked(radios[0])).toBe(true);
await radios[2].click();
@@ -64,6 +82,13 @@ describe('ods-radio behaviour', () => {
expect(await isInputRadioChecked(radios[0])).toBe(true);
expect(odsResetSpy).toHaveReceivedEventTimes(1);
+ expect(odsChangeSpy).toHaveReceivedEventTimes(2);
+ expect(odsChangeSpy).toHaveReceivedEventDetail({
+ checked: false,
+ name: 'radio-group',
+ validity: {},
+ value: 'value3',
+ });
});
});
diff --git a/packages/ods/src/components/select/src/components/ods-select/ods-select.tsx b/packages/ods/src/components/select/src/components/ods-select/ods-select.tsx
index 3699c9bde4..6885c32931 100644
--- a/packages/ods/src/components/select/src/components/ods-select/ods-select.tsx
+++ b/packages/ods/src/components/select/src/components/ods-select/ods-select.tsx
@@ -18,6 +18,7 @@ TomSelect.define('placeholder', placeholderPlugin);
export class OdsSelect {
private hasMovedNodes: boolean = false;
private isSelectSync: boolean = false;
+ private isValueSync: boolean = false;
private observer!: MutationObserver;
private select?: TomSelect;
private selectElement?: HTMLSelectElement;
@@ -52,8 +53,12 @@ export class OdsSelect {
@Method()
async clear(): Promise {
- this.value = null;
this.odsClear.emit();
+ if (this.value !== null) {
+ this.isValueSync = true;
+ }
+ this.value = null;
+ this.select?.focus();
}
@Method()
@@ -73,8 +78,11 @@ export class OdsSelect {
@Method()
async reset(): Promise {
- this.updateValue(this.defaultValue ?? null);
this.odsReset.emit();
+ if (this.value !== (this.defaultValue ?? null)) {
+ this.isValueSync = true;
+ }
+ this.updateValue(this.defaultValue ?? null);
}
@Watch('isDisabled')
@@ -104,7 +112,7 @@ export class OdsSelect {
}
@Watch('value')
- onValueChange(value: string | string[], previousValue?: string | string[]): void {
+ onValueChange(value: string | string[] | null, previousValue?: string | string[]): void {
// Value change can be triggered from either value attribute change or select change
// For the latter, we don't want to trigger a new change (as it may causes loop)
if (!this.isSelectSync) {
@@ -116,7 +124,7 @@ export class OdsSelect {
this.odsChange.emit({
name: this.name,
- previousValue: inlineValue(previousValue),
+ previousValue: inlineValue(previousValue) ?? undefined,
validity: this.selectElement?.validity,
value: inlineValue(value),
});
@@ -221,8 +229,11 @@ export class OdsSelect {
this.odsBlur.emit();
},
onChange: (value: string | string[]): void => {
- this.isSelectSync = true;
- this.updateValue(value);
+ if (!this.isValueSync) {
+ this.isSelectSync = true;
+ this.updateValue(value);
+ }
+ this.isValueSync = false;
},
onDropdownClose: (dropdown: HTMLDivElement): void => {
dropdown.classList.remove('ods-select__dropdown--bottom', 'ods-select__dropdown--top');
diff --git a/packages/ods/src/components/select/src/controller/ods-select.ts b/packages/ods/src/components/select/src/controller/ods-select.ts
index cc69f31263..40197ada91 100644
--- a/packages/ods/src/components/select/src/controller/ods-select.ts
+++ b/packages/ods/src/components/select/src/controller/ods-select.ts
@@ -23,11 +23,11 @@ function getSelectConfig(allowMultiple: boolean, multipleSelectionLabel: string,
return { plugin, template };
}
-function inlineValue(value: string | string[] | null | undefined): string {
+function inlineValue(value: string | string[] | null | undefined): string | null {
if (Array.isArray(value)) {
return value.join(',');
}
- return value || '';
+ return value ?? null;
}
function moveSlottedElements(targetElement: HTMLSelectElement, slottedElements: Element[]): void {
diff --git a/packages/ods/src/components/select/src/interfaces/events.ts b/packages/ods/src/components/select/src/interfaces/events.ts
index cd0f529f11..885522bfc9 100644
--- a/packages/ods/src/components/select/src/interfaces/events.ts
+++ b/packages/ods/src/components/select/src/interfaces/events.ts
@@ -2,7 +2,7 @@ interface OdsSelectEventChangeDetail {
name: string;
previousValue?: string;
validity?: ValidityState;
- value: string;
+ value: string | null;
}
type OdsSelectEventChange = CustomEvent;
diff --git a/packages/ods/src/components/switch/src/components/ods-switch/ods-switch.tsx b/packages/ods/src/components/switch/src/components/ods-switch/ods-switch.tsx
index bed30cbc96..edcb881778 100644
--- a/packages/ods/src/components/switch/src/components/ods-switch/ods-switch.tsx
+++ b/packages/ods/src/components/switch/src/components/ods-switch/ods-switch.tsx
@@ -25,14 +25,15 @@ export class OdsSwitch {
@Method()
async clear(): Promise {
- await clearItems(Array.from(this.el.children));
this.odsClear.emit();
+ await clearItems(Array.from(this.el.children));
+ this.el.focus();
}
@Method()
async reset(): Promise {
- await resetItems(Array.from(this.el.children));
this.odsReset.emit();
+ await resetItems(Array.from(this.el.children));
}
@Listen('odsSwitchItemFocus')
diff --git a/packages/ods/src/components/textarea/src/components/ods-textarea/ods-textarea.tsx b/packages/ods/src/components/textarea/src/components/ods-textarea/ods-textarea.tsx
index ee12dda81a..c2500057b8 100644
--- a/packages/ods/src/components/textarea/src/components/ods-textarea/ods-textarea.tsx
+++ b/packages/ods/src/components/textarea/src/components/ods-textarea/ods-textarea.tsx
@@ -36,8 +36,9 @@ export class OdsTextarea {
@Method()
async clear(): Promise {
- this.value = null;
this.odsClear.emit();
+ this.value = null;
+ this.textareaElement?.focus();
}
@Method()
@@ -47,8 +48,8 @@ export class OdsTextarea {
@Method()
async reset(): Promise {
- this.value = this.defaultValue ?? null;
this.odsReset.emit();
+ this.value = this.defaultValue ?? null;
}
@Watch('value')
@@ -59,7 +60,7 @@ export class OdsTextarea {
name: this.name,
previousValue,
validity: this.textareaElement?.validity,
- value: value ?? '',
+ value: value ?? null,
});
}
diff --git a/packages/ods/src/components/textarea/src/interfaces/events.ts b/packages/ods/src/components/textarea/src/interfaces/events.ts
index 93fb2ddded..8d128e8a62 100644
--- a/packages/ods/src/components/textarea/src/interfaces/events.ts
+++ b/packages/ods/src/components/textarea/src/interfaces/events.ts
@@ -2,7 +2,7 @@ interface OdsTextareaEventChangeDetail {
name: string;
previousValue?: string;
validity?: ValidityState;
- value: string;
+ value: string | null;
}
type OdsTextareaEventChange = CustomEvent;
diff --git a/packages/ods/src/components/timepicker/src/components/ods-timepicker/ods-timepicker.tsx b/packages/ods/src/components/timepicker/src/components/ods-timepicker/ods-timepicker.tsx
index 057fcb5b64..4a03711c65 100644
--- a/packages/ods/src/components/timepicker/src/components/ods-timepicker/ods-timepicker.tsx
+++ b/packages/ods/src/components/timepicker/src/components/ods-timepicker/ods-timepicker.tsx
@@ -119,7 +119,7 @@ export class OdsTimepicker {
name: this.name,
previousValue: this.previousValue ?? undefined,
validity: await this.odsInput?.getValidity(),
- value: this.value ?? '',
+ value: this.value ?? null,
});
}
diff --git a/packages/ods/src/components/timepicker/src/interfaces/event.ts b/packages/ods/src/components/timepicker/src/interfaces/event.ts
index 97003e6140..71baf3fbfd 100644
--- a/packages/ods/src/components/timepicker/src/interfaces/event.ts
+++ b/packages/ods/src/components/timepicker/src/interfaces/event.ts
@@ -5,7 +5,7 @@ interface OdsTimepickerValueChangeEventDetail {
name: string;
previousValue?: string;
validity?: ValidityState;
- value: string;
+ value: string | null;
}
type OdsTimepickerValueChangeEvent = CustomEvent;
diff --git a/packages/ods/src/style/_checkbox.scss b/packages/ods/src/style/_checkbox.scss
index 6fa0e1642a..c74fe540b5 100644
--- a/packages/ods/src/style/_checkbox.scss
+++ b/packages/ods/src/style/_checkbox.scss
@@ -25,7 +25,7 @@ $ods-checkbox-size: 16px;
font-size: 0.5rem;
}
- &:focus-visible {
+ &:focus-visible, &:focus {
@include focus.ods-focus();
}
diff --git a/packages/ods/src/style/_radio.scss b/packages/ods/src/style/_radio.scss
index 601905127e..073164cc90 100644
--- a/packages/ods/src/style/_radio.scss
+++ b/packages/ods/src/style/_radio.scss
@@ -21,7 +21,7 @@
content: "";
}
- &:focus-visible {
+ &:focus-visible, &:focus {
@include focus.ods-focus();
}