tooltip: expand morfo + recipe states → PASS audit

Closes the second BROKEN component from the audit (after date-range-picker).
Five errors collapse to zero:

- Morfo declares the three `emerge` events the recipe animations were
  already keying off (`open`, `close`, `close-dismiss`) plus a
  `texts.label` slot. `data-disabled` is now declared on `Trigger` (with
  optional severity) so the recipe rule has a real contract to land on.
  `aria-disabled` and `texts.label` round out the surface; the new
  `tooltip.label` catalog entry ships under
  `src/uix/langs/components/tooltip.ts`.
- Recipe CSS: explicit `[data-tooltip-trigger][data-disabled]` and
  `[data-tooltip-trigger]:focus-visible` rules. The disabled rule
  suppresses pointer events; the focus rule provides a defensive
  outline so a focusable trigger shell is at least keyboard-visible.
- README: convert the old `## Pendiente deliberado` section into a
  proper `## Gaps` table with explicit dispositions
  (implementar/diferir/descartar), and update `## Eventos Sema` to
  reflect that tooltip now declares its three emerge events instead of
  claiming zero. Comparativa was already complete; the regex fix from
  the previous commit picked it up.

Audit: tooltip BROKEN → PASS. Project-wide goes from 1/64/2 to 3/64/0
(PASS / NEEDS-WORK / BROKEN). No more BROKEN components.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent c61ba78d35
commit 2fcade063b

@ -42,18 +42,20 @@ Fuentes externas consultadas:
- Soma cierra al activar/clicar el trigger por defecto, alineado con Radix. Si
se necesita mantenerlo abierto, usar `disableCloseOnTriggerClick`.
## Pendiente deliberado
## Eventos Sema
- `ignoreNonKeyboardFocus` de Bits queda fuera hasta que haya un caso real en
accesibilidad/focus management. No debe vivir en Eidos.
- `customAnchor` para Tooltip queda fuera por ahora; Popover ya cubre el caso
de panel flotante anclado a otro elemento. Si Tooltip lo necesita, debe
anadirse primero a Soma.
El morfo declara tres eventos `emerge` minimos para que Sema pueda emitir
percepcion en hover/focus reveal: `open`, `close` y `close-dismiss` (Escape).
Ninguno carga intent — la aparicion de un tooltip no tiene peso evaluativo
propio. Si una experiencia necesita reforzar percepcion al mostrar ayuda
contextual con un tinte concreto, esa politica debe vivir en el componente
que dispara el tooltip, no en Tooltip por defecto.
## Eventos Sema
## Gaps
Tooltip declara 0 eventos semanticos de forma deliberada. Su apertura/cierre es
disclosure asistivo de hover/focus, no commit, seleccion ni feedback evaluativo.
Si una experiencia necesita percepcion al mostrar ayuda contextual, esa politica
debe declararse en el componente que dispara la ayuda, no en Tooltip por
defecto.
| ID | Disposicion | Detalle |
| --- | --- | --- |
| `ignoreNonKeyboardFocus` (Bits UI) | **diferir** | Sin caso real en accesibilidad/focus management. No debe vivir en Eidos hasta que aparezca el patron concreto. |
| `customAnchor` (Bits UI) | **diferir** | Popover ya cubre panel flotante anclado a otro elemento. Si Tooltip lo necesita, anadir primero a Soma. |
| `data-disabled` aplicado a `Trigger` | **implementar** | Morfo declara `data-disabled` opcional; soma debe propagar la prop del wrapped element. La receta ya estiliza el estado disabled. |
| Tinte sema configurable por `intent` prop | **descartar** | Reveal asistivo no carga intent; forzar uno duplica responsabilidades con el componente que dispara el tooltip. |

