UNPKG

@sap-ux/ui-components

Version:
222 lines 8.81 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.UISplitter = exports.UISplitterLayoutType = exports.UISplitterType = void 0; const react_1 = __importDefault(require("react")); require("./UISplitter.scss"); const Icons_1 = require("../Icons"); const UIIcon_1 = require("../UIIcon"); var UISplitterType; (function (UISplitterType) { UISplitterType["Resize"] = "resize"; UISplitterType["Toggle"] = "toggle"; })(UISplitterType || (exports.UISplitterType = UISplitterType = {})); var UISplitterLayoutType; (function (UISplitterLayoutType) { UISplitterLayoutType["Standard"] = "standard"; UISplitterLayoutType["Compact"] = "compact"; })(UISplitterLayoutType || (exports.UISplitterLayoutType = UISplitterLayoutType = {})); /** * */ class UISplitter extends react_1.default.Component { /** * * @param props */ constructor(props) { super(props); this.mousedownPosition = { x: 0, y: 0 }; this.size = 14; this.compactSize = 8; this.rootRef = react_1.default.createRef(); this.stopMousemoveResize = this.stopMousemoveResize.bind(this); this.doMousemoveResize = this.doMousemoveResize.bind(this); // Splitter overlay DOM for block dom during resize this.splitterOverlay = document.createElement('div'); this.splitterOverlay.setAttribute('class', `splitter__overlay ${props.vertical ? 'splitter__overlay--vertical' : 'splitter__overlay--horizontal'}`); } /** * Method called on mousedown over splitter. * Method would start resize session. * * @param {React.MouseEvent} event Mouse. */ startSplitterMove(event) { this.mousedownPosition.x = event.clientX; this.mousedownPosition.y = event.clientY; // Update DOM this.updateSplitterEvents(true); // Stop event bubbling event.stopPropagation(); } /** * Method called when resize session is in progress and user moves mouse. * * @param {React.MouseEvent} event Mouse event. */ doMousemoveResize(event) { const propertyName = this.props.vertical ? 'clientY' : 'clientX'; const coordinateNameName = this.props.vertical ? 'y' : 'x'; const newPosition = event[propertyName] - this.mousedownPosition[coordinateNameName]; this.doResize(newPosition); } /** * Method which receives new delta position of splitter and updates DOM with calling callback. * * @param {number} deltaPosition Delta position of splitter. */ doResize(deltaPosition) { const cssPropertyName = this.props.vertical ? 'top' : 'left'; if (this.animationFrame) { window.cancelAnimationFrame(this.animationFrame); } this.animationFrame = window.requestAnimationFrame(() => { // Call callback if (this.props.onResize(deltaPosition)) { // Update resizer DOM const dom = this.rootRef.current; if (dom) { dom.style[cssPropertyName] = `${deltaPosition - this.size / 2}px`; } } }); } /** * Method called on mouseup or mouseleave events. * Method would end resize session. */ stopMousemoveResize() { this.updateSplitterEvents(false); } /** * Method to update splitter DOM and events depending on if resize started or ended. * * @param {boolean} start Resize is started or ended. */ updateSplitterEvents(start) { if (start) { document.body.addEventListener('mouseup', this.stopMousemoveResize); document.body.addEventListener('mouseleave', this.stopMousemoveResize); document.body.addEventListener('mousemove', this.doMousemoveResize); document.body.appendChild(this.splitterOverlay); } else { document.body.removeEventListener('mouseup', this.stopMousemoveResize); document.body.removeEventListener('mouseleave', this.stopMousemoveResize); document.body.removeEventListener('mousemove', this.doMousemoveResize); document.body.removeChild(this.splitterOverlay); } if (start && this.props.onResizeStart) { this.props.onResizeStart(); } else if (!start && this.props.onResizeEnd) { this.props.onResizeEnd(); } } /** * Method called when clicked over splitter and splitter type is 'Toggle'. */ toggleSplitter() { if (this.props.onToggle) { this.props.onToggle(); } } /** * Gets icon. * * @param type * @param splitterLayoutType * @returns {string} */ getIcon(type, splitterLayoutType = UISplitterLayoutType.Standard) { if (type === UISplitterType.Toggle) { return Icons_1.UiIcons.ArrowLeft; } else if (splitterLayoutType === UISplitterLayoutType.Compact) { return Icons_1.UiIcons.Grabber; } else { return Icons_1.UiIcons.VerticalGrip; } } /** * Method called when keydown events fired while splitter is focused. * Method enables support for resize using keyboard. * * @param {React.KeyboardEvent<HTMLDivElement>} event KeyDown event. */ onKeyDown(event) { if (this.props.type === UISplitterType.Resize) { const stepSize = 10; let deltaPosition = 0; if (['ArrowLeft', 'ArrowUp'].includes(event.key)) { deltaPosition = -stepSize; } else if (['ArrowRight', 'ArrowDown'].includes(event.key)) { deltaPosition = stepSize; } if (deltaPosition === 0) { // There no resize with keyboard return; } // Do full resize cycle - start, move and end if (this.props.onResizeStart) { this.props.onResizeStart(); } this.doResize(deltaPosition); if (this.props.onResizeEnd) { this.props.onResizeEnd(); } } else if (this.props.type === UISplitterType.Toggle && event.key === 'Enter') { this.toggleSplitter(); } } /** * Method returns class names string depending on props and component state. * * @returns {number} Minimal size of section. */ getClassNames() { const { type, vertical, hidden, splitterLayoutType } = this.props; let classNames = `splitter splitter--${type}`; // vertical or horizontal classNames += ` ${vertical ? 'splitter--vertical' : 'splitter--horizontal'}`; if (hidden && type === UISplitterType.Resize) { classNames += ' splitter--hidden'; } classNames += ` ${splitterLayoutType === UISplitterLayoutType.Standard ? 'splitter--standard' : 'splitter--compact'}`; return classNames; } /** * @returns {React.ReactElement} */ render() { const { type, vertical, hidden, title, splitterLayoutType } = this.props; const tabIndex = this.props.splitterTabIndex ?? 0; const size = splitterLayoutType === UISplitterLayoutType.Standard ? this.size : this.compactSize; const splitterOffset = type === UISplitterType.Toggle ? -size : -size / 2; const role = type === UISplitterType.Toggle ? 'button' : 'separator'; const orientation = vertical ? 'horizontal' : 'vertical'; let ariaPressed; if (type === UISplitterType.Toggle) { ariaPressed = !hidden; } return (react_1.default.createElement("div", { ref: this.rootRef, role: role, "aria-orientation": orientation, "aria-pressed": ariaPressed, title: title, tabIndex: tabIndex, onKeyDown: this.onKeyDown.bind(this), style: { ...(vertical && { height: size, top: splitterOffset }), ...(!vertical && { width: size, left: splitterOffset }) }, onMouseDown: type === UISplitterType.Resize ? (event) => this.startSplitterMove(event) : undefined, onClick: type === UISplitterType.Toggle ? () => this.toggleSplitter() : undefined, className: this.getClassNames() }, react_1.default.createElement("div", { className: "splitter__grip" }, react_1.default.createElement(UIIcon_1.UIIcon, { iconName: this.getIcon(type, splitterLayoutType) })))); } } exports.UISplitter = UISplitter; //# sourceMappingURL=UISplitter.js.map