diff --git a/playwright/specs/sheet_excel_chrome.spec.ts b/playwright/specs/sheet_excel_chrome.spec.ts index 97635ab8..a85e706d 100644 --- a/playwright/specs/sheet_excel_chrome.spec.ts +++ b/playwright/specs/sheet_excel_chrome.spec.ts @@ -51,6 +51,24 @@ test.describe('Sheet Excel chrome', () => { await expect(cell(page, 0, 0)).toHaveCSS('font-size', /^26\.6/); }); + test('Ctrl+B/I/U toggle bold, italic, underline on the selection', async ({ page }) => { + const padId = `xl-fmtkeys-${Date.now()}`; + await openSheet(page, padId); + await commitCell(page, 0, 0, 'x'); // A1 + await cell(page, 0, 0).click(); + + await page.keyboard.press('Control+b'); + await expect(cell(page, 0, 0)).toHaveCSS('font-weight', /700|bold/); + await page.keyboard.press('Control+i'); + await expect(cell(page, 0, 0)).toHaveCSS('font-style', 'italic'); + await page.keyboard.press('Control+u'); + await expect(cell(page, 0, 0)).toHaveCSS('text-decoration', /underline/); + + // Ctrl+B again toggles bold back off. + await page.keyboard.press('Control+b'); + await expect(cell(page, 0, 0)).toHaveCSS('font-weight', /400|normal/); + }); + test('wrap text switches the cell to normal white-space', async ({ page }) => { const padId = `xl-wrap-${Date.now()}`; await openSheet(page, padId); diff --git a/ui/src/js/sheet/sheetEditor.ts b/ui/src/js/sheet/sheetEditor.ts index 74536f4e..08a83326 100644 --- a/ui/src/js/sheet/sheetEditor.ts +++ b/ui/src/js/sheet/sheetEditor.ts @@ -573,6 +573,24 @@ export function startSheetEditor(root: HTMLElement): void { doPaste(); return; } + // Ctrl/Cmd+B/I/U toggle the style on the selection, mirroring the ribbon's + // toggle buttons. Applying a style blurs the active cell, so the next + // shortcut arrives with focus on — we must NOT require grid focus + // (that would break chaining B then I). Instead just skip real form fields + // so the formula bar keeps these keys. preventDefault stops the browser's + // contenteditable rich-text default on the focused cell. + const tag = (e.target as HTMLElement | null)?.tagName; + const inField = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT'; + if (mod && !editingNow() && !readOnly && !inField) { + const k = e.key.toLowerCase(); + const styleKey = k === 'b' ? 'bold' : k === 'i' ? 'italic' : k === 'u' ? 'underline' : null; + if (styleKey) { + e.preventDefault(); + const on = propsOf(selection.focus.row, selection.focus.col)[styleKey] === '1'; + applyStyleToSelection({ [styleKey]: on ? '' : '1' }); + return; + } + } // Clear the selection (single cell or range), like Excel. The grid-focus // guard replaces the old single-cell exclusion: it lets Delete clear one // cell while still keeping Backspace working in the formula bar and any