palabras(F4.7): barra de estado — recuento + índice (TOC)

Cierra F4.7. Los derivados wordCount/charCount/tableOfContents ya vivían en el
snippet del provider; faltaba su affordance. Decisión del usuario (2026-07-19):
barra de estado al pie con el recuento (readout pasivo) + botón «Índice» que
abre un popover de encabezados; clic hace scroll al heading.

- palabras-status.svelte (NUEVO): «N palabras · M caracteres» + `<Popover>` con
  `tableOfContents` (sangrado por `level` vía data-level); jumpTo hace
  scrollIntoView del `[data-palabras-id]` del encabezado y cierra el popover.
  El trigger se deshabilita sin encabezados.
- palabras.svelte: montada al pie del shell (los 3 modos — el recuento es un
  readout del documento, no una propiedad; el usuario lo quitó del panel).
- palabras-chrome.css: sección [data-palabras-status] + outline (tokens).

El índice va en affordance PROPIA (no el panel de propiedades — decisión del
usuario 2026-07-18: es navegación, no una propiedad de página).

Langs es/en (status.words/chars/outline/untitled) DIFERIDOS (fichero de langs
entangled con el merge pendiente; los `ts()` renderizan por fallback).

Verificado: check 0 errores palabras; Playwright headless: «45 palabras · 253
caracteres», popover con 2 encabezados (niveles 1/2), clic en «Estrategias» →
popover cierra + h2 en vista; 0 errores consola. Screenshot MIRADO.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
alpha-0.1-sec-dom
dev 3 months ago
parent 72a6569c3e
commit a97d8db740

@ -350,6 +350,78 @@
background: var(--color-surface-muted);
}
/* Status bar (F4.7) — word / char readout + the heading-outline trigger. */
[data-palabras-status] {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
padding-block: var(--space-1);
font-size: var(--font-size-xs);
color: var(--color-content-secondary);
}
[data-palabras-outline-trigger] {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: var(--space-1) var(--space-2);
border-radius: var(--radius-sm);
color: var(--color-content-secondary);
font-size: var(--font-size-xs);
cursor: pointer;
}
[data-palabras-outline-trigger]:hover:not(:disabled) {
background: var(--color-surface-muted);
}
[data-palabras-outline-trigger]:disabled {
opacity: 0.5;
cursor: default;
}
[data-palabras-outline-trigger] svg {
inline-size: 1em;
block-size: 1em;
}
[data-palabras-outline] {
min-inline-size: 14rem;
max-block-size: min(60vh, 24rem);
overflow-y: auto;
}
[data-palabras-outline] ul {
list-style: none;
margin: 0;
padding: 0;
}
[data-palabras-outline-item] {
appearance: none;
border: 0;
background: transparent;
display: block;
inline-size: 100%;
text-align: start;
padding: var(--space-1) var(--space-2);
border-radius: var(--radius-sm);
color: var(--color-content-secondary);
font-size: var(--font-size-sm);
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
[data-palabras-outline-item]:hover {
background: var(--color-surface-muted);
}
[data-palabras-outline-item][data-level='2'] {
padding-inline-start: var(--space-3);
}
[data-palabras-outline-item][data-level='3'] {
padding-inline-start: var(--space-5);
}
[data-palabras-outline-item][data-level='4'],
[data-palabras-outline-item][data-level='5'],
[data-palabras-outline-item][data-level='6'] {
padding-inline-start: var(--space-8);
}
/* ═══ 2 · Inspector panel — the approved mockup, in theme tokens ════════════ */
/* UI sans across the panel (the Accordion content archetype would leak the

@ -0,0 +1,76 @@
<script lang="ts">
/**
* palabras status bar (F4.7) — a passive word / char readout plus the heading
* outline.
*
* Count is a READOUT, never a document property: the user's decision
* (2026-07-18) is that an index / count is document NAVIGATION, not a page
* property, so it lives in its own affordance rather than the properties panel.
* The «Índice» button opens a popover of the document's headings; clicking an
* entry scrolls its heading into view.
*/
import { Popover } from '$uix/eidos/components/popover';
import { TableOfContents } from '$uix/eidos/components/icon';
import { ActiveEidos } from '$uix/eidos';
import type { ProviderSnippetProps } from '$soma/components/palabras';
import type { ActiveDom } from '$adom';
let {
api,
content,
dom
}: {
api: ProviderSnippetProps;
content: HTMLElement | null;
dom: ActiveDom;
} = $props();
const eidos = ActiveEidos.require();
const ts = (ref: string) => eidos.langs.ts(ref);
const toc = $derived(api.tableOfContents);
let open = $state(false);
function jumpTo(id: string | undefined) {
if (!id) return;
const el = content ? dom.query<HTMLElement>(`[data-palabras-id="${CSS.escape(id)}"]`, content) : null;
el?.scrollIntoView({ block: 'start', behavior: 'smooth' });
open = false;
}
</script>
<div data-palabras-status>
<span data-palabras-status-count>
{api.wordCount}
{ts('#?components.palabras.status.words|palabras')} · {api.charCount}
{ts('#?components.palabras.status.chars|caracteres')}
</span>
<Popover bind:open>
<Popover.Trigger
data-palabras-outline-trigger
disabled={toc.length === 0}
aria-label={ts('#?components.palabras.status.outline|Índice')}
>
<TableOfContents />
<span>{ts('#?components.palabras.status.outline|Índice')}</span>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content data-palabras-outline>
<ul>
{#each toc as entry, i (entry.id ?? i)}
<li>
<button
type="button"
data-palabras-outline-item
data-level={entry.level}
onclick={() => jumpTo(entry.id)}
>
{entry.text || ts('#?components.palabras.status.untitled|(sin título)')}
</button>
</li>
{/each}
</ul>
</Popover.Content>
</Popover.Portal>
</Popover>
</div>

@ -28,6 +28,7 @@
import PalabrasHandle from './palabras-handle.svelte';
import PalabrasMenubar from './palabras-menubar.svelte';
import PalabrasPanel from './palabras-panel.svelte';
import PalabrasStatus from './palabras-status.svelte';
import { mergePalabrasScheme, resolvePalabrasScheme, schemeCssVars } from './palabras-scheme';
import type { PalabrasProps, PalabrasScheme } from './types';
@ -224,6 +225,9 @@
arrastra la cabecera para moverlo · clic fuera o <kbd>Esc</kbd> para cerrar
</p>
{/if}
<!-- Bottom status bar (F4.7) — word / char count + the heading outline;
present in every mode (count is a document readout, not a property). -->
<PalabrasStatus {api} content={contentEl} {dom} />
</Flex>
{/snippet}
</Palabras.Provider>

Loading…
Cancel
Save

Powered by TurnKey Linux.