Skip to content

Commit ced9c90

Browse files
authored
feat(cdk-input): move input autofill and autosize utils into cdk (#9831)
* move autofill to cdk * move textarea autosize into cdk * fix bazel build * rename input to text-field * set resize back to none for autosize in input css * fix tsconfig
1 parent 54252d5 commit ced9c90

37 files changed

+563
-403
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
/src/cdk/stepper/** @mmalerba
7373
/src/cdk/table/** @andrewseguin
7474
/src/cdk/testing/** @devversion
75+
/src/cdk/text-field/** @mmalerba
7576
/src/cdk/tree/** @tinayuangao
7677

7778
# Moment adapter package

src/cdk/text-field/BUILD.bazel

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package(default_visibility=["//visibility:public"])
2+
load("@angular//:index.bzl", "ng_module")
3+
load("@io_bazel_rules_sass//sass:sass.bzl", "sass_library", "sass_binary")
4+
5+
ng_module(
6+
name = "text-field",
7+
srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]),
8+
module_name = "@angular/cdk/text-field",
9+
deps = [
10+
"@rxjs",
11+
"//src/cdk/platform",
12+
],
13+
tsconfig = ":tsconfig-build.json",
14+
)
15+
16+
sass_library(
17+
name = "text_field_scss_lib",
18+
srcs = glob(["**/_*.scss"]),
19+
)
20+
21+
sass_binary(
22+
name = "text_field_prebuilt_scss",
23+
src = "text-field-prebuilt.scss",
24+
deps = [":text_field_scss_lib"],
25+
)
26+
27+
# TODO(jelbourn): remove this when sass_binary supports specifying an output filename and dir.
28+
# Copy the output of the sass_binary such that the filename and path match what we expect.
29+
genrule(
30+
name = "text_field_prebuilt_css",
31+
srcs = [":text_field_prebuilt_scss"],
32+
outs = ["text-field-prebuilt.css"],
33+
cmd = "cat $(locations :text_field_prebuilt_scss) > $@",
34+
)

src/cdk/text-field/_text-field.scss

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Core styles that enable monitoring autofill state of text fields.
2+
@mixin cdk-text-field {
3+
// Keyframes that apply no styles, but allow us to monitor when an text field becomes autofilled
4+
// by watching for the animation events that are fired when they start.
5+
// Based on: https://medium.com/@brunn/detecting-autofilled-fields-in-javascript-aed598d25da7
6+
@keyframes cdk-text-field-autofill-start {}
7+
@keyframes cdk-text-field-autofill-end {}
8+
9+
.cdk-text-field-autofill-monitored:-webkit-autofill {
10+
animation-name: cdk-text-field-autofill-start;
11+
}
12+
13+
.cdk-text-field-autofill-monitored:not(:-webkit-autofill) {
14+
animation-name: cdk-text-field-autofill-end;
15+
}
16+
17+
// Remove the resize handle on autosizing textareas, because whatever height
18+
// the user resized to will be overwritten once they start typing again.
19+
textarea.cdk-textarea-autosize {
20+
resize: none;
21+
}
22+
}
23+
24+
// Used to generate UIDs for keyframes used to change the text field autofill styles.
25+
$cdk-text-field-autofill-color-frame-count: 0;
26+
27+
// Mixin used to apply custom background and foreground colors to an autofilled text field.
28+
// Based on: https://stackoverflow.com/questions/2781549/
29+
// removing-input-background-colour-for-chrome-autocomplete#answer-37432260
30+
@mixin cdk-text-field-autofill-color($background, $foreground:'') {
31+
@keyframes cdk-text-field-autofill-color-#{$cdk-text-field-autofill-color-frame-count} {
32+
to {
33+
background: $background;
34+
@if $foreground != '' { color: $foreground; }
35+
}
36+
}
37+
38+
&:-webkit-autofill {
39+
animation-name: cdk-text-field-autofill-color-#{$cdk-text-field-autofill-color-frame-count};
40+
animation-fill-mode: both;
41+
}
42+
43+
&.cdk-text-field-autofill-monitored:-webkit-autofill {
44+
animation-name: cdk-text-field-autofill-start,
45+
cdk-text-field-autofill-color-#{$cdk-text-field-autofill-color-frame-count};
46+
}
47+
48+
$cdk-text-field-autofill-color-frame-count:
49+
$cdk-text-field-autofill-color-frame-count + 1 !global;
50+
}

src/lib/input/autofill.spec.ts renamed to src/cdk/text-field/autofill.spec.ts

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {Component, ElementRef, ViewChild} from '@angular/core';
1111
import {ComponentFixture, inject, TestBed} from '@angular/core/testing';
1212
import {empty as observableEmpty} from 'rxjs/observable/empty';
1313
import {AutofillEvent, AutofillMonitor} from './autofill';
14-
import {MatInputModule} from './input-module';
14+
import {TextFieldModule} from './text-field-module';
1515

