import { reactive, defineComponent, nextTick, computed, watch } from 'vue'; import FilterFilled from '@ant-design/icons-vue/FilterFilled'; import Menu, { SubMenu, MenuItem } from '../menu'; import closest from '../_util/dom-closest'; import classNames from '../_util/classNames'; import shallowequal from '../_util/shallowequal'; import Dropdown from '../dropdown'; import Checkbox from '../checkbox'; import Radio from '../radio'; import FilterDropdownMenuWrapper from './FilterDropdownMenuWrapper'; import { FilterMenuProps } from './interface'; import { isValidElement, findDOMNode } from '../_util/props-util'; import initDefaultProps from '../_util/props-util/initDefaultProps'; import { cloneElement } from '../_util/vnode'; import BaseMixin2 from '../_util/BaseMixin2'; import { generateValueMaps } from './util'; import { Key } from '../_util/type'; function stopPropagation(e) { e.stopPropagation(); } export default defineComponent({ name: 'FilterMenu', mixins: [BaseMixin2], inheritAttrs: false, props: initDefaultProps(FilterMenuProps, { column: {}, }), setup(props) { const sSelectedKeys = computed(() => props.selectedKeys); const sVisible = computed(() => { return 'filterDropdownVisible' in props.column ? props.column.filterDropdownVisible : false; }); const sValueKeys = computed(() => generateValueMaps(props.column.filters)); const state = reactive({ neverShown: false, sSelectedKeys: sSelectedKeys.value, sKeyPathOfSelectedItem: {}, // 记录所有有选中子菜单的祖先菜单 sVisible: sVisible.value, sValueKeys: sValueKeys.value, }); watch(sSelectedKeys, () => { state.sSelectedKeys = sSelectedKeys.value; }); watch(sVisible, () => { state.sVisible = sVisible.value; }); watch(sValueKeys, () => { state.sValueKeys = sValueKeys.value; }); // watchEffect( // () => { // const { column } = nextProps; // if (!shallowequal(preProps.selectedKeys, nextProps.selectedKeys)) { // state.sSelectedKeys = nextProps.selectedKeys; // } // if (!shallowequal((preProps.column || {}).filters, (nextProps.column || {}).filters)) { // state.sValueKeys = generateValueMaps(nextProps.column.filters); // } // if ('filterDropdownVisible' in column) { // state.sVisible = column.filterDropdownVisible; // } // preProps = { ...nextProps }; // }, // { flush: 'sync' }, // ); return state; }, mounted() { const { column } = this; nextTick(() => { this.setNeverShown(column); }); }, updated() { const { column } = this; nextTick(() => { this.setNeverShown(column); }); }, methods: { getDropdownVisible() { return this.neverShown ? false : this.sVisible; }, setNeverShown(column) { const rootNode = findDOMNode(this); const filterBelongToScrollBody = !!closest(rootNode, `.ant-table-scroll`); if (filterBelongToScrollBody) { // When fixed column have filters, there will be two dropdown menus // Filter dropdown menu inside scroll body should never be shown // To fix https://github.com/ant-design/ant-design/issues/5010 and // https://github.com/ant-design/ant-design/issues/7909 this.neverShown = !!column.fixed; } }, setSelectedKeys({ selectedKeys }) { this.setState({ sSelectedKeys: selectedKeys }); }, setVisible(visible: boolean) { const { column } = this; if (!('filterDropdownVisible' in column)) { this.setState({ sVisible: visible }); } if (column.onFilterDropdownVisibleChange) { column.onFilterDropdownVisibleChange(visible); } }, handleClearFilters() { this.setState( { sSelectedKeys: [], }, this.handleConfirm, ); }, handleConfirm() { this.setVisible(false); // Call `setSelectedKeys` & `confirm` in the same time will make filter data not up to date // https://github.com/ant-design/ant-design/issues/12284 this.$forceUpdate(); nextTick(this.confirmFilter2); }, onVisibleChange(visible: boolean) { this.setVisible(visible); const { column } = this.$props; // https://github.com/ant-design/ant-design/issues/17833 if (!visible && !(column.filterDropdown instanceof Function)) { this.confirmFilter2(); } }, handleMenuItemClick(info: { keyPath: Key[]; key: Key }) { const { sSelectedKeys: selectedKeys } = this; if (!info.keyPath || info.keyPath.length <= 1) { return; } const { sKeyPathOfSelectedItem: keyPathOfSelectedItem } = this; if (selectedKeys && selectedKeys.indexOf(info.key) >= 0) { // deselect SubMenu child delete keyPathOfSelectedItem[info.key]; } else { // select SubMenu child keyPathOfSelectedItem[info.key] = info.keyPath; } this.setState({ sKeyPathOfSelectedItem: keyPathOfSelectedItem }); }, hasSubMenu() { const { column: { filters = [] }, } = this; return filters.some(item => !!(item.children && item.children.length > 0)); }, confirmFilter2() { const { column, selectedKeys: propSelectedKeys, confirmFilter } = this.$props; const { sSelectedKeys: selectedKeys, sValueKeys: valueKeys } = this; const { filterDropdown } = column; if (!shallowequal(selectedKeys, propSelectedKeys)) { confirmFilter( column, filterDropdown ? selectedKeys : selectedKeys.map((key: any) => valueKeys[key]).filter(key => key !== undefined), ); } }, renderMenus(items) { const { dropdownPrefixCls, prefixCls } = this.$props; return items.map(item => { if (item.children && item.children.length > 0) { const { sKeyPathOfSelectedItem } = this; const containSelected = Object.keys(sKeyPathOfSelectedItem).some( key => sKeyPathOfSelectedItem[key].indexOf(item.value) >= 0, ); const subMenuCls = classNames(`${prefixCls}-dropdown-submenu`, { [`${dropdownPrefixCls}-submenu-contain-selected`]: containSelected, }); return ( {this.renderMenus(item.children)} ); } return this.renderMenuItem(item); }); }, renderFilterIcon() { const { column, locale, prefixCls, selectedKeys } = this; const filtered = selectedKeys && selectedKeys.length > 0; let filterIcon = column.filterIcon; if (typeof filterIcon === 'function') { filterIcon = filterIcon({ filtered, column }); } const dropdownIconClass = classNames({ [`${prefixCls}-selected`]: 'filtered' in column ? column.filtered : filtered, [`${prefixCls}-open`]: this.getDropdownVisible(), }); if (!filterIcon) { return ( ); } if (filterIcon.length === 1 && isValidElement(filterIcon[0])) { return cloneElement(filterIcon[0], { title: filterIcon.props?.title || locale.filterTitle, onClick: stopPropagation, class: classNames(`${prefixCls}-icon`, dropdownIconClass, filterIcon.props?.class), }); } return ( {filterIcon} ); }, renderMenuItem(item) { const { column } = this; const { sSelectedKeys: selectedKeys } = this; const multiple = 'filterMultiple' in column ? column.filterMultiple : true; const input = multiple ? ( = 0} /> ) : ( = 0} /> ); return ( {input} {item.text} ); }, }, render() { const { sSelectedKeys: originSelectedKeys } = this as any; const { column, locale, prefixCls, dropdownPrefixCls, getPopupContainer } = this; // default multiple selection in filter dropdown const multiple = 'filterMultiple' in column ? column.filterMultiple : true; const dropdownMenuClass = classNames({ [`${dropdownPrefixCls}-menu-without-submenu`]: !this.hasSubMenu(), }); let { filterDropdown } = column; if (filterDropdown instanceof Function) { filterDropdown = filterDropdown({ prefixCls: `${dropdownPrefixCls}-custom`, setSelectedKeys: selectedKeys => this.setSelectedKeys({ selectedKeys }), selectedKeys: originSelectedKeys, confirm: this.handleConfirm, clearFilters: this.handleClearFilters, filters: column.filters, visible: this.getDropdownVisible(), column, }); } const menus = filterDropdown ? ( {filterDropdown} ) : ( {this.renderMenus(column.filters)} ); return ( {this.renderFilterIcon()} ); }, });