@ -156,6 +156,24 @@
to { opacity: 0; transform: translateX(2px); }
}
/* ── Trigger states ─────────────────────────────────────────────────────────
* Trigger is the wrapper around any focusable element (button, link, …).
* The audit (R-1.2 / R-1.5) requires the recipe to acknowledge `data-disabled`
* and `:focus-visible`. The wrapped element keeps its own styling; the rules
* here suppress the tooltip behavior cleanly when the trigger is disabled. */
[data-tooltip-trigger][data-disabled] {
pointer-events: none;
cursor: not-allowed;
}
[data-tooltip-trigger]:focus-visible {
/* Defer the focus ring to the wrapped element — but ensure the outline
is at least visible if the trigger is rendered as a focusable shell. */
outline: var(--focus-ring-width) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
}
/* ── Reduced motion ─────────────────────────────────────────────────────────
* Honour the user's OS-level preference: tooltips appear instantly with
* no slide-in / slide-out, just a binary visibility toggle. The exit

@ -40,6 +40,7 @@ import { timeFieldLangs } from './time-field';
import { timePickerLangs } from './time-picker';
import { timeRangeFieldLangs } from './time-range-field';
import { timeRangePickerLangs } from './time-range-picker';
import { tooltipLangs } from './tooltip';
import { treeGridLangs } from './tree-grid';
import { treeViewLangs } from './tree-view';
@ -89,6 +90,7 @@ export const componentLangs = {
'time-picker': timePickerLangs,
'time-range-field': timeRangeFieldLangs,
'time-range-picker': timeRangePickerLangs,
tooltip: tooltipLangs,
'tree-grid': treeGridLangs,
'tree-view': treeViewLangs
} satisfies LangNode;

@ -0,0 +1,15 @@
import type { LangNode } from '$libs/langs';
/**
* Default strings for the tooltip component. Merged under
* `components.tooltip.*` by `ActiveUix` (via `componentLangs` barrel).
*
* Morfo references entries by absolute idlangref:
* `'#?components.tooltip.{key}|fallback'`.
*/
export const tooltipLangs = {
label: {
es: 'Información complementaria',
en: 'Tooltip'
}
} satisfies LangNode;

@ -1,11 +1,70 @@
import type { Morfo } from '../types';
import { v } from '../types';
/**
* Tooltip — non-modal hover/focus reveal.
*
* Two open states: `delayed-open` (hover, animated) and `instant-open`
* (keyboard focus, no entrance). The recipe CSS keys its entrance/exit
* animations off these values.
*/
export const tooltipMorfo = {
name: 'Tooltip',
kebab: 'tooltip',
scope: ['soma', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/',
texts: {
label: '#?components.tooltip.label|Tooltip'
},
events: [
{
// Pointer- or focus-initiated reveal.
name: 'open',
semantic: {
family: 'emerge',
verb: 'open',
target: v.partRef('content'),
sequence: 'pre'
},
commits: {
part: v.partRef('content'),
attr: 'data-state',
value: 'delayed-open'
}
},
{
// Pointer-leave or blur close.
name: 'close',
semantic: {
family: 'emerge',
verb: 'close',
target: v.partRef('content'),
sequence: 'pre'
},
commits: {
part: v.partRef('content'),
attr: 'data-state',
value: 'closed'
}
},
{
// Escape key dismiss.
name: 'close-dismiss',
semantic: {
family: 'emerge',
verb: 'dismiss',
target: v.partRef('content'),
sequence: 'pre'
},
commits: {
part: v.partRef('content'),
attr: 'data-state',
value: 'closed'
}
}
],
parts: [
{
name: 'Provider',
@ -30,7 +89,8 @@ export const tooltipMorfo = {
attr: 'data-state',
values: ['closed', 'delayed-open', 'instant-open'],
value: v.stateRef('instant-open')
}
},
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' }
],
aria: [
{
@ -38,7 +98,8 @@ export const tooltipMorfo = {
value: v.partRef('content'),
condition: { when: 'part-present', part: 'content' },
severity: 'recommended'
}
},
{ attr: 'aria-disabled', value: v.propRef('disabled'), severity: 'optional' }
],
keyboard: [{ key: 'Escape', action: 'close' }]
},

Loading…
Cancel
Save

Powered by TurnKey Linux.