Skip to content

fix(material/input): Number input not changing on wheel interaction #29449

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

Merged
merged 1 commit into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading