From db6532c07c25e6257e7f0552db85b24ff46d2d83 Mon Sep 17 00:00:00 2001 From: tjz <415800467@qq.com> Date: Fri, 30 Mar 2018 22:00:55 +0800 Subject: [PATCH] update table --- components/table/SelectionBox.jsx | 75 +++++++ components/table/SelectionBox.tsx | 68 ------- components/table/SelectionCheckboxAll.jsx | 182 +++++++++++++++++ components/table/SelectionCheckboxAll.tsx | 183 ----------------- components/table/filterDropdown.jsx | 229 ++++++++++++++++++++++ components/table/filterDropdown.tsx | 228 --------------------- package-lock.json | 13 ++ package.json | 1 + 8 files changed, 500 insertions(+), 479 deletions(-) create mode 100644 components/table/SelectionBox.jsx delete mode 100644 components/table/SelectionBox.tsx create mode 100644 components/table/SelectionCheckboxAll.jsx delete mode 100644 components/table/SelectionCheckboxAll.tsx create mode 100755 components/table/filterDropdown.jsx delete mode 100755 components/table/filterDropdown.tsx diff --git a/components/table/SelectionBox.jsx b/components/table/SelectionBox.jsx new file mode 100644 index 000000000..b29b85baf --- /dev/null +++ b/components/table/SelectionBox.jsx @@ -0,0 +1,75 @@ + +import Checkbox from '../checkbox' +import Radio from '../radio' +import { SelectionBoxProps } from './interface' +import BaseMixin from '../_util/BaseMixin' +import { getOptionProps } from '../_util/props-util' + +export default { + mixins: [BaseMixin], + name: 'SelectionBox', + props: SelectionBoxProps, + data () { + return { + checked: this.getCheckState(this.$props), + } + }, + + mounted () { + this.subscribe() + }, + + beforeDestroy () { + if (this.unsubscribe) { + this.unsubscribe() + } + }, + methods: { + subscribe () { + const { store } = this + this.unsubscribe = store.subscribe(() => { + const checked = this.getCheckState(this.$props) + this.setState({ checked }) + }) + }, + + getCheckState (props) { + const { store, defaultSelection, rowIndex } = props + let checked = false + if (store.getState().selectionDirty) { + checked = store.getState().selectedRowKeys.indexOf(rowIndex) >= 0 + } else { + checked = (store.getState().selectedRowKeys.indexOf(rowIndex) >= 0 || + defaultSelection.indexOf(rowIndex) >= 0) + } + return checked + }, + }, + + render () { + const { type, rowIndex, ...rest } = getOptionProps(this) + const { checked, $attrs, $listeners } = this + const checkboxProps = { + props: { + checked, + ...rest, + }, + attrs: $attrs, + on: $listeners, + } + if (type === 'radio') { + checkboxProps.props.value = rowIndex + return ( + + ) + } else { + return ( + + ) + } + }, +} diff --git a/components/table/SelectionBox.tsx b/components/table/SelectionBox.tsx deleted file mode 100644 index 17a6f0b60..000000000 --- a/components/table/SelectionBox.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import * as React from 'react'; -import Checkbox from '../checkbox'; -import Radio from '../radio'; -import { SelectionBoxProps, SelectionBoxState } from './interface'; - -export default class SelectionBox extends React.Component { - unsubscribe: () => void; - - constructor(props: SelectionBoxProps) { - super(props); - - this.state = { - checked: this.getCheckState(props), - }; - } - - componentDidMount() { - this.subscribe(); - } - - componentWillUnmount() { - if (this.unsubscribe) { - this.unsubscribe(); - } - } - - subscribe() { - const { store } = this.props; - this.unsubscribe = store.subscribe(() => { - const checked = this.getCheckState(this.props); - this.setState({ checked }); - }); - } - - getCheckState(props: SelectionBoxProps) { - const { store, defaultSelection, rowIndex } = props; - let checked = false; - if (store.getState().selectionDirty) { - checked = store.getState().selectedRowKeys.indexOf(rowIndex) >= 0; - } else { - checked = (store.getState().selectedRowKeys.indexOf(rowIndex) >= 0 || - defaultSelection.indexOf(rowIndex) >= 0); - } - return checked; - } - - render() { - const { type, rowIndex, ...rest } = this.props; - const { checked } = this.state; - - if (type === 'radio') { - return ( - - ); - } else { - return ( - - ); - } - } -} diff --git a/components/table/SelectionCheckboxAll.jsx b/components/table/SelectionCheckboxAll.jsx new file mode 100644 index 000000000..83e9b3abc --- /dev/null +++ b/components/table/SelectionCheckboxAll.jsx @@ -0,0 +1,182 @@ +import Checkbox from '../checkbox' +import Dropdown from '../dropdown' +import Menu from '../menu' +import Icon from '../icon' +import classNames from 'classnames' +import { SelectionCheckboxAllProps } from './interface' +import BaseMixin from '../_util/BaseMixin' + +export default { + props: SelectionCheckboxAllProps, + name: 'SelectionCheckboxAll', + mixins: [BaseMixin], + data () { + const { $props: props } = this + this.defaultSelections = props.hideDefaultSelections ? [] : [{ + key: 'all', + text: props.locale.selectAll, + onSelect: () => {}, + }, { + key: 'invert', + text: props.locale.selectInvert, + onSelect: () => {}, + }] + + this.state = { + checked: this.getCheckState(props), + indeterminate: this.getIndeterminateState(props), + } + }, + + mounted () { + this.subscribe() + }, + + componentWillReceiveProps (nextProps) { + this.setCheckState() + }, + + beforeDestroy () { + if (this.unsubscribe) { + this.unsubscribe() + } + }, + methods: { + subscribe () { + const { store } = this + this.unsubscribe = store.subscribe(() => { + this.setCheckState() + }) + }, + + checkSelection (data, type, byDefaultChecked) { + const { store, getCheckboxPropsByItem, getRecordKey } = this + // type should be 'every' | 'some' + if (type === 'every' || type === 'some') { + return ( + byDefaultChecked + ? data[type]((item, i) => getCheckboxPropsByItem(item, i).defaultChecked) + : data[type]((item, i) => + store.getState().selectedRowKeys.indexOf(getRecordKey(item, i)) >= 0) + ) + } + return false + }, + + setCheckState () { + const checked = this.getCheckState() + const indeterminate = this.getIndeterminateState() + if (checked !== this.checked) { + this.setState({ checked }) + } + if (indeterminate !== this.indeterminate) { + this.setState({ indeterminate }) + } + }, + + getCheckState () { + const { store, data } = this + let checked + if (!data.length) { + checked = false + } else { + checked = store.getState().selectionDirty + ? this.checkSelection(data, 'every', false) + : ( + this.checkSelection(data, 'every', false) || + this.checkSelection(data, 'every', true) + ) + } + return checked + }, + + getIndeterminateState () { + const { store, data } = this + let indeterminate + if (!data.length) { + indeterminate = false + } else { + indeterminate = store.getState().selectionDirty + ? ( + this.checkSelection(data, 'some', false) && + !this.checkSelection(data, 'every', false) + ) + : ((this.checkSelection(data, 'some', false) && + !this.checkSelection(data, 'every', false)) || + (this.checkSelection(data, 'some', true) && + !this.checkSelection(data, 'every', true)) + ) + } + return indeterminate + }, + + handleSelectAllChagne (e) { + const checked = e.target.checked + this.$emit('select', checked ? 'all' : 'removeAll', 0, null) + }, + + renderMenus (selections) { + return selections.map((selection, index) => { + return ( + +
{ this.$emit('select', selection.key, index, selection.onSelect) }} + > + {selection.text} +
+
+ ) + }) + }, + }, + + render () { + const { disabled, prefixCls, selections, getPopupContainer, checked, indeterminate } = this + + const selectionPrefixCls = `${prefixCls}-selection` + + let customSelections = null + + if (selections) { + const newSelections = Array.isArray(selections) ? this.defaultSelections.concat(selections) + : this.defaultSelections + + const menu = ( + + {this.renderMenus(newSelections)} + + ) + + customSelections = newSelections.length > 0 ? ( + + +
+ +
+
+ ) : null + } + + return ( +
+ + {customSelections} +
+ ) + }, +} diff --git a/components/table/SelectionCheckboxAll.tsx b/components/table/SelectionCheckboxAll.tsx deleted file mode 100644 index 8386d1af7..000000000 --- a/components/table/SelectionCheckboxAll.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import * as React from 'react'; -import Checkbox from '../checkbox'; -import Dropdown from '../dropdown'; -import Menu from '../menu'; -import Icon from '../icon'; -import classNames from 'classnames'; -import { SelectionCheckboxAllProps, SelectionCheckboxAllState, SelectionItem } from './interface'; - -export default class SelectionCheckboxAll extends - React.Component, SelectionCheckboxAllState> { - unsubscribe: () => void; - defaultSelections: SelectionItem[]; - - constructor(props: SelectionCheckboxAllProps) { - super(props); - - this.defaultSelections = props.hideDefaultSelections ? [] : [{ - key: 'all', - text: props.locale.selectAll, - onSelect: () => {}, - }, { - key: 'invert', - text: props.locale.selectInvert, - onSelect: () => {}, - }]; - - this.state = { - checked: this.getCheckState(props), - indeterminate: this.getIndeterminateState(props), - }; - } - - componentDidMount() { - this.subscribe(); - } - - componentWillReceiveProps(nextProps: SelectionCheckboxAllProps) { - this.setCheckState(nextProps); - } - - componentWillUnmount() { - if (this.unsubscribe) { - this.unsubscribe(); - } - } - - subscribe() { - const { store } = this.props; - this.unsubscribe = store.subscribe(() => { - this.setCheckState(this.props); - }); - } - - checkSelection(data: T[], type: string, byDefaultChecked: boolean) { - const { store, getCheckboxPropsByItem, getRecordKey } = this.props; - // type should be 'every' | 'some' - if (type === 'every' || type === 'some') { - return ( - byDefaultChecked - ? data[type]((item, i) => getCheckboxPropsByItem(item, i).defaultChecked) - : data[type]((item, i) => - store.getState().selectedRowKeys.indexOf(getRecordKey(item, i)) >= 0) - ); - } - return false; - } - - setCheckState(props: SelectionCheckboxAllProps) { - const checked = this.getCheckState(props); - const indeterminate = this.getIndeterminateState(props); - if (checked !== this.state.checked) { - this.setState({ checked }); - } - if (indeterminate !== this.state.indeterminate) { - this.setState({ indeterminate }); - } - } - - getCheckState(props: SelectionCheckboxAllProps) { - const { store, data } = props; - let checked; - if (!data.length) { - checked = false; - } else { - checked = store.getState().selectionDirty - ? this.checkSelection(data, 'every', false) - : ( - this.checkSelection(data, 'every', false) || - this.checkSelection(data, 'every', true) - ); - - } - return checked; - } - - getIndeterminateState(props: SelectionCheckboxAllProps) { - const { store, data } = props; - let indeterminate; - if (!data.length) { - indeterminate = false; - } else { - indeterminate = store.getState().selectionDirty - ? ( - this.checkSelection(data, 'some', false) && - !this.checkSelection(data, 'every', false) - ) - : ((this.checkSelection(data, 'some', false) && - !this.checkSelection(data, 'every', false)) || - (this.checkSelection(data, 'some', true) && - !this.checkSelection(data, 'every', true)) - ); - } - return indeterminate; - } - - handleSelectAllChagne = (e: React.ChangeEvent) => { - let checked = e.target.checked; - this.props.onSelect(checked ? 'all' : 'removeAll', 0, null); - } - - renderMenus(selections: SelectionItem[]) { - return selections.map((selection, index) => { - return ( - -
{this.props.onSelect(selection.key, index, selection.onSelect); }} - > - {selection.text} -
-
- ); - }); - } - - render() { - const { disabled, prefixCls, selections, getPopupContainer } = this.props; - const { checked, indeterminate } = this.state; - - let selectionPrefixCls = `${prefixCls}-selection`; - - let customSelections: React.ReactNode = null; - - if (selections) { - let newSelections = Array.isArray(selections) ? this.defaultSelections.concat(selections) - : this.defaultSelections; - - const menu = ( - - {this.renderMenus(newSelections)} - - ); - - customSelections = newSelections.length > 0 ? ( - -
- -
-
- ) : null; - } - - return ( -
- - {customSelections} -
- ); - } -} diff --git a/components/table/filterDropdown.jsx b/components/table/filterDropdown.jsx new file mode 100755 index 000000000..cbc15e465 --- /dev/null +++ b/components/table/filterDropdown.jsx @@ -0,0 +1,229 @@ + +import Menu, { SubMenu, Item as MenuItem } from '../vc-menu' +import closest from 'dom-closest' +import classNames from 'classnames' +import Dropdown from '../dropdown' +import Icon from '../icon' +import Checkbox from '../checkbox' +import Radio from '../radio' +import FilterDropdownMenuWrapper from './FilterDropdownMenuWrapper' +import { FilterMenuProps } from './interface' +import { initDefaultProps, cloneElement } from '../_util/props-util' +import BaseMixin from '../_util/BaseMixin' + +export default { + mixins: [BaseMixin], + name: 'FilterMenu', + props: initDefaultProps(FilterMenuProps, { + handleFilter () {}, + column: {}, + }), + + data () { + const visible = ('filterDropdownVisible' in this.column) + ? this.column.filterDropdownVisible : false + + return { + sSelectedKeys: this.selectedKeys, + sKeyPathOfSelectedItem: {}, // 记录所有有选中子菜单的祖先菜单 + sVisible: visible, + } + }, + + mounted () { + const { column } = this + this.$nextTick(() => { + this.setNeverShown(column) + }) + }, + + componentWillReceiveProps (nextProps) { + const { column } = nextProps + this.setNeverShown(column) + const newState = {} + if ('selectedKeys' in nextProps) { + newState.selectedKeys = nextProps.selectedKeys + } + if ('filterDropdownVisible' in column) { + newState.visible = column.filterDropdownVisible + } + if (Object.keys(newState).length > 0) { + this.setState(newState) + } + }, + methods: { + + }, + + setNeverShown (column) { + const rootNode = this.$el + 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) { + 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) + this.confirmFilter() + }, + + onVisibleChange (visible) { + this.setVisible(visible) + if (!visible) { + this.confirmFilter() + } + }, + + confirmFilter () { + if (this.sSelectedKeys !== this.selectedKeys) { + this.confirmFilter(this.column, this.sSelectedKeys) + } + }, + + renderMenuItem (item) { + const { column } = this + const multiple = ('filterMultiple' in column) ? column.filterMultiple : true + const input = multiple ? ( + = 0} /> + ) : ( + = 0} /> + ) + + return ( + + {input} + {item.text} + + ) + }, + + hasSubMenu () { + const { column: { filters = [] }} = this + return filters.some(item => !!(item.children && item.children.length > 0)) + }, + + renderMenus (items) { + 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 = containSelected ? `${this.dropdownPrefixCls}-submenu-contain-selected` : '' + return ( + + {this.renderMenus(item.children)} + + ) + } + return this.renderMenuItem(item) + }) + }, + + handleMenuItemClick (info) { + if (info.keyPath.length <= 1) { + return + } + const keyPathOfSelectedItem = this.sKeyPathOfSelectedItem + if (this.sSelectedKeys.indexOf(info.key) >= 0) { + // deselect SubMenu child + delete keyPathOfSelectedItem[info.key] + } else { + // select SubMenu child + keyPathOfSelectedItem[info.key] = info.keyPath + } + this.setState({ keyPathOfSelectedItem }) + }, + + renderFilterIcon () { + const { column, locale, prefixCls } = this + const filterIcon = column.filterIcon + const dropdownSelectedClass = this.selectedKeys.length > 0 ? `${prefixCls}-selected` : '' + + return filterIcon ? cloneElement(filterIcon, { + title: locale.filterTitle, + className: classNames(filterIcon.className, { + [`${prefixCls}-icon`]: true, + }), + }) : + }, + render () { + 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(), + }) + const menus = column.filterDropdown ? ( + + {column.filterDropdown} + + ) : ( + + + {this.renderMenus(column.filters)} + + + + ) + + return ( + + + {this.renderFilterIcon()} + + ) + }, +} diff --git a/components/table/filterDropdown.tsx b/components/table/filterDropdown.tsx deleted file mode 100755 index 5ff8eb0f2..000000000 --- a/components/table/filterDropdown.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import Menu, { SubMenu, Item as MenuItem } from 'rc-menu'; -import closest from 'dom-closest'; -import classNames from 'classnames'; -import Dropdown from '../dropdown'; -import Icon from '../icon'; -import Checkbox from '../checkbox'; -import Radio from '../radio'; -import FilterDropdownMenuWrapper from './FilterDropdownMenuWrapper'; -import { FilterMenuProps, FilterMenuState, ColumnProps, ColumnFilterItem } from './interface'; - -export default class FilterMenu extends React.Component, FilterMenuState> { - static defaultProps = { - handleFilter() {}, - column: {}, - }; - - neverShown: boolean; - - constructor(props: FilterMenuProps) { - super(props); - - const visible = ('filterDropdownVisible' in props.column) ? - props.column.filterDropdownVisible : false; - - this.state = { - selectedKeys: props.selectedKeys, - keyPathOfSelectedItem: {}, // 记录所有有选中子菜单的祖先菜单 - visible, - }; - } - - componentDidMount() { - const { column } = this.props; - this.setNeverShown(column); - } - - componentWillReceiveProps(nextProps: FilterMenuProps) { - const { column } = nextProps; - this.setNeverShown(column); - const newState = {} as { - selectedKeys: string[]; - visible: boolean; - }; - if ('selectedKeys' in nextProps) { - newState.selectedKeys = nextProps.selectedKeys; - } - if ('filterDropdownVisible' in column) { - newState.visible = column.filterDropdownVisible as boolean; - } - if (Object.keys(newState).length > 0) { - this.setState(newState); - } - } - - setNeverShown = (column: ColumnProps) => { - const rootNode = ReactDOM.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 }: { selectedKeys: string[] }) => { - this.setState({ selectedKeys }); - } - - setVisible(visible: boolean) { - const { column } = this.props; - if (!('filterDropdownVisible' in column)) { - this.setState({ visible }); - } - if (column.onFilterDropdownVisibleChange) { - column.onFilterDropdownVisibleChange(visible); - } - } - - handleClearFilters = () => { - this.setState({ - selectedKeys: [], - }, this.handleConfirm); - } - - handleConfirm = () => { - this.setVisible(false); - this.confirmFilter(); - } - - onVisibleChange = (visible: boolean) => { - this.setVisible(visible); - if (!visible) { - this.confirmFilter(); - } - } - - confirmFilter() { - if (this.state.selectedKeys !== this.props.selectedKeys) { - this.props.confirmFilter(this.props.column, this.state.selectedKeys); - } - } - - renderMenuItem(item: ColumnFilterItem) { - const { column } = this.props; - const multiple = ('filterMultiple' in column) ? column.filterMultiple : true; - const input = multiple ? ( - = 0} /> - ) : ( - = 0} /> - ); - - return ( - - {input} - {item.text} - - ); - } - - hasSubMenu() { - const { column: { filters = [] } } = this.props; - return filters.some(item => !!(item.children && item.children.length > 0)); - } - - renderMenus(items: ColumnFilterItem[]): React.ReactElement[] { - return items.map(item => { - if (item.children && item.children.length > 0) { - const { keyPathOfSelectedItem } = this.state; - const containSelected = Object.keys(keyPathOfSelectedItem).some( - key => keyPathOfSelectedItem[key].indexOf(item.value) >= 0, - ); - const subMenuCls = containSelected ? `${this.props.dropdownPrefixCls}-submenu-contain-selected` : ''; - return ( - - {this.renderMenus(item.children)} - - ); - } - return this.renderMenuItem(item); - }); - } - - handleMenuItemClick = (info: { keyPath: string, key: string }) => { - if (info.keyPath.length <= 1) { - return; - } - const keyPathOfSelectedItem = this.state.keyPathOfSelectedItem; - if (this.state.selectedKeys.indexOf(info.key) >= 0) { - // deselect SubMenu child - delete keyPathOfSelectedItem[info.key]; - } else { - // select SubMenu child - keyPathOfSelectedItem[info.key] = info.keyPath; - } - this.setState({ keyPathOfSelectedItem }); - } - - renderFilterIcon = () => { - const { column, locale, prefixCls } = this.props; - const filterIcon = column.filterIcon as any; - const dropdownSelectedClass = this.props.selectedKeys.length > 0 ? `${prefixCls}-selected` : ''; - - return filterIcon ? React.cloneElement(filterIcon as any, { - title: locale.filterTitle, - className: classNames(filterIcon.className, { - [`${prefixCls}-icon`]: true, - }), - }) : ; - } - render() { - const { column, locale, prefixCls, dropdownPrefixCls, getPopupContainer } = this.props; - // default multiple selection in filter dropdown - const multiple = ('filterMultiple' in column) ? column.filterMultiple : true; - const dropdownMenuClass = classNames({ - [`${dropdownPrefixCls}-menu-without-submenu`]: !this.hasSubMenu(), - }); - const menus = column.filterDropdown ? ( - - {column.filterDropdown} - - ) : ( - - - {this.renderMenus(column.filters!)} - - - - ); - - return ( - - {this.renderFilterIcon()} - - ); - } -} diff --git a/package-lock.json b/package-lock.json index b2cdd9e77..e1b9b05d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3424,6 +3424,14 @@ "resolved": "https://registry.npm.taobao.org/dom-align/download/dom-align-1.6.7.tgz", "integrity": "sha1-aFgTjvtrd0Bc6ZFG0L5eT3KCgT8=" }, + "dom-closest": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-closest/-/dom-closest-0.2.0.tgz", + "integrity": "sha1-69n5HRvyLo1vR3h2u80+yQIWwM8=", + "requires": { + "dom-matches": "2.0.0" + } + }, "dom-converter": { "version": "0.1.4", "resolved": "http://r.cnpmjs.org/dom-converter/download/dom-converter-0.1.4.tgz", @@ -3441,6 +3449,11 @@ } } }, + "dom-matches": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-matches/-/dom-matches-2.0.0.tgz", + "integrity": "sha1-0nKLQWqHUzmA6wibhI0lPPI6dYw=" + }, "dom-scroll-into-view": { "version": "1.2.1", "resolved": "https://registry.npm.taobao.org/dom-scroll-into-view/download/dom-scroll-into-view-1.2.1.tgz", diff --git a/package.json b/package.json index 80b0c5fae..81d5ce805 100644 --- a/package.json +++ b/package.json @@ -131,6 +131,7 @@ "component-classes": "^1.2.6", "css-animation": "^1.4.1", "dom-align": "^1.6.7", + "dom-closest": "^0.2.0", "dom-scroll-into-view": "^1.2.1", "enquire.js": "^2.1.6", "eslint-plugin-vue": "^3.13.0",