UNPKG

@douyinfe/semi-ui

Version:

A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.

394 lines 12.5 kB
import React, { PureComponent } from 'react'; import cls from 'classnames'; import PropTypes from 'prop-types'; import { cssClasses, strings } from '@douyinfe/semi-foundation/lib/es/cascader/constants'; import isEnterPress from '@douyinfe/semi-foundation/lib/es/utils/isEnterPress'; import ConfigContext from '../configProvider/context'; import LocaleConsumer from '../locale/localeConsumer'; import { IconChevronRight, IconTick } from '@douyinfe/semi-icons'; import Spin from '../spin'; import Checkbox from '../checkbox'; import { FixedSizeList as List } from 'react-window'; import VirtualRow from './virtualRow'; const prefixcls = cssClasses.PREFIX_OPTION; export default class Item extends PureComponent { constructor() { var _this; super(...arguments); _this = this; this.onClick = (e, item) => { const { onItemClick } = this.props; if (item.data.disabled || 'disabled' in item && item.disabled) { return; } onItemClick(e, item); }; /** * A11y: simulate item click */ this.handleItemEnterPress = (keyboardEvent, item) => { if (isEnterPress(keyboardEvent)) { this.onClick(keyboardEvent, item); } }; this.onHover = (e, item) => { const { showNext, onItemHover } = this.props; if (item.data.disabled) { return; } if (showNext === strings.SHOW_NEXT_BY_HOVER) { onItemHover(e, item); } }; this.onCheckboxChange = (e, item) => { const { onItemCheckboxClick } = this.props; // Prevent Checkbox's click event bubbling to trigger the li click event e.stopPropagation(); if (e.nativeEvent && typeof e.nativeEvent.stopImmediatePropagation === 'function') { e.nativeEvent.stopImmediatePropagation(); } onItemCheckboxClick(item); }; this.getItemStatus = key => { const { activeKeys, selectedKeys, loadedKeys, loadingKeys } = this.props; const state = { active: false, selected: false, loading: false }; if (activeKeys.has(key)) { state.active = true; } if (selectedKeys.has(key)) { state.selected = true; } if (loadingKeys.has(key) && !loadedKeys.has(key)) { state.loading = true; } return state; }; this.renderIcon = function (type) { let haveMarginLeft = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; const finalCls = style => { return style + (haveMarginLeft ? ` ${prefixcls}-icon-left` : ''); }; switch (type) { case 'child': const { expandIcon } = _this.props; if (expandIcon) { return expandIcon; } return /*#__PURE__*/React.createElement(IconChevronRight, { className: finalCls(`${prefixcls}-icon ${prefixcls}-icon-expand`) }); case 'tick': return /*#__PURE__*/React.createElement(IconTick, { className: finalCls(`${prefixcls}-icon ${prefixcls}-icon-active`) }); case 'loading': return /*#__PURE__*/React.createElement(Spin, { wrapperClassName: finalCls(`${prefixcls}-spin-icon`) }); case 'empty': return /*#__PURE__*/React.createElement("span", { "aria-hidden": true, className: finalCls(`${prefixcls}-icon ${prefixcls}-icon-empty`) }); default: return null; } }; this.highlight = searchText => { const content = []; const { keyword, separator } = this.props; searchText.forEach((item, idx) => { if (typeof item === 'string' && keyword) { // Keep historical DOM structure (span) and only make match case-insensitive. const lowerItem = item.toLowerCase(); const lowerKeyword = keyword.toLowerCase(); let searchFrom = 0; let keyIndex = 0; while (true) { const matchIndex = lowerItem.indexOf(lowerKeyword, searchFrom); if (matchIndex === -1) { const rest = item.slice(searchFrom); if (rest) { content.push(rest); } break; } const before = item.slice(searchFrom, matchIndex); if (before) { content.push(before); } content.push(/*#__PURE__*/React.createElement("span", { className: `${prefixcls}-label-highlight`, key: `${idx}-${matchIndex}-${keyIndex}` }, item.slice(matchIndex, matchIndex + keyword.length))); searchFrom = matchIndex + keyword.length; keyIndex++; } } else { content.push(item); } if (idx !== searchText.length - 1) { content.push(separator); } }); return content; }; this.renderFlattenOptionItem = (data, index, style) => { var _a; const { multiple, selectedKeys, checkedKeys, halfCheckedKeys, keyword, filterRender, virtualize } = this.props; const { searchText, key, disabled, pathData } = data; const selected = selectedKeys.has(key); const className = cls(prefixcls, { [`${prefixcls}-flatten`]: true && !filterRender, [`${prefixcls}-disabled`]: disabled, [`${prefixcls}-select`]: selected && !multiple }); const onClick = e => { this.onClick(e, data); }; const onKeyPress = e => this.handleItemEnterPress(e, data); const onCheck = e => this.onCheckboxChange(e, data); if (filterRender) { const props = { className, inputValue: keyword, disabled, data: pathData, checkStatus: { checked: checkedKeys.has(data.key), halfChecked: halfCheckedKeys.has(data.key) }, selected, onClick, onCheck }; const item = filterRender(props); const otherProps = virtualize ? { key, style: Object.assign(Object.assign({}, (_a = item.props.style) !== null && _a !== void 0 ? _a : {}), style) } : { key }; return /*#__PURE__*/React.cloneElement(item, otherProps); } return /*#__PURE__*/React.createElement("li", { role: 'menuitem', className: className, style: style, key: key, onClick: onClick, onKeyPress: onKeyPress }, /*#__PURE__*/React.createElement("span", { className: `${prefixcls}-label` }, !multiple && this.renderIcon('empty'), multiple && (/*#__PURE__*/React.createElement(Checkbox, { onChange: onCheck, disabled: disabled, indeterminate: halfCheckedKeys.has(data.key), checked: checkedKeys.has(data.key), className: `${prefixcls}-label-checkbox` })), this.highlight(searchText))); }; this.renderFlattenOption = data => { const { virtualize } = this.props; const content = /*#__PURE__*/React.createElement("ul", { className: `${prefixcls}-list`, key: 'flatten-list' }, virtualize ? this.renderVirtualizeList(data) : data.map(item => this.renderFlattenOptionItem(item))); return content; }; this.renderVirtualizeList = visibleOptions => { var _a; const { direction } = this.context; const { virtualize } = this.props; return /*#__PURE__*/React.createElement(List, { height: virtualize.height, itemCount: visibleOptions.length, itemSize: virtualize.itemSize, itemData: { visibleOptions, renderOption: this.renderFlattenOptionItem }, width: (_a = virtualize.width) !== null && _a !== void 0 ? _a : '100%', style: { direction } }, VirtualRow); }; } renderItem(renderData) { let content = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; const { multiple, checkedKeys, halfCheckedKeys } = this.props; let showChildItem; const ind = content.length; content.push(/*#__PURE__*/React.createElement("ul", { role: 'menu', className: `${prefixcls}-list`, key: renderData[0].key, onScroll: e => this.props.onListScroll(e, ind) }, renderData.map(item => { const { data, key, parentKey } = item; const { children, label, disabled, isLeaf } = data; const { active, selected, loading } = this.getItemStatus(key); const hasChild = Boolean(children) && children.length; const showExpand = hasChild || this.props.loadData && !isLeaf; if (active && hasChild) { showChildItem = item; } const className = cls(prefixcls, { [`${prefixcls}-active`]: active && !selected, [`${prefixcls}-select`]: selected && !multiple, [`${prefixcls}-disabled`]: disabled }); const otherAriaProps = parentKey ? { ['aria-owns']: `cascaderItem-${parentKey}` } : {}; return /*#__PURE__*/React.createElement("li", Object.assign({ role: 'menuitem', id: `cascaderItem-${key}`, "aria-expanded": active, "aria-haspopup": Boolean(showExpand), "aria-disabled": disabled }, otherAriaProps, { className: className, key: key, onClick: e => { this.onClick(e, item); }, onKeyPress: e => this.handleItemEnterPress(e, item), onMouseEnter: e => { this.onHover(e, item); } }), /*#__PURE__*/React.createElement("span", { className: `${prefixcls}-label` }, selected && !multiple && this.renderIcon('tick'), !selected && !multiple && this.renderIcon('empty'), multiple && (/*#__PURE__*/React.createElement(Checkbox, { onChange: e => this.onCheckboxChange(e, item), disabled: disabled, indeterminate: halfCheckedKeys.has(item.key), checked: checkedKeys.has(item.key), className: `${prefixcls}-label-checkbox` })), /*#__PURE__*/React.createElement("span", null, label)), showExpand ? this.renderIcon(loading ? 'loading' : 'child', true) : null); }))); if (showChildItem) { content.concat(this.renderItem(showChildItem.children, content)); } return content; } renderEmpty() { const { emptyContent } = this.props; if (emptyContent === null) { return null; } return /*#__PURE__*/React.createElement(LocaleConsumer, { componentName: "Cascader" }, locale => (/*#__PURE__*/React.createElement("ul", { className: `${prefixcls} ${prefixcls}-empty`, key: 'empty-list' }, /*#__PURE__*/React.createElement("span", { className: `${prefixcls}-label`, "x-semi-prop": "emptyContent" }, emptyContent || locale.emptyText)))); } render() { const { data, searchable } = this.props; const { direction } = this.context; const isEmpty = !data || !data.length; let content; const listsCls = cls({ [`${prefixcls}-lists`]: true, [`${prefixcls}-lists-rtl`]: direction === 'rtl', [`${prefixcls}-lists-empty`]: isEmpty }); if (isEmpty) { content = this.renderEmpty(); } else { content = searchable ? this.renderFlattenOption(data) : this.renderItem(data); } return /*#__PURE__*/React.createElement("div", { className: listsCls }, content); } } Item.contextType = ConfigContext; Item.propTypes = { data: PropTypes.array, emptyContent: PropTypes.node, searchable: PropTypes.bool, onItemClick: PropTypes.func, onItemHover: PropTypes.func, multiple: PropTypes.bool, showNext: PropTypes.oneOf([strings.SHOW_NEXT_BY_CLICK, strings.SHOW_NEXT_BY_HOVER]), checkedKeys: PropTypes.object, halfCheckedKeys: PropTypes.object, onItemCheckboxClick: PropTypes.func, separator: PropTypes.string, keyword: PropTypes.string, virtualize: PropTypes.object, expandIcon: PropTypes.node }; Item.defaultProps = { empty: false };