uix(palabras): esquema · granularidad typography.* dentro del mega-control

La whitelist `editable` ya decidía si el mega-control Tipografía aparece
ENTERO (schemeAllowsField); faltaba apagar filas INDIVIDUALES con las
claves con punto (`typography.size`…). Refleja el patrón que ya existe
para `adjustments` de imagen.

- PalabrasTypography gana `enabled: 'all' | readonly string[]`. UN solo
  mecanismo de visibilidad: `show(key)` unifica el `essential` existente
  (inspector corto de la barra) con el `enabled` del esquema — no dos ejes
  {#if} paralelos sobre las mismas filas.
- Vocabulario canónico (11 filas, nombres semánticos cortos que extienden
  los `size`/`tracking` ya fijados en ESQUEMA, no los nombres CSS):
  font·style·weight·size·leading·tracking·word·measure·case·wrap·tabular.
  Documentado en ESQUEMA §6 (tabla clave→fila→prop) + apuntado desde types.ts.
- Ambos consumidores (panel flotante/acoplado + inspector de barra) pasan
  `enabled={schemeSubControls(scheme, kind, 'typography')}`.
- Test: `typography.size,typography.leading` → sufijos `['size','leading']`.

Verificado Playwright: párrafo gateado a size+leading muestra SOLO Tamaño +
Interlineado; encabezado sin declarar muestra las 11 filas (retrocompatible).

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

@ -119,6 +119,22 @@ editable{
sub-controles individuales del mega-control Tipografía (`typography` a secas =
todo él); `adjustments.saturation`, `adjustments.blur`… hacen lo mismo con los
sliders del componente `ImageAdjustments` (`adjustments` a secas = los 8).
- **Sub-claves de `typography.*`** (vocabulario canónico del mega-control; orden =
orden de render):
| clave | fila | prop(s) |
| --- | --- | --- |
| `font` | Fuente | `fontFamily` |
| `style` | Estilo | `fontStyle` + `strikethrough` |
| `weight` | Peso | `fontWeight` |
| `size` | Tamaño | `fontSize` |
| `leading` | Interlineado | `lineHeight` |
| `tracking` | Tracking | `letterSpacing` |
| `word` | Espaciado | `wordSpacing` |
| `measure` | Medida | `maxWidth` |
| `case` | Caja | `textTransform` |
| `wrap` | Ajuste | `textWrap` |
| `tabular` | Cifras tabulares | `tabularNumbers` |
- Tipo declarado VACÍO = nada editable (el panel muestra el tipo pero sin campos).
### 6b. `marks` + `blocks` — qué puede APLICAR/INSERTAR (gobiernan bubble y slash)

@ -495,6 +495,7 @@
{sizeScale}
{disabled}
essential
enabled={schemeSubControls(scheme, kind, 'typography')}
onPatch={commitVisualPatch}
/>
{:else if field.type === 'adjustments'}

@ -1036,6 +1036,7 @@
defaultFontSize={blockDefaultPx}
{sizeScale}
{disabled}
enabled={schemeSubControls(scheme, kind, 'typography')}
onPatch={commitVisualPatch}
/>
{:else if field.type === 'adjustments'}

@ -170,6 +170,13 @@ describe('editable granularity readers', () => {
expect(schemeSubControls(s, 'heading', 'typography')).toBe('all');
expect(schemeSubControls(s, 'paragraph', 'typography')).toBe('all');
});
it('reports typography sub-controls by their dotted suffix (ESQUEMA §6 vocab)', () => {
const t = resolvePalabrasScheme('editable{paragraph=children,typography.size,typography.leading}');
expect(schemeSubControls(t, 'paragraph', 'typography')).toEqual(['size', 'leading']);
// A dotted descendant keeps the mega-control itself visible:
expect(schemeAllowsField(t, 'paragraph', 'typography')).toBe(true);
});
});
describe('schemeFontFor (effective family for the bubble)', () => {

@ -49,6 +49,7 @@
sizeScale = [],
disabled = false,
essential = false,
enabled = 'all',
onPatch
}: {
/** Current typographic visual values of the active block. */
@ -67,10 +68,21 @@
* tabulares). For the short menubar inspector, where the full 11-row control
* won't fit without scroll. The float/docked panel keeps the default (all rows). */
essential?: boolean;
/** Which Typography sub-controls the scheme's `editable` whitelist enables:
* `'all'` (default) or the dotted `typography.*` suffixes (e.g. `['size',
* 'leading']`) — the vocabulary is ESQUEMA §6. Composes with `essential`. */
enabled?: 'all' | readonly string[];
/** Merge-patch into the block's visual; an `undefined` value clears the prop. */
onPatch: (patch: Partial<Typo>) => void;
} = $props();
// ── Sub-control gating (ESQUEMA §6 `typography.*` whitelist) ──
// The essential subset is what the short menubar inspector shows; the scheme's
// `enabled` narrows further. ONE visibility computation, not two parallel axes.
const TYPO_ESSENTIAL = ['font', 'style', 'weight', 'size', 'leading', 'case'];
const show = (key: string) =>
(enabled === 'all' || enabled.includes(key)) && (!essential || TYPO_ESSENTIAL.includes(key));
// ── Theme scales — mirror generated/base.css (--leading-* / --tracking-* / --measure-*). ──
const LEADING_DEFAULT = '1.5'; // --leading-normal (the theme's resting leading).
// Named leading steps — the SHARED vocabulary (panel-schema), same steps as
@ -253,6 +265,7 @@
{/snippet}
<div data-palabras-typography data-disabled={disabled ? '' : undefined}>
{#if show('font')}
<!-- Fuente (font family) — one grid row: label | grouped dropdown (theme +
schema fonts; each option previews its own typeface). -->
<div data-palabras-field>
@ -291,7 +304,9 @@
</Select.Portal>
</Select>
</div>
{/if}
{#if show('style')}
<!-- Estilo — font-style (single-select) + an independent Tachada (strike) pill,
both rendered as text pills in the same style. -->
<div data-palabras-field>
@ -313,7 +328,9 @@
/>
</div>
</div>
{/if}
{#if show('weight')}
<!-- Peso — one grid row: label | weight dropdown. -->
<div data-palabras-field>
<span data-palabras-field-label>Peso</span>
@ -349,7 +366,9 @@
</Select.Portal>
</Select>
</div>
{/if}
{#if show('size')}
<!-- Tamaño — con la escala tipográfica del documento: etiqueta arriba y
(preset con los tamaños del documento · px manual), igual que Interlineado; el
preset mantiene el tamaño en la escala del documento. Sin escala → solo px. -->
@ -381,7 +400,9 @@
{@render sizeManualField()}
{/if}
</div>
{/if}
{#if show('leading')}
<!-- Interlineado — label on its own line, then (preset dropdown · manual em
field) pair full-width below, so the preset shows its full label. -->
<div data-palabras-field data-stack>
@ -406,8 +427,9 @@
</CssField>
</div>
</div>
{/if}
{#if !essential}
{#if show('tracking')}
<!-- Tracking — label on its own line, then (preset dropdown · manual px·em
field) pair full-width below. -->
<div data-palabras-field data-stack>
@ -433,7 +455,9 @@
</CssField>
</div>
</div>
{/if}
{#if show('word')}
<!-- Espaciado — word-spacing (no preset); one grid row: label | manual field. -->
<div data-palabras-field>
<span data-palabras-field-label>Espaciado</span>
@ -450,7 +474,9 @@
<CssField.IncrementTrigger>+</CssField.IncrementTrigger>
</CssField>
</div>
{/if}
{#if show('measure')}
<!-- Medida (measure) — label on its own line, then (preset dropdown · manual
ch·rem·px field) pair full-width below (two-control field → stacked). -->
<div data-palabras-field data-stack>
@ -472,22 +498,25 @@
</CssField>
</div>
</div>
{/if}
{#if show('case')}
<!-- Caja (text-transform) — one grid row: label | dropdown. -->
<div data-palabras-field>
<span data-palabras-field-label>Caja</span>
{@render presetSelect(caseValue, CASE_OPTS, pickCase, 'Normal')}
</div>
{/if}
{#if !essential}
{#if show('wrap')}
<!-- Ajuste (Wrap) — one grid row: label | dropdown. -->
<div data-palabras-field>
<span data-palabras-field-label>Ajuste</span>
{@render presetSelect(wrapValue, WRAP_OPTS, pickWrap, 'Normal')}
</div>
{/if}
{#if show('tabular')}
<!-- Cifras tabulares — fixed-width figures (switch on the control edge). -->
<div data-palabras-field>
<span data-palabras-field-label>Cifras tabulares</span>

@ -221,7 +221,8 @@ export interface PalabrasScheme {
* WHITELIST of editable field keys per block type — governs the panel.
* Undeclared type → full panel; declared EMPTY → nothing editable.
* Dotted keys reach sub-controls: `typography.size`,
* `adjustments.saturation`… (`typography` / `adjustments` alone = all).
* `adjustments.saturation`… (`typography` / `adjustments` alone = all;
* the full `typography.*` vocabulary is ESQUEMA §6).
*/
readonly editable?: Readonly<Record<string, readonly string[]>>;
/**

Loading…
Cancel
Save

Powered by TurnKey Linux.