1616

1717
const listenerOptions: any = supportsPassiveEventListeners() ? {passive: true} : false;
@@ -24,7 +24,7 @@ describe('AutofillMonitor', () => {
2424

2525
beforeEach(() => {
2626
TestBed.configureTestingModule({
27-
imports: [MatInputModule],
27+
imports: [TextFieldModule],
2828
declarations: [Inputs],
2929
}).compileComponents();
3030
});
@@ -52,7 +52,7 @@ describe('AutofillMonitor', () => {
5252
expect(inputEl.addEventListener).not.toHaveBeenCalled();
5353

5454
autofillMonitor.monitor(inputEl);
55-
expect(inputEl.classList).toContain('mat-input-autofill-monitored');
55+
expect(inputEl.classList).toContain('cdk-text-field-autofill-monitored');
5656
expect(inputEl.addEventListener)
5757
.toHaveBeenCalledWith('animationstart', jasmine.any(Function), listenerOptions);
5858
});
@@ -69,11 +69,11 @@ describe('AutofillMonitor', () => {
6969
it('should remove monitored class and listener upon stop monitoring', () => {
7070
const inputEl = testComponent.input1.nativeElement;
7171
autofillMonitor.monitor(inputEl);
72-
expect(inputEl.classList).toContain('mat-input-autofill-monitored');
72+
expect(inputEl.classList).toContain('cdk-text-field-autofill-monitored');
7373
expect(inputEl.removeEventListener).not.toHaveBeenCalled();
7474

7575
autofillMonitor.stopMonitoring(inputEl);
76-
expect(inputEl.classList).not.toContain('mat-input-autofill-monitored');
76+
expect(inputEl.classList).not.toContain('cdk-text-field-autofill-monitored');
7777
expect(inputEl.removeEventListener)
7878
.toHaveBeenCalledWith('animationstart', jasmine.any(Function), listenerOptions);
7979
});
@@ -103,10 +103,10 @@ describe('AutofillMonitor', () => {
103103
const autofillStream = autofillMonitor.monitor(inputEl);
104104
autofillStream.subscribe(event => autofillStreamEvent = event);
105105
expect(autofillStreamEvent).toBeNull();
106-
expect(inputEl.classList).not.toContain('mat-input-autofilled');
106+
expect(inputEl.classList).not.toContain('cdk-text-field-autofilled');
107107

108-
animationStartCallback({animationName: 'mat-input-autofill-start', target: inputEl});
109-
expect(inputEl.classList).toContain('mat-input-autofilled');
108+
animationStartCallback({animationName: 'cdk-text-field-autofill-start', target: inputEl});
109+
expect(inputEl.classList).toContain('cdk-text-field-autofilled');
110110
expect(autofillStreamEvent).toEqual({target: inputEl, isAutofilled: true} as any);
111111
});
112112

@@ -117,12 +117,12 @@ describe('AutofillMonitor', () => {
117117
inputEl.addEventListener.and.callFake((_, cb) => animationStartCallback = cb);
118118
const autofillStream = autofillMonitor.monitor(inputEl);
119119
autofillStream.subscribe(event => autofillStreamEvent = event);
120-
animationStartCallback({animationName: 'mat-input-autofill-start', target: inputEl});
121-
expect(inputEl.classList).toContain('mat-input-autofilled');
120+
animationStartCallback({animationName: 'cdk-text-field-autofill-start', target: inputEl});
121+
expect(inputEl.classList).toContain('cdk-text-field-autofilled');
122122
expect(autofillStreamEvent).toEqual({target: inputEl, isAutofilled: true} as any);
123123

124-
animationStartCallback({animationName: 'mat-input-autofill-end', target: inputEl});
125-
expect(inputEl.classList).not.toContain('mat-input-autofilled');
124+
animationStartCallback({animationName: 'cdk-text-field-autofill-end', target: inputEl});
125+
expect(inputEl.classList).not.toContain('cdk-text-field-autofilled');
126126
expect(autofillStreamEvent).toEqual({target: inputEl, isAutofilled: false} as any);
127127
});
128128

@@ -131,11 +131,11 @@ describe('AutofillMonitor', () => {
131131
let animationStartCallback: Function = () => {};
132132
inputEl.addEventListener.and.callFake((_, cb) => animationStartCallback = cb);
133133
autofillMonitor.monitor(inputEl);
134-
animationStartCallback({animationName: 'mat-input-autofill-start', target: inputEl});
135-
expect(inputEl.classList).toContain('mat-input-autofilled');
134+
animationStartCallback({animationName: 'cdk-text-field-autofill-start', target: inputEl});
135+
expect(inputEl.classList).toContain('cdk-text-field-autofilled');
136136

137137
autofillMonitor.stopMonitoring(inputEl);
138-
expect(inputEl.classlist).not.toContain('mat-input-autofilled');
138+
expect(inputEl.classlist).not.toContain('cdk-text-field-autofilled');
139139
});
140140

141141
it('should complete the stream when monitoring is stopped', () => {
@@ -152,23 +152,23 @@ describe('AutofillMonitor', () => {
152152

153153
});
154154

155-
describe('matAutofill', () => {
155+
describe('cdkAutofill', () => {
156156
let autofillMonitor: AutofillMonitor;
157-
let fixture: ComponentFixture<InputWithMatAutofilled>;
158-
let testComponent: InputWithMatAutofilled;
157+
let fixture: ComponentFixture<InputWithCdkAutofilled>;
158+
let testComponent: InputWithCdkAutofilled;
159159

160160
beforeEach(() => {
161161
TestBed.configureTestingModule({
162-
imports: [MatInputModule],
163-
declarations: [InputWithMatAutofilled],
162+
imports: [TextFieldModule],
163+
declarations: [InputWithCdkAutofilled],
164164
}).compileComponents();
165165
});
166166

167167
beforeEach(inject([AutofillMonitor], (afm: AutofillMonitor) => {
168168
autofillMonitor = afm;
169169
spyOn(autofillMonitor, 'monitor').and.returnValue(observableEmpty());
170170
spyOn(autofillMonitor, 'stopMonitoring');
171-
fixture = TestBed.createComponent(InputWithMatAutofilled);
171+
fixture = TestBed.createComponent(InputWithCdkAutofilled);
172172
testComponent = fixture.componentInstance;
173173
fixture.detectChanges();
174174
}));
@@ -198,8 +198,8 @@ class Inputs {
198198
}
199199

200200
@Component({
201-
template: `<input #input matAutofill>`
201+
template: `<input #input cdkAutofill>`
202202
})
203-
class InputWithMatAutofilled {
203+
class InputWithCdkAutofilled {
204204
@ViewChild('input') input: ElementRef;
205205
}

src/lib/input/autofill.ts renamed to src/cdk/text-field/autofill.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,17 @@ export class AutofillMonitor implements OnDestroy {
6969

7070
const result = new Subject<AutofillEvent>();
7171
const listener = (event: AnimationEvent) => {
72-
if (event.animationName === 'mat-input-autofill-start') {
73-
element.classList.add('mat-input-autofilled');
72+
if (event.animationName === 'cdk-text-field-autofill-start') {
73+
element.classList.add('cdk-text-field-autofilled');
7474
result.next({target: event.target as Element, isAutofilled: true});
75-
} else if (event.animationName === 'mat-input-autofill-end') {
76-
element.classList.remove('mat-input-autofilled');
75+
} else if (event.animationName === 'cdk-text-field-autofill-end') {
76+
element.classList.remove('cdk-text-field-autofilled');
7777
result.next({target: event.target as Element, isAutofilled: false});
7878
}
7979
};
8080

8181
element.addEventListener('animationstart', listener, listenerOptions);
82-
element.classList.add('mat-input-autofill-monitored');
82+
element.classList.add('cdk-text-field-autofill-monitored');
8383

8484
this._monitoredElements.set(element, {
8585
subject: result,
@@ -101,8 +101,8 @@ export class AutofillMonitor implements OnDestroy {
101101
if (info) {
102102
info.unlisten();
103103
info.subject.complete();
104-
element.classList.remove('mat-input-autofill-monitored');
105-
element.classList.remove('mat-input-autofilled');
104+
element.classList.remove('cdk-text-field-autofill-monitored');
105+
element.classList.remove('cdk-text-field-autofilled');
106106
this._monitoredElements.delete(element);
107107
}
108108
}
@@ -115,17 +115,17 @@ export class AutofillMonitor implements OnDestroy {
115115

116116
/** A directive that can be used to monitor the autofill state of an input. */
117117
@Directive({
118-
selector: '[matAutofill]',
118+
selector: '[cdkAutofill]',
119119
})
120-
export class MatAutofill implements OnDestroy, OnInit {
121-
@Output() matAutofill: EventEmitter<AutofillEvent> = new EventEmitter<AutofillEvent>();
120+
export class CdkAutofill implements OnDestroy, OnInit {
121+
@Output() cdkAutofill: EventEmitter<AutofillEvent> = new EventEmitter<AutofillEvent>();
122122

123123
constructor(private _elementRef: ElementRef, private _autofillMonitor: AutofillMonitor) {}
124124

125125
ngOnInit() {
126126
this._autofillMonitor
127127
.monitor(this._elementRef.nativeElement)
128-
.subscribe(event => this.matAutofill.emit(event));
128+
.subscribe(event => this.cdkAutofill.emit(event));
129129
}
130130

131131
ngOnDestroy() {

0 commit comments

Comments
 (0)