Skip to content

Commit

Permalink
feat(module:input): support nzAutosize property (#251)
Browse files Browse the repository at this point in the history
* feat(module:input): support nzAutosize property

* docs(module:input): add textarea autosize demo

close #252
  • Loading branch information
hsuanxyz authored and vthinkxie committed Sep 9, 2017
1 parent 578819d commit 5e950ab
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 3 deletions.
54 changes: 52 additions & 2 deletions src/components/input/nz-input.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,18 @@ import {
EventEmitter,
ContentChild,
forwardRef,
AfterContentInit, HostListener
AfterContentInit,
HostListener,
AfterViewInit,
ViewChild
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import calculateNodeHeight from '../util/calculate-node-height';

export interface AutoSizeType {
minRows?: number;
maxRows?: number;
}

@Component({
selector : 'nz-input',
Expand Down Expand Up @@ -43,6 +52,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
<textarea
(blur)="_emitBlur($event)"
(focus)="_emitFocus($event)"
(input)="textareaOnChange($event)"
[attr.id]="nzId"
#inputTextarea
[disabled]="nzDisabled"
Expand Down Expand Up @@ -76,7 +86,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
'./style/patch.less'
]
})
export class NzInputComponent implements AfterContentInit, ControlValueAccessor {
export class NzInputComponent implements AfterContentInit, ControlValueAccessor, AfterViewInit {

_el: HTMLElement;
_value: string;
Expand All @@ -86,6 +96,7 @@ export class NzInputComponent implements AfterContentInit, ControlValueAccessor
_classMap;
_disabled = false;
_readonly = false;
_autosize: boolean | AutoSizeType = false;

// ngModel Access
onChange: any = Function.prototype;
Expand Down Expand Up @@ -126,8 +137,25 @@ export class NzInputComponent implements AfterContentInit, ControlValueAccessor
this._readonly = value;
}

@Input()
get nzAutosize() {
return this._autosize;
}

set nzAutosize(value: string | boolean | AutoSizeType) {
if (value === '') {
this._autosize = true;
} else {
this._autosize = value;
}
if (this._autosize) {
this.nzRows = 1;
}
}

@Output() nzBlur: EventEmitter<MouseEvent> = new EventEmitter();
@Output() nzFocus: EventEmitter<MouseEvent> = new EventEmitter();
@ViewChild('inputTextarea') textAreaRef: ElementRef;
@ContentChild('addOnBefore') _addOnContentBefore: TemplateRef<any>;
@ContentChild('addOnAfter') _addOnContentAfter: TemplateRef<any>;

Expand Down Expand Up @@ -176,6 +204,23 @@ export class NzInputComponent implements AfterContentInit, ControlValueAccessor
};
}

resizeTextarea() {
const textAreaRef = this.textAreaRef.nativeElement;
// eliminate jitter
textAreaRef.style.height = 'auto';
const maxRows = this.nzAutosize ? (this.nzAutosize as AutoSizeType).maxRows || null : null;
const minRows = this.nzAutosize ? (this.nzAutosize as AutoSizeType).minRows || null : null;
const textareaStyles = calculateNodeHeight(textAreaRef, false, minRows, maxRows);
textAreaRef.style.height = `${textareaStyles.height}px`;
textAreaRef.style.overflowY = textareaStyles.overflowY;
}

textareaOnChange() {
if (this.nzType === 'textarea' && this.nzAutosize) {
this.resizeTextarea();
}
}

constructor(private _elementRef: ElementRef, private _renderer: Renderer2) {
this._el = this._elementRef.nativeElement;
}
Expand All @@ -191,6 +236,11 @@ export class NzInputComponent implements AfterContentInit, ControlValueAccessor
}
}

ngAfterViewInit() {
if (this.nzType === 'textarea' && this.nzAutosize) {
setTimeout(() => this.resizeTextarea(), 0)
}
}

writeValue(value: any): void {
this.nzValue = value;
Expand Down
21 changes: 21 additions & 0 deletions src/components/input/nz-input.directive.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ElementRef,
Input
} from '@angular/core';
import { AutoSizeType } from './nz-input.component';

