UNPKG

react-box-selection

Version:

React component for making box selections on elements

241 lines (202 loc) 7.31 kB
import React from 'react' import PropTypes from 'prop-types' import './Selection.css' import { getTop, getLeft, clearEventBubble, scroll, isInPath, getParents } from '../utils/utils' class Selection extends React.Component { static propTypes = { cols: PropTypes.number, rows: PropTypes.number, width: PropTypes.number, height: PropTypes.number, gap: PropTypes.number, wrapperScroll: PropTypes.object, itemClass: PropTypes.string, extraClass: PropTypes.array, activeClass: PropTypes.string, onMounted: PropTypes.func, onHovered: PropTypes.func, onLeaved: PropTypes.func, onSelected: PropTypes.func, onSingleSelected: PropTypes.func, } constructor(props) { super(props) this.state = { cols: props.cols || 24, rows: props.rows || 7, width: props.width || 30, height: props.height || 50, margin: props.gap || 0, isMouseDown: false, templates: [...Array(props.rows || 7)].map(item => [...Array(props.cols || 24).keys()]), allItems: [], selectedItems: [], selectedPostions: [], selectEle: { width: 0, height: 0, top: 0, left: 0, display: 'none' } } this.selectionTable = null } addActiveClass = target => { const { activeClass } = this.props target.classList.add(activeClass || 'selection_item_active') } removeActiveClass = target => { const { activeClass } = this.props target.classList.remove(activeClass || 'selection_item_active') } componentDidMount () { const { onMounted } = this.props const table = this.selectionTable const allItems = [...table.getElementsByTagName('td')] onMounted && onMounted(allItems) this.setState({ allItems }) } componentWillUnmount () { this.setState({ allItems: null, selectedItems: null }) this.setState = () => { } } down = e => { clearEventBubble(e) const { selectEle } = this.state const { wrapperScroll } = this.props const { clientX, clientY } = e const top = getTop(e.currentTarget) const left = getLeft(e.currentTarget) const { scrollTop, scrollLeft } = wrapperScroll || scroll() const startX = clientX - left + scrollLeft const startY = clientY - top + scrollTop selectEle.left = startX selectEle.top = startY this.setState({ isMouseDown: true, startX, startY, selectEle }) } move = e => { clearEventBubble(e) const { isMouseDown, selectEle, startX, startY, selectedItems, selectedPostions } = this.state const { wrapperScroll } = this.props if (!isMouseDown) return const top = getTop(e.currentTarget) const left = getLeft(e.currentTarget) const { scrollTop, scrollLeft } = wrapperScroll || scroll() const { clientX, clientY } = e const _x = clientX - left + scrollLeft const _y = clientY - top + scrollTop selectEle.left = _x > startX ? startX - 1 : _x + 1 selectEle.top = _y > startY ? startY - 1 : _y + 1 selectEle.width = Math.abs(_x - startX) selectEle.height = Math.abs(_y - startY) selectEle.display = 'block' const items = [...e.currentTarget.getElementsByTagName('td')] selectedPostions.length = 0 selectedItems.length = 0 items.forEach(item => { this.removeActiveClass(item) if (isInPath(item, selectEle)) { this.addActiveClass(item) selectedPostions.push(item.dataset['position']) selectedItems.push(item) } }) this.setState({ selectEle, selectedItems, selectedPostions }) } up = e => { clearEventBubble(e) const { allItems, selectEle, selectedPostions, selectedItems } = this.state const { onSelected, onSingleSelected } = this.props const target = e.target.tagName === 'TD' ? e.target : getParents(e.target, 'TD') selectEle.left = 0 selectEle.top = 0 selectEle.width = 0 selectEle.height = 0 selectEle.display = 'none' allItems.forEach(item => { this.removeActiveClass(item) }) if (selectedPostions.length === 0) { onSingleSelected && onSingleSelected(target.dataset['position'], target, allItems) } else if (selectedPostions.length === 1) { onSingleSelected && onSingleSelected(selectedPostions[0], selectedItems, allItems) } else { onSelected && onSelected(selectedPostions, selectedItems, allItems) } this.setState({ isMouseDown: false, selectEle, selectedPostions: [] }) } over = e => { clearEventBubble(e) const { onHovered } = this.props const target = e.target.tagName === 'TD' ? e.target : getParents(e.target, 'TD') this.addActiveClass(target) onHovered && onHovered(target.dataset['position'], target) } leave = e => { clearEventBubble(e) const { isMouseDown } = this.state const { onLeaved } = this.props const target = e.target.tagName === 'TD' ? e.target : getParents(e.target, 'TD') !isMouseDown && this.removeActiveClass(target) onLeaved && onLeaved(target.dataset['position'], target) } render () { const { templates, selectEle, width, height, cols, rows, margin } = this.state const { children, itemClass, extraClass } = this.props const self = this return ( <div className='selection_wrapper' style={{ width: cols * width + margin * (1 + cols), height: rows * height + margin * (1 + rows), }} onMouseDown={this.down} onMouseMove={this.move} onMouseUp={this.up} > <div className='selection_element' style={{ ...selectEle }} /> <table className='selection_content' border="1" cellSpacing="0" cellPadding="0" ref={ele => this.selectionTable = ele} > <tbody> { templates.map(function (rows, row) { return ( <tr key={`row-${row}`}> { rows.map(function (col) { const className = `${itemClass || 'selection_item'} ${extraClass ? extraClass.join(' ') : ''}` return ( <td onMouseOver={self.over} onMouseLeave={self.leave} onClick={self.single} className={className} style={{ width, height, margin: `${margin}px ${margin}px 0 ${margin}px` }} key={`col-${col}`} data-position={`${row}-${col}`} > { (children && children[row]) ? children[row][col] || '' : '' } </td> ) }) } </tr> ) }) } </tbody> </table> </div> ) } } export default Selection