From e81b53e806a6c489adc613744b3354f30ce26caf Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 31 May 2026 14:27:24 +0200 Subject: [PATCH] refactor(words)!: column block insertion moves to inspector (React Bricks pattern) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The floating in-canvas `+` overlay for column inserts was fundamentally at odds with contenteditable semantics — it needed pointer-events:none to let caret/text events pass through, but that ALSO disabled CSS :hover detection, and any visible variant covered the column's auto- trailing paragraph (the escape hatch the engine adds after atomic inserts), hijacking clicks intended for typing. Adopt React Bricks's pattern: container-block child insertion goes through the sidebar/inspector, not via floating overlays on the canvas. - New "Añadir bloque" dropdown per column in the Columns inspector panel (`words-block-panel.svelte`). Lists every insertable block type the engine knows (paragraph, headings, lists, image, divider, callout, etc.). Picking one dispatches `insertBlockInColumn`, appending it to the chosen column. - Inspector lives outside the contenteditable, so the dropdown doesn't fight focus traps, doesn't cover content, doesn't need pointer-events gymnastics. Zero of the bug classes we hit. - Deleted `words-column-inserter.svelte` and its mount in `words.svelte`. The `insertBlockInColumn` engine op stays — it's the right primitive, just driven from a different surface now. - Added `ARIA_ADD_BLOCK_TO_COLUMN_N` and `LABEL_ADD_BLOCK` to the inspector langs catalog. Top-level block insertion (between rows) keeps using the canonical gutter handle `⋮⋮ → Insert below` and the slash menu (for in-flow keyboard users). Inside-column editing keeps working as before: click on the block, type. Enter at end of a paragraph creates a new paragraph below within the column. Breaking: anyone who imported `words-column-inserter.svelte` directly is broken. Nobody outside this folder did. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../eidos/components/words/langs-inspector.ts | 4 + .../components/words/words-block-panel.svelte | 70 ++- .../words/words-column-inserter.svelte | 488 ------------------ src/uix/eidos/components/words/words.svelte | 9 +- 4 files changed, 72 insertions(+), 499 deletions(-) delete mode 100644 src/uix/eidos/components/words/words-column-inserter.svelte diff --git a/src/uix/eidos/components/words/langs-inspector.ts b/src/uix/eidos/components/words/langs-inspector.ts index 035ed859d..b2b2cd160 100644 --- a/src/uix/eidos/components/words/langs-inspector.ts +++ b/src/uix/eidos/components/words/langs-inspector.ts @@ -46,6 +46,10 @@ export const WORDS_INSPECTOR_LANGS = { '#?components.words.inspector.aria.add-column-end|Add column at end', ARIA_REMOVE_COLUMN_N: '#?components.words.inspector.aria.remove-column-n|Remove column {n}', + ARIA_ADD_BLOCK_TO_COLUMN_N: + '#?components.words.inspector.aria.add-block-to-column-n|Add block to column {n}', + LABEL_ADD_BLOCK: + '#?components.words.inspector.label.add-block|Add block', PLACEHOLDER_COLUMN_WIDTH: '#?components.words.inspector.placeholder.column-width|1fr, 200px, 30%', diff --git a/src/uix/eidos/components/words/words-block-panel.svelte b/src/uix/eidos/components/words/words-block-panel.svelte index 256c11c9e..be2b4298d 100644 --- a/src/uix/eidos/components/words/words-block-panel.svelte +++ b/src/uix/eidos/components/words/words-block-panel.svelte @@ -29,6 +29,7 @@ import { Button } from '$uix/eidos/components/button'; import { TextArea } from '$uix/eidos/components/textarea'; import Toggle from '$uix/eidos/components/toggle'; + import { DropdownMenu } from '$uix/eidos/components/dropdown-menu'; import WordsColorRow from './words-color-row.svelte'; import { TextAlignStart, @@ -41,15 +42,17 @@ } from '$uix/eidos/components/icon'; import { ActiveEidos } from '$uix/eidos'; import { WORDS_INSPECTOR_LANGS as L } from './langs-inspector'; - import type { - ProviderSnippetProps, - WordsAlign, - WordsBlock, - WordsHeadingLevel, - WordsImageAlign, - WordsIntent, - WordsListKind, - WordsVerticalAlign + import { + defaultWordsSchema, + type ProviderSnippetProps, + type WordsAlign, + type WordsBlock, + type WordsBlockMenuEntry, + type WordsHeadingLevel, + type WordsImageAlign, + type WordsIntent, + type WordsListKind, + type WordsVerticalAlign } from '$soma/components/words'; let { @@ -143,6 +146,24 @@ } } + // Insertable block-type menu for the per-column "Add block" dropdown. + // Reuses the engine's canonical insertable list (same source the slash + // menu and gutter inserter draw from) so the inspector path stays in + // sync with the rest of the editor's block catalog. + const insertableEntries = $derived(defaultWordsSchema.insertable()); + + function insertIntoColumn(colIdx: number, entry: WordsBlockMenuEntry): void { + if (block.type !== 'columns') return; + const newBlock = entry.create(); + if (!newBlock) return; + applyCommand({ + type: 'insertBlockInColumn', + columnsIdx: blockIndex, + colIdx, + block: newBlock as unknown as Readonly> + }); + } + // Columns helpers — operate on the whole `columns` array via // `updateBlock`. No dedicated engine ops; the array IS the data. function addColumn(): void { @@ -761,6 +782,37 @@ }} /> + +
+ + + {#snippet icon()}{/snippet} + {t(L.LABEL_ADD_BLOCK)} + + + + {#each insertableEntries as entry (entry.id)} + insertIntoColumn(idx, entry)}> + {entry.label} + + {/each} + + + +
{#if block.columns.length > 1}