From 9b2c5fd1f6ee1c6fbebad0e7a9b24f9f73fbc99e Mon Sep 17 00:00:00 2001 From: SamTV12345 <40429738+samtv12345@users.noreply.github.com> Date: Thu, 23 Jul 2026 23:05:35 +0200 Subject: [PATCH] feat(sheet): Ctrl+B/I/U toggle bold, italic, underline Keyboard shortcuts for the ribbon's style toggles, applied to the current selection. Skips real form fields so the formula bar keeps these keys, and does NOT require grid focus (applying a style blurs the cell, so requiring focus would break chaining B then I). preventDefault stops the browser's contenteditable rich-text default. Co-Authored-By: Claude Opus 4.8 (1M context) --- playwright/specs/sheet_excel_chrome.spec.ts | 18 ++++++++++++++++++ ui/src/js/sheet/sheetEditor.ts | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) 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