自适应高度文本框 react contenteditable
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const reduceTargetKeys = (target, keys, predicate) => Object.keys(target).reduce(predicate, {});
const omit = (target = {}, keys = []) =>
reduceTargetKeys(target, keys, (acc, key) => keys.some(omitKey => omitKey === key) ? acc : { ...acc, [key]: target[key] });
const pick = (target = {}, keys = []) =>
reduceTargetKeys(target, keys, (acc, key) => keys.some(pickKey => pickKey === key) ? { ...acc, [key]: target[key] } : acc);
const isEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
const propTypes = {
content: PropTypes.string,
editable: PropTypes.bool,
focus: PropTypes.bool,
maxLength: PropTypes.number,
multiLine: PropTypes.bool,
sanitise: PropTypes.bool,
caretPosition: PropTypes.oneOf(['start', 'end']),
tagName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), // The element to make contenteditable. Takes an element string ('div', 'span', 'h1') or a styled component
innerRef: PropTypes.func,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
onKeyDown: PropTypes.func,
onPaste: PropTypes.func,
onChange: PropTypes.func,
styled: PropTypes.bool, // If element is a styled component (uses innerRef instead of ref)
};
const defaultProps = {
content: '',
editable: true,
focus: false,
maxLength: Infinity,
multiLine: false,
sanitise: true,
caretPosition: null,
tagName: 'div',
innerRef: () => { },
onBlur: () => { },
onFocus: () => { },
onKeyDown: () => { },
onPaste: () => { },
onChange: () => { },
styled: false,
};
class ContentEditable extends Component {
constructor(props) {
super();
this.state = {
value: props.content,
isFocused: false,
};
}
componentDidMount() {
this.setFocus();
this.setCaret();
}
componentWillReceiveProps(nextProps) {
if (nextProps.content !== this.sanitiseValue(this.state.value)) {
this.setState({ value: nextProps.content }, () => {
if (!this.state.isFocused) this.forceUpdate();
});
}
}
shouldComponentUpdate(nextProps) {
const propKeys = Object.keys(nextProps).filter(key => key !== 'content');
return !isEqual(pick(nextProps, propKeys), pick(this.props, propKeys));
}
componentDidUpdate() {
this.setFocus();
this.setCaret();
}
setFocus = () => {
if (this.props.focus && this._element) {
this._element.focus();
}
};
setCaret = () => {
const { caretPosition } = this.props;
if (caretPosition && this._element) {
const { value } = this.state;
const offset = value.length && caretPosition === 'end' ? 1 : 0;
const range = document.createRange();
const selection = window.getSelection();
range.setStart(this._element, offset);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}
};
sanitiseValue(val) {
const { maxLength, multiLine, sanitise } = this.props;
if (!sanitise) {
return val;
}
// replace encoded spaces
let value = val.replace(/ /, ' ').replace(/[\u00a0\u2000-\u200b\u2028-\u2029\u202e-\u202f\u3000]/g, ' ');
if (multiLine) {
// replace any 2+ character whitespace (other than new lines) with a single space
value = value.replace(/[\t\v\f\r ]+/g, ' ');
} else {
value = value.replace(/\s+/g, ' ');
}
return value
.split('\n')
.map(line => line.trim())
.join('\n')
.replace(/\n{3,}/g, '\n\n') // replace 3+ line breaks with two
.trim()
.substr(0, maxLength);
}
_onChange = ev => {
const { sanitise } = this.props;
const rawValue = this._element.innerText;
const value = sanitise ? this.sanitiseValue(rawValue) : rawValue;
if (this.state.value !== value) {
this.setState({ value: rawValue }, () => {
this.props.onChange(ev, value);
});
}
};
_onPaste = ev => {
const { maxLength } = this.props;
ev.preventDefault();
const text = ev.clipboardData.getData('text').substr(0, maxLength);
document.execCommand('insertText', false, text);
this.props.onPaste(ev);
};
_onBlur = ev => {
const { sanitise } = this.props;
const rawValue = this._element.innerText;
const value = sanitise ? this.sanitiseValue(rawValue) : rawValue;
// We finally set the state to the sanitised version (rather than the `rawValue`) because we're blurring the field.
this.setState({
value,
isFocused: false,
}, () => {
this.props.onChange(ev, value);
this.forceUpdate();
});
this.props.onBlur(ev);
};
_onFocus = ev => {
this.setState({
isFocused: true,
});
this.props.onFocus(ev);
};
_onKeyDown = ev => {
const { maxLength, multiLine } = this.props;
const value = this._element.innerText;
// return key
if (!multiLine && ev.keyCode === 13) {
ev.preventDefault();
ev.currentTarget.blur();
// Call onKeyUp directly as ev.preventDefault() means that it will not be called
this._onKeyUp(ev);
}
// Ensure we don't exceed `maxLength` (keycode 8 === backspace)
if (maxLength && !ev.metaKey && ev.which !== 8 && value.replace(/\s\s/g, ' ').length >= maxLength) {
ev.preventDefault();
// Call onKeyUp directly as ev.preventDefault() means that it will not be called
this._onKeyUp(ev);
}
};
_onKeyUp = ev => {
// Call prop.onKeyDown callback from the onKeyUp event to mitigate both of these issues:
// Access to Synthetic event: https://github.com/ashleyw/react-sane-contenteditable/issues/14
// Current value onKeyDown: https://github.com/ashleyw/react-sane-contenteditable/pull/6
// this._onKeyDown can't be moved in it's entirety to onKeyUp as we lose the opportunity to preventDefault
this.props.onKeyDown(ev, this._element.innerText);
};
render() {
const { tagName: Element, content, editable, styled, ...props } = this.props;
return (
<Element
{...omit(props, Object.keys(propTypes))}
{...(styled
? {
innerRef: c => {
this._element = c;
props.innerRef(c);
},
}
: {
ref: c => {
this._element = c;
props.innerRef(c);
},
})}
style={{ minHeight: '0.28rem', minWidth: '100%', display: 'inline-block', whiteSpace: 'pre-wrap', wordWrap: 'break-word', wordBreak: 'break-all', ...props.style }}
contentEditable={editable}
key={Date()}
dangerouslySetInnerHTML={{ __html: this.state.value }}
onBlur={this._onBlur}
onFocus={this._onFocus}
onInput={this._onChange}
onKeyDown={this._onKeyDown}
onKeyUp={this._onKeyUp}
onPaste={this._onPaste}
/>
);
}
}
ContentEditable.propTypes = propTypes;
ContentEditable.defaultProps = defaultProps;
export default ContentEditable;
自适应高度文本框 react contenteditable的更多相关文章
- htm5 手机自适应问题 文本框被激活(获取焦点)时,页面会放大至原来尺寸。
加上这句话就ok啦 <meta name="viewport" content="width=device-width, initial-scale=1.0, mi ...
- html5 textarea 文本框根据输入内容自适应高度
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...
- jquery文本框textarea自适应高度
浏览器中默认的文本框是不能根据内容的增多变高,只能固定高度有滚动条,体验不是很好,找了很多方法兼容都不行,总算找到个兼容良好的方法: <body> <textarea id=&quo ...
- 自适应高度输入框(contenteditable/textarea)
一.用div模拟textarea div模拟输入域可以根据输入内容自动伸缩,而input和textarea输入内容较多时,高度固定,内容会显示不全. 1.坑1(IOS端无法输入) 在取消全局默认样 ...
- Jquery实现textarea根据文本内容自适应高度
本文给大家分享的是Jquery实现textarea根据文本内容自适应高度,这些在平时的项目中挺实用的,所以抽空封装了一个文本框根据输入内容自适应高度的插件,这里推荐给小伙伴们. autoTextare ...
- Jquery实现 TextArea 文本框根据输入内容自动适应高度
原文 Jquery实现 TextArea 文本框根据输入内容自动适应高度 在玩微博的时候我们可能会注意到一个细节就是不管是新浪微博还是腾讯微博在转发和评论的时候给你的默认文本框的高度都不会很高,这可能 ...
- JS案例 - 可自动伸缩高度的textarea文本框
文本框的默认现象: textarea如果设置cols和rows来规定textarea的尺寸,那么textarea的默认宽高是这俩属性设置的值,可以通过鼠标拖拽缩放文本框的尺寸. textarea如果设 ...
- [Swift通天遁地]二、表格表单-(12)设置表单文字对齐方式以及自适应高度的文本区域TextArea
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...
- iOS之自动调节输入文本框的高度
//自动调节输入文本框的高度 - (void)textViewDidChange:(UITableView *)textView{ float height; if ([[[UIDevice curr ...
随机推荐
- Node.js event loop 和 JS 浏览器环境下的事件循环的区别
Node.js event loop 和 JS 浏览器环境下的事件循环的区别: 1.线程与进程: JS 是单线程执行的,指的是一个进程里只有一个主线程,那到底什么是线程?什么是进程? 进程是 CPU ...
- shell脚本安装python、pip--交互式的
首先把pip-.tgz 安装包放在 /usr/local 下面,按照顺序先安装pip,再安装python.不要先安装或只安装python,否则很容易出错, [root@bogon ~]# cat pi ...
- Win10使用Tex Live和VS Code和Latex Workshop插件编写Latex文档(未完成版本)
首先取Tex Live官网下载安装包:https://www.tug.org/texlive/acquire-netinstall.html 我下载的是 http://mirror.ctan.org/ ...
- 搭建Android与多ble蓝牙设备并发通讯小框架
此框架支持多种不同类型的ble设备,同时连接.收发数据,互不干扰.比如APP同时连两个LED蓝牙灯.两个手环.一个蓝牙加热器,当然连接单个ble设备,或者只连接一种ble设备同样适用本框架. 前言 小 ...
- linux系统时间设定
更改系统时间并同步硬件时钟 sudo date -s '2018-12-27 12:46' sudo hwclock --systohc hwclock说明:hwclock --help
- Entitas--ECS框架插件
ECS Entity.Component.System Entity Component System 模块解耦 守望先锋 https://gameinstitute.qq.com/community ...
- iOS开发系列之app的一天
本文主要讲述我对 iOS 开发的一些理解,希望能通过 app 从启动到退出,将一些的知识整合起来,形成一条知识链,目前涉及到的知识点有 runloop.runtime.文件存储.界面布局.离线推送.内 ...
- UIPath工具取得多个文件的方法
下图是取得某个路径下的多个文件的做法.取得Excel文件的第一个sheet页[workBook.GetSheets(0)]
- javascript高德地图放到网页中的方法
javascript高德地图放到网页中的方法 1 先获取到经纬度http://lbs.amap.com/console/show/picker 2 下面代码直接设置下中心点 和标记点就可以了 < ...
- 批量添加删除Windows server DNS服务 恶意域名 * A记录 指向 127.0.0.1(2019年6月5日更新)
下载链接:https://pan.baidu.com/s/1OUHyvnIfXYF0PdiT-VRyHw 密码:7gjj 注意!本解决方案在本地的Windows server服务器上把恶意域名指向1 ...