@alifd/next
Version:
A configurable component library for web built on React.
539 lines (538 loc) • 23.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var react_1 = tslib_1.__importStar(require("react"));
var react_dom_1 = require("react-dom");
var prop_types_1 = tslib_1.__importDefault(require("prop-types"));
var classnames_1 = tslib_1.__importDefault(require("classnames"));
var util_1 = require("../util");
var menu_1 = tslib_1.__importDefault(require("../menu"));
var overlay_1 = tslib_1.__importDefault(require("../overlay"));
var input_1 = tslib_1.__importDefault(require("../input"));
var zh_cn_1 = tslib_1.__importDefault(require("../locale/zh-cn"));
var data_store_1 = tslib_1.__importDefault(require("./data-store"));
var virtual_list_1 = tslib_1.__importDefault(require("../virtual-list"));
var util_2 = require("./util");
var Popup = overlay_1.default.Popup;
var MenuItem = menu_1.default.Item, MenuGroup = menu_1.default.Group;
var noop = util_1.func.noop, bindCtx = util_1.func.bindCtx, makeChain = util_1.func.makeChain;
function preventDefault(e) {
e.preventDefault();
}
var Base = /** @class */ (function (_super) {
tslib_1.__extends(Base, _super);
function Base(props) {
var _this = _super.call(this, props) || this;
/**
* 防止 onBlur/onFocus 抖动
*/
_this.handleMouseDown = function (e) {
if (!_this.props.popupAutoFocus) {
preventDefault(e);
}
};
_this.saveSelectRef = function (ref) {
_this.selectDOM = (0, react_dom_1.findDOMNode)(ref);
};
_this.saveInputRef = function (ref) {
if (ref && ref.getInstance()) {
_this.inputRef = ref.getInstance();
}
};
_this.savePopupRef = function (ref) {
_this.popupRef = ref;
};
_this.dataStore = new data_store_1.default({
filter: props.filter,
filterLocal: props.filterLocal,
showDataSourceChildren: props.showDataSourceChildren,
});
var value = 'value' in props ? props.value : props.defaultValue;
// 多选情况下做 value 数组订正
if (props.mode !== 'single' && value && !Array.isArray(value)) {
value = [value];
}
_this.state = {
dataStore: _this.dataStore,
value: value,
visible: 'visible' in props ? props.visible : props.defaultVisible,
dataSource: _this.setDataSource(_this.props),
width: 100,
// highlightKey 应为 String 多选初始化只赋值受控 highlightKey/defaultHighlightKey
highlightKey: 'highlightKey' in props
? props.highlightKey
: props.mode === 'single'
? // FIXME 这里实现有些问题,假设 value 是 detailedValue 这里就是一个对象了
props.value ||
props.defaultHighlightKey ||
props.defaultValue
: props.defaultHighlightKey,
srReader: '',
};
bindCtx(_this, [
'handleMenuBodyClick',
'handleVisibleChange',
'focusInput',
'beforeOpen',
'beforeClose',
'afterClose',
'handleResize',
]);
return _this;
}
Base.prototype.componentDidMount = function () {
var _this = this;
// overlay 还没有完成 mount,所以需要滞后同步宽度
setTimeout(function () { return _this.syncWidth(); }, 0);
util_1.events.on(window, 'resize', this.handleResize);
};
Base.prototype.componentDidUpdate = function (prevProps, prevState) {
if (prevProps.label !== this.props.label || prevState.value !== this.state.value) {
this.syncWidth();
}
};
Base.prototype.componentWillUnmount = function () {
util_1.events.off(window, 'resize', this.handleResize);
clearTimeout(this.resizeTimeout);
};
/**
* Calculate and set width of popup menu
*/
Base.prototype.syncWidth = function () {
var _this = this;
var _a = this.props, popupStyle = _a.popupStyle, popupProps = _a.popupProps;
if ((popupStyle && 'width' in popupStyle) ||
(popupProps && popupProps.style && 'width' in popupProps.style)) {
return;
}
var width = util_1.dom.getStyle(this.selectDOM, 'width');
if (width && this.width !== width) {
this.width = width;
if (this.shouldAutoWidth()) {
// overy 的 node 节点可能没有挂载完成,所以这里需要异步
setTimeout(function () {
if (_this.popupRef) {
util_1.dom.setStyle(_this.popupRef, 'width', _this.width);
return;
}
}, 0);
}
}
};
Base.prototype.handleResize = function () {
var _this = this;
clearTimeout(this.resizeTimeout);
if (this.state.visible) {
this.resizeTimeout = window.setTimeout(function () {
_this.syncWidth();
}, 200);
}
};
/**
* Get structured dataSource, for cache
*/
Base.prototype.setDataSource = function (props) {
var dataSource = props.dataSource, children = props.children;
// children is higher priority then dataSource
if (react_1.Children.count(children)) {
return this.dataStore.updateByDS(children, true);
}
else if (Array.isArray(dataSource)) {
return this.dataStore.updateByDS(dataSource, false);
}
return [];
};
/**
* Set popup visible
*/
Base.prototype.setVisible = function (visible, type) {
// disabled 状态下只允许关闭不允许打开
if ((this.props.disabled && visible) || this.state.visible === visible) {
return;
}
if (!('visible' in this.props)) {
this.setState({
visible: visible,
});
}
this.props.onVisibleChange(visible, type);
};
Base.prototype.setFirstHightLightKeyForMenu = function (searchValue) {
// 判断 value/highlightKey 解决受控后,默认高亮第一个元素问题。(当搜索值时,搜索后应执行默认选择第一个元素)
var highlightKey = this.state.highlightKey;
if (!this.props.autoHighlightFirstItem) {
return;
}
// 设置高亮 item key
if (this.dataStore.getMenuDS().length &&
this.dataStore.getEnableDS().length &&
(!highlightKey || searchValue)) {
var highlightKey_1 = "".concat(this.dataStore.getEnableDS()[0].value);
this.setState({
highlightKey: highlightKey_1,
});
this.props.onToggleHighlightItem(highlightKey_1, 'autoFirstItem');
}
// 当有搜索值且搜索条件与 dataSource 不匹配时 (搜索条件不满足不会出现可选择的列表,所以高亮 key 应为 null)
if (searchValue && !this.dataStore.getEnableDS().length) {
this.setState({
highlightKey: null,
});
this.props.onToggleHighlightItem(null, 'highlightKeyToNull');
}
};
Base.prototype.handleChange = function (value) {
var _a;
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
// 非受控模式清空内部数据
if (!('value' in this.props)) {
this.setState({
value: value,
});
}
(_a = this.props).onChange.apply(_a, tslib_1.__spreadArray([value], tslib_1.__read(args), false));
};
/**
* Handle Menu body click
* @param e - click event
*/
Base.prototype.handleMenuBodyClick = function () {
if (!this.props.popupAutoFocus) {
this.focusInput();
}
};
/**
* Toggle highlight MenuItem
* @param dir - -1: up, 1: down
*/
Base.prototype.toggleHighlightItem = function (dir) {
if (!this.state.visible) {
this.setVisible(true, 'enter');
return;
}
var maxCount = this.dataStore.getEnableDS().length;
// When there is no enabled item
if (!maxCount) {
return false;
}
var highlightKey = this.state.highlightKey;
var highlightIndex = -1;
// find previous highlight index
highlightKey !== null &&
this.dataStore.getEnableDS().some(function (item, index) {
if ("".concat(item.value) === highlightKey) {
highlightIndex = index;
}
return highlightIndex > -1;
});
// toggle highlight index
highlightIndex += dir;
if (highlightIndex < 0) {
highlightIndex = maxCount - 1;
}
if (highlightIndex >= maxCount) {
highlightIndex = 0;
}
// get highlight key
var highlightItem = this.dataStore.getEnableDS()[highlightIndex];
highlightKey = highlightItem ? "".concat(highlightItem.value) : null;
this.setState({ highlightKey: highlightKey, srReader: highlightItem.label });
this.scrollMenuIntoView();
return highlightItem;
};
// scroll into focus item
Base.prototype.scrollMenuIntoView = function () {
var _this = this;
var prefix = this.props.prefix;
clearTimeout(this.highlightTimer);
this.highlightTimer = window.setTimeout(function () {
try {
var menuNode = (0, react_dom_1.findDOMNode)(_this.menuRef);
var itemNode = menuNode.querySelector(".".concat(prefix, "select-menu-item.").concat(prefix, "focused"));
itemNode && itemNode.scrollIntoViewIfNeeded && itemNode.scrollIntoViewIfNeeded();
}
catch (ex) {
// I don't care...
}
});
};
/**
* render popup menu header
*/
Base.prototype.renderMenuHeader = function () {
var menuProps = this.props.menuProps;
if (menuProps && 'header' in menuProps) {
return menuProps.header;
}
return null;
};
Base.prototype.handleSelect = function () { };
/**
* abstract
*/
Base.prototype.handleMenuSelect = function () {
var rest = [];
for (var _i = 0; _i < arguments.length; _i++) {
rest[_i] = arguments[_i];
}
};
/**
* abstract
*/
Base.prototype.handleItemClick = function () {
var rest = [];
for (var _i = 0; _i < arguments.length; _i++) {
rest[_i] = arguments[_i];
}
};
/**
* abstract
*/
Base.prototype.useDetailValue = function () {
return false;
};
/**
* abstract
*/
Base.prototype.handleVisibleChange = function () {
var rest = [];
for (var _i = 0; _i < arguments.length; _i++) {
rest[_i] = arguments[_i];
}
};
/**
* abstract
*/
Base.prototype.renderSelect = function () {
return react_1.default.createElement("div", null);
};
/**
* render popup children
* @param props -
*/
Base.prototype.renderMenu = function () {
var _a;
var _this = this;
var _b = this.props, prefix = _b.prefix, mode = _b.mode, locale = _b.locale, notFoundContent = _b.notFoundContent, useVirtual = _b.useVirtual, menuProps = _b.menuProps;
var _c = this.state, dataSource = _c.dataSource, highlightKey = _c.highlightKey;
var value = this.state.value;
var selectedKeys;
if ((0, util_2.isNull)(value) || value.length === 0 || this.isAutoComplete) {
selectedKeys = [];
}
else if ((0, util_2.isSingle)(mode)) {
selectedKeys = [(0, util_2.valueToSelectKey)(value)];
}
else {
selectedKeys = []
.concat(value)
.map(function (n) { return (0, util_2.valueToSelectKey)(n); });
}
var children = this.renderMenuItem(dataSource);
var menuClassName = (0, classnames_1.default)((_a = {},
_a["".concat(prefix, "select-menu")] = true,
_a["".concat(prefix, "select-menu-empty")] = !children || !children.length,
_a));
if (!children || !children.length) {
children = (react_1.default.createElement("span", { className: "".concat(prefix, "select-menu-empty-content") }, notFoundContent || locale.notFoundContent));
}
var customProps = tslib_1.__assign(tslib_1.__assign({}, menuProps), { children: children, role: 'listbox', selectedKeys: selectedKeys, focusedKey: highlightKey, focusable: false, selectMode: (0, util_2.isSingle)(mode) ? 'single' : 'multiple', onSelect: this.handleMenuSelect, onItemClick: this.handleItemClick, header: this.renderMenuHeader(), onClick: this.handleMenuBodyClick, onMouseDown: this.handleMouseDown, className: menuClassName });
var menuStyle = this.shouldAutoWidth() ? { width: '100%' } : { minWidth: this.width };
return useVirtual && children.length > 10 ? (react_1.default.createElement("div", tslib_1.__assign({ className: "".concat(prefix, "select-menu-wrapper"), style: tslib_1.__assign({ position: 'relative' }, menuStyle) }, util_1.obj.pickProps(['onScroll'], customProps)),
react_1.default.createElement(virtual_list_1.default, { itemsRenderer: function (items, ref) {
return (react_1.default.createElement(menu_1.default, tslib_1.__assign({ ref: function (c) {
ref(c);
_this.menuRef = c;
}, flatenContent: true }, util_1.obj.pickOthers(['onScroll'], customProps)), items));
} }, children))) : (react_1.default.createElement(menu_1.default, tslib_1.__assign({}, customProps, { style: menuStyle })));
};
/**
* render menu item
*/
Base.prototype.renderMenuItem = function (dataSource) {
var _this = this;
var _a = this.props, prefix = _a.prefix, itemRender = _a.itemRender, showDataSourceChildren = _a.showDataSourceChildren;
// If it has.
var searchKey;
if (this.isAutoComplete) {
// In AutoComplete, value is the searchKey
searchKey = this.state.value;
}
else {
searchKey = this.state.searchValue;
}
return dataSource.map(function (item, index) {
if (!item) {
return null;
}
if (Array.isArray(item.children) && showDataSourceChildren) {
return (react_1.default.createElement(MenuGroup, { key: index, label: item.label }, _this.renderMenuItem(item.children)));
}
else {
var itemProps = {
role: 'option',
className: "".concat(prefix, "select-menu-item"),
disabled: item.disabled,
};
if ('title' in item) {
itemProps.title = item.title;
}
return (react_1.default.createElement(MenuItem, tslib_1.__assign({ key: item.value }, itemProps), itemRender(item, searchKey)));
}
});
};
/**
* 点击 arrow 或 label 的时候焦点切到 input 中
* @override
*/
Base.prototype.focusInput = function () {
this.inputRef.focus(undefined, undefined, true);
};
Base.prototype.focus = function (start, end, preventScroll) {
if (preventScroll === void 0) { preventScroll = false; }
this.inputRef.focus(start, end, preventScroll);
};
Base.prototype.beforeOpen = function () {
if (this.props.mode === 'single') {
this.setFirstHightLightKeyForMenu();
}
this.syncWidth();
};
Base.prototype.beforeClose = function () { };
Base.prototype.afterClose = function () { };
Base.prototype.shouldAutoWidth = function () {
if (this.props.popupComponent) {
return false;
}
return this.props.autoWidth;
};
Base.prototype.render = function (props) {
var _a, _b;
var _this = this;
var _c = props, prefix = _c.prefix, mode = _c.mode, popupProps = _c.popupProps, popupContainer = _c.popupContainer, popupClassName = _c.popupClassName, popupStyle = _c.popupStyle, popupContent = _c.popupContent, canCloseByTrigger = _c.canCloseByTrigger, followTrigger = _c.followTrigger, cache = _c.cache, popupComponent = _c.popupComponent, isPreview = _c.isPreview, renderPreview = _c.renderPreview, style = _c.style, className = _c.className, valueRender = _c.valueRender;
var cls = (0, classnames_1.default)((_a = {},
_a["".concat(prefix, "select-auto-complete-menu")] = !popupContent && this.isAutoComplete,
_a["".concat(prefix, "select-").concat(mode, "-menu")] = !popupContent && !!mode,
_a), popupClassName || popupProps.className);
if (isPreview) {
if (this.isAutoComplete) {
return (react_1.default.createElement(input_1.default, { style: style, className: className, isPreview: isPreview, renderPreview: renderPreview, value: this.state.value }));
}
else {
var value = this.state.value;
var valueDS = this.state.value;
if (!this.useDetailValue()) {
if (value === this.valueDataSource.value) {
valueDS = this.valueDataSource.valueDS;
}
else {
valueDS = (0, util_2.getValueDataSource)(value, this.valueDataSource.mapValueDS, this.dataStore.getMapDS()).valueDS;
}
}
if (typeof renderPreview === 'function') {
var previewCls = (0, classnames_1.default)((_b = {},
_b["".concat(prefix, "form-preview")] = true,
_b[className] = !!className,
_b));
return (react_1.default.createElement("div", { style: style, className: previewCls }, renderPreview(valueDS, this.props)));
}
else {
var fillProps_1 = this.props.fillProps;
if (mode === 'single') {
var renderPreview_1 = function (valueDS) {
if (fillProps_1) {
return valueDS[fillProps_1];
}
else if (valueRender) {
return valueRender(valueDS, _this.props);
}
else {
return valueDS.label;
}
};
return (react_1.default.createElement(input_1.default, { style: style, className: className, isPreview: isPreview, value: valueDS ? renderPreview_1(valueDS) : '' }));
}
else {
return (react_1.default.createElement(input_1.default, { style: style, className: className, isPreview: isPreview, value: (Array.isArray(valueDS) ? valueDS : [])
.map(function (i) { return i.label; })
.join(', ') }));
}
}
}
}
var _props = tslib_1.__assign(tslib_1.__assign({ triggerType: 'click', autoFocus: !!this.props.popupAutoFocus, cache: cache }, popupProps), {
//beforeOpen node not mount, afterOpen too slow.
// from display:none to block, we may need to recompute width
beforeOpen: makeChain(this.beforeOpen, popupProps.beforeOpen), beforeClose: makeChain(this.beforeClose, popupProps.beforeClose), afterClose: makeChain(this.afterClose, popupProps.afterClose), canCloseByTrigger: canCloseByTrigger, followTrigger: followTrigger, visible: this.state.visible, onVisibleChange: this.handleVisibleChange, shouldUpdatePosition: true, container: popupContainer || popupProps.container, className: cls, style: popupStyle || popupProps.style });
if (popupProps.v2) {
delete _props.shouldUpdatePosition;
}
var Tag = popupComponent ? popupComponent : Popup;
return (react_1.default.createElement(Tag, tslib_1.__assign({}, _props, { trigger: this.renderSelect() }), popupContent ? (react_1.default.createElement("div", { className: "".concat(prefix, "select-popup-wrap"), style: this.shouldAutoWidth() ? { width: this.width } : {}, ref: this.savePopupRef }, popupContent)) : (react_1.default.createElement("div", { className: "".concat(prefix, "select-spacing-tb"), style: this.shouldAutoWidth() ? { width: this.width } : {}, ref: this.savePopupRef }, this.renderMenu()))));
};
Base.propTypes = {
prefix: prop_types_1.default.string,
size: prop_types_1.default.oneOf(['small', 'medium', 'large']),
value: prop_types_1.default.any,
defaultValue: prop_types_1.default.any,
placeholder: prop_types_1.default.string,
autoWidth: prop_types_1.default.bool,
label: prop_types_1.default.node,
hasClear: prop_types_1.default.bool,
state: prop_types_1.default.oneOf(['error', 'loading', 'success', 'warning']),
readOnly: prop_types_1.default.bool,
disabled: prop_types_1.default.bool,
visible: prop_types_1.default.bool,
defaultVisible: prop_types_1.default.bool,
onVisibleChange: prop_types_1.default.func,
popupContainer: prop_types_1.default.any,
popupClassName: prop_types_1.default.any,
popupStyle: prop_types_1.default.object,
popupProps: prop_types_1.default.object,
followTrigger: prop_types_1.default.bool,
popupContent: prop_types_1.default.node,
menuProps: prop_types_1.default.object,
filterLocal: prop_types_1.default.bool,
filter: prop_types_1.default.func,
defaultHighlightKey: prop_types_1.default.string,
highlightKey: prop_types_1.default.string,
onToggleHighlightItem: prop_types_1.default.func,
autoHighlightFirstItem: prop_types_1.default.bool,
useVirtual: prop_types_1.default.bool,
className: prop_types_1.default.any,
children: prop_types_1.default.any,
dataSource: prop_types_1.default.array,
itemRender: prop_types_1.default.func,
mode: prop_types_1.default.string,
notFoundContent: prop_types_1.default.node,
locale: prop_types_1.default.object,
rtl: prop_types_1.default.bool,
popupComponent: prop_types_1.default.any,
isPreview: prop_types_1.default.bool,
renderPreview: prop_types_1.default.func,
showDataSourceChildren: prop_types_1.default.bool,
};
Base.defaultProps = {
prefix: 'next-',
size: 'medium',
autoWidth: true,
onChange: noop,
onVisibleChange: noop,
onToggleHighlightItem: noop,
popupProps: {},
filterLocal: true,
filter: util_2.filter,
itemRender: function (item) {
return item.label || item.value;
},
locale: zh_cn_1.default.Select,
autoHighlightFirstItem: true,
showDataSourceChildren: true,
defaultHighlightKey: null,
};
return Base;
}(react_1.default.Component));
exports.default = Base;