UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

265 lines (259 loc) • 10.1 kB
/** * @jsxRuntime classic * @jsx jsx */ import { useCallback, useEffect, useRef, useState } from 'react'; // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports -- Ignored via go/DSP-18766; jsx required at runtime for @jsxRuntime classic import { css, jsx } from '@emotion/react'; import { bind } from 'bind-event-listener'; import { akEditorMenuZIndex } from '@atlaskit/editor-shared-styles'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { withReactEditorViewOuterListeners as withOuterListeners } from '../../ui-react'; import Popup from '../Popup'; import tableSelectorPopup, { TABLE_SELECTOR_BUTTON_GAP, TABLE_SELECTOR_BUTTON_SIZE } from './table-selector'; const TABLE_SELECTOR_PADDING_TOP = 8; const POPUP_OFFSET = [0, 3]; const TABLE_SELECTOR_PADDING_SIDE = 10; const DEFAULT_TABLE_SELECTOR_ROWS = 5; const DEFAULT_TABLE_SELECTOR_COLS = 10; const DEFAULT_TABLE_SELECTOR_SELECTION_SIZE = 1; const DEFAULT_MAX_TABLE_SELECTOR_ROWS = 10; const TableSelectorWithListeners = withOuterListeners(tableSelectorPopup); const initialSizeState = { col: DEFAULT_TABLE_SELECTOR_SELECTION_SIZE, row: DEFAULT_TABLE_SELECTOR_SELECTION_SIZE, maxCol: DEFAULT_TABLE_SELECTOR_COLS, maxRow: DEFAULT_TABLE_SELECTOR_ROWS }; const tableSelectorPopupWrapperStyles = css({ borderRadius: "var(--ds-radius-small, 3px)", backgroundColor: "var(--ds-surface-overlay, #FFFFFF)", boxShadow: "var(--ds-shadow-overlay, 0px 8px 12px #1E1F2126, 0px 0px 1px #1E1F214f)", padding: `${"var(--ds-space-100, 8px)"} ${TABLE_SELECTOR_PADDING_SIDE}px` }); export const TableSelectorPopup = props => { const [size, setSize] = useState({ ...initialSizeState, ...props.defaultSize }); const tablePopupRef = useRef(null); // If popup opened by keyboard enable keyboard mode const isKeyboardMode = useRef(props.isOpenedByKeyboard); const enableKeyboardMode = useCallback(() => { if (!isKeyboardMode.current) { isKeyboardMode.current = true; } }, [isKeyboardMode]); const disableKeyboardMode = useCallback(() => { if (isKeyboardMode.current) { isKeyboardMode.current = false; } }, [isKeyboardMode]); // Mouse move is used to allow selection changes outside of the popup and is more reactive to changes. const handleMouseMove = useCallback(e => { if (!tablePopupRef.current) { return; } disableKeyboardMode(); const tablePopup = tablePopupRef.current; const { left, top } = tablePopup.getBoundingClientRect(); // Mouse position on popup const selectedWidth = e.clientX - left; const selectedHeight = e.clientY - top; // Calculate number grid cells selected let selectedGridCols = Math.ceil((selectedWidth - TABLE_SELECTOR_PADDING_SIDE + TABLE_SELECTOR_BUTTON_GAP) / (TABLE_SELECTOR_BUTTON_GAP + TABLE_SELECTOR_BUTTON_SIZE)); let selectedGridRows = Math.ceil((selectedHeight - TABLE_SELECTOR_PADDING_TOP + TABLE_SELECTOR_BUTTON_GAP) / (TABLE_SELECTOR_BUTTON_GAP + TABLE_SELECTOR_BUTTON_SIZE)); // Keep the selected rows and columns within the defined bounds let gridRows = DEFAULT_TABLE_SELECTOR_ROWS; if (selectedGridCols < 1) { selectedGridCols = 1; } if (selectedGridCols > size.maxCol) { selectedGridCols = size.maxCol; } if (selectedGridRows < 1) { selectedGridRows = 1; } // Expand grid when row selection is greater than the default grid size if (selectedGridRows >= DEFAULT_TABLE_SELECTOR_ROWS && selectedGridRows < DEFAULT_MAX_TABLE_SELECTOR_ROWS) { gridRows = selectedGridRows + 1; } if (selectedGridRows >= DEFAULT_MAX_TABLE_SELECTOR_ROWS) { selectedGridRows = DEFAULT_MAX_TABLE_SELECTOR_ROWS; gridRows = DEFAULT_MAX_TABLE_SELECTOR_ROWS; } if (selectedGridCols !== size.col || selectedGridRows !== size.row) { setSize({ col: selectedGridCols, row: selectedGridRows, maxCol: DEFAULT_TABLE_SELECTOR_COLS, maxRow: gridRows }); } }, [disableKeyboardMode, size, setSize]); const decreasingSequence = (maxNumber, prevNumber) => { let nextNumber = prevNumber - 1; if (prevNumber === 1) { nextNumber = maxNumber; } return nextNumber; }; const getMaxRow = (prevSize, eventKey) => { switch (eventKey) { case 'ArrowDown': // Expand the grid size when last row selected if (prevSize.maxRow < DEFAULT_MAX_TABLE_SELECTOR_ROWS && prevSize.row >= DEFAULT_TABLE_SELECTOR_ROWS - 1) { return prevSize.maxRow + 1; } if (prevSize.row === DEFAULT_MAX_TABLE_SELECTOR_ROWS) { return DEFAULT_TABLE_SELECTOR_ROWS; } return prevSize.maxRow; case 'ArrowLeft': const moveToPrevRow = prevSize.col === 1 && prevSize.row > 1; const moveToLastRow = prevSize.row === 1 && prevSize.col === 1; // Expand the popup to max size when selected row wraps around to last row if (moveToLastRow) { return DEFAULT_MAX_TABLE_SELECTOR_ROWS; } // Decrease the popup when decreased row selection if (prevSize.maxRow > DEFAULT_TABLE_SELECTOR_ROWS && moveToPrevRow) { return prevSize.row; } return prevSize.maxRow; case 'ArrowUp': if (prevSize.row === 1) { return DEFAULT_MAX_TABLE_SELECTOR_ROWS; // Decrease the popup size when decreased row selection } else if (prevSize.maxRow > DEFAULT_TABLE_SELECTOR_ROWS) { return prevSize.row; } return prevSize.maxRow; case 'ArrowRight': const moveToNextRow = prevSize.col === DEFAULT_TABLE_SELECTOR_COLS; const increaseMaxRow = prevSize.maxRow < DEFAULT_MAX_TABLE_SELECTOR_ROWS && moveToNextRow && prevSize.row + 1 === prevSize.maxRow; // Decrease popup size for wrap around to selection 1 x 1 if (prevSize.row === DEFAULT_MAX_TABLE_SELECTOR_ROWS && prevSize.col === DEFAULT_TABLE_SELECTOR_COLS) { return DEFAULT_TABLE_SELECTOR_ROWS; // Decrease the popup size when decreased row selection } else if (increaseMaxRow) { return prevSize.maxRow + 1; } return prevSize.maxRow; default: return prevSize.maxRow; } }; const handleInitialButtonFocus = useCallback(() => { if (isKeyboardMode.current !== true) { enableKeyboardMode(); setSize(initialSizeState); } }, [enableKeyboardMode, setSize]); const handleKeyDown = useCallback( // eslint-disable-next-line @typescript-eslint/no-explicit-any event => { if (event.key === 'ArrowDown') { enableKeyboardMode(); setSize(prevSize => { return { ...prevSize, row: prevSize.row % prevSize.maxRow + 1, maxRow: getMaxRow(prevSize, event.key) }; }); if (editorExperiment('platform_editor_controls', 'variant1')) { event.preventDefault(); } } if (event.key === 'ArrowRight') { enableKeyboardMode(); setSize(prevSize => { const moveToNextRow = prevSize.col === DEFAULT_TABLE_SELECTOR_COLS; return { ...prevSize, col: prevSize.col % DEFAULT_TABLE_SELECTOR_COLS + 1, row: moveToNextRow ? prevSize.row % prevSize.maxRow + 1 : prevSize.row, maxRow: getMaxRow(prevSize, event.key) }; }); } if (event.key === 'ArrowLeft') { enableKeyboardMode(); setSize(prevSize => { const getRow = (prevRow, prevCol) => { const row = prevRow; // Move to previous row for wrap around if (prevSize.col === 1 && prevSize.row > 1) { return prevRow - 1; // Increase the selection to max size when selected row and column wraps around } else if (prevRow === 1 && prevCol === 1) { return DEFAULT_MAX_TABLE_SELECTOR_ROWS; } return row; }; return { ...prevSize, col: decreasingSequence(prevSize.maxCol, prevSize.col), row: getRow(prevSize.row, prevSize.col), maxRow: getMaxRow(prevSize, event.key) }; }); } if (event.key === 'ArrowUp') { enableKeyboardMode(); setSize(prevSize => { const moveToLastRow = prevSize.row === 1; return { ...prevSize, row: moveToLastRow ? DEFAULT_MAX_TABLE_SELECTOR_ROWS : decreasingSequence(prevSize.maxRow, prevSize.row), maxRow: getMaxRow(prevSize, event.key) }; }); if (editorExperiment('platform_editor_controls', 'variant1')) { event.preventDefault(); } } }, [enableKeyboardMode, setSize]); useEffect(() => { let unbind; const target = props.allowOutsideSelection ? window : tablePopupRef.current; if (target) { unbind = bind(target, { type: 'mousemove', listener: handleMouseMove }); } return unbind; }, [handleMouseMove, props.allowOutsideSelection, tablePopupRef]); const offset = expValEquals('platform_editor_perf_lint_cleanup', 'isEnabled', true) ? POPUP_OFFSET : [0, 3]; return jsx(Popup, { target: props.target, offset: offset, mountTo: props.popupsMountPoint, boundariesElement: props.popupsBoundariesElement, scrollableElement: props.popupsScrollableElement, focusTrap: true, onUnmount: props.onUnmount, zIndex: akEditorMenuZIndex }, jsx("div", { css: tableSelectorPopupWrapperStyles, ref: tablePopupRef }, jsx(TableSelectorWithListeners, { handleClickOutside: props.handleClickOutside, handleEscapeKeydown: props.handleEscapeKeydown, maxCols: size.maxCol, maxRows: size.maxRow, onSelection: props.onSelection, selectedCol: size.col, selectedRow: size.row, onKeyDown: handleKeyDown, isFocused: isKeyboardMode.current, handleInitialButtonFocus: handleInitialButtonFocus }))); }; export default TableSelectorPopup;