UNPKG

antd-virtual-select

Version:

Long list optimization antd select component

499 lines (387 loc) 21.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _react = _interopRequireWildcard(require("react")); var _antd = require("antd"); var _DropDownWrap = _interopRequireDefault(require("./DropDownWrap")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } // 页面实际渲染的下拉菜单数量,实际为 2 * ITEM_ELEMENT_NUMBER var ITEM_ELEMENT_NUMBER = 30; // Select size 配置 var ITEM_HEIGHT_CFG = { small: 24, large: 40, "default": 32 }; var ARROW_CODE = { 40: "down", 38: "up" }; var DROPDOWN_HEIGHT = 224; var Option = _antd.Select.Option; var SuperSelect = /*#__PURE__*/ function (_PureComponent) { _inherits(SuperSelect, _PureComponent); function SuperSelect(props) { var _this; _classCallCheck(this, SuperSelect); _this = _possibleConstructorReturn(this, _getPrototypeOf(SuperSelect).call(this, props)); _defineProperty(_assertThisInitialized(_this), "getItemStyle", function (i) { return { position: "absolute", top: _this.ITEM_HEIGHT * i, width: "100%", height: _this.ITEM_HEIGHT }; }); _defineProperty(_assertThisInitialized(_this), "addEvent", function () { _this.scrollEle = document.querySelector(".".concat(_this.dropdownClassName)); // 下拉菜单未展开时元素不存在 if (!_this.scrollEle) return; _this.scrollEle.addEventListener("scroll", _this.onScroll, false); _this.inputEle = document.querySelector("#".concat(_this.id)); if (!_this.inputEle) return; _this.inputEle.addEventListener("keydown", _this.onKeyDown, false); }); _defineProperty(_assertThisInitialized(_this), "onKeyDown", function (e) { var _ref = e || {}, keyCode = _ref.keyCode; setTimeout(function () { var activeItem = document.querySelector(".".concat(_this.dropdownClassName, " .ant-select-dropdown-menu-item-active")); if (!activeItem) return; var offsetTop = activeItem.offsetTop; var isUp = ARROW_CODE[keyCode] === "up"; var isDown = ARROW_CODE[keyCode] === "down"; // 在所有列表第一行按上键 if (offsetTop - _this.prevTop > DROPDOWN_HEIGHT && isUp) { _this.scrollEle.scrollTo(0, _this.allHeight - DROPDOWN_HEIGHT); _this.prevTop = _this.allHeight; return; } // 在所有列表中最后一行按下键 if (_this.prevTop > offsetTop + DROPDOWN_HEIGHT && isDown) { _this.scrollEle.scrollTo(0, 0); _this.prevTop = 0; return; } _this.prevTop = offsetTop; // 向下滚动到下拉框最后一行时,向下滚动一行的高度 if (offsetTop > _this.scrollEle.scrollTop + DROPDOWN_HEIGHT - _this.ITEM_HEIGHT + 10 && isDown) { _this.scrollEle.scrollTo(0, _this.scrollTop + _this.ITEM_HEIGHT); return; } // 向上滚动到下拉框第一一行时,向上滚动一行的高度 if (offsetTop < _this.scrollEle.scrollTop && isUp) { _this.scrollEle.scrollTo(0, _this.scrollTop - _this.ITEM_HEIGHT); } }, 100); }); _defineProperty(_assertThisInitialized(_this), "onScroll", function () { return _this.throttleByHeight(_this.onScrollReal); }); _defineProperty(_assertThisInitialized(_this), "onScrollReal", function () { _this.allList = _this.getUseChildrenList(); var _this$getStartAndEndI = _this.getStartAndEndIndex(), startIndex = _this$getStartAndEndI.startIndex, endIndex = _this$getStartAndEndI.endIndex; _this.prevScrollTop = _this.scrollTop; // 重新渲染列表组件 Wrap var allHeight = _this.allList.length * _this.ITEM_HEIGHT || 100; _this.wrap.reactList(allHeight, startIndex, endIndex); }); _defineProperty(_assertThisInitialized(_this), "throttleByHeight", function () { _this.scrollTop = _this.scrollEle.scrollTop; // 滚动的高度 var delta = _this.prevScrollTop - _this.scrollTop; delta = delta < 0 ? 0 - delta : delta; delta > _this.reactDelta && _this.onScrollReal(); }); _defineProperty(_assertThisInitialized(_this), "getUseChildrenList", function () { return _this.state.filterChildren || _this.state.children; }); _defineProperty(_assertThisInitialized(_this), "getStartAndEndIndex", function () { // 滚动后显示在列表可视区中的第一个 item 的 index var showIndex = Number((_this.scrollTop / _this.ITEM_HEIGHT).toFixed(0)); var startIndex = showIndex - ITEM_ELEMENT_NUMBER < 0 ? 0 : showIndex - ITEM_ELEMENT_NUMBER / 2; var endIndex = showIndex + ITEM_ELEMENT_NUMBER; return { startIndex: startIndex, endIndex: endIndex }; }); _defineProperty(_assertThisInitialized(_this), "setSuperDrowDownMenu", function (visible) { if (!visible) return; _this.allList = _this.getUseChildrenList(); if (!_this.eventTimer) { _this.eventTimer = setTimeout(function () { return _this.addEvent(); }, 0); } else { var allHeight = _this.allList.length * _this.ITEM_HEIGHT || 100; // 下拉列表单独重新渲染 var _this$getStartAndEndI2 = _this.getStartAndEndIndex(), startIndex = _this$getStartAndEndI2.startIndex, endIndex = _this$getStartAndEndI2.endIndex; setTimeout(function () { _this.wrap && _this.wrap.reactList(allHeight, startIndex, endIndex); }, 0); } }); _defineProperty(_assertThisInitialized(_this), "onDropdownVisibleChange", function (visible) { var onDropdownVisibleChange = _this.props.onDropdownVisibleChange; onDropdownVisibleChange && onDropdownVisibleChange(visible); // 关闭下拉框前清空筛选条件,防止下次打开任然显示筛选后的 options if (!visible) { // 定时器确保关闭后再设置 filterChildren,防止列表刷新闪烁 setTimeout(function () { _this.setState({ filterChildren: null }); }); } else { // 如果已有 value, 设置默认滚动位置 _this.setDefaultScrollTop(); // 设置下拉列表显示数据 _this.setSuperDrowDownMenu(visible); } }); _defineProperty(_assertThisInitialized(_this), "onDeselect", function (value) { var onDeselect = _this.props.onDeselect; onDeselect && onDeselect(value); }); _defineProperty(_assertThisInitialized(_this), "onChange", function (value, opt) { var _this$props = _this.props, showSearch = _this$props.showSearch, onChange = _this$props.onChange, autoClearSearchValue = _this$props.autoClearSearchValue; if (showSearch || _this.isMultiple) { // 搜索模式下选择后是否需要重置搜索状态 if (autoClearSearchValue !== false) { _this.setState({ filterChildren: null }, function () { // 搜索成功后重新设置列表的总高度 _this.setSuperDrowDownMenu(true); }); } } _this.setState({ value: value }); onChange && onChange(value, opt); }); _defineProperty(_assertThisInitialized(_this), "onSearch", function (v) { var _this$props2 = _this.props, showSearch = _this$props2.showSearch, onSearch = _this$props2.onSearch, filterOption = _this$props2.filterOption, children = _this$props2.children; if (showSearch && filterOption !== false) { // 须根据 filterOption(如有该自定义函数)手动 filter 搜索匹配的列表 var filterChildren = null; if (typeof filterOption === "function") { filterChildren = children.filter(function (item) { return filterOption(v, item); }); } else if (filterOption === undefined) { filterChildren = children.filter(function (item) { return _this.filterOption(v, item); }); } // 设置下拉列表显示数据 _this.setState({ filterChildren: v === "" ? null : filterChildren }, function () { setTimeout(function () { // 搜索后需要重置滚动位置到0,防止上次 scrollTop 位置无数据 if (filterChildren) { _this.scrollTop = 0; } // 搜索成功后需要重新设置列表的总高度 _this.setSuperDrowDownMenu(true); }, 0); }); } onSearch && onSearch(v); }); _defineProperty(_assertThisInitialized(_this), "filterOption", function (v, option) { // 自定义过滤对应的 option 属性配置 var filterProps = _this.props.optionFilterProp || "value"; return "".concat(option.props[filterProps]).indexOf(v) >= 0; }); _defineProperty(_assertThisInitialized(_this), "setDefaultScrollTop", function () { var value = _this.state.value; var children = _this.props.children; for (var i = 0; i < children.length; i++) { var item = children[i]; var itemValue = item.props.value; if (itemValue === value || Array.isArray(value) && value.includes(itemValue)) { var _ret = function () { var targetScrollTop = i * _this.ITEM_HEIGHT; setTimeout(function () { _this.scrollEle.scrollTo(0, targetScrollTop); }, 100); return { v: void 0 }; }(); if (_typeof(_ret) === "object") return _ret.v; } } }); _defineProperty(_assertThisInitialized(_this), "removeEvent", function () { if (!_this.scrollEle) return; _this.scrollEle.removeEventListener("scroll", _this.onScroll, false); if (!_this.inputEle) return; _this.inputEle.removeEventListener("keydown", _this.onKeyDown, false); }); var mode = props.mode, defaultValue = props.defaultValue, _value = props.value, optionHeight = props.optionHeight; _this.isMultiple = ["tags", "multiple"].includes(mode); // 设置默认 value var defaultV = _this.isMultiple ? [] : ""; defaultV = _value || defaultValue || defaultV; _this.state = { children: props.children || [], filterChildren: null, // 筛选后的 options,优先显示,所以清除筛选后手动设为 null value: defaultV }; // 下拉菜单项行高 _this.ITEM_HEIGHT = optionHeight || ITEM_HEIGHT_CFG[props.size || "default"]; // 可视区 dom 高度 _this.visibleDomHeight = _this.ITEM_HEIGHT * ITEM_ELEMENT_NUMBER; // 滚动时重新渲染的 scrollTop 判断值,大于 reactDelta 则刷新下拉列表 _this.reactDelta = _this.visibleDomHeight / 3; // 是否拖动滚动条快速滚动状态 _this.isStopReact = false; // 上一次滚动的 scrollTop 值 _this.prevScrollTop = 0; // 上一次按下方向键时 scrollTop 值 _this.prevTop = 0; _this.scrollTop = 0; // className _this.dropdownClassName = "dc".concat(+new Date()); _this.id = "sid".concat(+new Date()); return _this; } _createClass(SuperSelect, [{ key: "componentDidMount", value: function componentDidMount() { var _this2 = this; // defaultOpens=true 时添加滚动事件 setTimeout(function () { _this2.addEvent(); }, 500); } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps) { var _this$props3 = this.props, mode = _this$props3.mode, defaultValue = _this$props3.defaultValue, value = _this$props3.value, children = _this$props3.children; if (prevProps.children !== children) { this.isMultiple = ["tags", "multiple"].includes(mode); this.setState({ children: children || [], filterChildren: null }); } if (prevProps.value !== value) { // 更新时设置默认 value var defaultV = this.isMultiple ? [] : ""; defaultV = value || defaultValue || defaultV; this.setState({ value: defaultV }); } } }, { key: "componentWillUnmount", value: function componentWillUnmount() { this.removeEvent(); } // value 存在时需要滚动到 value 所在位置 // scrollToValue = () => { // if (!this.scrollEle) return; // const { children } = this.props; // const { value } = this.state; // const index = children.findIndex(item => item.key === value) || 0; // const y = this.ITEM_HEIGHT * index; // this.scrollEle.scrollTop = y; // setTimeout(() => { // this.forceUpdate(); // }, 0); // }; }, { key: "render", value: function render() { var _this3 = this; var _this$props4 = this.props, dropdownStyle = _this$props4.dropdownStyle, optionLabelProp = _this$props4.optionLabelProp, notFoundContent = _this$props4.notFoundContent, dropdownClassName = _this$props4.dropdownClassName, props = _objectWithoutProperties(_this$props4, ["dropdownStyle", "optionLabelProp", "notFoundContent", "dropdownClassName"]); this.allList = this.getUseChildrenList(); this.allHeight = this.allList.length * this.ITEM_HEIGHT || 100; var _this$getStartAndEndI3 = this.getStartAndEndIndex(), startIndex = _this$getStartAndEndI3.startIndex, endIndex = _this$getStartAndEndI3.endIndex; dropdownStyle = _objectSpread({ maxHeight: "".concat(DROPDOWN_HEIGHT, "px") }, dropdownStyle, { overflow: "auto", position: "relative" }); var value = this.state.value; // 判断处于 antd Form 中时不自动设置 value var _props = _objectSpread({}, props); // 先删除 value,再手动赋值,防止空 value 影响 placeholder delete _props.value; // value 为空字符会隐藏 placeholder,改为 undefined if (typeof value === "string" && !value) { _props.value = undefined; } else { _props.value = value; } optionLabelProp = optionLabelProp || "children"; return _react["default"].createElement(_antd.Select, _extends({}, _props, { id: this.id, onSearch: this.onSearch, onChange: this.onChange, dropdownClassName: "".concat(this.dropdownClassName, " ").concat(dropdownClassName || ""), optionLabelProp: optionLabelProp, dropdownStyle: dropdownStyle, onDropdownVisibleChange: this.onDropdownVisibleChange, onDeselect: this.onDeselect, ref: function ref(ele) { return _this3.select = ele; }, dropdownRender: function dropdownRender(menu) { if (_this3.allList.length === 0) { return _react["default"].createElement("div", { style: { padding: "5px 12px" } }, notFoundContent); } return _react["default"].createElement(_DropDownWrap["default"], _extends({ startIndex: startIndex, endIndex: endIndex, allHeight: _this3.allHeight, menu: menu, itemHeight: _this3.ITEM_HEIGHT }, { ref: function ref(ele) { _this3.wrap = ele; } })); } }), this.allList); } }]); return SuperSelect; }(_react.PureComponent); SuperSelect.Option = Option; var _default = SuperSelect; exports["default"] = _default;