UNPKG

@mui/x-data-grid

Version:

The Community plan edition of the Data Grid components (MUI X).

404 lines (398 loc) 15.8 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.useGridFocus = exports.focusStateInitializer = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var React = _interopRequireWildcard(require("react")); var _utils = require("@mui/utils"); var _gridClasses = require("../../../constants/gridClasses"); var _useGridApiMethod = require("../../utils/useGridApiMethod"); var _useGridLogger = require("../../utils/useGridLogger"); var _useGridApiEventHandler = require("../../utils/useGridApiEventHandler"); var _keyboardUtils = require("../../../utils/keyboardUtils"); var _gridFocusStateSelector = require("./gridFocusStateSelector"); var _gridColumnsSelector = require("../columns/gridColumnsSelector"); var _useGridVisibleRows = require("../../utils/useGridVisibleRows"); var _utils2 = require("../../../utils/utils"); var _gridRowsSelector = require("../rows/gridRowsSelector"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } const focusStateInitializer = state => (0, _extends2.default)({}, state, { focus: { cell: null, columnHeader: null, columnHeaderFilter: null, columnGroupHeader: null }, tabIndex: { cell: null, columnHeader: null, columnHeaderFilter: null, columnGroupHeader: null } }); /** * @requires useGridParamsApi (method) * @requires useGridRows (method) * @requires useGridEditing (event) */ exports.focusStateInitializer = focusStateInitializer; const useGridFocus = (apiRef, props) => { const logger = (0, _useGridLogger.useGridLogger)(apiRef, 'useGridFocus'); const lastClickedCell = React.useRef(null); const publishCellFocusOut = React.useCallback((cell, event) => { if (cell) { // The row might have been deleted if (apiRef.current.getRow(cell.id)) { apiRef.current.publishEvent('cellFocusOut', apiRef.current.getCellParams(cell.id, cell.field), event); } } }, [apiRef]); const setCellFocus = React.useCallback((id, field) => { const focusedCell = (0, _gridFocusStateSelector.gridFocusCellSelector)(apiRef); if (focusedCell?.id === id && focusedCell?.field === field) { return; } apiRef.current.setState(state => { logger.debug(`Focusing on cell with id=${id} and field=${field}`); return (0, _extends2.default)({}, state, { tabIndex: { cell: { id, field }, columnHeader: null, columnHeaderFilter: null, columnGroupHeader: null }, focus: { cell: { id, field }, columnHeader: null, columnHeaderFilter: null, columnGroupHeader: null } }); }); apiRef.current.forceUpdate(); // The row might have been deleted if (!apiRef.current.getRow(id)) { return; } if (focusedCell) { // There's a focused cell but another cell was clicked // Publishes an event to notify that the focus was lost publishCellFocusOut(focusedCell, {}); } apiRef.current.publishEvent('cellFocusIn', apiRef.current.getCellParams(id, field)); }, [apiRef, logger, publishCellFocusOut]); const setColumnHeaderFocus = React.useCallback((field, event = {}) => { const cell = (0, _gridFocusStateSelector.gridFocusCellSelector)(apiRef); publishCellFocusOut(cell, event); apiRef.current.setState(state => { logger.debug(`Focusing on column header with colIndex=${field}`); return (0, _extends2.default)({}, state, { tabIndex: { columnHeader: { field }, columnHeaderFilter: null, cell: null, columnGroupHeader: null }, focus: { columnHeader: { field }, columnHeaderFilter: null, cell: null, columnGroupHeader: null } }); }); apiRef.current.forceUpdate(); }, [apiRef, logger, publishCellFocusOut]); const setColumnHeaderFilterFocus = React.useCallback((field, event = {}) => { const cell = (0, _gridFocusStateSelector.gridFocusCellSelector)(apiRef); publishCellFocusOut(cell, event); apiRef.current.setState(state => { logger.debug(`Focusing on column header filter with colIndex=${field}`); return (0, _extends2.default)({}, state, { tabIndex: { columnHeader: null, columnHeaderFilter: { field }, cell: null, columnGroupHeader: null }, focus: { columnHeader: null, columnHeaderFilter: { field }, cell: null, columnGroupHeader: null } }); }); apiRef.current.forceUpdate(); }, [apiRef, logger, publishCellFocusOut]); const setColumnGroupHeaderFocus = React.useCallback((field, depth, event = {}) => { const cell = (0, _gridFocusStateSelector.gridFocusCellSelector)(apiRef); if (cell) { apiRef.current.publishEvent('cellFocusOut', apiRef.current.getCellParams(cell.id, cell.field), event); } apiRef.current.setState(state => { return (0, _extends2.default)({}, state, { tabIndex: { columnGroupHeader: { field, depth }, columnHeader: null, columnHeaderFilter: null, cell: null }, focus: { columnGroupHeader: { field, depth }, columnHeader: null, columnHeaderFilter: null, cell: null } }); }); apiRef.current.forceUpdate(); }, [apiRef]); const getColumnGroupHeaderFocus = React.useCallback(() => (0, _gridFocusStateSelector.gridFocusColumnGroupHeaderSelector)(apiRef), [apiRef]); const moveFocusToRelativeCell = React.useCallback((id, field, direction) => { let columnIndexToFocus = apiRef.current.getColumnIndex(field); const visibleColumns = (0, _gridColumnsSelector.gridVisibleColumnDefinitionsSelector)(apiRef); const currentPage = (0, _useGridVisibleRows.getVisibleRows)(apiRef, { pagination: props.pagination, paginationMode: props.paginationMode }); const pinnedRows = (0, _gridRowsSelector.gridPinnedRowsSelector)(apiRef); // Include pinned rows as well const currentPageRows = [].concat(pinnedRows.top || [], currentPage.rows, pinnedRows.bottom || []); let rowIndexToFocus = currentPageRows.findIndex(row => row.id === id); if (direction === 'right') { columnIndexToFocus += 1; } else if (direction === 'left') { columnIndexToFocus -= 1; } else { rowIndexToFocus += 1; } if (columnIndexToFocus >= visibleColumns.length) { // Go to next row if we are after the last column rowIndexToFocus += 1; if (rowIndexToFocus < currentPageRows.length) { // Go to first column of the next row if there's one more row columnIndexToFocus = 0; } } else if (columnIndexToFocus < 0) { // Go to previous row if we are before the first column rowIndexToFocus -= 1; if (rowIndexToFocus >= 0) { // Go to last column of the previous if there's one more row columnIndexToFocus = visibleColumns.length - 1; } } rowIndexToFocus = (0, _utils2.clamp)(rowIndexToFocus, 0, currentPageRows.length - 1); const rowToFocus = currentPageRows[rowIndexToFocus]; if (!rowToFocus) { return; } const colSpanInfo = apiRef.current.unstable_getCellColSpanInfo(rowToFocus.id, columnIndexToFocus); if (colSpanInfo && colSpanInfo.spannedByColSpan) { if (direction === 'left' || direction === 'below') { columnIndexToFocus = colSpanInfo.leftVisibleCellIndex; } else if (direction === 'right') { columnIndexToFocus = colSpanInfo.rightVisibleCellIndex; } } columnIndexToFocus = (0, _utils2.clamp)(columnIndexToFocus, 0, visibleColumns.length - 1); const columnToFocus = visibleColumns[columnIndexToFocus]; apiRef.current.setCellFocus(rowToFocus.id, columnToFocus.field); }, [apiRef, props.pagination, props.paginationMode]); const handleCellDoubleClick = React.useCallback(({ id, field }) => { apiRef.current.setCellFocus(id, field); }, [apiRef]); const handleCellKeyDown = React.useCallback((params, event) => { // GRID_CELL_NAVIGATION_KEY_DOWN handles the focus on Enter, Tab and navigation keys if (event.key === 'Enter' || event.key === 'Tab' || event.key === 'Shift' || (0, _keyboardUtils.isNavigationKey)(event.key)) { return; } apiRef.current.setCellFocus(params.id, params.field); }, [apiRef]); const handleColumnHeaderFocus = React.useCallback(({ field }, event) => { if (event.target !== event.currentTarget) { return; } apiRef.current.setColumnHeaderFocus(field, event); }, [apiRef]); const handleColumnGroupHeaderFocus = React.useCallback(({ fields, depth }, event) => { if (event.target !== event.currentTarget) { return; } const focusedColumnGroup = (0, _gridFocusStateSelector.gridFocusColumnGroupHeaderSelector)(apiRef); if (focusedColumnGroup !== null && focusedColumnGroup.depth === depth && fields.includes(focusedColumnGroup.field)) { // This group cell has already been focused return; } apiRef.current.setColumnGroupHeaderFocus(fields[0], depth, event); }, [apiRef]); const handleBlur = React.useCallback((_, event) => { if (event.relatedTarget?.className.includes(_gridClasses.gridClasses.columnHeader)) { return; } logger.debug(`Clearing focus`); apiRef.current.setState(state => (0, _extends2.default)({}, state, { focus: { cell: null, columnHeader: null, columnHeaderFilter: null, columnGroupHeader: null } })); }, [logger, apiRef]); const handleCellMouseDown = React.useCallback(params => { lastClickedCell.current = params; }, []); const handleDocumentClick = React.useCallback(event => { const cellParams = lastClickedCell.current; lastClickedCell.current = null; const focusedCell = (0, _gridFocusStateSelector.gridFocusCellSelector)(apiRef); const canUpdateFocus = apiRef.current.unstable_applyPipeProcessors('canUpdateFocus', true, { event, cell: cellParams }); if (!canUpdateFocus) { return; } if (!focusedCell) { if (cellParams) { apiRef.current.setCellFocus(cellParams.id, cellParams.field); } return; } if (cellParams?.id === focusedCell.id && cellParams?.field === focusedCell.field) { return; } const cellElement = apiRef.current.getCellElement(focusedCell.id, focusedCell.field); if (cellElement?.contains(event.target)) { return; } if (cellParams) { apiRef.current.setCellFocus(cellParams.id, cellParams.field); } else { apiRef.current.setState(state => (0, _extends2.default)({}, state, { focus: { cell: null, columnHeader: null, columnHeaderFilter: null, columnGroupHeader: null } })); apiRef.current.forceUpdate(); // There's a focused cell but another element (not a cell) was clicked // Publishes an event to notify that the focus was lost publishCellFocusOut(focusedCell, event); } }, [apiRef, publishCellFocusOut]); const handleCellModeChange = React.useCallback(params => { if (params.cellMode === 'view') { return; } const cell = (0, _gridFocusStateSelector.gridFocusCellSelector)(apiRef); if (cell?.id !== params.id || cell?.field !== params.field) { apiRef.current.setCellFocus(params.id, params.field); } }, [apiRef]); const handleRowSet = React.useCallback(() => { const cell = (0, _gridFocusStateSelector.gridFocusCellSelector)(apiRef); // If the focused cell is in a row which does not exist anymore, then remove the focus if (cell && !apiRef.current.getRow(cell.id)) { apiRef.current.setState(state => (0, _extends2.default)({}, state, { focus: { cell: null, columnHeader: null, columnHeaderFilter: null, columnGroupHeader: null } })); } }, [apiRef]); const handlePaginationModelChange = (0, _utils.unstable_useEventCallback)(() => { const currentFocusedCell = (0, _gridFocusStateSelector.gridFocusCellSelector)(apiRef); if (!currentFocusedCell) { return; } const currentPage = (0, _useGridVisibleRows.getVisibleRows)(apiRef, { pagination: props.pagination, paginationMode: props.paginationMode }); const rowIsInCurrentPage = currentPage.rows.find(row => row.id === currentFocusedCell.id); if (rowIsInCurrentPage) { return; } const visibleColumns = (0, _gridColumnsSelector.gridVisibleColumnDefinitionsSelector)(apiRef); apiRef.current.setState(state => { return (0, _extends2.default)({}, state, { tabIndex: { cell: { id: currentPage.rows[0].id, field: visibleColumns[0].field }, columnGroupHeader: null, columnHeader: null, columnHeaderFilter: null } }); }); }); const focusApi = { setCellFocus, setColumnHeaderFocus, setColumnHeaderFilterFocus }; const focusPrivateApi = { moveFocusToRelativeCell, setColumnGroupHeaderFocus, getColumnGroupHeaderFocus }; (0, _useGridApiMethod.useGridApiMethod)(apiRef, focusApi, 'public'); (0, _useGridApiMethod.useGridApiMethod)(apiRef, focusPrivateApi, 'private'); React.useEffect(() => { const doc = (0, _utils.unstable_ownerDocument)(apiRef.current.rootElementRef.current); doc.addEventListener('mouseup', handleDocumentClick); return () => { doc.removeEventListener('mouseup', handleDocumentClick); }; }, [apiRef, handleDocumentClick]); (0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'columnHeaderBlur', handleBlur); (0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'cellDoubleClick', handleCellDoubleClick); (0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'cellMouseDown', handleCellMouseDown); (0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'cellKeyDown', handleCellKeyDown); (0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'cellModeChange', handleCellModeChange); (0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'columnHeaderFocus', handleColumnHeaderFocus); (0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'columnGroupHeaderFocus', handleColumnGroupHeaderFocus); (0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'rowsSet', handleRowSet); (0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'paginationModelChange', handlePaginationModelChange); }; exports.useGridFocus = useGridFocus;