-
Notifications
You must be signed in to change notification settings - Fork 80
feat(text-area): add support for minLength
#10970
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
7e7a588
d0bd9b4
dc0b7ed
42ba789
40ed428
079d1dd
246993c
6b4d1f4
6608343
7a490d3
ac7be20
8da4580
2bcdf3d
42b7b79
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
{ | ||
"invalid": "Invalid", | ||
"tooLong": "The current character length is {currentLength}, which exceeds the maximum character length of {maxLength}." | ||
"tooLong": "The current character length is {currentLength}, which exceeds the maximum character length of {maxLength}.", | ||
"tooShort": "The current character length is {currentLength}, which is less than the minimum character length of {minLength}." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please refrain from using this message string in component code while translations are pending to avoid errors for non-en locales. Currently, we don't provide fallback values for new strings added in existing bundles. Fallback is used only for new components/ new bundles. Once translations are back one can access the cc @jcfranco There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @anveshmekala Can you create an issue for us to explore an option for us to use messages regardless of translation status? |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
{ | ||
"invalid": "Invalid", | ||
"tooLong": "The current character length is {currentLength}, which exceeds the maximum character length of {maxLength}." | ||
"tooLong": "The current character length is {currentLength}, which exceeds the maximum character length of {maxLength}.", | ||
"tooShort": "The current character length is {currentLength}, which is less than the minimum character length of {minLength}." | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
export interface CharacterLengthObj { | ||
currentLength: string; | ||
maxLength: string; | ||
minLength: string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -161,7 +161,7 @@ export class TextArea | |
@property() label: string; | ||
|
||
/** | ||
* Specifies the maximum number of characters allowed. | ||
* Specifies the maximum number of characters allowed. Must be greater than or equal to the value of `minLength`, if present and valid. | ||
* | ||
* @mdn [maxlength](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-maxlength) | ||
*/ | ||
|
@@ -178,7 +178,7 @@ export class TextArea | |
messages = useT9n<typeof T9nStrings>({ blocking: true }); | ||
|
||
/** | ||
* Specifies the minimum number of characters allowed. | ||
* Specifies the minimum number of characters allowed. Must be less than or equal to the value of `maxLength`, if present and valid. | ||
* | ||
* @mdn [minlength](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-minlength) | ||
*/ | ||
|
@@ -360,9 +360,10 @@ export class TextArea | |
|
||
private getLocalizedCharacterLength(): CharacterLengthObj { | ||
const currentLength = this.value ? this.value.length.toString() : "0"; | ||
const maxLength = this.maxLength.toString(); | ||
const maxLength = this.maxLength?.toString(); | ||
const minLength = this.minLength?.toString(); | ||
if (this.numberingSystem === "latn") { | ||
return { currentLength, maxLength }; | ||
return { currentLength, maxLength, minLength }; | ||
} | ||
|
||
numberStringFormatter.numberFormatOptions = { | ||
|
@@ -374,13 +375,16 @@ export class TextArea | |
return { | ||
currentLength: numberStringFormatter.localize(currentLength), | ||
maxLength: numberStringFormatter.localize(maxLength), | ||
minLength: numberStringFormatter.localize(minLength), | ||
}; | ||
} | ||
|
||
syncHiddenFormInput(input: HTMLInputElement): void { | ||
input.setCustomValidity(""); | ||
if (this.isCharacterLimitExceeded()) { | ||
input.setCustomValidity(this.replacePlaceholdersInMessages()); | ||
if (this.isCharacterOverMaxLimit()) { | ||
input.setCustomValidity(this.replacePlaceholdersInLongMessages()); | ||
} else if (this.isCharacterUnderMinLimit()) { | ||
input.setCustomValidity(this.replacePlaceholdersInShortMessages()); | ||
} | ||
|
||
syncHiddenFormInput("textarea", this, input); | ||
|
@@ -426,34 +430,61 @@ export class TextArea | |
}; | ||
} | ||
|
||
private replacePlaceholdersInMessages(): string { | ||
return this.messages.tooLong | ||
.replace("{maxLength}", this.localizedCharacterLengthObj.maxLength) | ||
.replace("{currentLength}", this.localizedCharacterLengthObj.currentLength); | ||
private replacePlaceholders( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: can we retain the name |
||
template: string, | ||
placeholderToValueObject: { [key: string]: string }, | ||
): string { | ||
let result; | ||
for (const key in placeholderToValueObject) { | ||
result = template.replace(key, placeholderToValueObject[key]); | ||
} | ||
return result; | ||
} | ||
|
||
private replacePlaceholdersInLongMessages(): string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: can we use a different name here to be more meaningful.
|
||
return this.replacePlaceholders(this.messages.tooLong, { | ||
"{currentLength}": this.localizedCharacterLengthObj.currentLength, | ||
"{maxLength}": this.localizedCharacterLengthObj.maxLength, | ||
}); | ||
} | ||
|
||
private isCharacterLimitExceeded(): boolean { | ||
private replacePlaceholdersInShortMessages(): string { | ||
Elijbet marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: can we use a different name here to be more meaningful. replaceMinLengthPlaceHolderMessage( ) or getMinLengthMessage( ). |
||
return this.replacePlaceholders(this.messages.tooShort, { | ||
"{currentLength}": this.localizedCharacterLengthObj.currentLength, | ||
"{minLength}": this.localizedCharacterLengthObj.minLength, | ||
}); | ||
} | ||
|
||
private isCharacterOverMaxLimit(): boolean { | ||
return this.value?.length > this.maxLength; | ||
} | ||
|
||
private isCharacterUnderMinLimit(): boolean { | ||
return this.value?.length < this.minLength; | ||
} | ||
|
||
// #endregion | ||
|
||
// #region Rendering | ||
|
||
override render(): JsxNode { | ||
const hasFooter = this.startSlotHasElements || this.endSlotHasElements || !!this.maxLength; | ||
const hasFooter = | ||
this.startSlotHasElements || this.endSlotHasElements || !!this.maxLength || !!this.minLength; | ||
const isOverMaxLimit = this.isCharacterOverMaxLimit(); | ||
const isUnderMinLimit = this.isCharacterUnderMinLimit(); | ||
|
||
return ( | ||
<InteractiveContainer disabled={this.disabled}> | ||
<textarea | ||
aria-describedby={this.guid} | ||
aria-errormessage={IDS.validationMessage} | ||
ariaInvalid={this.status === "invalid" || this.isCharacterLimitExceeded()} | ||
ariaInvalid={this.status === "invalid" || isOverMaxLimit || isUnderMinLimit} | ||
ariaLabel={getLabelText(this)} | ||
autofocus={this.el.autofocus} | ||
class={{ | ||
[CSS.textArea]: true, | ||
[CSS.readOnly]: this.readOnly, | ||
[CSS.textAreaInvalid]: this.isCharacterLimitExceeded(), | ||
[CSS.textAreaInvalid]: isOverMaxLimit || isUnderMinLimit, | ||
[CSS.footerSlotted]: this.endSlotHasElements && this.startSlotHasElements, | ||
[CSS.textAreaOnly]: !hasFooter, | ||
}} | ||
|
@@ -504,9 +535,14 @@ export class TextArea | |
{this.renderCharacterLimit()} | ||
</footer> | ||
<HiddenFormInputSlot component={this} /> | ||
{this.isCharacterLimitExceeded() && ( | ||
{isOverMaxLimit && ( | ||
<span ariaLive="polite" class={CSS.assistiveText} id={this.guid}> | ||
{this.replacePlaceholdersInLongMessages()} | ||
</span> | ||
)} | ||
{isUnderMinLimit && ( | ||
<span ariaLive="polite" class={CSS.assistiveText} id={this.guid}> | ||
{this.replacePlaceholdersInMessages()} | ||
{this.replacePlaceholdersInShortMessages()} | ||
</span> | ||
)} | ||
{this.validationMessage && this.status === "invalid" ? ( | ||
|
@@ -523,11 +559,16 @@ export class TextArea | |
} | ||
|
||
private renderCharacterLimit(): JsxNode | null { | ||
if (this.maxLength) { | ||
if (this.maxLength || this.minLength) { | ||
this.localizedCharacterLengthObj = this.getLocalizedCharacterLength(); | ||
return ( | ||
<span class={CSS.characterLimit}> | ||
<span class={{ [CSS.characterOverLimit]: this.isCharacterLimitExceeded() }}> | ||
<span | ||
class={{ | ||
[CSS.characterLimitInvalid]: | ||
this.isCharacterOverMaxLimit() || this.isCharacterUnderMinLimit(), | ||
}} | ||
> | ||
{this.localizedCharacterLengthObj.currentLength} | ||
</span> | ||
{"/"} | ||
|
Uh oh!
There was an error while loading. Please reload this page.