From a8bf8622c9f08d866543bd2b6f9996bfd63addb8 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 24 Sep 2024 19:57:09 -0400 Subject: [PATCH] Implemented logic to perform form validation - When using a button with buttonType="submit", all the validateable fields will get their checkValidity() method called, which makes them display our custom version of validity state. - If any field is not valid, form submission is prevented. - If any field is invalid, the first invalid fied is scrolled into view and focused. --- .hintrc | 9 ++++ package-lock.json | 8 +-- packages/stencil-library/custom-elements.json | 19 +++++-- packages/stencil-library/src/components.d.ts | 28 ++++++---- .../src/components/dnn-button/dnn-button.tsx | 53 +++++++++++++++---- .../src/components/dnn-button/readme.md | 23 ++++---- .../src/components/dnn-input/dnn-input.tsx | 22 +++++++- .../src/components/dnn-select/dnn-select.tsx | 13 ++--- .../src/components/dnn-select/readme.md | 13 ++--- .../dnn-example-form/dnn-example-form.tsx | 4 +- packages/stencil-library/src/index.html | 16 +++--- packages/stencil-library/vscode-data.json | 22 +++++++- 12 files changed, 159 insertions(+), 71 deletions(-) create mode 100644 .hintrc diff --git a/.hintrc b/.hintrc new file mode 100644 index 000000000..76dfd6e13 --- /dev/null +++ b/.hintrc @@ -0,0 +1,9 @@ +{ + "extends": [ + "development" + ], + "hints": { + "axe/name-role-value": "off", + "no-inline-styles": "off" + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 13454f9cd..30afa0307 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1929,9 +1929,9 @@ "dev": true }, "node_modules/@chromatic-com/storybook": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-1.6.1.tgz", - "integrity": "sha512-x1x1NB3j4xpfeSWKr96emc+7ZvfsvH+/WVb3XCjkB24PPbT8VZXb3mJSAQMrSzuQ8+eQE9kDogYHH9Fj3tb/Cw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-2.0.2.tgz", + "integrity": "sha512-7bPIliISedeIpnVKbzktysFYW5n56bN91kxuOj1XXKixmjbUHRUMvcXd4K2liN6MiR5ZqJtmtcPsZ6CebbGlEA==", "dev": true, "dependencies": { "chromatic": "^11.4.0", @@ -23685,7 +23685,7 @@ "jodit": "^4.2.27" }, "devDependencies": { - "@chromatic-com/storybook": "^1.5.0", + "@chromatic-com/storybook": "^2.0.2", "@stencil-community/eslint-plugin": "0.8.0", "@stencil/core": "4.21.0", "@stencil/react-output-target": "^0.5.1", diff --git a/packages/stencil-library/custom-elements.json b/packages/stencil-library/custom-elements.json index fd497482a..ccecef875 100644 --- a/packages/stencil-library/custom-elements.json +++ b/packages/stencil-library/custom-elements.json @@ -184,6 +184,15 @@ "tagName": "dnn-button", "description": "", "attributes": [ + { + "name": "appearance", + "type": { + "text": "\"danger\" | \"primary\" | \"secondary\" | \"tertiary\"" + }, + "description": "Defines the look of the button.", + "default": "'primary'", + "required": false + }, { "name": "confirm", "type": { @@ -234,7 +243,7 @@ "type": { "text": "\"button\" | \"reset\" | \"submit\"" }, - "description": "Optional button type,\ncan be either submit, reset or button and defaults to button if not specified.\nWarning: DNN wraps the whole page in a form, only use this if you are handling\nform submission manually.", + "description": "Optional button type,\ncan be either submit, reset or button and defaults to button if not specified.\nWarning: DNN wraps the whole page in a form, only use this if you are handling\nform submission manually.\nWarning: This will be deprecated in the next version and replaced with a new 'type' property.", "default": "'button'", "required": false }, @@ -261,7 +270,7 @@ "type": { "text": "\"danger\" | \"primary\" | \"secondary\" | \"tertiary\"" }, - "description": "Optional button style,\ncan be either primary, secondary or tertiary or danger and defaults to primary if not specified", + "description": "Optional button style,", "default": "'primary'", "required": false } @@ -1803,9 +1812,9 @@ "members": [ { "kind": "method", - "name": "reportValidity", - "description": "Reports the element validity.", - "signature": "reportValidity(valid: boolean, message?: string) => Promise" + "name": "checkValidity", + "description": "Reports the input validity details. See https://developer.mozilla.org/en-US/docs/Web/API/ValidityState", + "signature": "checkValidity() => Promise" } ], "events": [ diff --git a/packages/stencil-library/src/components.d.ts b/packages/stencil-library/src/components.d.ts index fc86b501f..c99e5c8b4 100644 --- a/packages/stencil-library/src/components.d.ts +++ b/packages/stencil-library/src/components.d.ts @@ -83,6 +83,10 @@ export namespace Components { "value": string; } interface DnnButton { + /** + * Defines the look of the button. + */ + "appearance": 'primary' | 'danger' | 'secondary' | 'tertiary'; /** * Optionally add a confirmation dialog before firing the action. */ @@ -104,7 +108,7 @@ export namespace Components { */ "disabled": boolean; /** - * Optional button type, can be either submit, reset or button and defaults to button if not specified. Warning: DNN wraps the whole page in a form, only use this if you are handling form submission manually. + * Optional button type, can be either submit, reset or button and defaults to button if not specified. Warning: DNN wraps the whole page in a form, only use this if you are handling form submission manually. Warning: This will be deprecated in the next version and replaced with a new 'type' property. */ "formButtonType": 'submit' | 'reset' | 'button'; /** @@ -116,7 +120,8 @@ export namespace Components { */ "size"?: 'small' | 'normal' | 'large'; /** - * Optional button style, can be either primary, secondary or tertiary or danger and defaults to primary if not specified + * Optional button style, + * @deprecated This property will be reused in the next version to represent the type of button like "submit" or "reset". Use the appearance property instead. */ "type": 'primary' | 'danger' | 'secondary' | 'tertiary'; } @@ -566,6 +571,10 @@ export namespace Components { "query": string; } interface DnnSelect { + /** + * Reports the input validity details. See https://developer.mozilla.org/en-US/docs/Web/API/ValidityState + */ + "checkValidity": () => Promise; /** * @deprecated This control has its own validatin reporting, will be removed in v0.25.0 */ @@ -586,12 +595,6 @@ export namespace Components { * The name for this input, if used in forms. */ "name": string; - /** - * Reports the element validity. - * @param valid - Whether the element is valid or not. - * @param message - The message to show when the element is invalid, optional if valid. - */ - "reportValidity": (valid: boolean, message?: string) => Promise; /** * Defines whether the field requires having a value. */ @@ -1344,6 +1347,10 @@ declare namespace LocalJSX { "value"?: string; } interface DnnButton { + /** + * Defines the look of the button. + */ + "appearance"?: 'primary' | 'danger' | 'secondary' | 'tertiary'; /** * Optionally add a confirmation dialog before firing the action. */ @@ -1365,7 +1372,7 @@ declare namespace LocalJSX { */ "disabled"?: boolean; /** - * Optional button type, can be either submit, reset or button and defaults to button if not specified. Warning: DNN wraps the whole page in a form, only use this if you are handling form submission manually. + * Optional button type, can be either submit, reset or button and defaults to button if not specified. Warning: DNN wraps the whole page in a form, only use this if you are handling form submission manually. Warning: This will be deprecated in the next version and replaced with a new 'type' property. */ "formButtonType"?: 'submit' | 'reset' | 'button'; /** @@ -1385,7 +1392,8 @@ declare namespace LocalJSX { */ "size"?: 'small' | 'normal' | 'large'; /** - * Optional button style, can be either primary, secondary or tertiary or danger and defaults to primary if not specified + * Optional button style, + * @deprecated This property will be reused in the next version to represent the type of button like "submit" or "reset". Use the appearance property instead. */ "type"?: 'primary' | 'danger' | 'secondary' | 'tertiary'; } diff --git a/packages/stencil-library/src/components/dnn-button/dnn-button.tsx b/packages/stencil-library/src/components/dnn-button/dnn-button.tsx index 8073072bc..89b1d9618 100644 --- a/packages/stencil-library/src/components/dnn-button/dnn-button.tsx +++ b/packages/stencil-library/src/components/dnn-button/dnn-button.tsx @@ -13,15 +13,21 @@ export class DnnButton { /** * Optional button style, - * can be either primary, secondary or tertiary or danger and defaults to primary if not specified + * @deprecated This property will be reused in the next version to represent the type of button like "submit" or "reset". Use the appearance property instead. */ @Prop() type: 'primary' | 'danger' | 'secondary' | 'tertiary' = 'primary'; + /** + * Defines the look of the button. + */ + @Prop() appearance: 'primary' | 'danger' | 'secondary' | 'tertiary' = 'primary'; + /** * Optional button type, * can be either submit, reset or button and defaults to button if not specified. * Warning: DNN wraps the whole page in a form, only use this if you are handling * form submission manually. + * Warning: This will be deprecated in the next version and replaced with a new 'type' property. */ @Prop() formButtonType: 'submit' | 'reset' | 'button' = 'button'; @@ -104,18 +110,44 @@ export class DnnButton { if (this.confirm && !this.modalVisible){ this.modal.show(); this.modalVisible = true; + return; } if (this.formButtonType === 'submit') { var form = this.internals.form; if (form){ - var submitButton = document.createElement('button'); - submitButton.type = 'submit'; - submitButton.style.display = 'none'; - form.appendChild(submitButton); - submitButton.click(); - form.removeChild(submitButton); + var validity = form.checkValidity(); + if (validity){ + const submitButton = document.createElement('button'); + submitButton.type = 'submit'; + submitButton.style.display = 'none'; + form.appendChild(submitButton); + submitButton.click(); + form.removeChild(submitButton); + } + else + { + var formControls = form.elements; + for (let i = 0; i < formControls.length; i++){ + var control = formControls[i] as HTMLFormElement; + try{ + if ('checkValidity' in control && typeof control['checkValidity'] === 'function') { + control.checkValidity(); + } + } + catch(e){ + console.error(e, control); + } + } + var elementToScrollTo = form.querySelector(':invalid'); + if (elementToScrollTo){ + elementToScrollTo.scrollIntoView({behavior: 'smooth', block: 'center'}); + if ('focus' in elementToScrollTo && typeof elementToScrollTo['focus'] === 'function') { + elementToScrollTo.focus(); + } + } + } } } if (this.formButtonType === 'reset') @@ -134,7 +166,7 @@ export class DnnButton { private getElementClasses(): string | { [className: string]: boolean; } { const classes: string[] = []; - classes.push(this.type); + classes.push(this.appearance); if (this.reversed){ classes.push('reversed'); } @@ -147,7 +179,6 @@ export class DnnButton { return classes.join(' '); } - render() { return ( - this.handleCancel()}>{this.confirmNoText} - this.handleConfirm()}>{this.confirmYesText} + this.handleCancel()}>{this.confirmNoText} + this.handleConfirm()}>{this.confirmYesText} } diff --git a/packages/stencil-library/src/components/dnn-button/readme.md b/packages/stencil-library/src/components/dnn-button/readme.md index 216f0154c..ec2a5067a 100644 --- a/packages/stencil-library/src/components/dnn-button/readme.md +++ b/packages/stencil-library/src/components/dnn-button/readme.md @@ -38,17 +38,18 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------ | -| `confirm` | `confirm` | Optionally add a confirmation dialog before firing the action. | `boolean` | `false` | -| `confirmMessage` | `confirm-message` | The text of the confirmation message; | `string` | `"Are you sure ?"` | -| `confirmNoText` | `confirm-no-text` | The text of the no button for confirmation. | `string` | `"No"` | -| `confirmYesText` | `confirm-yes-text` | The text of the yes button for confirmation. | `string` | `"Yes"` | -| `disabled` | `disabled` | Disables the button | `boolean` | `false` | -| `formButtonType` | `form-button-type` | Optional button type, can be either submit, reset or button and defaults to button if not specified. Warning: DNN wraps the whole page in a form, only use this if you are handling form submission manually. | `"button" \| "reset" \| "submit"` | `'button'` | -| `reversed` | `reversed` | Optionally reverses the button style. | `boolean` | `false` | -| `size` | `size` | Optionally sets the button size, small normal or large, defaults to normal | `"large" \| "normal" \| "small"` | `'normal'` | -| `type` | `type` | Optional button style, can be either primary, secondary or tertiary or danger and defaults to primary if not specified | `"danger" \| "primary" \| "secondary" \| "tertiary"` | `'primary'` | +| Property | Attribute | Description | Type | Default | +| ---------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------ | +| `appearance` | `appearance` | Defines the look of the button. | `"danger" \| "primary" \| "secondary" \| "tertiary"` | `'primary'` | +| `confirm` | `confirm` | Optionally add a confirmation dialog before firing the action. | `boolean` | `false` | +| `confirmMessage` | `confirm-message` | The text of the confirmation message; | `string` | `"Are you sure ?"` | +| `confirmNoText` | `confirm-no-text` | The text of the no button for confirmation. | `string` | `"No"` | +| `confirmYesText` | `confirm-yes-text` | The text of the yes button for confirmation. | `string` | `"Yes"` | +| `disabled` | `disabled` | Disables the button | `boolean` | `false` | +| `formButtonType` | `form-button-type` | Optional button type, can be either submit, reset or button and defaults to button if not specified. Warning: DNN wraps the whole page in a form, only use this if you are handling form submission manually. Warning: This will be deprecated in the next version and replaced with a new 'type' property. | `"button" \| "reset" \| "submit"` | `'button'` | +| `reversed` | `reversed` | Optionally reverses the button style. | `boolean` | `false` | +| `size` | `size` | Optionally sets the button size, small normal or large, defaults to normal | `"large" \| "normal" \| "small"` | `'normal'` | +| `type` | `type` | **[DEPRECATED]** This property will be reused in the next version to represent the type of button like "submit" or "reset". Use the appearance property instead.

Optional button style, | `"danger" \| "primary" \| "secondary" \| "tertiary"` | `'primary'` | ## Events diff --git a/packages/stencil-library/src/components/dnn-input/dnn-input.tsx b/packages/stencil-library/src/components/dnn-input/dnn-input.tsx index 2dcc6fe08..ffb3ca7da 100644 --- a/packages/stencil-library/src/components/dnn-input/dnn-input.tsx +++ b/packages/stencil-library/src/components/dnn-input/dnn-input.tsx @@ -76,6 +76,10 @@ export class DnnInput { /** Reports the input validity details. See https://developer.mozilla.org/en-US/docs/Web/API/ValidityState */ @Method() async checkValidity(): Promise { + var validity = this.inputField.checkValidity(); + if (!validity) { + this.fieldset.setValidity(false, this.inputField.validationMessage); + } return this.inputField.validity; } @@ -99,10 +103,15 @@ export class DnnInput { this.labelId = generateRandomId(); } + componentDidLoad() { + var validity = this.inputField.validity; + var validityMessage = validity.valid ? "" : this.inputField.validationMessage; + this.internals.setValidity(this.inputField.validity, validityMessage); + } + // eslint-disable-next-line @stencil-community/own-methods-must-be-private formResetCallback() { this.inputField.setCustomValidity(""); - this.valid = true; this.value = ""; this.internals.setValidity({}); this.internals.setFormValue(""); @@ -117,6 +126,7 @@ export class DnnInput { private handleInvalid(): void { this.fieldset.setValidity(false, this.inputField.validationMessage); + this.internals.setValidity(this.inputField.validity, this.inputField.validationMessage); } private handleChange() { @@ -157,6 +167,14 @@ export class DnnInput { return true; } + handleBlur(): void { + this.focused = false + var validity = this.inputField.checkValidity(); + this.valid = validity; + this.fieldset.setValidity(validity, this.inputField.validationMessage); + this.internals.setValidity(this.inputField.validity, this.inputField.validationMessage); + } + render() { return ( this.focused = false} + onBlur={() => this.handleBlur()} onFocus={() => this.focused = true} onInput={e => this.handleInput((e.target as HTMLInputElement).value)} onInvalid={() => this.handleInvalid()} diff --git a/packages/stencil-library/src/components/dnn-select/dnn-select.tsx b/packages/stencil-library/src/components/dnn-select/dnn-select.tsx index b47aceb49..26e2b2aed 100644 --- a/packages/stencil-library/src/components/dnn-select/dnn-select.tsx +++ b/packages/stencil-library/src/components/dnn-select/dnn-select.tsx @@ -39,13 +39,14 @@ export class DnnSelect { /** Fires when the value has changed and the user exits the input. */ @Event() valueChange: EventEmitter; - /** Reports the element validity. - * @param valid - Whether the element is valid or not. - * @param message - The message to show when the element is invalid, optional if valid. - */ + /** Reports the input validity details. See https://developer.mozilla.org/en-US/docs/Web/API/ValidityState */ @Method() - async reportValidity(valid: boolean, message?: string) { - return this.fieldset.setValidity(valid, message); + async checkValidity() { + var validity = this.select.checkValidity(); + if (!validity) { + this.fieldset.setValidity(false, this.select.validationMessage); + } + return this.select.validity; } @AttachInternals() internals: ElementInternals; diff --git a/packages/stencil-library/src/components/dnn-select/readme.md b/packages/stencil-library/src/components/dnn-select/readme.md index 5349dcf80..51b0aa88f 100644 --- a/packages/stencil-library/src/components/dnn-select/readme.md +++ b/packages/stencil-library/src/components/dnn-select/readme.md @@ -27,20 +27,13 @@ ## Methods -### `reportValidity(valid: boolean, message?: string) => Promise` +### `checkValidity() => Promise` -Reports the element validity. - -#### Parameters - -| Name | Type | Description | -| --------- | --------- | --------------------------------------------------------------------- | -| `valid` | `boolean` | - Whether the element is valid or not. | -| `message` | `string` | - The message to show when the element is invalid, optional if valid. | +Reports the input validity details. See https://developer.mozilla.org/en-US/docs/Web/API/ValidityState #### Returns -Type: `Promise` +Type: `Promise` diff --git a/packages/stencil-library/src/components/examples/dnn-example-form/dnn-example-form.tsx b/packages/stencil-library/src/components/examples/dnn-example-form/dnn-example-form.tsx index d1510a9c2..b84711fb0 100644 --- a/packages/stencil-library/src/components/examples/dnn-example-form/dnn-example-form.tsx +++ b/packages/stencil-library/src/components/examples/dnn-example-form/dnn-example-form.tsx @@ -336,7 +336,7 @@ export class DnnExampleForm { {this.resume &&

File: {this.resume.name} - this.resume = undefined}>Remove + this.resume = undefined}>Remove

} @@ -352,7 +352,7 @@ export class DnnExampleForm { [ Profile Picture , - { this.profilePicData = undefined; diff --git a/packages/stencil-library/src/index.html b/packages/stencil-library/src/index.html index 94e650966..cfd480130 100644 --- a/packages/stencil-library/src/index.html +++ b/packages/stencil-library/src/index.html @@ -471,14 +471,14 @@

dnn-permissions-grid

dnn-button

Default - Primary - Secondary - Tertiary - Danger - Primary reversed - Secondary reversed - Tertiary reversed - Danger reversed + Primary + Secondary + Tertiary + Danger + Primary reversed + Secondary reversed + Tertiary reversed + Danger reversed Disabled Small Large diff --git a/packages/stencil-library/vscode-data.json b/packages/stencil-library/vscode-data.json index ba55445ab..c9916f07b 100644 --- a/packages/stencil-library/vscode-data.json +++ b/packages/stencil-library/vscode-data.json @@ -49,6 +49,24 @@ "value": "" }, "attributes": [ + { + "name": "appearance", + "description": "Defines the look of the button.", + "values": [ + { + "name": "danger" + }, + { + "name": "primary" + }, + { + "name": "secondary" + }, + { + "name": "tertiary" + } + ] + }, { "name": "confirm", "description": "Optionally add a confirmation dialog before firing the action." @@ -71,7 +89,7 @@ }, { "name": "form-button-type", - "description": "Optional button type,\ncan be either submit, reset or button and defaults to button if not specified.\nWarning: DNN wraps the whole page in a form, only use this if you are handling\nform submission manually.", + "description": "Optional button type,\ncan be either submit, reset or button and defaults to button if not specified.\nWarning: DNN wraps the whole page in a form, only use this if you are handling\nform submission manually.\nWarning: This will be deprecated in the next version and replaced with a new 'type' property.", "values": [ { "name": "button" @@ -105,7 +123,7 @@ }, { "name": "type", - "description": "Optional button style,\ncan be either primary, secondary or tertiary or danger and defaults to primary if not specified", + "description": "Optional button style,", "values": [ { "name": "danger"