Skip to content

Commit 9973b90

Browse files
asyncLizcopybara-github
authored andcommitted
fix(textfield): counter showing when max length is 0 or removed
Fixes #4998 This also fixes an error being thrown in text field's validator when minlength/maxlength change to out of bounds if they're not set in the correct order. PiperOrigin-RevId: 594013553
1 parent 4ae9db6 commit 9973b90

File tree

4 files changed

+59
-10
lines changed

4 files changed

+59
-10
lines changed

field/internal/field.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,16 @@ export class Field extends LitElement {
4747
private readonly slottedAriaDescribedBy!: HTMLElement[];
4848

4949
private get counterText() {
50-
if (this.count < 0 || this.max < 0) {
50+
// Count and max are typed as number, but can be set to null when Lit removes
51+
// their attributes. These getters coerce back to a number for calculations.
52+
const countAsNumber = this.count ?? -1;
53+
const maxAsNumber = this.max ?? -1;
54+
// Counter does not show if count is negative, or max is negative or 0.
55+
if (countAsNumber < 0 || maxAsNumber <= 0) {
5156
return '';
5257
}
5358

54-
return `${this.count} / ${this.max}`;
59+
return `${countAsNumber} / ${maxAsNumber}`;
5560
}
5661

5762
private get supportingOrErrorText() {

labs/behaviors/validators/text-field-validator.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,22 @@ export class TextFieldValidator extends Validator<TextFieldState> {
181181
// Use -1 to represent no minlength and maxlength, which is what the
182182
// platform input returns. However, it will throw an error if you try to
183183
// manually set it to -1.
184-
if (state.minLength > -1) {
185-
inputOrTextArea.minLength = state.minLength;
184+
//
185+
// While the type is `number`, it may actually be `null` at runtime.
186+
// `null > -1` is true since `null` coerces to `0`, so we default null and
187+
// undefined to -1.
188+
//
189+
// We set attributes instead of properties since setting a property may
190+
// throw an out of bounds error in relation to the other property.
191+
// Attributes will not throw errors while the state is updating.
192+
if ((state.minLength ?? -1) > -1) {
193+
inputOrTextArea.setAttribute('minlength', String(state.minLength));
186194
} else {
187195
inputOrTextArea.removeAttribute('minlength');
188196
}
189197

190-
if (state.maxLength > -1) {
191-
inputOrTextArea.maxLength = state.maxLength;
198+
if ((state.maxLength ?? -1) > -1) {
199+
inputOrTextArea.setAttribute('maxlength', String(state.maxLength));
192200
} else {
193201
inputOrTextArea.removeAttribute('maxlength');
194202
}

labs/behaviors/validators/text-field-validator_test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,38 @@ describe('TextFieldValidator', () => {
8989
expect(validity.valueMissing).withContext('valueMissing').toBeFalse();
9090
expect(validationMessage).withContext('validationMessage').toBe('');
9191
});
92+
93+
it('does not throw an error when setting minlength and maxlength out of bounds', () => {
94+
type WritableInputState = {
95+
-readonly [K in keyof InputState]: InputState[K];
96+
};
97+
98+
const state: WritableInputState = {
99+
type: 'text',
100+
value: '',
101+
required: true,
102+
pattern: '',
103+
min: '',
104+
max: '',
105+
minLength: 5,
106+
maxLength: 10,
107+
step: '',
108+
};
109+
110+
const validator = new TextFieldValidator(() => ({
111+
state,
112+
renderedControl: null,
113+
}));
114+
115+
// Compute initial validity with valid minlength of 5 and maxlength of 10
116+
validator.getValidity();
117+
// set to something that is out of bounds of current maxlength="10"
118+
state.minLength = 20;
119+
120+
expect(() => {
121+
validator.getValidity();
122+
}).not.toThrow();
123+
});
92124
});
93125

94126
describe('type="email"', () => {

textfield/internal/text-field.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,10 @@ export abstract class TextField extends textFieldBaseClass {
585585
// tslint:disable-next-line:no-any
586586
const autocomplete = this.autocomplete as any;
587587

588+
// These properties may be set to null if the attribute is removed, and
589+
// `null > -1` is incorrectly `true`.
590+
const hasMaxLength = (this.maxLength ?? -1) > -1;
591+
const hasMinLength = (this.minLength ?? -1) > -1;
588592
if (this.type === 'textarea') {
589593
return html`
590594
<textarea
@@ -595,8 +599,8 @@ export abstract class TextField extends textFieldBaseClass {
595599
aria-label=${ariaLabel}
596600
autocomplete=${autocomplete || nothing}
597601
?disabled=${this.disabled}
598-
maxlength=${this.maxLength > -1 ? this.maxLength : nothing}
599-
minlength=${this.minLength > -1 ? this.minLength : nothing}
602+
maxlength=${hasMaxLength ? this.maxLength : nothing}
603+
minlength=${hasMinLength ? this.minLength : nothing}
600604
placeholder=${this.placeholder || nothing}
601605
?readonly=${this.readOnly}
602606
?required=${this.required}
@@ -631,9 +635,9 @@ export abstract class TextField extends textFieldBaseClass {
631635
?disabled=${this.disabled}
632636
inputmode=${inputMode || nothing}
633637
max=${(this.max || nothing) as unknown as number}
634-
maxlength=${this.maxLength > -1 ? this.maxLength : nothing}
638+
maxlength=${hasMaxLength ? this.maxLength : nothing}
635639
min=${(this.min || nothing) as unknown as number}
636-
minlength=${this.minLength > -1 ? this.minLength : nothing}
640+
minlength=${hasMinLength ? this.minLength : nothing}
637641
pattern=${this.pattern || nothing}
638642
placeholder=${this.placeholder || nothing}
639643
?readonly=${this.readOnly}

0 commit comments

Comments
 (0)