@yandex/ui
Version:
Yandex UI components
90 lines (89 loc) • 5.63 kB
JavaScript
import { __assign, __extends } from "tslib";
import React, { PureComponent, createRef } from 'react';
import { mergeAllRefs } from '../../lib/mergeRefs';
import { throttle } from '../../lib/throttle';
import { getDisplayName } from '../../lib/getDisplayName';
import { cnTextarea } from '../Textarea';
import './Textarea_autoResize.css';
/**
* Модификатор который увеличивает размер контрола при наборе текста.
*/
export function withAutoResize(WrappedComponent) {
var WithAutoResize = /** @class */ (function (_super) {
__extends(WithAutoResize, _super);
function WithAutoResize() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.wrapRef = createRef();
_this.controlRef = createRef();
_this.initialHeight = 0;
_this.newHeight = 0;
_this.onResize = throttle(_this.updateHeight).bind(_this);
return _this;
}
WithAutoResize.prototype.componentDidMount = function () {
this.saveInitialHeight();
this.updateHeight();
window.addEventListener('resize', this.onResize);
};
WithAutoResize.prototype.componentDidUpdate = function (prevProps) {
// Если компонент скрыт на момент componentDidMount, то минимальная высота будет вычислена неверно (= 0).
// Используем активацию фокуса как признак того, что компонент был показан пользователю.
if (this.props.focused && this.props.focused !== prevProps.focused) {
this.recalcInitialHeight();
}
this.updateHeight();
};
WithAutoResize.prototype.componentWillUnmount = function () {
window.removeEventListener('resize', this.onResize);
};
WithAutoResize.prototype.render = function () {
var _a = this.props, className = _a.className, _b = _a.controlRef, controlRef = _b === void 0 ? null : _b, _c = _a.wrapRef, wrapRef = _c === void 0 ? null : _c;
return (React.createElement(WrappedComponent, __assign({}, this.props, { className: cnTextarea({ autoResize: true }, [className]), controlRef: mergeAllRefs(this.controlRef, controlRef), wrapRef: mergeAllRefs(this.wrapRef, wrapRef) })));
};
/**
* Сохраняем минимальную высоту которую нужно будет проставлять при удалении текста.
*/
WithAutoResize.prototype.saveInitialHeight = function () {
if (this.wrapRef.current) {
this.initialHeight = this.wrapRef.current.clientHeight;
}
};
/**
* Пересчет минимальной высоты которую нужно будет проставлять при удалении текста.
*/
WithAutoResize.prototype.recalcInitialHeight = function () {
if (this.wrapRef.current) {
this.wrapRef.current.style.height = 'auto';
this.saveInitialHeight();
}
};
/**
* Обновление высоты корневому блоку компонента в зависимости от размера контрола.
* Для уменьшении блока при удалении текста нужно вначале выставить его стиль высоты в initialHeight.
* Иначе блок с удаленным текстом будет всегда оставаться с тем размером который ему выставили в стилях.
* Получается, что scrollHeight вычисляется всегда относительно initialHeight размера контейнера.
* Высота контейнера вычисляется относительно контрола учитывая его отступы.
*/
WithAutoResize.prototype.updateHeight = function () {
if (this.wrapRef && this.wrapRef.current) {
if (this.newHeight > this.initialHeight) {
this.wrapRef.current.style.height = this.initialHeight + "px";
this.newHeight = this.initialHeight;
}
if (this.controlRef && this.controlRef.current) {
// Цикл, чтобы перепроверить получившийся элемент, т.к. после его увеличения может появиться скролл
// который выдавит текст внутри него и тогда часть текста может быть не видна
var i = 2; // Подстраховка
while (this.controlRef.current.scrollHeight > this.controlRef.current.offsetHeight && i--) {
var padding = this.controlRef.current.offsetHeight - this.controlRef.current.clientHeight;
this.newHeight = this.controlRef.current.scrollHeight;
this.wrapRef.current.style.height = this.newHeight + padding + "px";
}
}
}
};
WithAutoResize.displayName = "withAutoResize(" + getDisplayName(WrappedComponent) + ")";
return WithAutoResize;
}(PureComponent));
return WithAutoResize;
}