office-ui-fabric-react
Version:
Reusable React components for building experiences for Office 365.
288 lines • 13.6 kB
JavaScript
import * as tslib_1 from "tslib";
import * as React from 'react';
import { AutoScroll, BaseComponent, classNamesFunction, findScrollableParent, getDistanceBetweenPoints, getRTL, createRef } from '../../Utilities';
var getClassNames = classNamesFunction();
// We want to make the marquee selection start when the user drags a minimum distance. Otherwise we'd start
// the drag even if they just click an item without moving.
var MIN_DRAG_DISTANCE = 5;
/**
* MarqueeSelection component abstracts managing a draggable rectangle which sets items selected/not selected.
* Elements which have data-selectable-index attributes are queried and measured once to determine if they
* fall within the bounds of the rectangle. The measure is memoized during the drag as a performance optimization
* so if the items change sizes while dragging, that could cause incorrect results.
*/
var MarqueeSelectionBase = /** @class */ (function (_super) {
tslib_1.__extends(MarqueeSelectionBase, _super);
function MarqueeSelectionBase(props) {
var _this = _super.call(this, props) || this;
_this._root = createRef();
_this._onMouseDown = function (ev) {
var _a = _this.props, isEnabled = _a.isEnabled, onShouldStartSelection = _a.onShouldStartSelection;
// Ensure the mousedown is within the boundaries of the target. If not, it may have been a click on a scrollbar.
if (_this._isMouseEventOnScrollbar(ev)) {
return;
}
if (_this._isInSelectionToggle(ev)) {
return;
}
if (!_this._isTouch &&
isEnabled &&
!_this._isDragStartInSelection(ev) &&
(!onShouldStartSelection || onShouldStartSelection(ev))) {
if (_this._scrollableSurface && ev.button === 0 && _this._root.current) {
_this._selectedIndicies = {};
_this._preservedIndicies = undefined;
_this._events.on(window, 'mousemove', _this._onAsyncMouseMove);
_this._events.on(_this._scrollableParent, 'scroll', _this._onAsyncMouseMove);
_this._events.on(window, 'click', _this._onMouseUp, true);
_this._autoScroll = new AutoScroll(_this._root.current);
_this._scrollTop = _this._scrollableSurface.scrollTop;
_this._rootRect = _this._root.current.getBoundingClientRect();
_this._onMouseMove(ev);
}
}
};
_this._onTouchStart = function (ev) {
_this._isTouch = true;
_this._async.setTimeout(function () {
_this._isTouch = false;
}, 0);
};
_this._onPointerDown = function (ev) {
if (ev.pointerType === 'touch') {
_this._isTouch = true;
_this._async.setTimeout(function () {
_this._isTouch = false;
}, 0);
}
};
_this.state = {
dragRect: undefined
};
return _this;
}
MarqueeSelectionBase.prototype.componentDidMount = function () {
this._scrollableParent = findScrollableParent(this._root.current);
this._scrollableSurface = this._scrollableParent === window ? document.body : this._scrollableParent;
// When scroll events come from window, we need to read scrollTop values from the body.
var hitTarget = this.props.isDraggingConstrainedToRoot ? this._root.current : this._scrollableSurface;
this._events.on(hitTarget, 'mousedown', this._onMouseDown);
this._events.on(hitTarget, 'touchstart', this._onTouchStart, true);
this._events.on(hitTarget, 'pointerdown', this._onPointerDown, true);
};
MarqueeSelectionBase.prototype.componentWillUnmount = function () {
if (this._autoScroll) {
this._autoScroll.dispose();
}
};
MarqueeSelectionBase.prototype.render = function () {
var _a = this.props, rootProps = _a.rootProps, children = _a.children, theme = _a.theme, className = _a.className, styles = _a.styles;
var dragRect = this.state.dragRect;
var classNames = getClassNames(styles, {
theme: theme,
className: className
});
return (React.createElement("div", tslib_1.__assign({}, rootProps, { className: classNames.root, ref: this._root }),
children,
dragRect && React.createElement("div", { className: classNames.dragMask }),
dragRect && (React.createElement("div", { className: classNames.box, style: dragRect },
React.createElement("div", { className: classNames.boxFill })))));
};
/** Determine if the mouse event occured on a scrollbar of the target element. */
MarqueeSelectionBase.prototype._isMouseEventOnScrollbar = function (ev) {
var targetElement = ev.target;
var targetScrollbarWidth = targetElement.offsetWidth - targetElement.clientWidth;
if (targetScrollbarWidth) {
var targetRect = targetElement.getBoundingClientRect();
// Check vertical scroll
if (getRTL()) {
if (ev.clientX < targetRect.left + targetScrollbarWidth) {
return true;
}
}
else {
if (ev.clientX > targetRect.left + targetElement.clientWidth) {
return true;
}
}
// Check horizontal scroll
if (ev.clientY > targetRect.top + targetElement.clientHeight) {
return true;
}
}
return false;
};
MarqueeSelectionBase.prototype._getRootRect = function () {
return {
left: this._rootRect.left,
top: this._rootRect.top + (this._scrollTop - this._scrollableSurface.scrollTop),
width: this._rootRect.width,
height: this._rootRect.height
};
};
MarqueeSelectionBase.prototype._onAsyncMouseMove = function (ev) {
var _this = this;
this._async.requestAnimationFrame(function () {
_this._onMouseMove(ev);
});
ev.stopPropagation();
ev.preventDefault();
};
MarqueeSelectionBase.prototype._onMouseMove = function (ev) {
if (!this._autoScroll) {
return;
}
if (ev.clientX !== undefined) {
this._lastMouseEvent = ev;
}
var rootRect = this._getRootRect();
var currentPoint = { x: ev.clientX - rootRect.left, y: ev.clientY - rootRect.top };
if (!this._dragOrigin) {
this._dragOrigin = currentPoint;
}
if (ev.buttons !== undefined && ev.buttons === 0) {
this._onMouseUp(ev);
}
else {
if (this.state.dragRect || getDistanceBetweenPoints(this._dragOrigin, currentPoint) > MIN_DRAG_DISTANCE) {
if (!this.state.dragRect) {
var selection = this.props.selection;
this._preservedIndicies = selection && selection.getSelectedIndices && selection.getSelectedIndices();
}
// We need to constrain the current point to the rootRect boundaries.
var constrainedPoint = this.props.isDraggingConstrainedToRoot
? {
x: Math.max(0, Math.min(rootRect.width, this._lastMouseEvent.clientX - rootRect.left)),
y: Math.max(0, Math.min(rootRect.height, this._lastMouseEvent.clientY - rootRect.top))
}
: {
x: this._lastMouseEvent.clientX - rootRect.left,
y: this._lastMouseEvent.clientY - rootRect.top
};
var dragRect = {
left: Math.min(this._dragOrigin.x, constrainedPoint.x),
top: Math.min(this._dragOrigin.y, constrainedPoint.y),
width: Math.abs(constrainedPoint.x - this._dragOrigin.x),
height: Math.abs(constrainedPoint.y - this._dragOrigin.y)
};
this._evaluateSelection(dragRect, rootRect);
this.setState({ dragRect: dragRect });
}
}
return false;
};
MarqueeSelectionBase.prototype._onMouseUp = function (ev) {
this._events.off(window);
this._events.off(this._scrollableParent, 'scroll');
if (this._autoScroll) {
this._autoScroll.dispose();
}
this._autoScroll = this._dragOrigin = this._lastMouseEvent = this._selectedIndicies = this._itemRectCache = undefined;
if (this.state.dragRect) {
this.setState({
dragRect: undefined
});
ev.preventDefault();
ev.stopPropagation();
}
};
MarqueeSelectionBase.prototype._isPointInRectangle = function (rectangle, point) {
return (rectangle.top < point.y && rectangle.bottom > point.y && rectangle.left < point.x && rectangle.right > point.x);
};
/**
* We do not want to start the marquee if we're trying to marquee
* from within an existing marquee selection.
*/
MarqueeSelectionBase.prototype._isDragStartInSelection = function (ev) {
var selection = this.props.selection;
if (!this._root.current || (selection && selection.getSelectedCount() === 0)) {
return false;
}
var allElements = this._root.current.querySelectorAll('[data-selection-index]');
for (var i = 0; i < allElements.length; i++) {
var element = allElements[i];
var index = Number(element.getAttribute('data-selection-index'));
if (selection.isIndexSelected(index)) {
var itemRect = element.getBoundingClientRect();
if (this._isPointInRectangle(itemRect, { x: ev.x, y: ev.y })) {
return true;
}
}
}
return false;
};
MarqueeSelectionBase.prototype._isInSelectionToggle = function (ev) {
var element = ev.target;
while (element && element !== this._root.current) {
if (element.getAttribute('data-selection-toggle') === 'true') {
return true;
}
element = element.parentElement;
}
return false;
};
MarqueeSelectionBase.prototype._evaluateSelection = function (dragRect, rootRect) {
// Break early if we don't need to evaluate.
if (!dragRect || !this._root.current) {
return;
}
var selection = this.props.selection;
var allElements = this._root.current.querySelectorAll('[data-selection-index]');
if (!this._itemRectCache) {
this._itemRectCache = {};
}
// Stop change events, clear selection to re-populate.
selection.setChangeEvents(false);
selection.setAllSelected(false);
for (var i = 0; i < allElements.length; i++) {
var element = allElements[i];
var index = element.getAttribute('data-selection-index');
// Pull the memoized rectangle for the item, or the get the rect and memoize.
var itemRect = this._itemRectCache[index];
if (!itemRect) {
itemRect = element.getBoundingClientRect();
// Normalize the item rect to the dragRect coordinates.
itemRect = {
left: itemRect.left - rootRect.left,
top: itemRect.top - rootRect.top,
width: itemRect.width,
height: itemRect.height,
right: itemRect.left - rootRect.left + itemRect.width,
bottom: itemRect.top - rootRect.top + itemRect.height
};
if (itemRect.width > 0 && itemRect.height > 0) {
this._itemRectCache[index] = itemRect;
}
}
if (itemRect.top < dragRect.top + dragRect.height &&
itemRect.bottom > dragRect.top &&
itemRect.left < dragRect.left + dragRect.width &&
itemRect.right > dragRect.left) {
this._selectedIndicies[index] = true;
}
else {
delete this._selectedIndicies[index];
}
}
for (var index in this._selectedIndicies) {
if (this._selectedIndicies.hasOwnProperty(index)) {
selection.setIndexSelected(Number(index), true, false);
}
}
if (this._preservedIndicies) {
for (var _i = 0, _a = this._preservedIndicies; _i < _a.length; _i++) {
var index = _a[_i];
selection.setIndexSelected(index, true, false);
}
}
selection.setChangeEvents(true);
};
MarqueeSelectionBase.defaultProps = {
rootTagName: 'div',
rootProps: {},
isEnabled: true
};
return MarqueeSelectionBase;
}(BaseComponent));
export { MarqueeSelectionBase };
//# sourceMappingURL=MarqueeSelection.base.js.map