UNPKG

react-konva-grid

Version:

Declarative React Canvas Grid primitive for Data table, Pivot table, Excel Worksheets

296 lines 11.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const react_1 = require("react"); const helpers_1 = require("./../helpers"); const types_1 = require("./../types"); const EMPTY_SELECTION = []; /** * useSelection hook to enable selection in datagrid * @param initialSelection */ const useSelection = (options) => { const { gridRef, initialActiveCell = null, initialSelections = [], columnCount = 0, rowCount = 0, } = options || {}; const [activeCell, setActiveCell] = react_1.useState(initialActiveCell); const [selections, setSelections] = react_1.useState(initialSelections); const selectionStart = react_1.useRef(); const selectionEnd = react_1.useRef(); const isSelectionMode = react_1.useRef(); /* New selection */ const newSelection = (start, end = start) => { selectionStart.current = start; selectionEnd.current = end; const bounds = selectionFromStartEnd(start, end); if (!bounds) return; setActiveCell({ rowIndex: bounds.top, columnIndex: bounds.left }); setSelections(EMPTY_SELECTION); }; /* selection object from start, end */ const selectionFromStartEnd = (start, end) => { if (!gridRef) return null; const boundsStart = gridRef.current.getCellBounds(start); const boundsEnd = gridRef.current.getCellBounds(end); return { top: Math.min(boundsStart.top, boundsEnd.top), bottom: Math.max(boundsStart.bottom, boundsEnd.bottom), left: Math.min(boundsStart.left, boundsEnd.left), right: Math.max(boundsStart.right, boundsEnd.right), }; }; /* Modify current selection */ const modifySelection = (coords, setInProgress) => { if (!selectionStart.current) return; selectionEnd.current = coords; const bounds = selectionFromStartEnd(selectionStart.current, coords); if (!bounds) return; /** * 1. Multiple selections on mousedown/mousemove * 2. Move the activeCell to newly selection. Done by appendSelection */ setSelections((prevSelection) => { const len = prevSelection.length; if (!len) { return [{ bounds, inProgress: setInProgress ? true : false }]; } return prevSelection.map((sel, i) => { if (len - 1 === i) { return Object.assign(Object.assign({}, sel), { bounds, inProgress: setInProgress ? true : false }); } return sel; }); }); }; /* Adds a new selection, CMD key */ const appendSelection = (coords) => { selectionStart.current = coords; selectionEnd.current = coords; const bounds = selectionFromStartEnd(coords, coords); if (!bounds) return; setActiveCell({ rowIndex: bounds.top, columnIndex: bounds.left }); setSelections((prev) => [...prev, { bounds }]); }; /** * Triggers a new selection start */ const handleMouseDown = react_1.useCallback((e) => { /* Exit early if grid is not initialized */ if (!gridRef || !gridRef.current) return; /* Activate selection mode */ isSelectionMode.current = true; const { rowIndex, columnIndex } = gridRef.current.getCellCoordsFromOffset(e.clientX, e.clientY); /** * Save the initial Selection in ref * so we can adjust the bounds in mousemove */ const coords = { rowIndex, columnIndex }; /* Shift key */ if (e.nativeEvent.shiftKey) { modifySelection(coords); return; } /* Command or Control key */ if (e.nativeEvent.metaKey || e.nativeEvent.ctrlKey) { appendSelection(coords); return; } /* Trigger new selection */ newSelection(coords); }, []); /** * Mousemove handler */ const handleMouseMove = react_1.useCallback((e) => { /* Exit if user is not in selection mode */ if (!isSelectionMode.current || !gridRef || !selectionEnd.current) return; const { rowIndex, columnIndex } = gridRef.current.getCellCoordsFromOffset(e.clientX, e.clientY); /** * If the user is moving across the Active Cell, lets not add it to selection */ if ((activeCell === null || activeCell === void 0 ? void 0 : activeCell.rowIndex) === rowIndex && (activeCell === null || activeCell === void 0 ? void 0 : activeCell.columnIndex) === columnIndex) return; modifySelection({ rowIndex, columnIndex }, true); }, [activeCell]); /** * Mouse up handler */ const handleMouseUp = react_1.useCallback(() => { /* Reset selection mode */ isSelectionMode.current = false; if (!selections.length) return; /* Update last selection */ setSelections((prevSelection) => { const len = prevSelection.length; return prevSelection.map((sel, i) => { if (len - 1 === i) { return Object.assign(Object.assign({}, sel), { inProgress: false }); } return sel; }); }); }, [selections]); /** * Navigate selection using keyboard * @param direction * @param modify */ const keyNavigate = react_1.useCallback((direction, modify) => { if (!selectionStart.current || !selectionEnd.current || !gridRef || !activeCell) return; var { rowIndex, columnIndex } = modify ? selectionEnd.current : activeCell; const isMergedCell = gridRef === null || gridRef === void 0 ? void 0 : gridRef.current.isMergedCell({ rowIndex, columnIndex, }); const bounds = gridRef.current.getCellBounds({ rowIndex, columnIndex }); switch (direction) { case types_1.Direction.Up: if (isMergedCell) rowIndex = bounds.top; rowIndex = Math.max(rowIndex - 1, 0); break; case types_1.Direction.Down: if (isMergedCell) rowIndex = bounds.bottom; rowIndex = Math.min(rowIndex + 1, rowCount - 1); break; case types_1.Direction.Left: if (isMergedCell) columnIndex = bounds.left; columnIndex = Math.max(columnIndex - 1, 0); break; case types_1.Direction.Right: if (isMergedCell) columnIndex = bounds.right; columnIndex = Math.min(columnIndex + 1, columnCount - 1); break; } const scrollToCell = modify ? selectionEnd.current.rowIndex === rowIndex ? { columnIndex } : { rowIndex } : { rowIndex, columnIndex }; if (modify) { modifySelection({ rowIndex, columnIndex }); } else { newSelection({ rowIndex, columnIndex }); } /* Keep the item in view */ gridRef.current.scrollToItem(scrollToCell); }, [activeCell]); // ⌘A or ⌘+Shift+Space const selectAll = () => { selectionStart.current = { rowIndex: 0, columnIndex: 0 }; modifySelection({ rowIndex: rowCount - 1, columnIndex: columnCount - 1 }); }; // Ctrl+Space const selectColumn = () => { if (!selectionEnd.current || !selectionStart.current) return; selectionStart.current = { rowIndex: 0, columnIndex: selectionStart.current.columnIndex, }; modifySelection({ rowIndex: rowCount - 1, columnIndex: selectionEnd.current.columnIndex, }); }; // Shift+Space const selectRow = () => { if (!selectionEnd.current || !selectionStart.current) return; selectionStart.current = { rowIndex: selectionStart.current.rowIndex, columnIndex: 0, }; modifySelection({ rowIndex: selectionEnd.current.rowIndex, columnIndex: columnCount - 1, }); }; const handleKeyDown = react_1.useCallback((e) => { const isShiftKey = e.nativeEvent.shiftKey; const isMetaKey = e.nativeEvent.ctrlKey || e.nativeEvent.metaKey; switch (e.nativeEvent.which) { case types_1.KeyCodes.Right: keyNavigate(types_1.Direction.Right, isShiftKey); break; case types_1.KeyCodes.Left: keyNavigate(types_1.Direction.Left, isShiftKey); break; // Up case types_1.KeyCodes.Up: keyNavigate(types_1.Direction.Up, isShiftKey); break; case types_1.KeyCodes.Down: keyNavigate(types_1.Direction.Down, isShiftKey); break; case types_1.KeyCodes.A: // Select All if (isMetaKey) { selectAll(); } break; case types_1.KeyCodes.SPACE: if (isMetaKey && isShiftKey) { selectAll(); } else if (isMetaKey) { selectColumn(); } else if (isShiftKey) { selectRow(); } break; case types_1.KeyCodes.Tab: /* Cycle through the selections if selections.length > 0 */ if (selections.length && activeCell && gridRef) { const { bounds } = selections[0]; const activeCellBounds = gridRef.current.getCellBounds(activeCell); const direction = isShiftKey ? types_1.Movement.backwards : types_1.Movement.forwards; const nextCell = helpers_1.findNextCellWithinBounds(activeCellBounds, bounds, direction); if (nextCell) setActiveCell(nextCell); } else { if (isShiftKey) { keyNavigate(types_1.Direction.Left); } else { keyNavigate(types_1.Direction.Right); } } e.preventDefault(); break; } }, [rowCount, columnCount, activeCell, selections]); return { activeCell, selections, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onKeyDown: handleKeyDown, newSelection, setSelections, setActiveCell, }; }; exports.default = useSelection; //# sourceMappingURL=useSelection.js.map