From 0d7b654a2d6b74d9b8d47748d08633a54c4a025a Mon Sep 17 00:00:00 2001 From: Hyko Date: Sat, 25 Apr 2026 18:01:18 -0400 Subject: [PATCH] feat(BlockEditor): add multi-block selection with ctrl+a and delete support - add `isSelected` prop and overlay highlight to Block component - implement double ctrl+a: first selects block content, second selects all blocks - add `onSelectAllBlocks` callback prop to Block - add `selectedBlockIds` state and `selectAllBlocks`/`deleteSelectedBlocks` helpers in BlockEditor - detect cross-block native selection via `selectionchange` and convert to block selection - handle backspace/delete key to remove all selected blocks - clear block selection on click outside or focus change - update README to document multi-block selection behaviour - export new icons used by the feature --- .../components/BlockEditor/Block.client.js | 31 +++- .../BlockEditor/BlockEditor.client.js | 142 ++++++++++++++++++ src/shared/components/BlockEditor/README.md | 16 ++ src/shared/icons/index.js | 17 +++ 4 files changed, 199 insertions(+), 7 deletions(-) diff --git a/src/shared/components/BlockEditor/Block.client.js b/src/shared/components/BlockEditor/Block.client.js index 8a7f822..3ba5ed2 100644 --- a/src/shared/components/BlockEditor/Block.client.js +++ b/src/shared/components/BlockEditor/Block.client.js @@ -26,11 +26,13 @@ const Block = forwardRef(function Block( disabled, isDragOverTop, isDragOverBottom, + isSelected, onContentChange, onEnter, onBackspaceAtStart, onSlashOpen, onSlashClose, + onSelectAllBlocks, onShortcutMatch, onFocus, onDragStart, @@ -120,17 +122,26 @@ const Block = forwardRef(function Block( return; } - // Ctrl/Cmd+A : limite la sélection au bloc courant. La sélection native - // s'étend sur plusieurs contentEditable et leur suppression fusionne le - // texte en un seul bloc — bug. On force la sélection à rester ici. + // Ctrl/Cmd+A : 1er appui → sélectionne le contenu du bloc courant. + // 2e appui (le contenu est déjà entièrement sélectionné) → bascule en + // sélection multi-blocs (tous les blocs). if ((e.ctrlKey || e.metaKey) && (e.key === 'a' || e.key === 'A')) { if (el) { e.preventDefault(); - const range = document.createRange(); - range.selectNodeContents(el); const sel = window.getSelection(); - sel?.removeAllRanges(); - sel?.addRange(range); + const txt = el.textContent ?? ''; + const fullySelected = + !!sel && sel.rangeCount > 0 && !sel.isCollapsed && + el.contains(sel.anchorNode) && el.contains(sel.focusNode) && + sel.toString() === txt && txt.length > 0; + if (fullySelected) { + onSelectAllBlocks?.(); + } else { + const range = document.createRange(); + range.selectNodeContents(el); + sel?.removeAllRanges(); + sel?.addRange(range); + } } return; } @@ -227,6 +238,12 @@ const Block = forwardRef(function Block( data-block-id={block.id} > {dropIndicator} + {isSelected && ( +