# BlockEditor Éditeur WYSIWYG par blocs, style Notion. Construit en interne (pas de ProseMirror/Lexical/Tiptap). Un `contentEditable` par bloc, pas un seul contentEditable global — c'est plus robuste et plus simple à étendre. ## Utilisation ```jsx import { BlockEditor } from '@zen/core/shared/components'; const [blocks, setBlocks] = useState([]); ``` `value` est un **tableau de blocs JSON**. C'est la source de vérité. `MarkdownEditor` reste disponible en parallèle pour les usages markdown. ## Format des blocs (Phase 1) Chaque bloc a un `id` (UUID) et un `type`. Selon le type : | type | champs | description | |----------------|---------------|----------------------------| | `paragraph` | `content` | texte brut | | `heading_1..6` | `content` | titre niveau 1 à 6 | | `bullet_item` | `content` | élément de liste à puces | | `numbered_item`| `content` | élément de liste numérotée | | `quote` | `content` | citation | | `code` | `content` | bloc de code (monospace) | | `divider` | — | séparateur horizontal | Phase 2 ajoutera : `checklist`, `image`, et le format de `content` passera de `string` à `InlineNode[]` pour supporter le formatting inline (gras, italique, couleur, lien). Phase 3 : `table`. ## Props ```jsx void label, error, placeholder, disabled, className enabledBlocks={[...]} // optionnel : restreindre les types disponibles /> ``` ## Interactions clavier - `/` → ouvre le menu de commandes (filtrable au clavier, ↑ ↓ Entrée pour valider, Échap pour fermer) - `# ` → titre 1, `## ` → 2, …, `###### ` → 6 - `- ` → liste à puces - `1. ` → liste numérotée - `> ` → citation - ` ``` ` → bloc de code - `---` → séparateur - `Backspace` au début d'un bloc typé → repasse en paragraphe ; au début d'un paragraphe, fusionne avec le bloc précédent (uniquement si la sélection est repliée — sinon le navigateur supprime le texte sélectionné, ex. après `Ctrl+A`) - `Entrée` sur un item de liste vide → sort de la liste - `Ctrl/Cmd + Z` / `Ctrl/Cmd + Shift + Z` → undo / redo ## Drag and drop Chaque bloc affiche au survol : - une poignée `+` pour insérer un bloc en dessous (ouvre le slash menu) - une poignée `⋮⋮` pour glisser-déposer (réordonner) Le drag-and-drop utilise l'API HTML5 native, pas de dépendance externe. ## Étendre — enregistrer un bloc custom ```js import { registerBlock, newBlockId } from '@zen/core/shared/components/BlockEditor'; registerBlock({ type: 'kpi', label: 'KPI', icon: '📊', keywords: ['kpi', 'metric', 'stat'], isText: false, create: () => ({ id: newBlockId(), type: 'kpi', value: 0 }), Component: ({ block, onChange, disabled }) => ( onChange({ value: Number(e.target.value) })} /> ), }); ``` Le nouveau type apparaît automatiquement dans le slash menu de tout `` rendu après l'enregistrement. Pour les blocs **texte**, on fournit à la place `isText: true`, `textTag`, `textClassName`, et optionnellement `renderPrefix({ block, index, numberedIndex })` pour un préfixe (puce, numéro). ## Architecture interne ``` BlockEditor.client.js orchestrateur : value/onChange, undo, slash menu, drag-drop Block.client.js wrapper d'un bloc : handles, contentEditable, paste sanitize SlashMenu.client.js menu flottant filtrable blockRegistry.js map type → définition, API publique d'extension blockTypes/ un fichier par type built-in utils/ids.js UUID pour les blocs utils/caret.js gestion du caret dans un contentEditable ``` ## Limitations connues (Phase 1) - Inline formatting (gras, italique, couleur, lien) **pas encore** : tout est texte brut. Phase 2. - Pas d'imbrication de listes. - Paste : seul le texte brut est conservé (sanitize HTML). - Tables : Phase 3.