UNPKG

@kindly/react-selectable-fast

Version:

Enable other React components to be selectable by drawing a box with your mouse/touch

601 lines 26.7 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spread = (this && this.__spread) || function () { for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i])); return ar; }; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; result["default"] = mod; return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var react_1 = __importStar(require("react")); var utils_1 = require("./utils"); var Context_1 = __importDefault(require("./Context")); var Selectbox_1 = __importDefault(require("./Selectbox")); var SelectableGroup = (function (_super) { __extends(SelectableGroup, _super); function SelectableGroup() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.state = { selectionMode: false }; _this.mouseDownStarted = false; _this.mouseMoveStarted = false; _this.mouseMoved = false; _this.mouseUpStarted = false; _this.selectionStarted = false; _this.deselectionStarted = false; _this.mouseDownData = { selectboxY: 0, selectboxX: 0, target: null }; _this.registry = new Set(); _this.selectedItems = new Set(); _this.selectingItems = new Set(); _this.ignoreCheckCache = new Map(); _this.ignoreList = _this.props.ignoreList.concat(['.selectable-select-all', '.selectable-deselect-all']); _this.disableSelectStartList = _this.props.disableSelectStartList; _this.ignoreListNodes = []; _this.disableSelectStartListNodes = []; _this.selectbox = null; _this.selectableGroup = null; _this.scrollContainer = null; _this.maxScrollTop = 0; _this.maxScrollLeft = 0; _this.scrollBounds = null; _this.updateRegistry = function () { var e_1, _a; var containerScroll = { scrollTop: _this.scrollContainer.scrollTop, scrollLeft: _this.scrollContainer.scrollLeft }; try { for (var _b = __values(_this.registry.values()), _c = _b.next(); !_c.done; _c = _b.next()) { var selectableItem = _c.value; selectableItem.registerSelectable(containerScroll); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_1) throw e_1.error; } } }; _this.registerSelectable = function (selectableItem) { _this.registry.add(selectableItem); if (selectableItem.state.isSelected) { _this.selectedItems.add(selectableItem); } }; _this.unregisterSelectable = function (selectableItem) { _this.registry.delete(selectableItem); _this.selectedItems.delete(selectableItem); _this.selectingItems.delete(selectableItem); _this.props.onSelectionFinish(__spread(_this.selectedItems)); }; _this.updateContainerScroll = function (evt) { var _a = _this.scrollContainer, scrollTop = _a.scrollTop, scrollLeft = _a.scrollLeft; _this.checkScrollTop(evt.clientY, scrollTop); _this.checkScrollBottom(evt.clientY, scrollTop); _this.checkScrollLeft(evt.clientX, scrollLeft); _this.checkScrollRight(evt.clientX, scrollLeft); }; _this.getScrollStep = function (offset) { var _a = _this.props, minimumSpeedFactor = _a.minimumSpeedFactor, scrollSpeed = _a.scrollSpeed; return Math.max(offset, minimumSpeedFactor) * scrollSpeed; }; _this.checkScrollTop = function (clientY, currentTop) { var offset = _this.scrollBounds.top - clientY; if (offset > 0 || clientY < 0) { _this.scrollContainer.scrollTop = currentTop - _this.getScrollStep(offset); } }; _this.checkScrollBottom = function (clientY, currentTop) { var offset = clientY - _this.scrollBounds.bottom; if (offset > 0 || clientY > window.innerHeight) { var newTop = currentTop + _this.getScrollStep(offset); _this.scrollContainer.scrollTop = Math.min(newTop, _this.maxScrollTop); } }; _this.checkScrollLeft = function (clientX, currentLeft) { var offset = _this.scrollBounds.left - clientX; if (offset > 0 || clientX < 0) { var newLeft = currentLeft - _this.getScrollStep(offset); _this.scrollContainer.scrollLeft = newLeft; } }; _this.checkScrollRight = function (clientX, currentLeft) { var offset = clientX - _this.scrollBounds.right; if (offset > 0 || clientX > window.innerWidth) { var newLeft = currentLeft + _this.getScrollStep(offset); _this.scrollContainer.scrollLeft = Math.min(newLeft, _this.maxScrollLeft); } }; _this.updateSelectBox = function (event) { var evt = utils_1.castTouchToMouseEvent(event); _this.updateContainerScroll(evt); if (_this.mouseMoveStarted) { return; } _this.mouseMoveStarted = true; _this.mouseMoved = true; var mouseDownData = _this.mouseDownData; var clientX = evt.clientX, clientY = evt.clientY; var _a = _this.scrollContainer, scrollLeft = _a.scrollLeft, scrollTop = _a.scrollTop; var pointY = clientY - _this.scrollBounds.top + scrollTop; var selectboxY = Math.min(pointY, mouseDownData.selectboxY); var pointX = clientX - _this.scrollBounds.left + scrollLeft; var selectboxX = Math.min(pointX, mouseDownData.selectboxX); _this.selectbox.setState({ x: selectboxX, y: selectboxY, isSelecting: true, width: Math.abs(pointX - mouseDownData.selectboxX), height: Math.abs(pointY - mouseDownData.selectboxY) }, function () { _this.updateSelecting(); _this.props.duringSelection(__spread(_this.selectingItems)); _this.mouseMoveStarted = false; }); }; _this.updateSelecting = function () { var selectboxNode = _this.selectbox.getRef(); if (!selectboxNode) { return; } var selectboxBounds = utils_1.getBoundsForNode(selectboxNode); _this.selectItems(__assign(__assign({}, selectboxBounds), { offsetWidth: selectboxBounds.offsetWidth || 1, offsetHeight: selectboxBounds.offsetHeight || 1 })); }; _this.selectItems = function (selectboxBounds, options) { var e_2, _a; if (options === void 0) { options = {}; } var _b = _this.props, tolerance = _b.tolerance, enableDeselect = _b.enableDeselect, mixedDeselect = _b.mixedDeselect; selectboxBounds.top += _this.scrollContainer.scrollTop; selectboxBounds.left += _this.scrollContainer.scrollLeft; try { for (var _c = __values(_this.registry.values()), _d = _c.next(); !_d.done; _d = _c.next()) { var item = _d.value; _this.processItem({ item: item, selectboxBounds: selectboxBounds, tolerance: tolerance, mixedDeselect: mixedDeselect, enableDeselect: enableDeselect, isFromClick: options && options.isFromClick }); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } finally { if (e_2) throw e_2.error; } } }; _this.clearSelection = function () { var e_3, _a; try { for (var _b = __values(_this.selectedItems.values()), _c = _b.next(); !_c.done; _c = _b.next()) { var item = _c.value; item.setState({ isSelected: false }); _this.selectedItems.delete(item); } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_3) throw e_3.error; } } _this.setState({ selectionMode: false }); _this.props.onSelectionFinish(__spread(_this.selectedItems)); _this.props.onSelectionClear(); }; _this.selectAll = function () { var e_4, _a; _this.updateWhiteListNodes(); _this.updateDisableSelectStartListNodes(); try { for (var _b = __values(_this.registry.values()), _c = _b.next(); !_c.done; _c = _b.next()) { var item = _c.value; if (!_this.isInIgnoreList(item.node) && !item.state.isSelected) { item.setState({ isSelected: true }); _this.selectedItems.add(item); } } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_4) throw e_4.error; } } _this.setState({ selectionMode: true }); _this.props.onSelectionFinish(__spread(_this.selectedItems)); }; _this.mouseDown = function (e) { if (_this.isInDisableSelectStartList(e.target)) { _this.mouseDownStarted = false; return; } var isNotLeftButtonClick = !e.type.includes('touch') && !utils_1.detectMouseButton(e, 1, { allowShiftClick: _this.props.allowShiftClick }); if (_this.mouseDownStarted || _this.props.disabled || isNotLeftButtonClick) { return; } _this.updateWhiteListNodes(); _this.updateDisableSelectStartListNodes(); if (_this.isInIgnoreList(e.target)) { _this.mouseDownStarted = false; return; } if (_this.props.resetOnStart) { _this.clearSelection(); } _this.mouseDownStarted = true; _this.mouseUpStarted = false; var evt = utils_1.castTouchToMouseEvent(e); if (!_this.props.globalMouse && !utils_1.isNodeInRoot(evt.target, _this.selectableGroup)) { var offsetData = utils_1.getBoundsForNode(_this.selectableGroup); var collides = utils_1.doObjectsCollide({ top: offsetData.top, left: offsetData.left, width: 0, height: 0, offsetHeight: offsetData.offsetHeight, offsetWidth: offsetData.offsetWidth }, { top: evt.pageY, left: evt.pageX, width: 0, height: 0, offsetWidth: 0, offsetHeight: 0 }); if (!collides) { return; } } _this.updateRootBounds(); _this.updateRegistry(); _this.mouseDownData = { target: evt.target, selectboxY: evt.clientY - _this.scrollBounds.top + _this.scrollContainer.scrollTop, selectboxX: evt.clientX - _this.scrollBounds.left + _this.scrollContainer.scrollLeft }; evt.preventDefault(); document.addEventListener('mousemove', _this.updateSelectBox); document.addEventListener('touchmove', _this.updateSelectBox); document.addEventListener('mouseup', _this.mouseUp); document.addEventListener('touchend', _this.mouseUp); }; _this.mouseUp = function (event) { var e_5, _a; if (_this.mouseUpStarted) { return; } _this.mouseUpStarted = true; _this.mouseDownStarted = false; _this.removeTempEventListeners(); if (!_this.mouseDownData) { return; } var evt = utils_1.castTouchToMouseEvent(event); var pageX = evt.pageX, pageY = evt.pageY; if (!_this.mouseMoved && utils_1.isNodeInRoot(evt.target, _this.selectableGroup)) { _this.handleClick(evt, pageY, pageX); } else { try { for (var _b = __values(_this.selectingItems.values()), _c = _b.next(); !_c.done; _c = _b.next()) { var item = _c.value; item.setState({ isSelected: true, isSelecting: false }); } } catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_5) throw e_5.error; } } _this.selectedItems = new Set(__spread(_this.selectedItems, _this.selectingItems)); _this.selectingItems.clear(); if (evt.which === 1 && _this.mouseDownData.target === evt.target) { _this.preventEvent(evt.target, 'click'); } _this.selectbox.setState({ isSelecting: false, width: 0, height: 0 }); _this.props.onSelectionFinish(__spread(_this.selectedItems)); } _this.toggleSelectionMode(); _this.cleanUp(); _this.mouseMoved = false; }; _this.keyListener = function (evt) { if (evt.keyCode === 27) { _this.clearSelection(); } }; _this.getGroupRef = function (ref) { _this.selectableGroup = ref; }; _this.getSelectboxRef = function (ref) { _this.selectbox = ref; }; _this.defaultContainerStyle = { position: 'relative' }; _this.contextValue = { selectable: { register: _this.registerSelectable, unregister: _this.unregisterSelectable, selectAll: _this.selectAll, clearSelection: _this.clearSelection, getScrolledContainer: function () { return _this.scrollContainer; } } }; return _this; } SelectableGroup.prototype.componentDidMount = function () { if (this.props.scrollContainer) { this.scrollContainer = document.querySelector(this.props.scrollContainer); } else { this.scrollContainer = this.selectableGroup; } this.selectableGroup.addEventListener('mousedown', this.mouseDown); this.selectableGroup.addEventListener('touchstart', this.mouseDown); if (this.props.deselectOnEsc) { document.addEventListener('keydown', this.keyListener); document.addEventListener('keyup', this.keyListener); } }; SelectableGroup.prototype.componentWillUnmount = function () { this.selectableGroup.removeEventListener('mousedown', this.mouseDown); this.selectableGroup.removeEventListener('touchstart', this.mouseDown); if (this.props.deselectOnEsc) { document.removeEventListener('keydown', this.keyListener); document.removeEventListener('keyup', this.keyListener); } this.removeTempEventListeners(); }; SelectableGroup.prototype.removeTempEventListeners = function () { document.removeEventListener('mousemove', this.updateSelectBox); document.removeEventListener('touchmove', this.updateSelectBox); document.removeEventListener('mouseup', this.mouseUp); document.removeEventListener('touchend', this.mouseUp); }; SelectableGroup.prototype.updateRootBounds = function () { this.scrollBounds = this.scrollContainer.getBoundingClientRect(); this.maxScrollTop = this.scrollContainer.scrollHeight - this.scrollContainer.clientHeight; this.maxScrollLeft = this.scrollContainer.scrollWidth - this.scrollContainer.clientWidth; }; SelectableGroup.prototype.toggleSelectionMode = function () { var _a = this, selectedItems = _a.selectedItems, selectionMode = _a.state.selectionMode; if (selectedItems.size && !selectionMode) { this.setState({ selectionMode: true }); } if (!selectedItems.size && selectionMode) { this.setState({ selectionMode: false }); } }; SelectableGroup.prototype.processItem = function (options) { var item = options.item, tolerance = options.tolerance, selectboxBounds = options.selectboxBounds, enableDeselect = options.enableDeselect, mixedDeselect = options.mixedDeselect, isFromClick = options.isFromClick; if (this.isInIgnoreList(item.node)) { return null; } var isCollided = utils_1.doObjectsCollide(selectboxBounds, item.bounds, tolerance, this.props.delta); var _a = item.state, isSelecting = _a.isSelecting, isSelected = _a.isSelected; if (isFromClick && isCollided) { if (isSelected) { this.selectedItems.delete(item); } else { this.selectedItems.add(item); } item.setState({ isSelected: !isSelected }); return (this.clickedItem = item); } if (!isFromClick && isCollided) { if (isSelected && enableDeselect && (!this.selectionStarted || mixedDeselect)) { item.setState({ isSelected: false }); item.deselected = true; this.deselectionStarted = true; return this.selectedItems.delete(item); } var canSelect = mixedDeselect ? !item.deselected : !this.deselectionStarted; if (!isSelecting && !isSelected && canSelect) { item.setState({ isSelecting: true }); this.selectionStarted = true; this.selectingItems.add(item); return { updateSelecting: true }; } } if (!isFromClick && !isCollided && isSelecting) { if (this.selectingItems.has(item)) { item.setState({ isSelecting: false }); this.selectingItems.delete(item); return { updateSelecting: true }; } } return null; }; SelectableGroup.prototype.isInIgnoreList = function (target) { if (!target) { return; } if (this.ignoreCheckCache.get(target) !== undefined) { return this.ignoreCheckCache.get(target); } var shouldBeIgnored = this.ignoreListNodes.some(function (ignoredNode) { return target === ignoredNode || ignoredNode.contains(target); }); this.ignoreCheckCache.set(target, shouldBeIgnored); return shouldBeIgnored; }; SelectableGroup.prototype.isInDisableSelectStartList = function (target) { if (!target) { return; } return this.disableSelectStartListNodes.some(function (disabledNode) { return target === disabledNode || disabledNode.contains(target); }); }; SelectableGroup.prototype.updateWhiteListNodes = function () { this.ignoreListNodes = Array.from(document.querySelectorAll(this.ignoreList.join(', '))); }; SelectableGroup.prototype.updateDisableSelectStartListNodes = function () { if (this.disableSelectStartList.length > 0) { this.disableSelectStartListNodes = Array.from(document.querySelectorAll(this.disableSelectStartList.join(', '))); } }; SelectableGroup.prototype.preventEvent = function (target, type) { var preventHandler = function (evt) { target.removeEventListener(type, preventHandler, true); evt.preventDefault(); evt.stopPropagation(); }; target.addEventListener(type, preventHandler, true); }; SelectableGroup.prototype.handleClick = function (evt, top, left) { if (!this.props.selectOnClick) { return; } var _a = this.props, clickClassName = _a.clickClassName, allowClickWithoutSelected = _a.allowClickWithoutSelected, onSelectionFinish = _a.onSelectionFinish; var classNames = evt.target.classList || []; var isMouseUpOnClickElement = Array.from(classNames).indexOf(clickClassName) > -1; if (allowClickWithoutSelected || this.selectedItems.size || isMouseUpOnClickElement || evt.ctrlKey) { this.selectItems({ top: top, left: left, width: 0, height: 0, offsetWidth: 0, offsetHeight: 0 }, { isFromClick: true }); onSelectionFinish(__spread(this.selectedItems), this.clickedItem); if (evt.which === 1) { this.preventEvent(evt.target, 'click'); } if (evt.which === 2 || evt.which === 3) { this.preventEvent(evt.target, 'contextmenu'); } } }; SelectableGroup.prototype.cleanUp = function () { var e_6, _a; this.deselectionStarted = false; this.selectionStarted = false; if (this.props.mixedDeselect) { try { for (var _b = __values(this.registry.values()), _c = _b.next(); !_c.done; _c = _b.next()) { var item = _c.value; item.deselected = false; } } catch (e_6_1) { e_6 = { error: e_6_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_6) throw e_6.error; } } } }; SelectableGroup.prototype.render = function () { var selectionMode = this.state.selectionMode; var _a = this.props, _b = _a.component, GroupComponent = _b === void 0 ? 'div' : _b, className = _a.className, style = _a.style, selectionModeClass = _a.selectionModeClass, fixedPosition = _a.fixedPosition, selectboxClassName = _a.selectboxClassName, children = _a.children; return (react_1.default.createElement(Context_1.default.Provider, { value: this.contextValue }, react_1.default.createElement(GroupComponent, { ref: this.getGroupRef, style: Object.assign({}, this.defaultContainerStyle, style), className: className + " " + (selectionMode ? selectionModeClass : '') }, react_1.default.createElement(Selectbox_1.default, { ref: this.getSelectboxRef, className: selectboxClassName, fixedPosition: fixedPosition }), children))); }; SelectableGroup.defaultProps = { clickClassName: '', tolerance: 0, globalMouse: false, ignoreList: [], disableSelectStartList: [], scrollSpeed: 0.25, minimumSpeedFactor: 60, duringSelection: utils_1.noop, onSelectionFinish: utils_1.noop, onSelectionClear: utils_1.noop, allowClickWithoutSelected: true, selectionModeClass: 'in-selection-mode', resetOnStart: false, disabled: false, deselectOnEsc: true, fixedPosition: false, delta: 1, allowShiftClick: false, selectOnClick: true }; return SelectableGroup; }(react_1.Component)); exports.default = SelectableGroup; //# sourceMappingURL=SelectableGroup.js.map