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 - Soma cierra al activar/clicar el trigger por defecto, alineado con Radix. Si
se necesita mantenerlo abierto, usar `disableCloseOnTriggerClick`. se necesita mantenerlo abierto, usar `disableCloseOnTriggerClick`.
## Pendiente deliberado ## Eventos Sema
- `ignoreNonKeyboardFocus` de Bits queda fuera hasta que haya un caso real en El morfo declara tres eventos `emerge` minimos para que Sema pueda emitir
accesibilidad/focus management. No debe vivir en Eidos. percepcion en hover/focus reveal: `open`, `close` y `close-dismiss` (Escape).
- `customAnchor` para Tooltip queda fuera por ahora; Popover ya cubre el caso Ninguno carga intent — la aparicion de un tooltip no tiene peso evaluativo
de panel flotante anclado a otro elemento. Si Tooltip lo necesita, debe propio. Si una experiencia necesita reforzar percepcion al mostrar ayuda
anadirse primero a Soma. 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 | ID | Disposicion | Detalle |
disclosure asistivo de hover/focus, no commit, seleccion ni feedback evaluativo. | --- | --- | --- |
Si una experiencia necesita percepcion al mostrar ayuda contextual, esa politica | `ignoreNonKeyboardFocus` (Bits UI) | **diferir** | Sin caso real en accesibilidad/focus management. No debe vivir en Eidos hasta que aparezca el patron concreto. |
debe declararse en el componente que dispara la ayuda, no en Tooltip por | `customAnchor` (Bits UI) | **diferir** | Popover ya cubre panel flotante anclado a otro elemento. Si Tooltip lo necesita, anadir primero a Soma. |
defecto. | `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); } 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 ───────────────────────────────────────────────────────── /* ── Reduced motion ─────────────────────────────────────────────────────────
* Honour the user's OS-level preference: tooltips appear instantly with * Honour the user's OS-level preference: tooltips appear instantly with
* no slide-in / slide-out, just a binary visibility toggle. The exit * 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 { timePickerLangs } from './time-picker';
import { timeRangeFieldLangs } from './time-range-field'; import { timeRangeFieldLangs } from './time-range-field';
import { timeRangePickerLangs } from './time-range-picker'; import { timeRangePickerLangs } from './time-range-picker';
import { tooltipLangs } from './tooltip';
import { treeGridLangs } from './tree-grid'; import { treeGridLangs } from './tree-grid';
import { treeViewLangs } from './tree-view'; import { treeViewLangs } from './tree-view';
@ -89,6 +90,7 @@ export const componentLangs = {
'time-picker': timePickerLangs, 'time-picker': timePickerLangs,
'time-range-field': timeRangeFieldLangs, 'time-range-field': timeRangeFieldLangs,
'time-range-picker': timeRangePickerLangs, 'time-range-picker': timeRangePickerLangs,
tooltip: tooltipLangs,
'tree-grid': treeGridLangs, 'tree-grid': treeGridLangs,
'tree-view': treeViewLangs 'tree-view': treeViewLangs
} satisfies LangNode; } 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 type { Morfo } from '../types';
import { v } 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 = { export const tooltipMorfo = {
name: 'Tooltip', name: 'Tooltip',
kebab: 'tooltip', kebab: 'tooltip',
scope: ['soma', 'eidos'], scope: ['soma', 'eidos'],
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/', 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: [ parts: [
{ {
name: 'Provider', name: 'Provider',
@ -30,7 +89,8 @@ export const tooltipMorfo = {
attr: 'data-state', attr: 'data-state',
values: ['closed', 'delayed-open', 'instant-open'], values: ['closed', 'delayed-open', 'instant-open'],
value: v.stateRef('instant-open') value: v.stateRef('instant-open')
} },
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' }
], ],
aria: [ aria: [
{ {
@ -38,7 +98,8 @@ export const tooltipMorfo = {
value: v.partRef('content'), value: v.partRef('content'),
condition: { when: 'part-present', part: 'content' }, condition: { when: 'part-present', part: 'content' },
severity: 'recommended' severity: 'recommended'
} },
{ attr: 'aria-disabled', value: v.propRef('disabled'), severity: 'optional' }
], ],
keyboard: [{ key: 'Escape', action: 'close' }] keyboard: [{ key: 'Escape', action: 'close' }]
}, },

Loading…
Cancel
Save

Powered by TurnKey Linux.