You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
9.4 KiB
195 lines
9.4 KiB
/* ── Content (variant-shared base) ──────────────────────────────────────── */
|
|
|
|
[data-floating-wrapper]:has(> [data-tooltip-content]) {
|
|
z-index: var(--tooltip-content-z);
|
|
}
|
|
|
|
[data-tooltip-content] {
|
|
--_tooltip-padding-block: var(--tooltip-padding-block-md);
|
|
--_tooltip-padding-inline: var(--tooltip-padding-inline-md);
|
|
--_tooltip-font-size: var(--tooltip-font-size-md);
|
|
--_tooltip-bg: var(--tooltip-bg);
|
|
--_tooltip-border: var(--tooltip-border);
|
|
--_tooltip-shadow: var(--tooltip-shadow);
|
|
/* private: per-instance VALUE CHANNEL — `--_tooltip-content-width-override`
|
|
and `--_tooltip-match-anchor-width` are written per instance and nobody
|
|
declares them; `auto` is the identity when neither is set. A theme pinning
|
|
one would break `matchAnchorWidth`. Same class `drawer` §5 signed. */
|
|
--_tooltip-width: var(--_tooltip-content-width-override, var(--_tooltip-match-anchor-width, auto));
|
|
--_tooltip-max-width: var(--_tooltip-content-max-width-override, var(--tooltip-max-width));
|
|
--_tooltip-max-height: var(
|
|
--_tooltip-content-max-height-override,
|
|
var(--floating-available-height, calc(100dvh - var(--space-4)))
|
|
);
|
|
|
|
display: block;
|
|
box-sizing: border-box;
|
|
inline-size: var(--_tooltip-width);
|
|
min-inline-size: var(--_tooltip-content-min-width-override, auto);
|
|
max-inline-size: min(var(--_tooltip-max-width), calc(100vw - var(--space-4)));
|
|
block-size: var(--_tooltip-content-height-override, auto);
|
|
min-block-size: var(--_tooltip-content-min-height-override, auto);
|
|
max-block-size: var(--_tooltip-max-height);
|
|
padding-block: var(--_tooltip-padding-block);
|
|
padding-inline: var(--_tooltip-padding-inline);
|
|
/* `visible` so the absolutely-positioned caret (containing block = this relative
|
|
* content) is NOT clipped where it pokes past the panel edge — the same reason
|
|
* the popover content is `overflow: visible`. Without this the arrow was clipped
|
|
* away entirely. Tooltips are short, so no inner scroll viewport is needed. */
|
|
overflow: visible;
|
|
border: var(--tooltip-border-width) solid var(--_tooltip-border);
|
|
border-radius: var(--tooltip-radius);
|
|
background: var(--_tooltip-bg);
|
|
color: var(--tooltip-fg);
|
|
box-shadow: var(--_tooltip-shadow);
|
|
transform-origin: var(--floating-transform-origin, center);
|
|
font-family: var(--tooltip-font-family);
|
|
font-size: var(--_tooltip-font-size);
|
|
font-weight: var(--tooltip-font-weight);
|
|
line-height: var(--tooltip-line-height);
|
|
text-wrap: pretty;
|
|
}
|
|
|
|
/* ── Sizes ──────────────────────────────────────────────────────────────── */
|
|
|
|
[data-tooltip-content][data-size='sm'] {
|
|
--_tooltip-padding-block: var(--tooltip-padding-block-sm);
|
|
--_tooltip-padding-inline: var(--tooltip-padding-inline-sm);
|
|
--_tooltip-font-size: var(--tooltip-font-size-sm);
|
|
}
|
|
|
|
[data-tooltip-content][data-size='lg'] {
|
|
--_tooltip-padding-block: var(--tooltip-padding-block-lg);
|
|
--_tooltip-padding-inline: var(--tooltip-padding-inline-lg);
|
|
--_tooltip-font-size: var(--tooltip-font-size-lg);
|
|
}
|
|
|
|
/* ── Variants ───────────────────────────────────────────────────────────── */
|
|
|
|
/* `solid` is the default surface — the base block already paints it. */
|
|
|
|
[data-tooltip-content][data-variant='outline'] {
|
|
--_tooltip-bg: transparent; /* literal: the outline variant IS transparent — identity, not a knob */
|
|
--_tooltip-border: var(--color-border-strong);
|
|
--_tooltip-shadow: none; /* literal: this variant IS shadowless — identity, not a knob */
|
|
}
|
|
|
|
[data-tooltip-content][data-variant='ghost'] {
|
|
/* El plano `overlay` pinta la superficie y gana a esta declaración: se deja
|
|
con su valor literal porque acuñar un token aquí sería acuñar una mentira
|
|
(medido 2026-08-23; §12.9). */
|
|
--_tooltip-bg: color-mix(in srgb, var(--color-surface-overlay) 70%, transparent);
|
|
--_tooltip-border: transparent;
|
|
--_tooltip-shadow: none; /* literal: this variant IS shadowless — identity, not a knob */
|
|
backdrop-filter: blur(var(--tooltip-ghost-blur));
|
|
}
|
|
|
|
/* ── Arrow ──────────────────────────────────────────────────────────────── */
|
|
|
|
/* `display: block` keeps the SVG off the text baseline — inline added ~21px of
|
|
* phantom height that both misaligned the caret AND pushed the panel too far from
|
|
* the trigger (the positioner adds the measured arrow height to the gap). */
|
|
[data-tooltip-arrow] svg {
|
|
display: block;
|
|
overflow: visible;
|
|
}
|
|
|
|
/* Colour the SVG shapes directly — the polygon/path carry `fill`/`stroke =
|
|
* "currentColor"` presentation attributes that a `fill`/`stroke` on the wrapping
|
|
* <span> can't override. The body matches the panel bg (a seamless caret, not a
|
|
* dark blob) and the two exposed edges take the panel border. Now that the content
|
|
* is `overflow: visible` (the caret is no longer clipped), that border outline is
|
|
* what makes the matched caret read against the page — same as the popover. */
|
|
[data-tooltip-arrow] polygon {
|
|
fill: var(--_tooltip-bg, var(--tooltip-bg));
|
|
stroke: none;
|
|
}
|
|
|
|
[data-tooltip-arrow] path {
|
|
stroke: var(--_tooltip-border, var(--tooltip-border));
|
|
stroke-width: var(--tooltip-border-width);
|
|
vector-effect: non-scaling-stroke;
|
|
}
|
|
|
|
/* Pull the caret base into the panel by the border width (per side) so the fill
|
|
* covers the panel's border there and the side strokes continue the outline —
|
|
* otherwise the panel border draws a line across the caret base. Mirrors the
|
|
* popover recipe. */
|
|
[data-tooltip-arrow][data-side='bottom'] {
|
|
margin-block-start: var(--tooltip-border-width);
|
|
}
|
|
[data-tooltip-arrow][data-side='top'] {
|
|
margin-block-end: var(--tooltip-border-width);
|
|
}
|
|
[data-tooltip-arrow][data-side='left'] {
|
|
margin-inline-end: var(--tooltip-border-width);
|
|
}
|
|
[data-tooltip-arrow][data-side='right'] {
|
|
margin-inline-start: var(--tooltip-border-width);
|
|
}
|
|
|
|
/* Outline variant: transparent bg → fill with the surface, stroke the edges. */
|
|
[data-tooltip-content][data-variant='outline'] ~ [data-tooltip-arrow] polygon,
|
|
[data-tooltip-content][data-variant='outline'] [data-tooltip-arrow] polygon {
|
|
fill: var(--tooltip-outline-arrow-bg);
|
|
}
|
|
[data-tooltip-content][data-variant='outline'] ~ [data-tooltip-arrow] path,
|
|
[data-tooltip-content][data-variant='outline'] [data-tooltip-arrow] path {
|
|
stroke: var(--tooltip-outline-arrow-border);
|
|
}
|
|
|
|
/* ── Entrance / exit ─────────────────────────────────────────────────────────
|
|
* Tooltip morfo's data-state values: 'closed' | 'delayed-open' | 'instant-open'.
|
|
* The panel runs the canonical `scale-fade` preset (its original look: subtle
|
|
* scale from the floating transform-origin + fade), stamped by the wrapper as
|
|
* `data-animation-style`. The generator's open-state ALIAS (`delayed-open` ≡
|
|
* open — PRESET_STATE_ALIASES, render-css.ts) is what lets the tri-state
|
|
* machine drive the preset; `instant-open` is deliberately NOT an alias, so a
|
|
* keyboard-focus open appears with no entrance (its semantics). The recipe
|
|
* keeps its tuned durations via the preset's per-element hooks. Reduced motion
|
|
* is the preset's policy (`opacity-only` → quick fade). */
|
|
|
|
[data-tooltip-content] {
|
|
--motion-duration-enter: var(--duration-normal);
|
|
--motion-duration-exit: var(--duration-fast);
|
|
}
|
|
|
|
/* instant-open: no entrance — element appears at final position. */
|
|
[data-tooltip-content][data-state='instant-open'] {
|
|
opacity: 1; /* literal: an open tooltip is fully visible — identity, not a knob */
|
|
transform: none;
|
|
}
|
|
|
|
/* The emerge firma must not add the entrance this state deliberately lacks.
|
|
* On the delayed path the preset entrance wins the cascade over the global
|
|
* `present-rise` signature (measured 2026-08-13); on instant-open there is no
|
|
* state-keyed animation competing, so the signature would animate a focus /
|
|
* skip-delay appearance — against its semantics. Visual silence, scoped to
|
|
* exactly that path: the same doctrine the sema pack signs for sound
|
|
* («una señal … sabe cuándo permanecer mínima o incluso silenciosa»). */
|
|
[data-tooltip-content][data-state='instant-open'][data-event-phase='active'] {
|
|
animation: none;
|
|
}
|
|
|
|
/* ── 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 is owned by the preset's policy (`scale-fade` → opacity-only:
|
|
* a brief fade, transforms dropped) — emitted by the generator under both
|
|
* `[data-motion='reduce']` and the media query. No local override needed. */
|