Skip to content

Commit 9a36e40

Browse files
authored
feat(gcds-textarea): Add native HTML attributes and validation (#917)
* feat(gcds-textarea): Add native HTML attributes and validation * Convert gcds-input to use shared formatHTMLErrorMessage function * Add tests for formatHTMLErrorMessage * Delete extra lines
1 parent 602ba75 commit 9a36e40

File tree

14 files changed

+676
-81
lines changed

14 files changed

+676
-81
lines changed

packages/angular/src/lib/stencil-generated/components.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2188,26 +2188,34 @@ export declare interface GcdsText extends Components.GcdsText {}
21882188

21892189

21902190
@ProxyCmp({
2191-
inputs: ['characterCount', 'cols', 'disabled', 'errorMessage', 'hideLabel', 'hint', 'label', 'name', 'required', 'rows', 'textareaId', 'validateOn', 'validator', 'value'],
2192-
methods: ['validate'],
2191+
inputs: ['autofocus', 'characterCount', 'cols', 'disabled', 'errorMessage', 'hideLabel', 'hint', 'label', 'minlength', 'name', 'required', 'rows', 'textareaId', 'validateOn', 'validator', 'validity', 'value'],
2192+
methods: ['validate', 'checkValidity', 'getValidationMessage'],
21932193
outputs: ['gcdsFocus', 'gcdsBlur', 'gcdsChange', 'gcdsInput', 'gcdsError', 'gcdsValid']
21942194
})
21952195
@Component({
21962196
selector: 'gcds-textarea',
21972197
changeDetection: ChangeDetectionStrategy.OnPush,
21982198
template: '<ng-content></ng-content>',
21992199
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2200-
inputs: ['characterCount', 'cols', 'disabled', 'errorMessage', 'hideLabel', 'hint', 'label', 'name', 'required', 'rows', 'textareaId', 'validateOn', 'validator', 'value'],
2200+
inputs: ['autofocus', 'characterCount', 'cols', 'disabled', 'errorMessage', 'hideLabel', 'hint', 'label', 'minlength', 'name', 'required', 'rows', 'textareaId', 'validateOn', 'validator', 'validity', 'value'],
22012201
outputs: ['gcdsFocus', 'gcdsBlur', 'gcdsChange', 'gcdsInput', 'gcdsError', 'gcdsValid'],
22022202
standalone: false,
22032203
})
22042204
export class GcdsTextarea {
22052205
protected el: HTMLGcdsTextareaElement;
22062206
/**
2207+
* If true, the input will be focused on component render
2208+
*/
2209+
set autofocus(_: Components.GcdsTextarea['autofocus']) {};
2210+
/**
22072211
* Sets the maxlength attribute for the textarea element.
22082212
*/
22092213
set characterCount(_: Components.GcdsTextarea['characterCount']) {};
22102214
/**
2215+
* The minimum number of characters that the input field can accept.
2216+
*/
2217+
set minlength(_: Components.GcdsTextarea['minlength']) {};
2218+
/**
22112219
* Defines width for textarea cols (the min-width for textarea's is 50%).
22122220
*/
22132221
set cols(_: Components.GcdsTextarea['cols']) {};
@@ -2259,6 +2267,10 @@ export class GcdsTextarea {
22592267
* Set event to call validator @default 'blur'
22602268
*/
22612269
set validateOn(_: Components.GcdsTextarea['validateOn']) {};
2270+
/**
2271+
* Read-only property of the textarea, returns a ValidityState object that represents the validity states this element is in. @readonly
2272+
*/
2273+
set validity(_: Components.GcdsTextarea['validity']) {};
22622274
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
22632275
c.detach();
22642276
this.el = r.nativeElement;

packages/vue/lib/components.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,9 @@ export const GcdsText: StencilVueComponent<JSX.GcdsText> = /*@__PURE__*/ defineC
604604

605605

606606
export const GcdsTextarea: StencilVueComponent<JSX.GcdsTextarea, JSX.GcdsTextarea["value"]> = /*@__PURE__*/ defineContainer<JSX.GcdsTextarea, JSX.GcdsTextarea["value"]>('gcds-textarea', defineGcdsTextarea, [
607+
'autofocus',
607608
'characterCount',
609+
'minlength',
608610
'cols',
609611
'disabled',
610612
'errorMessage',
@@ -618,6 +620,7 @@ export const GcdsTextarea: StencilVueComponent<JSX.GcdsTextarea, JSX.GcdsTextare
618620
'value',
619621
'validator',
620622
'validateOn',
623+
'validity',
621624
'gcdsFocus',
622625
'gcdsBlur',
623626
'gcdsChange',

packages/web/src/components.d.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,10 +1267,18 @@ export namespace Components {
12671267
* A text area is a space to enter long-form information in response to a question or instruction.
12681268
*/
12691269
interface GcdsTextarea {
1270+
/**
1271+
* If true, the input will be focused on component render
1272+
*/
1273+
"autofocus": boolean;
12701274
/**
12711275
* Sets the maxlength attribute for the textarea element.
12721276
*/
12731277
"characterCount"?: number;
1278+
/**
1279+
* Check the validity of gcds-textarea
1280+
*/
1281+
"checkValidity": () => Promise<boolean>;
12741282
/**
12751283
* Defines width for textarea cols (the min-width for textarea's is 50%).
12761284
*/
@@ -1284,6 +1292,10 @@ export namespace Components {
12841292
* Error message for an invalid textarea element.
12851293
*/
12861294
"errorMessage"?: string;
1295+
/**
1296+
* Get validationMessage of gcds-textarea
1297+
*/
1298+
"getValidationMessage": () => Promise<string>;
12871299
/**
12881300
* Specifies if the label is hidden or not.
12891301
* @default false
@@ -1297,6 +1309,10 @@ export namespace Components {
12971309
* Form field label
12981310
*/
12991311
"label": string;
1312+
/**
1313+
* The minimum number of characters that the input field can accept.
1314+
*/
1315+
"minlength"?: number;
13001316
/**
13011317
* Name attribute for a textarea element.
13021318
*/
@@ -1330,6 +1346,11 @@ export namespace Components {
13301346
"validator": Array<
13311347
string | ValidatorEntry | Validator<string>
13321348
>;
1349+
/**
1350+
* Read-only property of the textarea, returns a ValidityState object that represents the validity states this element is in.
1351+
* @readonly
1352+
*/
1353+
"validity": ValidityState;
13331354
/**
13341355
* Default value for an input element.
13351356
*/
@@ -3717,6 +3738,10 @@ declare namespace LocalJSX {
37173738
* A text area is a space to enter long-form information in response to a question or instruction.
37183739
*/
37193740
interface GcdsTextarea {
3741+
/**
3742+
* If true, the input will be focused on component render
3743+
*/
3744+
"autofocus"?: boolean;
37203745
/**
37213746
* Sets the maxlength attribute for the textarea element.
37223747
*/
@@ -3747,6 +3772,10 @@ declare namespace LocalJSX {
37473772
* Form field label
37483773
*/
37493774
"label": string;
3775+
/**
3776+
* The minimum number of characters that the input field can accept.
3777+
*/
3778+
"minlength"?: number;
37503779
/**
37513780
* Name attribute for a textarea element.
37523781
*/
@@ -3800,6 +3829,11 @@ declare namespace LocalJSX {
38003829
"validator"?: Array<
38013830
string | ValidatorEntry | Validator<string>
38023831
>;
3832+
/**
3833+
* Read-only property of the textarea, returns a ValidityState object that represents the validity states this element is in.
3834+
* @readonly
3835+
*/
3836+
"validity"?: ValidityState;
38033837
/**
38043838
* Default value for an input element.
38053839
*/

packages/web/src/components/gcds-input/gcds-input.tsx

Lines changed: 15 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
handleValidationResult,
1818
inheritAttributes,
1919
observerConfig,
20+
formatHTMLErrorMessage,
2021
} from '../../utils/utils';
2122
import {
2223
Validator,
@@ -27,8 +28,6 @@ import {
2728
ValidatorOld,
2829
} from '../../validators';
2930

30-
import I18N from './i18n/i18n.js';
31-
3231
/**
3332
* An input is a space to enter short-form information in response to a question or instruction.
3433
*/
@@ -320,7 +319,11 @@ export class GcdsInput {
320319
!this.internals.checkValidity()
321320
) {
322321
if (!this.internals.validity.valueMissing) {
323-
this.errorMessage = this.formatHTMLErrorMessage();
322+
this.errorMessage = formatHTMLErrorMessage(
323+
this.htmlValidationErrors[0],
324+
this.lang,
325+
this.el,
326+
);
324327
this.inputTitle = this.errorMessage;
325328
}
326329
}
@@ -415,10 +418,14 @@ export class GcdsInput {
415418
? { ...this.shadowElement.validity, ...override }
416419
: this.shadowElement.validity;
417420

418-
const validationMessage =
419-
this.htmlValidationErrors.length > 0
420-
? this.formatHTMLErrorMessage()
421-
: null;
421+
let validationMessage = null;
422+
if (this.htmlValidationErrors.length > 0) {
423+
validationMessage = formatHTMLErrorMessage(
424+
this.htmlValidationErrors[0],
425+
this.lang,
426+
this.el,
427+
);
428+
}
422429

423430
this.internals.setValidity(
424431
validityState,
@@ -427,60 +434,7 @@ export class GcdsInput {
427434
);
428435

429436
// Set input title when HTML error occruring
430-
this.inputTitle =
431-
this.htmlValidationErrors.length > 0 ? this.formatHTMLErrorMessage() : '';
432-
}
433-
434-
/**
435-
* Format HTML error message based off assigned attributes
436-
* This lets us assign custom error messages
437-
*/
438-
private formatHTMLErrorMessage() {
439-
switch (this.htmlValidationErrors[0]) {
440-
case 'valueMissing':
441-
return I18N[this.lang][this.htmlValidationErrors[0]];
442-
case 'typeMismatch':
443-
if (this.type === 'url' || this.type === 'email') {
444-
return I18N[this.lang][this.htmlValidationErrors[0]][this.type];
445-
} else {
446-
return I18N[this.lang][this.htmlValidationErrors[0]];
447-
}
448-
case 'tooLong':
449-
return I18N[this.lang][this.htmlValidationErrors[0]]
450-
.replace('{max}', this.maxlength)
451-
.replace('{current}', this.value.length);
452-
case 'tooShort':
453-
return I18N[this.lang][this.htmlValidationErrors[0]]
454-
.replace('{min}', this.minlength)
455-
.replace('{current}', this.value.length);
456-
case 'rangeUnderflow':
457-
return I18N[this.lang][this.htmlValidationErrors[0]].replace(
458-
'{min}',
459-
this.min,
460-
);
461-
case 'rangeOverflow':
462-
return I18N[this.lang][this.htmlValidationErrors[0]].replace(
463-
'{max}',
464-
this.max,
465-
);
466-
case 'stepMismatch':
467-
return I18N[this.lang][this.htmlValidationErrors[0]]
468-
.replace(
469-
'{lower}',
470-
Math.floor(Number(this.value) / Number(this.step)) *
471-
Number(this.step),
472-
)
473-
.replace(
474-
'{upper}',
475-
Math.floor(Number(this.value) / Number(this.step)) *
476-
Number(this.step) +
477-
Number(this.step),
478-
);
479-
case 'badInput':
480-
case 'patternMismatch':
481-
default:
482-
return I18N[this.lang][this.htmlValidationErrors[0]];
483-
}
437+
this.inputTitle = validationMessage;
484438
}
485439

486440
/*

packages/web/src/components/gcds-input/test/gcds-input.e2e.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const { AxeBuilder } = require('@axe-core/playwright');
33
import { expect } from '@playwright/test';
44
import { test } from '../../../../tests/base';
55

6-
import I18N from '../i18n/i18n.js';
6+
import I18N from '../../../utils/i18n/i18n.js';
77

88
test.describe('gcds-input', () => {
99
test('renders', async ({ page }) => {

0 commit comments

Comments
 (0)