@alifd/next
Version:
A configurable component library for web built on React.
851 lines (703 loc) • 27.4 kB
JavaScript
'use strict';
exports.__esModule = true;
exports.default = undefined;
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _class, _temp;
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactDom = require('react-dom');
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
var _util = require('../util');
var _menu = require('../menu');
var _menu2 = _interopRequireDefault(_menu);
var _overlay = require('../overlay');
var _overlay2 = _interopRequireDefault(_overlay);
var _input = require('../input');
var _input2 = _interopRequireDefault(_input);
var _zhCn = require('../locale/zh-cn');
var _zhCn2 = _interopRequireDefault(_zhCn);
var _dataStore = require('./data-store');
var _dataStore2 = _interopRequireDefault(_dataStore);
var _virtualList = require('../virtual-list');
var _virtualList2 = _interopRequireDefault(_virtualList);
var _util2 = require('./util');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var Popup = _overlay2.default.Popup;
var MenuItem = _menu2.default.Item,
MenuGroup = _menu2.default.Group;
var noop = _util.func.noop,
bindCtx = _util.func.bindCtx,
makeChain = _util.func.makeChain;
function preventDefault(e) {
e.preventDefault();
}
var Base = (_temp = _class = function (_React$Component) {
(0, _inherits3.default)(Base, _React$Component);
function Base(props) {
(0, _classCallCheck3.default)(this, Base);
var _this = (0, _possibleConstructorReturn3.default)(this, _React$Component.call(this, props));
_this.handleMouseDown = function (e) {
if (!_this.props.popupAutoFocus) {
preventDefault(e);
}
};
_this.saveSelectRef = function (ref) {
_this.selectDOM = (0, _reactDom.findDOMNode)(ref);
};
_this.saveInputRef = function (ref) {
if (ref && ref.getInstance()) {
_this.inputRef = ref.getInstance();
}
};
_this.savePopupRef = function (ref) {
_this.popupRef = ref;
};
_this.dataStore = new _dataStore2.default({
filter: props.filter,
filterLocal: props.filterLocal,
showDataSourceChildren: props.showDataSourceChildren
});
var mode = props.mode;
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' ? props.value || props.defaultHighlightKey || props.defaultValue : props.defaultHighlightKey,
srReader: ''
};
bindCtx(_this, ['handleMenuBodyClick', 'handleVisibleChange', 'focusInput', 'beforeOpen', 'beforeClose', 'afterClose', 'handleResize']);
return _this;
}
Base.prototype.componentDidMount = function componentDidMount() {
var _this2 = this;
// overlay 还没有完成 mount,所以需要滞后同步宽度
setTimeout(function () {
return _this2.syncWidth();
}, 0);
_util.events.on(window, 'resize', this.handleResize);
};
Base.prototype.componentDidUpdate = function componentDidUpdate(prevProps, prevState) {
if (prevProps.label !== this.props.label || prevState.value !== this.state.value) {
this.syncWidth();
}
};
Base.prototype.componentWillUnmount = function componentWillUnmount() {
_util.events.off(window, 'resize', this.handleResize);
clearTimeout(this.resizeTimeout);
};
/**
* Calculate and set width of popup menu
* @protected
*/
Base.prototype.syncWidth = function syncWidth() {
var _this3 = this;
var _props2 = this.props,
popupStyle = _props2.popupStyle,
popupProps = _props2.popupProps;
if (popupStyle && 'width' in popupStyle || popupProps && popupProps.style && 'width' in popupProps.style) {
return;
}
var width = _util.dom.getStyle(this.selectDOM, 'width');
if (width && this.width !== width) {
this.width = width;
if (this.popupRef && this.shouldAutoWidth()) {
// overy 的 node 节点可能没有挂载完成,所以这里需要异步
setTimeout(function () {
if (_this3.popupRef) {
_util.dom.setStyle(_this3.popupRef, 'width', _this3.width);
return;
}
}, 0);
}
}
};
Base.prototype.handleResize = function handleResize() {
var _this4 = this;
clearTimeout(this.resizeTimeout);
if (this.state.visible) {
this.resizeTimeout = setTimeout(function () {
_this4.syncWidth();
}, 200);
}
};
/**
* Get structured dataSource, for cache
* @protected
* @param {Object} [props=this.props]
* @return {Array}
*/
Base.prototype.setDataSource = function setDataSource(props) {
var dataSource = props.dataSource,
children = props.children;
// children is higher priority then dataSource
if (_react.Children.count(children)) {
return this.dataStore.updateByDS(children, true);
} else if (Array.isArray(dataSource)) {
return this.dataStore.updateByDS(dataSource, false);
}
return [];
};
/**
* Set popup visible
* @protected
* @param {boolean} visible
* @param {string} type trigger type
*/
Base.prototype.setVisible = function setVisible(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 setFirstHightLightKeyForMenu(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 = '' + this.dataStore.getEnableDS()[0].value;
this.setState({
highlightKey: _highlightKey
});
this.props.onToggleHighlightItem(_highlightKey, 'autoFirstItem');
}
// 当有搜索值且搜索条件与dataSource不匹配时(搜索条件不满足不会出现可选择的列表,所以高亮key应为null)
if (searchValue && !this.dataStore.getEnableDS().length) {
this.setState({
highlightKey: null
});
this.props.onToggleHighlightItem(null, 'highlightKeyToNull');
}
};
Base.prototype.handleChange = function handleChange(value) {
var _props3;
// 非受控模式清空内部数据
if (!('value' in this.props)) {
this.setState({
value: value
});
}
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
(_props3 = this.props).onChange.apply(_props3, [value].concat(args));
};
/**
* Handle Menu body click
* @param {Event} e click event
*/
Base.prototype.handleMenuBodyClick = function handleMenuBodyClick(e) {
if (!this.props.popupAutoFocus) {
this.focusInput(e);
}
};
/**
* Toggle highlight MenuItem
* @private
* @param {number} dir -1: up, 1: down
*/
Base.prototype.toggleHighlightItem = function toggleHighlightItem(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 ('' + 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 ? '' + highlightItem.value : null;
this.setState({ highlightKey: highlightKey, srReader: highlightItem.label });
this.scrollMenuIntoView();
return highlightItem;
};
// scroll into focus item
Base.prototype.scrollMenuIntoView = function scrollMenuIntoView() {
var _this5 = this;
var prefix = this.props.prefix;
clearTimeout(this.highlightTimer);
this.highlightTimer = setTimeout(function () {
try {
var menuNode = (0, _reactDom.findDOMNode)(_this5.menuRef);
var itemNode = menuNode.querySelector('.' + prefix + 'select-menu-item.' + prefix + 'focused');
itemNode && itemNode.scrollIntoViewIfNeeded && itemNode.scrollIntoViewIfNeeded();
} catch (ex) {
// I don't care...
}
});
};
/**
* render popup menu header
* @abstract
*/
Base.prototype.renderMenuHeader = function renderMenuHeader() {
var menuProps = this.props.menuProps;
if (menuProps && 'header' in menuProps) {
return menuProps.header;
}
return null;
};
Base.prototype.handleSelect = function handleSelect() {};
/**
* 防止 onBlur/onFocus 抖动
*/
/**
* render popup children
* @protected
* @param {object} props
*/
Base.prototype.renderMenu = function renderMenu() {
var _classNames,
_this6 = this;
var _props4 = this.props,
prefix = _props4.prefix,
mode = _props4.mode,
locale = _props4.locale,
notFoundContent = _props4.notFoundContent,
useVirtual = _props4.useVirtual,
menuProps = _props4.menuProps;
var _state = this.state,
dataSource = _state.dataSource,
highlightKey = _state.highlightKey;
var value = this.state.value;
var selectedKeys = void 0;
if ((0, _util2.isNull)(value) || value.length === 0 || this.isAutoComplete) {
selectedKeys = [];
} else if ((0, _util2.isSingle)(mode)) {
selectedKeys = [(0, _util2.valueToSelectKey)(value)];
} else {
selectedKeys = [].concat(value).map(function (n) {
return (0, _util2.valueToSelectKey)(n);
});
}
var children = this.renderMenuItem(dataSource);
var menuClassName = (0, _classnames2.default)((_classNames = {}, _classNames[prefix + 'select-menu'] = true, _classNames[prefix + 'select-menu-empty'] = !children || !children.length, _classNames));
if (!children || !children.length) {
children = _react2.default.createElement(
'span',
{ className: prefix + 'select-menu-empty-content' },
notFoundContent || locale.notFoundContent
);
}
var customProps = (0, _extends3.default)({}, menuProps, {
children: children,
role: 'listbox',
selectedKeys: selectedKeys,
focusedKey: highlightKey,
focusable: false,
selectMode: (0, _util2.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 ? _react2.default.createElement(
'div',
{ className: prefix + 'select-menu-wrapper', style: (0, _extends3.default)({ position: 'relative' }, menuStyle) },
_react2.default.createElement(
_virtualList2.default,
{
itemsRenderer: function itemsRenderer(items, _ref) {
return _react2.default.createElement(
_menu2.default,
(0, _extends3.default)({
ref: function ref(c) {
_ref(c);
_this6.menuRef = c;
},
flatenContent: true
}, customProps),
items
);
}
},
children
)
) : _react2.default.createElement(_menu2.default, (0, _extends3.default)({}, customProps, { style: menuStyle }));
};
/**
* render menu item
* @protected
* @param {Array} dataSource
*/
Base.prototype.renderMenuItem = function renderMenuItem(dataSource) {
var _this7 = this;
var _props5 = this.props,
prefix = _props5.prefix,
itemRender = _props5.itemRender,
showDataSourceChildren = _props5.showDataSourceChildren;
// If it has.
var searchKey = void 0;
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 _react2.default.createElement(
MenuGroup,
{ key: index, label: item.label },
_this7.renderMenuItem(item.children)
);
} else {
var itemProps = {
role: 'option',
key: item.value,
className: prefix + 'select-menu-item',
disabled: item.disabled
};
if ('title' in item) {
itemProps.title = item.title;
}
return _react2.default.createElement(
MenuItem,
itemProps,
itemRender(item, searchKey)
);
}
});
};
/**
* 点击 arrow 或 label 的时候焦点切到 input 中
* @override
*/
Base.prototype.focusInput = function focusInput() {
this.inputRef.focus();
};
Base.prototype.focus = function focus() {
var _inputRef;
(_inputRef = this.inputRef).focus.apply(_inputRef, arguments);
};
Base.prototype.beforeOpen = function beforeOpen() {
if (this.props.mode === 'single') {
this.setFirstHightLightKeyForMenu();
}
this.syncWidth();
};
Base.prototype.beforeClose = function beforeClose() {};
Base.prototype.afterClose = function afterClose() {};
Base.prototype.shouldAutoWidth = function shouldAutoWidth() {
if (this.props.popupComponent) {
return false;
}
return this.props.autoWidth;
};
Base.prototype.render = function render(props) {
var _classNames2;
var prefix = props.prefix,
mode = props.mode,
popupProps = props.popupProps,
popupContainer = props.popupContainer,
popupClassName = props.popupClassName,
popupStyle = props.popupStyle,
popupContent = props.popupContent,
canCloseByTrigger = props.canCloseByTrigger,
followTrigger = props.followTrigger,
cache = props.cache,
popupComponent = props.popupComponent,
isPreview = props.isPreview,
renderPreview = props.renderPreview,
style = props.style,
className = props.className;
var cls = (0, _classnames2.default)((_classNames2 = {}, _classNames2[prefix + 'select-auto-complete-menu'] = !popupContent && this.isAutoComplete, _classNames2[prefix + 'select-' + mode + '-menu'] = !popupContent && !!mode, _classNames2), popupClassName || popupProps.className);
if (isPreview) {
if (this.isAutoComplete) {
return _react2.default.createElement(_input2.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, _util2.getValueDataSource)(value, this.valueDataSource.mapValueDS, this.dataStore.getMapDS()).valueDS;
}
}
if (typeof renderPreview === 'function') {
var _classNames3;
var previewCls = (0, _classnames2.default)((_classNames3 = {}, _classNames3[prefix + 'form-preview'] = true, _classNames3[className] = !!className, _classNames3));
return _react2.default.createElement(
'div',
{ style: style, className: previewCls },
renderPreview(valueDS, this.props)
);
} else {
var fillProps = this.props.fillProps;
if (mode === 'single') {
return _react2.default.createElement(_input2.default, {
style: style,
className: className,
isPreview: isPreview,
value: valueDS ? fillProps ? valueDS[fillProps] : valueDS.label : ''
});
} else {
return _react2.default.createElement(_input2.default, {
style: style,
className: className,
isPreview: isPreview,
value: (valueDS || []).map(function (i) {
return i.label;
}).join(', ')
});
}
}
}
}
var _props = (0, _extends3.default)({
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 _react2.default.createElement(
Tag,
(0, _extends3.default)({}, _props, { trigger: this.renderSelect() }),
popupContent ? _react2.default.createElement(
'div',
{
className: prefix + 'select-popup-wrap',
style: this.shouldAutoWidth() ? { width: this.width } : {},
ref: this.savePopupRef
},
popupContent
) : _react2.default.createElement(
'div',
{
className: prefix + 'select-spacing-tb',
style: this.shouldAutoWidth() ? { width: this.width } : {},
ref: this.savePopupRef
},
this.renderMenu()
)
);
};
return Base;
}(_react2.default.Component), _class.propTypes = {
prefix: _propTypes2.default.string,
/**
* 选择器尺寸
*/
size: _propTypes2.default.oneOf(['small', 'medium', 'large']),
// 当前值,用于受控模式
value: _propTypes2.default.any, // to be override
// 初始化的默认值
defaultValue: _propTypes2.default.any, // to be override
/**
* 没有值的时候的占位符
*/
placeholder: _propTypes2.default.string,
/**
* 下拉菜单是否与选择器对齐
*/
autoWidth: _propTypes2.default.bool,
/**
* 自定义内联 label
*/
label: _propTypes2.default.node,
/**
* 是否有清除按钮(单选模式有效)
*/
hasClear: _propTypes2.default.bool,
/**
* 校验状态
*/
state: _propTypes2.default.oneOf(['error', 'loading', 'success', 'warning']),
/**
* 是否只读,只读模式下可以展开弹层但不能选
*/
readOnly: _propTypes2.default.bool,
/**
* 是否禁用选择器
*/
disabled: _propTypes2.default.bool,
/**
* 当前弹层是否显示
*/
visible: _propTypes2.default.bool,
/**
* 弹层初始化是否显示
*/
defaultVisible: _propTypes2.default.bool,
/**
* 弹层显示或隐藏时触发的回调
* @param {Boolean} visible 弹层是否显示
* @param {String} type 触发弹层显示或隐藏的来源 fromContent 表示由Dropdown内容触发; fromTrigger 表示由trigger的点击触发; docClick 表示由document的点击触发
*/
onVisibleChange: _propTypes2.default.func,
/**
* 弹层挂载的容器节点
*/
popupContainer: _propTypes2.default.any,
/**
* 弹层的 className
*/
popupClassName: _propTypes2.default.any,
/**
* 弹层的内联样式
*/
popupStyle: _propTypes2.default.object,
/**
* 添加到弹层上的属性
*/
popupProps: _propTypes2.default.object,
/**
* 是否跟随滚动
*/
followTrigger: _propTypes2.default.bool,
/**
* 自定义弹层的内容
*/
popupContent: _propTypes2.default.node,
/**
* 添加到菜单上的属性
* @version 1.18
*/
menuProps: _propTypes2.default.object,
/**
* 是否使用本地过滤,在数据源为远程的时候需要关闭此项
*/
filterLocal: _propTypes2.default.bool,
/**
* 本地过滤方法,返回一个 Boolean 值确定是否保留
* @param {String} key 搜索关键字
* @param {Object} item 渲染节点的item
* @return {Boolean} 是否匹配
*/
filter: _propTypes2.default.func,
/**
* 默认高亮的 key,不要和 autoHighlightFirstItem 同时使用
*/
defaultHighlightKey: _propTypes2.default.string,
/**
* 高亮 key,不要和 autoHighlightFirstItem 同时使用,用于受控模式
*/
highlightKey: _propTypes2.default.string,
/**
* 键盘上下键切换菜单高亮选项的回调
*/
onToggleHighlightItem: _propTypes2.default.func,
/**
* 自动高亮第一个元素
*/
autoHighlightFirstItem: _propTypes2.default.bool,
/**
* 是否开启虚拟滚动模式
*/
useVirtual: _propTypes2.default.bool,
// 自定义类名
className: _propTypes2.default.any,
children: _propTypes2.default.any,
dataSource: _propTypes2.default.array,
itemRender: _propTypes2.default.func,
mode: _propTypes2.default.string,
notFoundContent: _propTypes2.default.node,
locale: _propTypes2.default.object,
rtl: _propTypes2.default.bool,
popupComponent: _propTypes2.default.any,
/**
* 是否为预览态
*/
isPreview: _propTypes2.default.bool,
/**
* 预览态模式下渲染的内容
* @param {number} value 评分值
*/
renderPreview: _propTypes2.default.func,
showDataSourceChildren: _propTypes2.default.bool
}, _class.defaultProps = {
prefix: 'next-',
size: 'medium',
autoWidth: true,
onChange: noop,
onVisibleChange: noop,
onToggleHighlightItem: noop,
popupProps: {},
filterLocal: true,
filter: _util2.filter,
itemRender: function itemRender(item) {
return item.label || item.value;
},
locale: _zhCn2.default.Select,
autoHighlightFirstItem: true,
showDataSourceChildren: true,
defaultHighlightKey: null
}, _temp);
Base.displayName = 'Base';
exports.default = Base;
module.exports = exports['default'];