@Component({
selector : '[nz-input]',
Expand All @@ -19,6 +20,7 @@ export class NzInputDirectiveComponent {
size = 'default';
nativeElement: HTMLElement;
_readonly = false;
_autosize: boolean | AutoSizeType = false;
@HostBinding(`class.ant-input-disabled`) _disabled = false;

@Input()
Expand Down Expand Up @@ -58,6 +60,25 @@ export class NzInputDirectiveComponent {
this._readonly = value;
}

@Input()
get nzAutosize() {
return this._autosize;
}

set nzAutosize(value: string | boolean | AutoSizeType) {
if (value === '') {
this._autosize = true;
} else {
this._autosize = value;
}

if (this._autosize) {
this._render.setAttribute(this.nativeElement, 'autosize', '');
} else {
this._render.removeAttribute(this.nativeElement, 'autosize');
}
}

@HostBinding(`class.ant-input`) _nzInput = true;


Expand Down
151 changes: 151 additions & 0 deletions src/components/util/calculate-node-height.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
const HIDDEN_TEXTAREA_STYLE = `
min-height:0 !important;
max-height:none !important;
height:0 !important;
visibility:hidden !important;
overflow:hidden !important;
position:absolute !important;
z-index:-1000 !important;
top:0 !important;
right:0 !important
`;

const SIZING_STYLE = [
'letter-spacing',
'line-height',
'padding-top',
'padding-bottom',
'font-family',
'font-weight',
'font-size',
'text-rendering',
'text-transform',
'width',
'text-indent',
'padding-left',
'padding-right',
'border-width',
'box-sizing',
];

const computedStyleCache = {};
let hiddenTextarea;

function calculateNodeStyling(node, useCache = false) {
const nodeRef = (
node.getAttribute('id') ||
node.getAttribute('data-reactid') ||
node.getAttribute('name')
);

if (useCache && computedStyleCache[nodeRef]) {
return computedStyleCache[nodeRef];
}

const style = window.getComputedStyle(node);

const boxSizing = (
style.getPropertyValue('box-sizing') ||
style.getPropertyValue('-moz-box-sizing') ||
style.getPropertyValue('-webkit-box-sizing')
);

const paddingSize = (
parseFloat(style.getPropertyValue('padding-bottom')) +
parseFloat(style.getPropertyValue('padding-top'))
);

const borderSize = (
parseFloat(style.getPropertyValue('border-bottom-width')) +
parseFloat(style.getPropertyValue('border-top-width'))
);

const sizingStyle = SIZING_STYLE
.map(name => `${name}:${style.getPropertyValue(name)}`)
.join(';');

const nodeInfo = {
sizingStyle,
paddingSize,
borderSize,
boxSizing,
};

if (useCache && nodeRef) {
computedStyleCache[nodeRef] = nodeInfo;
}

return nodeInfo;
}

export default function calculateNodeHeight(
uiTextNode,
useCache = false,
minRows: number | null = null,
maxRows: number | null = null,
) {
if (!hiddenTextarea) {
hiddenTextarea = document.createElement('textarea');
document.body.appendChild(hiddenTextarea);
}

// Fix wrap="off" issue
// https://github.com/ant-design/ant-design/issues/6577
if (uiTextNode.getAttribute('wrap')) {
hiddenTextarea.setAttribute('wrap', uiTextNode.getAttribute('wrap'));
} else {
hiddenTextarea.removeAttribute('wrap');
}

// Copy all CSS properties that have an impact on the height of the content in
// the textbox
const {
paddingSize, borderSize,
boxSizing, sizingStyle,
} = calculateNodeStyling(uiTextNode, useCache);

// Need to have the overflow attribute to hide the scrollbar otherwise
// text-lines will not calculated properly as the shadow will technically be
// narrower for content
hiddenTextarea.setAttribute('style', `${sizingStyle};${HIDDEN_TEXTAREA_STYLE}`);
hiddenTextarea.value = uiTextNode.value || uiTextNode.placeholder || '';

let minHeight = -Infinity;
let maxHeight = Infinity;
let height = hiddenTextarea.scrollHeight;
let overflowY;

if (boxSizing === 'border-box') {
// border-box: add border, since height = content + padding + border
height = height + borderSize;
} else if (boxSizing === 'content-box') {
// remove padding, since height = content
height = height - paddingSize;
}

if (minRows !== null || maxRows !== null) {
// measure height of a textarea with a single row
hiddenTextarea.value = '';
const singleRowHeight = hiddenTextarea.scrollHeight - paddingSize;
if (minRows !== null) {
minHeight = singleRowHeight * minRows;
if (boxSizing === 'border-box') {
minHeight = minHeight + paddingSize + borderSize;
}
height = Math.max(minHeight, height);
}
if (maxRows !== null) {
maxHeight = singleRowHeight * maxRows;
if (boxSizing === 'border-box') {
maxHeight = maxHeight + paddingSize + borderSize;
}
overflowY = height > maxHeight ? '' : 'hidden';
height = Math.min(maxHeight, height);
}
}
// Remove scroll bar flash when autosize without maxRows
if (!maxRows) {
overflowY = 'hidden';
}
return { height, minHeight, maxHeight, overflowY };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Component, OnInit } from '@angular/core';

@Component({
selector: 'nz-demo-input-textarea-auot-size',
template: `
<nz-input [(ngModel)]="inputValueOne" nzType="textarea" nzAutosize nzPlaceHolder="Autosize height based on content lines"></nz-input>
<div style="margin-top: 16px;">
<nz-input [(ngModel)]="inputValueTwo" nzType="textarea" [nzAutosize]="autosize" nzPlaceHolder="Autosize height with minimum and maximum number of lines"></nz-input>
</div>
`,
styles : []
})
export class NzDemoInputTextareaAutoSizeComponent implements OnInit {
inputValueOne: string;
inputValueTwo: string;
autosize = {
minRows: 2,
maxRows: 6
};

constructor() {
}

ngOnInit() {
}
}

1 change: 1 addition & 0 deletions src/showcase/nz-demo-input/nz-demo-input.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class NzDemoInputComponent implements OnInit {
NzDemoInputGroupCode = require('!!raw-loader!./nz-demo-input-group.component');
NzDemoInputSearchCode = require('!!raw-loader!./nz-demo-input-search.component');
NzDemoInputTextareaCode = require('!!raw-loader!./nz-demo-input-textarea.component');
NzDemoInputTextareaAutoSizeCode = require('!!raw-loader!./nz-demo-input-textarea-auot-size.component');
NzDemoInputAffixCode = require('!!raw-loader!./nz-demo-input-affix.component');

constructor() {
Expand Down
28 changes: 28 additions & 0 deletions src/showcase/nz-demo-input/nz-demo-input.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ <h2>代码演示<i class="code-box-expand-trigger anticon anticon-appstore" titl
<p>带有搜索按钮的输入框。</p>
</div>
</nz-code-box>
<nz-code-box [nzTitle]="'适应文本高度的文本域'" id="components-input-demo-textarea-auot-size" [nzCode]="NzDemoInputTextareaAutoSizeCode">
<nz-demo-input-textarea-auot-size demo></nz-demo-input-textarea-auot-size>
<div intro>
<p><code>nzAutosize</code> 属性适用于 <code>textarea</code> 节点,并且只有高度会自动变化。另外 <code>nzAutosize</code> 可以设定为一个对象,指定最小行数和最大行数。</p>
</div>
</nz-code-box>
<nz-code-box [nzTitle]="'前缀与后缀'" id="components-input-demo-affix" [nzCode]="NzDemoInputAffixCode">
<nz-demo-input-affix demo></nz-demo-input-affix>
<div intro>
Expand Down Expand Up @@ -146,6 +152,28 @@ <h3 id="Input"><span>nz-input</span>
</tr>
</tbody>
</table>
<h4 id="Input.TextArea"><span>nz-input[type=textarea]</span>
<!-- <a class="anchor">#</a> -->
</h4>
<p><code>nzType="textarea"</code> 时,特有的API</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>说明</th>
<th>类型</th>
<th>默认值</th>
</tr>
</thead>
<tbody>
<tr>
<td>nzAutosize</td>
<td>自适应内容高度,可设置为 true|false 或对象:<code>{{'{ minRows: 2, maxRows: 6 }'}}</code></td>
<td>Boolean|Object</td>
<td><code>false</code></td>
</tr>
</tbody>
</table>
<h4 id="Input.Group"><span>nz-input-group</span>
<!-- <a class="anchor">#</a> -->
</h4>
Expand Down
3 changes: 2 additions & 1 deletion src/showcase/nz-demo-input/nz-demo-input.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { NzDemoInputAddOnComponent } from './nz-demo-input-add-on.component';
import { NzDemoInputGroupComponent } from './nz-demo-input-group.component';
import { NzDemoInputSearchComponent } from './nz-demo-input-search.component';
import { NzDemoInputTextareaComponent } from './nz-demo-input-textarea.component';
import { NzDemoInputTextareaAutoSizeComponent } from './nz-demo-input-textarea-auot-size.component';
import { NzDemoInputAffixComponent } from './nz-demo-input-affix.component';
import { NzDemoInputComponent } from './nz-demo-input.component';
import { NzCodeBoxModule } from '../share/nz-codebox/nz-codebox.module';
Expand All @@ -18,7 +19,7 @@ import { NzDemoInputRoutingModule } from './nz-demo-input.routing.module';

@NgModule({
imports : [ NzDemoInputRoutingModule, CommonModule, NzCodeBoxModule, NgZorroAntdModule, FormsModule ],
declarations: [ NzDemoInputComponent, NzDemoInputBasicComponent, NzDemoInputSizeComponent, NzDemoInputAddOnComponent, NzDemoInputGroupComponent, NzDemoInputSearchComponent, NzDemoInputTextareaComponent, NzDemoInputAffixComponent ]
declarations: [ NzDemoInputComponent, NzDemoInputBasicComponent, NzDemoInputSizeComponent, NzDemoInputAddOnComponent, NzDemoInputGroupComponent, NzDemoInputSearchComponent, NzDemoInputTextareaComponent, NzDemoInputTextareaAutoSizeComponent, NzDemoInputAffixComponent ]
})

export class NzDemoInputModule {
Expand Down

0 comments on commit 5e950ab

Please sign in to comment.