diff --git a/src/dev-app/input/BUILD.bazel b/src/dev-app/input/BUILD.bazel index 56ea279eb053..387aae9b04c7 100644 --- a/src/dev-app/input/BUILD.bazel +++ b/src/dev-app/input/BUILD.bazel @@ -21,6 +21,7 @@ ng_module( "//src/material/input", "//src/material/tabs", "//src/material/toolbar", + "//src/material/tooltip", "@npm//@angular/forms", ], ) diff --git a/src/dev-app/input/input-demo.html b/src/dev-app/input/input-demo.html index 0742fa5c891e..d41d02055512 100644 --- a/src/dev-app/input/input-demo.html +++ b/src/dev-app/input/input-demo.html @@ -58,6 +58,19 @@ + + Number Input + Tooltip + +
+ + Pump Flow + + ml/min + +
+
+
+ Error messages diff --git a/src/dev-app/input/input-demo.ts b/src/dev-app/input/input-demo.ts index fbe2668b1d1a..36caf31c80e0 100644 --- a/src/dev-app/input/input-demo.ts +++ b/src/dev-app/input/input-demo.ts @@ -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; @@ -55,6 +56,7 @@ const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA FormFieldCustomControlExample, MyTelInput, ReactiveFormsModule, + MatTooltipModule, ], }) export class InputDemo { diff --git a/src/material/input/input.ts b/src/material/input/input.ts index b97ef537734f..ad0a96b58a3f 100644 --- a/src/material/input/input.ts +++ b/src/material/input/input.ts @@ -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; @@ -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'; @@ -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, @@ -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); }); } @@ -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() { @@ -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; + } + } } diff --git a/tools/public_api_guard/material/input.md b/tools/public_api_guard/material/input.md index 3f7c5abab3bf..36ff273b47a2 100644 --- a/tools/public_api_guard/material/input.md +++ b/tools/public_api_guard/material/input.md @@ -47,7 +47,7 @@ export { MatHint } // @public (undocumented) export class MatInput implements MatFormFieldControl, OnChanges, OnDestroy, AfterViewInit, DoCheck { - constructor(_elementRef: ElementRef, _platform: Platform, ngControl: NgControl, parentForm: NgForm, parentFormGroup: FormGroupDirective, defaultErrorStateMatcher: ErrorStateMatcher, inputValueAccessor: any, _autofillMonitor: AutofillMonitor, ngZone: NgZone, _formField?: MatFormField | undefined); + constructor(_elementRef: ElementRef, _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;