feat(words): inline heading-level control in the Block drawer panel

Adds an H1/H2/H3 toggle to the drawer's Block panel, shown only when
the caret is in a heading (currentBlock === 'heading'). The toggle
reflects currentHeadingLevel and dispatches the structured
`setBlock` command with the numeric level via applyCommand:

  applyCommand({ type: 'setBlock', block: 'heading', level })

runCommand / WordsCommandName has no per-level id (heading-1/2/3 are
slash/insert-menu ids), so the structured command carries the level;
`as const` keeps each level a 1|2|3 literal.

Verified in browser: caret in a heading shows "Heading level"; the
toggle reflects the active level and converts bidirectionally
(H1→H3→H2) with the toggle updating. npm run check: 0 errors.

Also closes the stale "drag handle outside the rail over <code>" P1:
it does not reproduce — the rail was removed in the gutter redesign
(5450d5f7) and the handle now anchors to contentRect.left+8 for every
block type (verified: identical left over code and paragraph). Both
P1 items marked resolved in continue.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
active-uix
dev 4 months ago
parent cc4412b8e9
commit 005239c732

@ -148,21 +148,31 @@ panel renderiza los 4 controles nuevos; click en swatch de Background
actualiza el figure (`background-color:#22c55e`) preservando actualiza el figure (`background-color:#22c55e`) preservando
radius/border/shadow (shallow-merge correcto). `npm run check` 0 errors. radius/border/shadow (shallow-merge correcto). `npm run check` 0 errors.
## Pendiente para mañana ### ✅ P1 — Drag handle sobre `<code>` (NO reproduce)
### P1 — Drag handle FUERA del rail sobre bloques `<code>` (heredado) Investigado: NO reproduce en el código actual. El "rail" al que se
refería el bug fue ELIMINADO en el rediseño de gutter (commit
El grip se va fuera de la rail al hacer hover sobre un code block. `5450d5f7`, "rail out"), posterior al sprint EV donde se reportó.
Sin diagnosticar. Lugar: `eidos/components/words/words-block-handle.svelte `measureFromEl` (`words-block-handle.svelte`) ancla el handle a
> measureFromEl()`. Pasos: comparar `getBoundingClientRect().left` de `contentRect.left + 8` — block-type-agnostic. Verificado en browser:
`<pre>` vs `<p>`; si difieren, buscar padding/margin/transform en sobre code y sobre paragraph el gutter cae en `left: 350.333` idéntico
`[data-words-content] :where(pre)` en `words.css`. (solo difiere `top`). No hay nada que arreglar; bug obsoleto.
### ✅ P1 — Heading inline level change (h1 ↔ h2 ↔ h3)
Añadido al Block panel del drawer
(`eidos/components/words/words-drawer.svelte > blockPanel`): cuando
`currentBlock === 'heading'`, un ToggleGroup H1/H2/H3 (value =
`currentHeadingLevel`) despacha `applyCommand({ type: 'setBlock',
block: 'heading', level })`. `runCommand` / `WordsCommandName` no tiene
id por-nivel (`heading-1/2/3` son ids de slash / insert-menu), por eso
se usa el comando estructurado `setBlock` con el nivel numérico (`as
const` mantiene cada `level` como literal 1|2|3). Verificado en
browser: caret en heading → el panel muestra "Heading level"; el toggle
refleja el nivel actual; el click convierte bidireccional (H1→H3→H2) y
el toggle se actualiza. `npm run check` 0 errors.
### P1 — Heading inline level change (h1 ↔ h2 ↔ h3) (heredado) ## Pendiente para mañana
Cambiar nivel de heading inline sin abrir drawer. Recomendación:
empezar por el Block panel del drawer (verificar que el HeadingPicker
se muestra cuando `currentBlock === 'heading'`).
### P2 — Heredados ### P2 — Heredados

@ -196,6 +196,15 @@
{ id: 'align-right', label: '⫸' }, { id: 'align-right', label: '⫸' },
{ id: 'align-justify', label: '☷' } { id: 'align-justify', label: '☷' }
] as const satisfies readonly { id: WordsCommandName; label: string }[]; ] as const satisfies readonly { id: WordsCommandName; label: string }[];
// Heading level uses the structured `setBlock` command (carries the
// numeric level) — `runCommand` / WordsCommandName has no per-level
// id. `as const` keeps each `level` a 1|2|3 literal for the command.
const HEADING_LEVEL_ACTIONS = [
{ level: 1, value: '1', label: 'H1', title: 'Heading 1' },
{ level: 2, value: '2', label: 'H2', title: 'Heading 2' },
{ level: 3, value: '3', label: 'H3', title: 'Heading 3' }
] as const;
</script> </script>
<Words.Drawer bind:open {defaultOpen} {disabled} {...rest} data-side={side}> <Words.Drawer bind:open {defaultOpen} {disabled} {...rest} data-side={side}>
@ -530,6 +539,26 @@
{/snippet} {/snippet}
{#snippet blockPanel(s: ProviderSnippetProps)} {#snippet blockPanel(s: ProviderSnippetProps)}
{#if s.currentBlock === 'heading'}
<div data-words-drawer-section-subtitle>Heading level</div>
<ToggleGroup
type="single"
size="sm"
variant="ghost"
value={s.currentHeadingLevel ? [String(s.currentHeadingLevel)] : []}
onValueChange={(next) => {
const action = HEADING_LEVEL_ACTIONS.find((a) => a.value === next[0]);
if (action) s.applyCommand({ type: 'setBlock', block: 'heading', level: action.level });
}}
aria-label="Heading level"
>
{#each HEADING_LEVEL_ACTIONS as h (h.value)}
<ToggleGroup.Item value={h.value} title={h.title} onmousedown={(e) => e.preventDefault()}>
{h.label}
</ToggleGroup.Item>
{/each}
</ToggleGroup>
{/if}
<div data-words-drawer-section-subtitle>Text align</div> <div data-words-drawer-section-subtitle>Text align</div>
<ToggleGroup <ToggleGroup
type="single" type="single"

Loading…
Cancel
Save

Powered by TurnKey Linux.