From a6e069b74dc3ff35b05f0bc982750ace44f8f33d Mon Sep 17 00:00:00 2001 From: Gareth Beukes Date: Tue, 24 Feb 2026 11:30:18 +0200 Subject: [PATCH 1/6] Add shortcuts / keyboard controls for opening the context menu for selections and headers. --- packages/core/src/locale/en.ts | 5 + packages/core/src/locale/es.ts | 5 + packages/core/src/locale/hi.ts | 5 + packages/core/src/locale/ru.ts | 6 + packages/core/src/locale/zh.ts | 5 + packages/core/src/locale/zh_tw.ts | 5 + .../react/src/components/Workbook/index.tsx | 112 +++++++++++++++++- 7 files changed, 142 insertions(+), 1 deletion(-) diff --git a/packages/core/src/locale/en.ts b/packages/core/src/locale/en.ts index 83f20867..197b0d45 100644 --- a/packages/core/src/locale/en.ts +++ b/packages/core/src/locale/en.ts @@ -11094,6 +11094,11 @@ export default { confirmCellEditShortcut: "Confirm cell edit and move down: Enter.", moveRightShortcut: "Move right: Tab.", moveLeftShortcut: "Move left: Shift, Tab.", + contextMenuShortcut: "Open context menu: Control or Meta key, Shift, M.", + rowContextMenuShortcut: + "Open row header context menu: Control or Meta key, Shift, R.", + columnContextMenuShortcut: + "Open column header context menu: Control or Meta key, Shift, L.", shortcuts: "Keyboard Shortcuts", }, currencyDetail: [ diff --git a/packages/core/src/locale/es.ts b/packages/core/src/locale/es.ts index 74887570..5f39945e 100644 --- a/packages/core/src/locale/es.ts +++ b/packages/core/src/locale/es.ts @@ -11069,6 +11069,11 @@ export default { "Confirmar edición de celda y mover hacia abajo: Enter.", moveRightShortcut: "Mover a la derecha: Tab.", moveLeftShortcut: "Mover a la izquierda: Shift, Tab.", + contextMenuShortcut: "Abrir menú contextual: Control o Meta, Shift, M.", + rowContextMenuShortcut: + "Abrir menú contextual de fila: Control o Meta, Shift, R.", + columnContextMenuShortcut: + "Abrir menú contextual de columna: Control o Meta, Shift, L.", shortcuts: "Atajos de teclado", }, currencyDetail: { diff --git a/packages/core/src/locale/hi.ts b/packages/core/src/locale/hi.ts index dd8d9003..dd8cd386 100644 --- a/packages/core/src/locale/hi.ts +++ b/packages/core/src/locale/hi.ts @@ -11099,6 +11099,11 @@ export default { "Confirmar edición de celda y mover hacia abajo: Enter.", moveRightShortcut: "Mover a la derecha: Tab.", moveLeftShortcut: "Mover a la izquierda: Shift, Tab.", + contextMenuShortcut: "Open context menu: Control or Meta, Shift, M.", + rowContextMenuShortcut: + "Open row header context menu: Control or Meta, Shift, R.", + columnContextMenuShortcut: + "Open column header context menu: Control or Meta, Shift, L.", shortcuts: "कुंजीपटल अल्प मार्ग", }, currencyDetail: [ diff --git a/packages/core/src/locale/ru.ts b/packages/core/src/locale/ru.ts index 97c074e4..5e362da4 100644 --- a/packages/core/src/locale/ru.ts +++ b/packages/core/src/locale/ru.ts @@ -12975,6 +12975,12 @@ export default { "Подтвердить редактирование ячейки и перейти вниз: Enter.", moveRightShortcut: "Перейти вправо: Tab.", moveLeftShortcut: "Перейти влево: Shift, Tab.", + contextMenuShortcut: + "Открыть контекстное меню: Control или Meta, Shift, M.", + rowContextMenuShortcut: + "Открыть контекстное меню заголовка строки: Control или Meta, Shift, R.", + columnContextMenuShortcut: + "Открыть контекстное меню заголовка столбца: Control или Meta, Shift, L.", shortcuts: "Горячие клавиши", }, currencyDetail: [ diff --git a/packages/core/src/locale/zh.ts b/packages/core/src/locale/zh.ts index 8def8cd7..87d34567 100644 --- a/packages/core/src/locale/zh.ts +++ b/packages/core/src/locale/zh.ts @@ -11075,6 +11075,11 @@ export default { confirmCellEditShortcut: "确认编辑并向下移动: Enter。", moveRightShortcut: "向右移动: Tab。", moveLeftShortcut: "向左移动: Shift, Tab。", + contextMenuShortcut: "打开上下文菜单: Control 或 Meta 键, Shift, M。", + rowContextMenuShortcut: + "打开行标题上下文菜单: Control 或 Meta 键, Shift, R。", + columnContextMenuShortcut: + "打开列标题上下文菜单: Control 或 Meta 键, Shift, L。", shortcuts: "键盘快捷键", }, currencyDetail: [ diff --git a/packages/core/src/locale/zh_tw.ts b/packages/core/src/locale/zh_tw.ts index ab6f1291..ced6cf1b 100644 --- a/packages/core/src/locale/zh_tw.ts +++ b/packages/core/src/locale/zh_tw.ts @@ -11042,6 +11042,11 @@ export default { confirmCellEditShortcut: "確認編輯並向下移動: Enter。", moveRightShortcut: "向右移動: Tab。", moveLeftShortcut: "向左移動: Shift, Tab。", + contextMenuShortcut: "開啟內容選單: Control 或 Meta 鍵, Shift, M。", + rowContextMenuShortcut: + "開啟列標題內容選單: Control 或 Meta 鍵, Shift, R。", + columnContextMenuShortcut: + "開啟欄標題內容選單: Control 或 Meta 鍵, Shift, L。", shortcuts: "鍵盤快速鍵", }, currencyDetail: { diff --git a/packages/react/src/components/Workbook/index.tsx b/packages/react/src/components/Workbook/index.tsx index d91e2bfc..3f55ee7d 100644 --- a/packages/react/src/components/Workbook/index.tsx +++ b/packages/react/src/components/Workbook/index.tsx @@ -10,6 +10,7 @@ import { handleGlobalKeyDown, getSheetIndex, handlePaste, + handleContextMenu, filterPatch, patchToOp, Op, @@ -604,6 +605,112 @@ const Workbook = React.forwardRef( const onKeyDown = useCallback( (e: React.KeyboardEvent) => { + const isContextMenuShortcut = + (e.ctrlKey || e.metaKey) && e.shiftKey && e.code === "KeyM"; + const isRowContextMenuShortcut = + (e.ctrlKey || e.metaKey) && e.shiftKey && e.code === "KeyR"; + const isColumnContextMenuShortcut = + (e.ctrlKey || e.metaKey) && e.shiftKey && e.code === "KeyL"; + if ( + (isContextMenuShortcut || + isRowContextMenuShortcut || + isColumnContextMenuShortcut) && + context.allowEdit + ) { + const selected = + context.luckysheet_select_save?.[ + context.luckysheet_select_save.length - 1 + ]; + if (selected && workbookContainer.current && cellArea.current) { + const workbookRect = + workbookContainer.current.getBoundingClientRect(); + const cellAreaRect = cellArea.current.getBoundingClientRect(); + const selectionLeft = selected.left ?? 0; + const selectionTop = selected.top ?? 0; + let area: "cell" | "rowHeader" | "columnHeader" = "cell"; + let pageX = + window.scrollX + workbookRect.left + context.rowHeaderWidth; + let pageY = + window.scrollY + workbookRect.top + context.columnHeaderHeight; + if (isRowContextMenuShortcut) { + area = "rowHeader"; + pageX = + window.scrollX + workbookRect.left + context.rowHeaderWidth / 2; + pageY = + window.scrollY + + cellAreaRect.top + + selectionTop - + context.scrollTop + + 1; + } else if (isColumnContextMenuShortcut) { + area = "columnHeader"; + pageX = + window.scrollX + + cellAreaRect.left + + selectionLeft - + context.scrollLeft + + 1; + pageY = + window.scrollY + + workbookRect.top + + context.columnHeaderHeight / 2; + } else if (selected.row_select) { + area = "rowHeader"; + pageX = + window.scrollX + workbookRect.left + context.rowHeaderWidth / 2; + pageY = + window.scrollY + + cellAreaRect.top + + selectionTop - + context.scrollTop + + 1; + } else if (selected.column_select) { + area = "columnHeader"; + pageX = + window.scrollX + + cellAreaRect.left + + selectionLeft - + context.scrollLeft + + 1; + pageY = + window.scrollY + + workbookRect.top + + context.columnHeaderHeight / 2; + } else { + pageX = + window.scrollX + + cellAreaRect.left + + selectionLeft - + context.scrollLeft + + 1; + pageY = + window.scrollY + + cellAreaRect.top + + selectionTop - + context.scrollTop + + 1; + } + const syntheticContextEvent = new MouseEvent("contextmenu", { + bubbles: true, + cancelable: true, + clientX: pageX - window.scrollX, + clientY: pageY - window.scrollY, + }); + setContextWithProduce((draftCtx) => { + handleContextMenu( + draftCtx, + mergedSettings, + syntheticContextEvent, + workbookContainer.current!, + cellArea.current!, + area + ); + }); + e.preventDefault(); + e.stopPropagation(); + return; + } + } const { nativeEvent } = e; // handling undo and redo ahead because handleUndo and handleRedo // themselves are calling setContext, and should not be nested @@ -636,7 +743,7 @@ const Workbook = React.forwardRef( ); }); }, - [handleRedo, handleUndo, setContextWithProduce] + [context, handleRedo, handleUndo, mergedSettings, setContextWithProduce] ); const onPaste = useCallback( @@ -773,6 +880,9 @@ const Workbook = React.forwardRef(
  • {info.confirmCellEditShortcut}
  • {info.moveRightShortcut}
  • {info.moveLeftShortcut}
  • +
  • {info.contextMenuShortcut}
  • +
  • {info.rowContextMenuShortcut}
  • +
  • {info.columnContextMenuShortcut}
  • From 977ee6379caccef8c8d4c5bd680e501db56ea224 Mon Sep 17 00:00:00 2001 From: Gareth Beukes Date: Tue, 24 Feb 2026 11:31:12 +0200 Subject: [PATCH 2/6] Fix context menu items to trigger Click event on Enter. --- packages/react/src/components/ContextMenu/Menu.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/react/src/components/ContextMenu/Menu.tsx b/packages/react/src/components/ContextMenu/Menu.tsx index e855ee8a..7b093a4d 100644 --- a/packages/react/src/components/ContextMenu/Menu.tsx +++ b/packages/react/src/components/ContextMenu/Menu.tsx @@ -35,6 +35,19 @@ const Menu: React.FC = ({ ref={containerRef} className="luckysheet-cols-menuitem luckysheet-mousedown-cancel" onClick={(e) => onClick?.(e, containerRef.current!)} + onKeyDown={(e) => { + if (e.key === "Enter") { + if (e.repeat) return; + e.preventDefault(); + e.stopPropagation(); + containerRef.current?.dispatchEvent( + new MouseEvent("click", { + bubbles: true, + cancelable: true, + }) + ); + } + }} onMouseLeave={(e) => onMouseLeave?.(e, containerRef.current!)} onMouseEnter={(e) => onMouseEnter?.(e, containerRef.current!)} tabIndex={0} From 4ce51f95f0e1b33c285a5643b67e433e561f07e2 Mon Sep 17 00:00:00 2001 From: Gareth Beukes Date: Tue, 24 Feb 2026 11:39:14 +0200 Subject: [PATCH 3/6] Sort modal visual fixes --- packages/core/src/locale/en.ts | 2 +- packages/core/src/locale/es.ts | 2 +- .../react/src/components/CustomSort/index.css | 8 +++++++ .../react/src/components/CustomSort/index.tsx | 21 ++++++++++++------- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/core/src/locale/en.ts b/packages/core/src/locale/en.ts index 197b0d45..785416a7 100644 --- a/packages/core/src/locale/en.ts +++ b/packages/core/src/locale/en.ts @@ -11424,7 +11424,7 @@ export default { sortBy: "Sort by", addOthers: "Add another sort column", close: "close", - confirm: "sort", + confirm: "Sort", columnOperation: "Column", secondaryTitle: "then by", diff --git a/packages/core/src/locale/es.ts b/packages/core/src/locale/es.ts index 5f39945e..4f61a3cc 100644 --- a/packages/core/src/locale/es.ts +++ b/packages/core/src/locale/es.ts @@ -11390,7 +11390,7 @@ export default { sortBy: "Ordenar por", addOthers: "Añadir otra columna de ordenación", close: "cerrar", - confirm: "ordenar", + confirm: "Ordenar", columnOperation: "Columna", secondaryTitle: "y después por", diff --git a/packages/react/src/components/CustomSort/index.css b/packages/react/src/components/CustomSort/index.css index 7db67faf..fddf41f7 100644 --- a/packages/react/src/components/CustomSort/index.css +++ b/packages/react/src/components/CustomSort/index.css @@ -8,6 +8,10 @@ margin: 0 0 16px; } +.fortune-sort-title-range { + margin: 0 4px; +} + .fortune-sort-modal > div { margin-bottom: 10px; } @@ -17,6 +21,10 @@ white-space: nowrap; } +.fortune-sort-by-select { + margin-left: 8px; +} + .fortune-sort-button { margin-top: 10px; margin-bottom: 25px; diff --git a/packages/react/src/components/CustomSort/index.tsx b/packages/react/src/components/CustomSort/index.tsx index 8c7a370a..cc9d14fe 100644 --- a/packages/react/src/components/CustomSort/index.tsx +++ b/packages/react/src/components/CustomSort/index.tsx @@ -19,7 +19,7 @@ import { useDialog } from "../../hooks/useDialog"; type RadioChangeEvent = React.ChangeEvent; const CustomSort: React.FC<{}> = () => { - const [rangeColChar, setRangeColChar] = useState([]); + const [rangeColChar, setRangeColChar] = useState([]); const [ascOrDesc, setAscOrDesc] = useState(true); const { context, setContext } = useContext(WorkbookContext); const [selectedValue, setSelectedValue] = useState("0"); @@ -31,6 +31,8 @@ const CustomSort: React.FC<{}> = () => { const col_end = context.luckysheet_select_save![0].column[1]; const row_start = context.luckysheet_select_save![0].row[0]; const row_end = context.luckysheet_select_save![0].row[1]; + const startCell = `${indexToColumnChar(col_start)}${row_start + 1}`; + const endCell = `${indexToColumnChar(col_end)}${row_end + 1}`; const sheetIndex = getSheetIndex(context, context.currentSheetId) as number; @@ -89,11 +91,9 @@ const CustomSort: React.FC<{}> = () => {
    {sort.sortRangeTitle} - {indexToColumnChar(col_start)} - {row_start + 1} + {startCell} {sort.sortRangeTitleTo} - {indexToColumnChar(col_end)} - {row_end + 1} + {endCell}
    @@ -113,8 +113,15 @@ const CustomSort: React.FC<{}> = () => { - {sort.sortBy} - {rangeColChar.map((col, index) => { return (