Skip to content

Commit

Permalink
fix(material/input): Number input not changing on wheel interaction (#…
Browse files Browse the repository at this point in the history
…29449)

In blink and webkit browsers the default behavior of increasing or decreasing a focused number input on wheel events is broken until a wheel event listener is explicitly added.

Fixes #29074
  • Loading branch information
ChristophWieske authored Jul 19, 2024
1 parent 174cf42 commit 10da6c6
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/dev-app/input/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ ng_module(
"//src/material/input",
"//src/material/tabs",
"//src/material/toolbar",
"//src/material/tooltip",
"@npm//@angular/forms",
],
)
Expand Down
13 changes: 13 additions & 0 deletions src/dev-app/input/input-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@
</mat-card-content>
</mat-card>

<mat-card class="demo-card demo-number-input-tooltip">
<mat-toolbar color="primary">Number Input + Tooltip</mat-toolbar>
<mat-card-content>
<form>
<mat-form-field>
<mat-label>Pump Flow</mat-label>
<input matNativeControl value="10" type="number"><!-- -->
<span matSuffix matTooltip="Milliliter per Minute">ml/min</span>
</mat-form-field>
</form>
</mat-card-content>
</mat-card>

<mat-card class="demo-card demo-basic">
<mat-toolbar color="primary">Error messages</mat-toolbar>
<mat-card-content>
Expand Down
2 changes: 2 additions & 0 deletions src/dev-app/input/input-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatIconModule} from '@angular/material/icon';
import {MatTabsModule} from '@angular/material/tabs';
import {MatToolbarModule} from '@angular/material/toolbar';
import {MatTooltipModule} from '@angular/material/tooltip';

let max = 5;

Expand Down Expand Up @@ -55,6 +56,7 @@ const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA
FormFieldCustomControlExample,
MyTelInput,
ReactiveFormsModule,
MatTooltipModule,
],
})
export class InputDemo {
Expand Down
43 changes: 41 additions & 2 deletions src/material/input/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export class MatInput
private _inputValueAccessor: {value: any};
private _previousPlaceholder: string | null;
private _errorStateTracker: _ErrorStateTracker;
private _webkitBlinkWheelListenerAttached = false;

/** Whether the component is being rendered on the server. */
readonly _isServer: boolean;
Expand Down Expand Up @@ -197,6 +198,8 @@ export class MatInput
if (!this._isTextarea && getSupportedInputTypes().has(this._type)) {
(this._elementRef.nativeElement as HTMLInputElement).type = this._type;
}

this._ensureWheelDefaultBehavior();
}
protected _type = 'text';

Expand Down Expand Up @@ -266,7 +269,7 @@ export class MatInput
defaultErrorStateMatcher: ErrorStateMatcher,
@Optional() @Self() @Inject(MAT_INPUT_VALUE_ACCESSOR) inputValueAccessor: any,
private _autofillMonitor: AutofillMonitor,
ngZone: NgZone,
private _ngZone: NgZone,
// TODO: Remove this once the legacy appearance has been removed. We only need
// to inject the form field for determining whether the placeholder has been promoted.
@Optional() @Inject(MAT_FORM_FIELD) protected _formField?: MatFormField,
Expand All @@ -287,7 +290,7 @@ export class MatInput
// key. In order to get around this we need to "jiggle" the caret loose. Since this bug only
// exists on iOS, we only bother to install the listener on iOS.
if (_platform.IOS) {
ngZone.runOutsideAngular(() => {
_ngZone.runOutsideAngular(() => {
_elementRef.nativeElement.addEventListener('keyup', this._iOSKeyupListener);
});
}
Expand Down Expand Up @@ -334,6 +337,10 @@ export class MatInput
if (this._platform.IOS) {
this._elementRef.nativeElement.removeEventListener('keyup', this._iOSKeyupListener);
}

if (this._webkitBlinkWheelListenerAttached) {
this._elementRef.nativeElement.removeEventListener('wheel', this._webkitBlinkWheelListener);
}
}

ngDoCheck() {
Expand Down Expand Up @@ -527,4 +534,36 @@ export class MatInput
el.setSelectionRange(0, 0);
}
};

private _webkitBlinkWheelListener = (): void => {
// This is a noop function and is used to enable mouse wheel input
// on number inputs
// on blink and webkit browsers.
};

/**
* In blink and webkit browsers a focused number input does not increment or decrement its value
* on mouse wheel interaction unless a wheel event listener is attached to it or one of its ancestors or a passive wheel listener is attached somewhere in the DOM.
* For example: Hitting a tooltip once enables the mouse wheel input for all number inputs as long as it exists.
* In order to get reliable and intuitive behavior we apply a wheel event on our own
* thus making sure increment and decrement by mouse wheel works every time.
* @docs-private
*/
private _ensureWheelDefaultBehavior(): void {
if (
!this._webkitBlinkWheelListenerAttached &&
this._type === 'number' &&
(this._platform.BLINK || this._platform.WEBKIT)
) {
this._ngZone.runOutsideAngular(() => {
this._elementRef.nativeElement.addEventListener('wheel', this._webkitBlinkWheelListener);
});
this._webkitBlinkWheelListenerAttached = true;
}

if (this._webkitBlinkWheelListenerAttached && this._type !== 'number') {
this._elementRef.nativeElement.removeEventListener('wheel', this._webkitBlinkWheelListener);
this._webkitBlinkWheelListenerAttached = true;
}
}
}
2 changes: 1 addition & 1 deletion tools/public_api_guard/material/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export { MatHint }

// @public (undocumented)
export class MatInput implements MatFormFieldControl<any>, OnChanges, OnDestroy, AfterViewInit, DoCheck {
constructor(_elementRef: ElementRef<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>, _platform: Platform, ngControl: NgControl, parentForm: NgForm, parentFormGroup: FormGroupDirective, defaultErrorStateMatcher: ErrorStateMatcher, inputValueAccessor: any, _autofillMonitor: AutofillMonitor, ngZone: NgZone, _formField?: MatFormField | undefined);
constructor(_elementRef: ElementRef<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>, _platform: Platform, ngControl: NgControl, parentForm: NgForm, parentFormGroup: FormGroupDirective, defaultErrorStateMatcher: ErrorStateMatcher, inputValueAccessor: any, _autofillMonitor: AutofillMonitor, _ngZone: NgZone, _formField?: MatFormField | undefined);
autofilled: boolean;
controlType: string;
protected _dirtyCheckNativeValue(): void;
Expand Down

0 comments on commit 10da6c6

Please sign in to comment.