@ -39,7 +39,7 @@
ArrowLeft
} from '$uix/eidos/components/icon';
import { PALABRAS_FONTS } from './panel-schema';
import { scheme SizeFor } from './palabras-scheme';
import { scheme AllowsMark, scheme SizeFor } from './palabras-scheme';
import type { ProviderSnippetProps , PalabrasMark } from '$soma/components/palabras';
import type { PalabrasScheme } from './types';
@ -59,6 +59,15 @@
// ── Range typography (F2.4) — engine marks over the live selection. ──
const fontGroups = $derived(scheme.fonts ?? PALABRAS_FONTS);
const allFonts = $derived(fontGroups.flatMap((g) => g.fonts));
// Scheme governance: the bubble offers ONLY the marks the scheme permits
// (undeclared `marks` → all). Boolean marks filter the row; sub/sup/size/
// family gate the «Aa» typography sub-row (the «Aa» button hides when none).
const allowedMarks = $derived(MARKS.filter((m) => schemeAllowsMark(scheme, m.command)));
const showSub = $derived(schemeAllowsMark(scheme, 'sub'));
const showSup = $derived(schemeAllowsMark(scheme, 'sup'));
const showSize = $derived(schemeAllowsMark(scheme, 'fontSize'));
const showFamily = $derived(schemeAllowsMark(scheme, 'fontFamily'));
const showTypo = $derived(showSub || showSup || showSize || showFamily);
const marks = $derived(api.activeMarks);
const hasBool = (m: 'sub' | 'sup') => marks.includes(m);
const paramValue = < T , > (type: 'fontSize' | 'fontFamily'): T | undefined => {
@ -158,32 +167,37 @@
>
{ # snippet icon ()} < ArrowLeft /> { /snippet }
< / Button >
< span data-palabras-bubble-divider > < / span >
< Button
variant="ghost"
size="sm"
iconOnly
data-active={ hasBool ( 'sub' ) ? '' : undefined }
aria-pressed={ hasBool ( 'sub' )}
aria-label={ ts ( '#?components.palabras.mark.sub|Subíndice' )}
onpointerdown={ keepSelection }
onclick={() => toggleBool ( 'sub' )}
>
{ # snippet icon ()} < Subscript /> { /snippet }
< / Button >
< Button
variant="ghost"
size="sm"
iconOnly
data-active={ hasBool ( 'sup' ) ? '' : undefined }
aria-pressed={ hasBool ( 'sup' )}
aria-label={ ts ( '#?components.palabras.mark.sup|Superíndice' )}
onpointerdown={ keepSelection }
onclick={() => toggleBool ( 'sup' )}
>
{ # snippet icon ()} < Superscript /> { /snippet }
< / Button >
< span data-palabras-bubble-divider > < / span >
{ #if showSub || showSup } < span data-palabras-bubble-divider ></ span > { /if }
{ #if showSub }
< Button
variant="ghost"
size="sm"
iconOnly
data-active={ hasBool ( 'sub' ) ? '' : undefined }
aria-pressed={ hasBool ( 'sub' )}
aria-label={ ts ( '#?components.palabras.mark.sub|Subíndice' )}
onpointerdown={ keepSelection }
onclick={() => toggleBool ( 'sub' )}
>
{ # snippet icon ()} < Subscript /> { /snippet }
< / Button >
{ /if }
{ #if showSup }
< Button
variant="ghost"
size="sm"
iconOnly
data-active={ hasBool ( 'sup' ) ? '' : undefined }
aria-pressed={ hasBool ( 'sup' )}
aria-label={ ts ( '#?components.palabras.mark.sup|Superíndice' )}
onpointerdown={ keepSelection }
onclick={() => toggleBool ( 'sup' )}
>
{ # snippet icon ()} < Superscript /> { /snippet }
< / Button >
{ /if }
{ #if ( showSub || showSup ) && ( showSize || showFamily )} < span data-palabras-bubble-divider ></ span > { /if }
{ #if showSize }
< div data-palabras-bubble-size >
< NumberField
size="sm"
@ -199,6 +213,8 @@
< NumberField.IncrementTrigger > +< / NumberField.IncrementTrigger >
< / NumberField >
< / div >
{ /if }
{ #if showFamily }
< div data-palabras-bubble-family >
< Select
selectionMode="single"
@ -228,9 +244,10 @@
< / Select.Portal >
< / Select >
< / div >
{ /if }
< / div >
{ : else }
{ #each MARKS as mark ( mark . command )}
{ #each allowedMarks as mark ( mark . command )}
{ @const Glyph = mark . icon }
< Palabras.CommandButton command = { mark . command } aria-label= { ts ( mark . label )} >
{ # snippet child ({ props })}
@ -241,18 +258,20 @@
< / Palabras.CommandButton >
{ /each }
<span data-palabras-bubble-divider > < /span >
{#if allowedMarks . length } <span data-palabras-bubble-divider ></span > { /if }
< Button
variant="ghost"
size="sm"
iconOnly
aria-label={ ts ( '#?components.palabras.mark.typography|Tipografía' )}
onpointerdown={ keepSelection }
onclick={() => ( typoOpen = true )}
>
{ # snippet icon ()} < Type /> { /snippet }
< / Button >
{ #if showTypo }
< Button
variant="ghost"
size="sm"
iconOnly
aria-label={ ts ( '#?components.palabras.mark.typography|Tipografía' )}
onpointerdown={ keepSelection }
onclick={() => ( typoOpen = true )}
>
{ # snippet icon ()} < Type /> { /snippet }
< / Button >
{ /if }
< Button
variant="ghost"
size="sm"