From ade02b7e3c67266832470ea474023d7ae47d5ad4 Mon Sep 17 00:00:00 2001 From: Luke Vo Date: Tue, 3 Oct 2023 16:09:38 -0500 Subject: [PATCH] Added formDisabledCallback for formAssociated elements. --- button/internal/button.ts | 21 +++++++++++++++++- checkbox/internal/checkbox.ts | 27 +++++++++++++++++++---- iconbutton/internal/icon-button.ts | 25 ++++++++++++++++++--- radio/internal/radio.ts | 25 ++++++++++++++++++--- select/internal/select.ts | 27 +++++++++++++++++++---- slider/internal/slider.ts | 35 +++++++++++++++++++++++------- switch/internal/switch.ts | 25 ++++++++++++++++++--- textfield/internal/text-field.ts | 29 ++++++++++++++++++++----- 8 files changed, 183 insertions(+), 31 deletions(-) diff --git a/button/internal/button.ts b/button/internal/button.ts index bb7e2766ac..75d2dbf6c9 100644 --- a/button/internal/button.ts +++ b/button/internal/button.ts @@ -107,7 +107,7 @@ export abstract class Button extends LitElement implements FormSubmitter { protected override render() { // Link buttons may not be disabled - const isDisabled = this.disabled && !this.href; + const isDisabled = this.shouldBeDisabled && !this.href; const button = this.href ? literal`a` : literal`button`; // Needed for closure conformance @@ -164,4 +164,23 @@ export abstract class Button extends LitElement implements FormSubmitter { private handleSlotChange() { this.hasIcon = this.assignedIcons.length > 0; } + + /** + * Whether the element should be disabled either through its own `disabled` or + * its formDisabled. + */ + private get shouldBeDisabled() { + return this.disabled || this.formDisabled; + } + + /** + * Whether the element is currently disabled due to form state. + */ + private formDisabled = false; + + /** @private */ + formDisabledCallback(formDisabled: boolean) { + this.formDisabled = formDisabled; + this.requestUpdate(); + } } diff --git a/checkbox/internal/checkbox.ts b/checkbox/internal/checkbox.ts index f0911483ab..c2255dfc94 100644 --- a/checkbox/internal/checkbox.ts +++ b/checkbox/internal/checkbox.ts @@ -199,7 +199,7 @@ export class Checkbox extends LitElement { if (changed.has('checked') || changed.has('disabled') || changed.has('indeterminate')) { this.prevChecked = changed.get('checked') ?? this.checked; - this.prevDisabled = changed.get('disabled') ?? this.disabled; + this.prevDisabled = changed.get('disabled') ?? this.shouldBeDisabled; this.prevIndeterminate = changed.get('indeterminate') ?? this.indeterminate; } @@ -218,7 +218,7 @@ export class Checkbox extends LitElement { const isIndeterminate = this.indeterminate; const containerClasses = classMap({ - 'disabled': this.disabled, + 'disabled': this.shouldBeDisabled, 'selected': isChecked || isIndeterminate, 'unselected': !isChecked && !isIndeterminate, 'checked': isChecked, @@ -240,7 +240,7 @@ export class Checkbox extends LitElement { aria-checked=${isIndeterminate ? 'mixed' : nothing} aria-label=${ariaLabel || nothing} aria-invalid=${ariaInvalid || nothing} - ?disabled=${this.disabled} + ?disabled=${this.shouldBeDisabled} ?required=${this.required} .indeterminate=${this.indeterminate} .checked=${this.checked} @@ -250,7 +250,7 @@ export class Checkbox extends LitElement {
- + `; @@ -143,7 +143,7 @@ export class Radio extends LitElement { } private async handleClick(event: Event) { - if (this.disabled) { + if (this.shouldBeDisabled) { return; } @@ -185,4 +185,23 @@ export class Radio extends LitElement { formStateRestoreCallback(state: string) { this.checked = state === 'true'; } + + /** + * Whether the element should be disabled either through its own `disabled` or + * its formDisabled. + */ + private get shouldBeDisabled() { + return this.disabled || this.formDisabled; + } + + /** + * Whether the element is currently disabled due to form state. + */ + private formDisabled = false; + + /** @private */ + formDisabledCallback(formDisabled: boolean) { + this.formDisabled = formDisabled; + this.requestUpdate(); + } } diff --git a/select/internal/select.ts b/select/internal/select.ts index 937ec628a5..76870d95ce 100644 --- a/select/internal/select.ts +++ b/select/internal/select.ts @@ -414,7 +414,7 @@ export abstract class Select extends LitElement { private getRenderClasses() { return { - 'disabled': this.disabled, + 'disabled': this.shouldBeDisabled, 'error': this.error, 'open': this.open, }; @@ -428,7 +428,7 @@ export abstract class Select extends LitElement { role="combobox" part="field" id="field" - tabindex=${this.disabled ? '-1' : '0'} + tabindex=${this.shouldBeDisabled ? '-1' : '0'} aria-label=${(this as ARIAMixinStrict).ariaLabel || nothing} aria-describedby="description" aria-expanded=${this.open ? 'true' : nothing} @@ -437,7 +437,7 @@ export abstract class Select extends LitElement { label=${this.label} .focused=${this.focused || this.open} .populated=${!!this.displayText} - .disabled=${this.disabled} + .disabled=${this.shouldBeDisabled} .required=${this.required} .error=${this.hasError} ?has-start=${this.hasLeadingIcon} @@ -525,7 +525,7 @@ export abstract class Select extends LitElement { * is closed. */ private handleKeydown(event: KeyboardEvent) { - if (this.open || this.disabled || !this.menu) { + if (this.open || this.shouldBeDisabled || !this.menu) { return; } @@ -820,4 +820,23 @@ export abstract class Select extends LitElement { formStateRestoreCallback(state: string) { this.value = state; } + + /** + * Whether the element should be disabled either through its own `disabled` or + * its formDisabled. + */ + private get shouldBeDisabled() { + return this.disabled || this.formDisabled; + } + + /** + * Whether the element is currently disabled due to form state. + */ + private formDisabled = false; + + /** @private */ + formDisabledCallback(formDisabled: boolean) { + this.formDisabled = formDisabled; + this.requestUpdate(); + } } diff --git a/slider/internal/slider.ts b/slider/internal/slider.ts index 8c1d94f9a7..dcc952eaea 100644 --- a/slider/internal/slider.ts +++ b/slider/internal/slider.ts @@ -434,8 +434,8 @@ export class Slider extends LitElement { private renderHandle({start, hover, label}: {start: boolean, hover: boolean, label: string}) { - const onTop = !this.disabled && start === this.startOnTop; - const isOverlapping = !this.disabled && this.handlesOverlapping; + const onTop = !this.shouldBeDisabled && start === this.startOnTop; + const isOverlapping = !this.shouldBeDisabled && this.handlesOverlapping; const name = start ? 'start' : 'end'; return html`
+ this.shouldBeDisabled}>
`; } @@ -478,7 +478,7 @@ export class Slider extends LitElement { @input=${this.handleInput} @change=${this.handleChange} id=${name} - .disabled=${this.disabled} + .disabled=${this.shouldBeDisabled} .min=${String(this.min)} aria-valuemin=${ariaMin} .max=${String(this.max)} @@ -543,8 +543,8 @@ export class Slider extends LitElement { // Since handle moves to pointer on down and there may not be a move, // it needs to be considered hovered.. this.handleStartHover = - !this.disabled && isStart && Boolean(this.handleStart); - this.handleEndHover = !this.disabled && !isStart && Boolean(this.handleEnd); + !this.shouldBeDisabled && isStart && Boolean(this.handleStart); + this.handleEndHover = !this.shouldBeDisabled && !isStart && Boolean(this.handleEnd); } private async handleUp(event: PointerEvent) { @@ -583,8 +583,8 @@ export class Slider extends LitElement { * slider is updated. */ private handleMove(event: PointerEvent) { - this.handleStartHover = !this.disabled && inBounds(event, this.handleStart); - this.handleEndHover = !this.disabled && inBounds(event, this.handleEnd); + this.handleStartHover = !this.shouldBeDisabled && inBounds(event, this.handleStart); + this.handleEndHover = !this.shouldBeDisabled && inBounds(event, this.handleEnd); } private handleEnter(event: PointerEvent) { @@ -735,6 +735,25 @@ export class Slider extends LitElement { this.value = Number(state); this.range = false; } + + /** + * Whether the element should be disabled either through its own `disabled` or + * its formDisabled. + */ + private get shouldBeDisabled() { + return this.disabled || this.formDisabled; + } + + /** + * Whether the element is currently disabled due to form state. + */ + private formDisabled = false; + + /** @private */ + formDisabledCallback(formDisabled: boolean) { + this.formDisabled = formDisabled; + this.requestUpdate(); + } } function inBounds({x, y}: PointerEvent, element?: HTMLElement|null) { diff --git a/switch/internal/switch.ts b/switch/internal/switch.ts index 5f18dbe954..b4366bb3a5 100644 --- a/switch/internal/switch.ts +++ b/switch/internal/switch.ts @@ -216,7 +216,7 @@ export class Switch extends LitElement { role="switch" aria-label=${(this as ARIAMixin).ariaLabel || nothing} ?checked=${this.selected} - ?disabled=${this.disabled} + ?disabled=${this.shouldBeDisabled} ?required=${this.required} @change=${this.handleChange} > @@ -239,7 +239,7 @@ export class Switch extends LitElement { return { 'selected': this.selected, 'unselected': !this.selected, - 'disabled': this.disabled, + 'disabled': this.shouldBeDisabled, }; } @@ -250,7 +250,7 @@ export class Switch extends LitElement { return html` ${this.renderTouchTarget()} - + ${this.shouldShowIcons() ? this.renderIcons() : html``} @@ -345,4 +345,23 @@ export class Switch extends LitElement { formStateRestoreCallback(state: string) { this.selected = state === 'true'; } + + /** + * Whether the element should be disabled either through its own `disabled` or + * its formDisabled. + */ + private get shouldBeDisabled() { + return this.disabled || this.formDisabled; + } + + /** + * Whether the element is currently disabled due to form state. + */ + private formDisabled = false; + + /** @private */ + formDisabledCallback(formDisabled: boolean) { + this.formDisabled = formDisabled; + this.requestUpdate(); + } } diff --git a/textfield/internal/text-field.ts b/textfield/internal/text-field.ts index 5cb8b60b29..18ad1500a3 100644 --- a/textfield/internal/text-field.ts +++ b/textfield/internal/text-field.ts @@ -526,8 +526,8 @@ export abstract class TextField extends LitElement { protected override render() { const classes = { - 'disabled': this.disabled, - 'error': !this.disabled && this.hasError, + 'disabled': this.shouldBeDisabled, + 'error': !this.shouldBeDisabled && this.hasError, 'textarea': this.type === 'textarea', }; @@ -561,7 +561,7 @@ export abstract class TextField extends LitElement { return staticHtml`<${this.fieldTag} class="field" count=${this.value.length} - ?disabled=${this.disabled} + ?disabled=${this.shouldBeDisabled} ?error=${this.hasError} error-text=${this.getErrorText()} ?focused=${this.focused} @@ -614,7 +614,7 @@ export abstract class TextField extends LitElement { aria-invalid=${this.hasError} aria-label=${ariaLabel} autocomplete=${autocomplete || nothing} - ?disabled=${this.disabled} + ?disabled=${this.shouldBeDisabled} maxlength=${this.maxLength > -1 ? this.maxLength : nothing} minlength=${this.minLength > -1 ? this.minLength : nothing} placeholder=${this.placeholder || nothing} @@ -648,7 +648,7 @@ export abstract class TextField extends LitElement { aria-invalid=${this.hasError} aria-label=${ariaLabel} autocomplete=${autocomplete || nothing} - ?disabled=${this.disabled} + ?disabled=${this.shouldBeDisabled} inputmode=${inputMode || nothing} max=${(this.max || nothing) as unknown as number} maxlength=${this.maxLength > -1 ? this.maxLength : nothing} @@ -779,4 +779,23 @@ export abstract class TextField extends LitElement { formStateRestoreCallback(state: string) { this.value = state; } + + /** + * Whether the element should be disabled either through its own `disabled` or + * its formDisabled. + */ + private get shouldBeDisabled() { + return this.disabled || this.formDisabled; + } + + /** + * Whether the element is currently disabled due to form state. + */ + private formDisabled = false; + + /** @private */ + formDisabledCallback(formDisabled: boolean) { + this.formDisabled = formDisabled; + this.requestUpdate(); + } }