-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
feat(textarea): add md-textarea element #1562
Conversation
Not sure why this particular test is failing, nothing I've done is affecting the name attribute.. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First pass of comments.
Unit tests should be pretty straightforward based on the existing tests; you should start with a few basic ones for the new attributes and then add tests for the autosize once we hammer out the behavior.
selector: 'textarea[mdAutosize]' | ||
}) | ||
export class MdAutosize implements OnInit { | ||
@HostListener('input', ['$event.target']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You shouldn't need the second argument to @HostListener
@@ -34,6 +35,38 @@ | |||
[(ngModel)]="value" | |||
(change)="_handleChange($event)"> | |||
|
|||
<textarea |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just FYI: we're probably going to do away with these internal elements and content-project an input from the user.
@@ -95,6 +95,10 @@ md-input { | |||
} | |||
} | |||
|
|||
.md-input-element--textarea { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stick with single dash for now- I'm going to update all of the CSS at once for the no-conflict stuff.
@@ -30,6 +32,9 @@ import {Observable} from 'rxjs/Observable'; | |||
|
|||
const noop = () => {}; | |||
|
|||
const MD_INPUT_SELECTOR = 'md-input'; | |||
const MD_TEXTAREA_SELECTOR = 'md-textarea'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just use the strings inline without introducing constants; the values are obvious and aren't going to change frequently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it was for my own convenience, but ok.
(focus)="_handleFocus($event)" | ||
(blur)="_handleBlur($event)" | ||
[(ngModel)]="value" | ||
(change)="_handleChange($event)"></textarea> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove min, max, and autocorrect. They don't exist on <textarea>
We should add rows
, cols
, and wrap
. Need to think about how rows
interacts with autosize.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea
}) | ||
export class MdAutosize implements OnInit { | ||
@HostListener('input', ['$event.target']) | ||
public onChange() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see where this onChange
method is called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's what the @HostListener
is calling. Or you mean it should be private? I suppose..
|
||
private _adjust() { | ||
this._elRef.nativeElement.style.overflow = 'hidden'; | ||
this._elRef.nativeElement.style.height = 'auto'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two properties don't need to be set every time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overflow
I does not, height = auto
does, it's what causes the textarea shrink when you remove text
private _adjust() { | ||
this._elRef.nativeElement.style.overflow = 'hidden'; | ||
this._elRef.nativeElement.style.height = 'auto'; | ||
this._elRef.nativeElement.style.height = `${this._elRef.nativeElement.scrollHeight}px`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for sizing the textarea is much more complicated in material1, which makes me think this may be missing something.
@crisbeto do you have insights on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems to be working fine, seems to be supported in all browsers as well https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well.. it's considerably more complex when you have to deal with the rows
attribute. So there are two options, not allowing autosize and rows on the same element(because honestly, who's using rows nowadays, it's only good for devices not supporting css) or go with the 10 times more complex code. I started working on the latter, but I'd be much happier with the former.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, most of the complexity is because of the rows
, however I think that they're a pretty important part of the component, because they're what sets an empty textarea
apart from an input
. In Material 1 we also do the following, although I'm not the one who wrote it so I'm not sure what the reasoning is:
function getHeight() {
var offsetHeight = node.offsetHeight;
var line = node.scrollHeight - offsetHeight;
return offsetHeight + Math.max(line, 0);
}
// if line height is explicitly set(to a pixel value), use that | ||
if (computedStyles.lineHeight && computedStyles.lineHeight.toLowerCase().indexOf('px') >= 0) { | ||
// return stripped of the "px" and as a number | ||
return +computedStyles.lineHeight.replace('px', ''); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can achieve the same if you used parseFloat(computedStyles.lineHeight)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that might be faster.
); | ||
|
||
// fill in one row | ||
tempEl.innerHTML = '-'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to use the value
property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well since I won't ever need the actual value, it doesn't really matter.. having said that, value
is apparently marginally faster https://jsperf.com/innerhtml-vs-value-on-textarea so yeah.
@jelbourn I have modified the autosize directive to work along A row's height is calculated from So I'm creating a temp textarea element, reseting it's paddings and margins, visually hiding it, setting the rows attribute to Now that I have actual
The rest is similar to how it was before when Also none of above happens unless // offtopic |
I tried adding a test for the rows attribute but I can't get it to succeed.
fails with "expected null to equal '8'" or something |
@fxck do you want to break the autosize into a separate PR? I want to merge the textarea, but need some more time to review/think about the autosize logic. |
Yeah I can, but I still can't figure out those tests.. |
I don't see anything obviously wrong with the test; if you rebase the PR I can try to take a look |
@jelbourn should be rebased + autosize removed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you need @Input
properties for rows
, cols
, and wrap
:
// textarea-specific
@Input() rows: number = null;
@Input() cols: number = null;
@Input() wrap: string = null;
As for unit tests, I tried this out on your PR and it passes (added to the end of the top-level describe block:
describe('md-textarea', () => {
it('supports the rows, cols, and wrap attributes', () => {
let fixture = TestBed.createComponent(MdTextareaWithBindings);
fixture.detectChanges();
const textarea: HTMLTextAreaElement = fixture.nativeElement.querySelector('textarea');
expect(textarea.rows).toBe(4);
expect(textarea.cols).toBe(8);
expect(textarea.wrap).toBe('hard');
});
});
With this test component at the end of the file (and then added to the testing module):
@Component({template:
`<md-textarea [rows]="rows" [cols]="cols" [wrap]="wrap" placeholder="Snacks"></md-textarea>`})
class MdTextareaWithBindings {
rows: number = 4;
cols: number = 8;
wrap: string = 'hard';
}
which enables auto expanding functionality. | ||
|
||
```html | ||
<md-textarea autosize placeholder="Textarea with autosize"></md-textarea> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should remove autosize from the readme for this PR
@@ -95,6 +95,10 @@ md-input { | |||
} | |||
} | |||
|
|||
.md-input-element-textarea { | |||
min-height: 60px; | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does this 60px come from? I don't see anything in the spec and it looks like material1 uses a min-height to match the line-height. Can we remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it should be equal to two rows(which is default for rows), which is line-height * 2.
But line-height can be overriden by user etc.. so I guess I'll remove it completely
60px was just a number that looked nice and reasonable in the demo
@@ -229,6 +229,10 @@ export class MdInput implements ControlValueAccessor, AfterContentInit, OnChange | |||
|
|||
@ViewChild('input') _inputElement: ElementRef; | |||
|
|||
public elementType: 'input' | 'textarea' = undefined; | |||
|
|||
constructor(private _elRef: ElementRef) { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you move the setting of elementType
here, you don't need the private
and can just call it elementRef
.
this.elementType = 'input'; | ||
} else { | ||
this.elementType = 'textarea'; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should be able to do this in the constructor. I'd also make it a bit more concise with a ternary:
this.elementType = this._elementRef.nativeElement.nodeName.toLowerCase() === 'md-input' ?
'input' :
'textarea';
@@ -229,6 +229,10 @@ export class MdInput implements ControlValueAccessor, AfterContentInit, OnChange | |||
|
|||
@ViewChild('input') _inputElement: ElementRef; | |||
|
|||
public elementType: 'input' | 'textarea' = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can omit the public
(as the default). Can you also prefix this with an underscore (which we're using with the absence of the private
keyword as "internal").
I had those inputs there, must have accidentally deleted them while rebasing.. anyway addressed all comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM aside from one last minor comment
@@ -229,6 +234,15 @@ export class MdInput implements ControlValueAccessor, AfterContentInit, OnChange | |||
|
|||
@ViewChild('input') _inputElement: ElementRef; | |||
|
|||
_elementType: 'input' | 'textarea'; | |||
|
|||
constructor(public _elementRef: ElementRef) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can just be
constructor(elementRef: ElementRef) {
Adding a public
or private
modifier here makes it a field on the class which isn't necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, can it? Changed.
Gah, needs rebase now.. sec. |
Done, passing, all good I guess. |
LGTM |
Thanks! |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
I went with the having one class and multiple selector way. Also added the autosize / autoexpand directive. Modified demo page and README aswell.
I suppose some tests are needed, I'm not really good at those, so I'd appreciate assistance with that.
Should close #546
cc @jelbourn