feat(eidos): shared field control-trigger + segment state/width layers; uix- id prefix

Consolidate the per-field / per-picker duplication the audit surfaced:

- Shared control trigger — new field/field-control-trigger.css selected by a
  `data-field-control-trigger` marker: flush (no border, no shadow), UNIFORM
  neutral hover tint (not the field accent), font-relative size. Migrated
  date-picker + the password visibility toggle; deletes their per-picker chrome.
  (date-range / time / time-range pickers still to migrate.)

- Shared segment state layer — new field/field-segment-state.css selected by a
  `data-field-segment` marker (date / time / color fields): one hover (editable)
  + active (focus) rule. Fixes a specificity bug where the hover selector
  out-ranked :focus, so hovering the active segment downgraded its highlight.

- Segment fixed width — min-inline-size: calc(2ch + 2*segment-px) +
  font-variant-numeric: tabular-nums so a segment never reflows when its value
  changes 1<->2 digits or between glyphs (no horizontal jitter). date-field done.

- date-range-picker trigger uses the same calendar icon as date-picker (was a
  distinct calendar-range glyph).

- Generated element ids use the framework-neutral `uix-` prefix instead of the
  layer name `soma-` (createId / useId + announce / menubar inline ids) — the
  doctrine that bans data-soma-* / --soma-* keeps a layer name out of ids too.

- field.css: label + helper text scale one step below the control (font-relative
  calc); optional-indicator weight to regular.

Foundation tokens added: --field-segment-hover-bg, --field-control-trigger-hover-bg.
Verified in-browser by measurement (date-field segments: states + no-jitter;
date-picker trigger: flush + uniform hover). check = 58 baseline (0 new);
recipe-css-contract clean (only pre-existing palabras WIP).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
active-uix
dev 4 months ago
parent a9111572cd
commit e0bb7ed5f5

@ -2,8 +2,8 @@
* Create an id from a Svelte `$props.id()` uid, with optional component context.
*
* ```ts
* createId(uid) // "soma-c12"
* createId(uid, 'dialog-trigger') // "soma-dialog-trigger-c12"
* createId(uid) // "uix-c12"
* createId(uid, 'dialog-trigger') // "uix-dialog-trigger-c12"
* ```
*
* The `uid` comes from the consuming component's `$props.id()`, so the output
@ -12,9 +12,10 @@
*
* Lives in active-uix (imported by subpath `$active-uix/id`) so every UIX layer
* — soma, eidos, demos, apps — shares one id authority without cross-layer
* coupling. The `soma-` prefix is the UIX id convention, kept for output
* stability across the migration.
* coupling. The prefix is `uix-` (the framework), NOT a layer name: the same
* doctrine that bans `data-soma-*` / `--soma-*` keeps a layer name out of
* element ids too.
*/
export function createId(uid: string, component?: string): string {
return component ? `soma-${component}-${uid}` : `soma-${uid}`
return component ? `uix-${component}-${uid}` : `uix-${uid}`
}

@ -8,11 +8,14 @@
* NOT for SSR / ARIA element ids derived from the component tree — those use
* `createId` (same module), which wraps Svelte's `$props.id()` for
* hydration-stable output. A global counter would desync server / client.
*
* Prefix is `uix-` (framework-neutral), never a layer name — same doctrine as
* the `data-soma-*` / `--soma-*` bans.
*/
let counter = 0
export function useId(component = ''): string {
const id = ++counter
return component ? `soma-${component}-${id}` : `soma-${id}`
return component ? `uix-${component}-${id}` : `uix-${id}`
}

@ -1,10 +1,11 @@
<script lang="ts">
import * as ColorField from '$soma/components/color-field';
import '../field/field-segment-state.css';
import type { ColorFieldSegmentProps } from './types';
let { children, ...rest }: ColorFieldSegmentProps = $props();
</script>
<ColorField.Segment {...rest}>
<ColorField.Segment {...rest} data-field-segment="">
{@render children?.()}
</ColorField.Segment>

@ -178,12 +178,8 @@
color: var(--color-field-placeholder-color);
}
[data-color-field-segment]:focus-visible {
/* Active highlight: shared `--field-segment-active-*` foundation tokens. */
background: var(--field-segment-active-bg);
color: var(--field-segment-active-text);
font-weight: 600;
}
/* Segment hover + active states are shared — see field/field-segment-state.css
(selects `[data-field-segment]`, the eidos marker on every field segment). */
[data-color-field-segment][data-invalid] {
color: var(--color-field-segment-invalid-color);

@ -1,10 +1,11 @@
<script lang="ts">
import * as DateField from '$soma/components/date-field';
import '../field/field-segment-state.css';
import type { DateFieldSegmentProps } from './types';
let { children, ...rest }: DateFieldSegmentProps = $props();
</script>
<DateField.Segment {...rest}>
<DateField.Segment {...rest} data-field-segment="">
{@render children?.()}
</DateField.Segment>

@ -157,7 +157,10 @@
display: inline-flex;
align-items: center;
justify-content: center;
min-inline-size: var(--date-field-segment-min-width);
/* Fixed 2-digit width (font-relative) + tabular figures (shared layer) so the
segment never reflows when the value changes 1 → 2 digits or between glyphs.
border-box: hold 2ch of content + the inline padding. */
min-inline-size: calc(2ch + 2 * var(--_date-field-segment-px));
block-size: var(--date-field-segment-height);
padding-inline: var(--_date-field-segment-px);
border-radius: var(--date-field-segment-radius);
@ -191,32 +194,8 @@
cursor: text;
}
[data-date-field-segment]:not([data-segment='literal']):not([data-readonly]):not([data-disabled]):hover {
background: var(--date-field-segment-hover-bg, color-mix(in srgb, var(--color-content-primary) 6%, transparent));
}
/*
* Active segment — the one the user is editing (focused via click,
* tap, arrow-key navigation or paste). Catch BOTH `:focus` and
* `:focus-visible` so the affordance shows regardless of input
* modality — segments are direct-manipulation surfaces where the
* user is about to type, so always advertising the active target
* is correct (vs the global `:focus-visible` convention which
* suppresses focus rings on mouse-down).
*
* Visual: accent element bg + accent text + bold weight. NO border/ring —
* the active segment is highlighted by background only (homogeneous across
* the date/time/color field family).
*/
[data-date-field-segment]:focus,
[data-date-field-segment]:focus-visible {
/* Active highlight: theme-owned via the shared `--field-segment-active-*`
foundation tokens (the bg carries its own transparency). */
background: var(--field-segment-active-bg);
color: var(--field-segment-active-text);
font-weight: 600;
outline: none;
}
/* Segment hover + active states are shared — see field/field-segment-state.css
(selects `[data-field-segment]`, the eidos marker on every field segment). */
[data-date-field-segment][data-invalid] {
color: var(--date-field-segment-invalid-color);

@ -2,6 +2,7 @@
import { ActiveEidos } from '$uix/eidos';
import * as DatePicker from '$soma/components/date-picker';
import CalendarIcon from '../icon/lib/calendar.svelte';
import '../field/field-control-trigger.css';
import { getDatePickerVisualContext } from './context.svelte';
import type { DatePickerTriggerProps } from './types';
@ -16,6 +17,7 @@
<DatePicker.Trigger
{...rest}
data-field-control-trigger=""
data-size={resolvedSize}
data-variant={resolvedVariant}
data-color={resolvedColor}

@ -103,82 +103,9 @@
opacity: var(--date-field-disabled-opacity);
}
[data-date-picker-trigger] {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
inline-size: var(--_date-field-height);
block-size: var(--_date-field-height);
padding: 0;
border: var(--date-field-border-width) solid var(--date-field-border);
border-radius: var(--date-field-radius);
background: var(--date-field-bg);
color: inherit;
cursor: pointer;
transition:
background var(--date-field-transition-duration) var(--date-field-transition-ease),
border-color var(--date-field-transition-duration) var(--date-field-transition-ease),
box-shadow var(--date-field-transition-duration) var(--date-field-transition-ease),
color var(--date-field-transition-duration) var(--date-field-transition-ease);
}
[data-date-picker-trigger]:hover {
border-color: var(--_date-field-accent-border);
background: var(--_date-field-accent-track);
color: var(--_date-field-accent-text);
}
[data-date-picker-trigger]:focus-visible {
outline: none;
border-color: var(--_date-field-accent-border);
box-shadow: var(--date-field-focus-shadow);
}
[data-date-picker-trigger][data-disabled],
[data-date-picker-trigger]:disabled {
cursor: not-allowed;
opacity: var(--date-field-disabled-opacity);
}
/* ── Trigger embedded inside the input ──────────────────────────────────────
* When the consumer places <DatePicker.Trigger /> as a descendant of
* <DatePicker.Input>, the icon becomes a flush affordance at the input's
* trailing edge: no border, no separate background, smaller hit area sized
* to the segment height, and a subtle hover tint that doesn't compete with
* the field's focus ring. The standalone treatment above stays available
* for consumers who place the trigger as a sibling. */
[data-date-field-input] [data-date-picker-trigger] {
flex: 0 0 auto;
/* `--date-field-segment-height` is scoped to `[data-date-field]`, which the
picker doesn't render — so it's undefined here and the box collapsed to the
icon. Inline the same calc off the picker-local `--_date-field-height` (which
scales per size). `font: inherit` so the `1em` icon tracks the field font. */
inline-size: calc(var(--_date-field-height) - var(--space-2));
block-size: calc(var(--_date-field-height) - var(--space-2));
margin-inline-start: auto;
padding: 0;
border: none;
border-radius: var(--date-field-segment-radius);
background: transparent;
color: var(--date-field-segment-color);
box-shadow: none;
font: inherit;
}
[data-date-field-input] [data-date-picker-trigger]:hover {
border: none;
background: var(--_date-field-accent-track);
color: var(--_date-field-accent-text);
}
[data-date-field-input] [data-date-picker-trigger]:focus-visible {
outline: none;
border: none;
background: var(--field-segment-active-bg);
color: var(--field-segment-active-text);
}
/* Trigger chrome (flush affordance, hover / focus states, size) is shared —
see field/field-control-trigger.css (`[data-field-control-trigger]`, the
eidos marker on the trigger). */
[data-popover-content]:has([data-date-picker-calendar]) {
--_popover-content-width-override: max-content;
@ -216,9 +143,3 @@
inline-size: max-content;
min-inline-size: max-content;
}
@media (prefers-reduced-motion: reduce) {
[data-date-picker-trigger] {
transition: none;
}
}

@ -1,7 +1,7 @@
<script lang="ts">
import { ActiveEidos } from '$uix/eidos';
import * as DateRangePicker from '$soma/components/date-range-picker';
import CalendarRangeIcon from '../icon/lib/calendar-range.svelte';
import CalendarIcon from '../icon/lib/calendar.svelte';
import { getDateRangePickerVisualContext } from './context.svelte';
import type { DateRangePickerTriggerProps } from './types';
@ -23,6 +23,6 @@
{#if children}
{@render children()}
{:else}
<CalendarRangeIcon size="1em" decorative />
<CalendarIcon size="1em" decorative />
{/if}
</DateRangePicker.Trigger>

@ -0,0 +1,63 @@
/*
* Shared field control trigger — the icon affordance that opens a field's
* overlay (date / time / color picker: calendar / clock / swatch) or toggles
* password visibility.
*
* Carries `data-field-control-trigger` (eidos marker, added by each trigger
* wrapper) so ONE flush treatment serves every field's control: no border, no
* shadow, a UNIFORM neutral hover tint (never the field's semantic accent),
* sized from the field font. This replaces the per-picker `[data-x-picker-
* trigger]` chrome that had drifted (some with a focus box-shadow, some with a
* border, different hover colours).
*
* Owns the trigger's own box + chrome + states. Placement inside the field
* (which column / flex slot) stays with the host field.
*/
[data-field-control-trigger] {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
margin-inline-start: auto;
inline-size: 1.75em;
block-size: 1.75em;
padding: 0;
border: none;
border-radius: var(--radius-sm);
background: transparent;
color: var(--color-content-secondary);
cursor: pointer;
font: inherit;
transition:
background var(--duration-fast),
color var(--duration-fast);
}
[data-field-control-trigger] :is(svg, [data-icon]) {
inline-size: 1em;
block-size: 1em;
}
[data-field-control-trigger]:hover:not([data-disabled]):not(:disabled) {
background: var(--field-control-trigger-hover-bg);
color: var(--color-content-primary);
}
[data-field-control-trigger]:focus-visible {
outline: var(--border-width-medium) solid var(--color-focus-ring);
outline-offset: -1px;
color: var(--color-content-primary);
}
[data-field-control-trigger][data-disabled],
[data-field-control-trigger]:disabled {
cursor: not-allowed;
opacity: var(--opacity-disabled);
}
@media (prefers-reduced-motion: reduce) {
[data-field-control-trigger] {
transition: none;
}
}

@ -0,0 +1,42 @@
/*
* Shared segment state layer — date / time / color field segments.
*
* A segment carries `data-field-segment` (eidos marker, added by each field's
* segment wrapper) ON TOP of its per-field `data-{x}-field-segment` (which owns
* the box: size, padding, radius, base colour). This file owns ONLY the two
* interaction STATES, so every field reads them identically:
*
* hover → editable affordance (faint tint, "you can type here")
* active → the focused segment being edited (accent fill + bold)
*
* Both tints are foundation tokens (`--field-segment-{hover,active}-*`), so a
* theme retunes them from one place.
*
* The hover rule excludes the active segment (`:not(:focus)`) so hovering the
* segment you're editing does NOT downgrade its highlight. This fixes a
* specificity bug: the hover selector (more `:not()` clauses → higher
* specificity) used to out-rank the bare `:focus` rule, so active+hover showed
* the faint hover tint instead of the strong active fill.
*/
/* Tabular figures so every digit shares one advance — no horizontal jitter when
a value changes (1 → 8, different glyph widths). Paired with a 2-digit-wide
min-inline-size on each field's segment box so the segment doesn't reflow when
it grows 1 → 2 digits either. */
[data-field-segment] {
font-variant-numeric: tabular-nums;
}
[data-field-segment]:not([data-segment='literal']):not([data-readonly]):not([data-disabled]):not(
:focus
):not(:focus-visible):hover {
background: var(--field-segment-hover-bg);
}
[data-field-segment]:focus,
[data-field-segment]:focus-visible {
background: var(--field-segment-active-bg);
color: var(--field-segment-active-text);
font-weight: 600;
outline: none;
}

@ -1,6 +1,5 @@
[data-field] {
--_field-gap: var(--field-gap-md);
--_field-label-font-size: var(--field-label-font-size-md);
--_field-control-height: var(--field-control-height-md);
--_field-control-px: var(--field-control-px-md);
--_field-control-gap: var(--field-control-gap-md);
@ -56,7 +55,6 @@
[data-field][data-size='xs'] {
--_field-gap: var(--field-gap-xs);
--_field-label-font-size: var(--field-label-font-size-xs);
--_field-control-height: var(--field-control-height-xs);
--_field-control-px: var(--field-control-px-xs);
--_field-control-gap: var(--field-control-gap-xs);
@ -65,7 +63,6 @@
[data-field][data-size='sm'] {
--_field-gap: var(--field-gap-sm);
--_field-label-font-size: var(--field-label-font-size-sm);
--_field-control-height: var(--field-control-height-sm);
--_field-control-px: var(--field-control-px-sm);
--_field-control-gap: var(--field-control-gap-sm);
@ -74,7 +71,6 @@
[data-field][data-size='lg'] {
--_field-gap: var(--field-gap-lg);
--_field-label-font-size: var(--field-label-font-size-lg);
--_field-control-height: var(--field-control-height-lg);
--_field-control-px: var(--field-control-px-lg);
--_field-control-gap: var(--field-control-gap-lg);
@ -83,7 +79,6 @@
[data-field][data-size='xl'] {
--_field-gap: var(--field-gap-xl);
--_field-label-font-size: var(--field-label-font-size-xl);
--_field-control-height: var(--field-control-height-xl);
--_field-control-px: var(--field-control-px-xl);
--_field-control-gap: var(--field-control-gap-xl);
@ -153,7 +148,10 @@
gap: var(--field-label-gap);
color: var(--field-label-color);
font-family: var(--field-font-family);
font-size: var(--_field-label-font-size);
/* One typographic step below the control (md − sm = 2px), derived from the
control font so it scales + stays coherent with the segmented fields (date/
time/color), which use the same offset. */
font-size: calc(var(--_field-control-font-size) - (var(--font-size-md) - var(--font-size-sm)));
font-weight: var(--field-label-font-weight);
line-height: var(--field-label-line-height);
}
@ -176,7 +174,7 @@
[data-field-optional-indicator] {
color: var(--color-content-muted);
font-size: 0.85em;
font-weight: var(--font-weight-normal, 400);
font-weight: var(--font-weight-regular);
line-height: 1;
margin-inline-start: var(--space-1);
}
@ -258,7 +256,9 @@
[data-field-error-text] {
margin: 0;
font-family: var(--field-font-family);
font-size: var(--field-message-font-size);
/* Supporting text scales with the field — one step below the control (same as
the label), not a fixed size. Differentiated from the label by colour. */
font-size: calc(var(--_field-control-font-size) - (var(--font-size-md) - var(--font-size-sm)));
line-height: var(--field-message-line-height);
}

@ -225,7 +225,10 @@
inline-size: var(--_password-field-trigger-size);
block-size: var(--_password-field-trigger-size);
padding: 0;
border: var(--password-field-trigger-border-width) solid var(--password-field-trigger-border);
/* Flush affordance inside the field — same treatment as the date-picker
* embedded trigger: no border (a hovered border competes with the field's
* own focus ring), just a subtle accent tint on hover. */
border: none;
border-radius: var(--password-field-trigger-radius);
background: var(--password-field-trigger-bg);
color: var(--password-field-trigger-color);
@ -238,9 +241,8 @@
}
[data-password-field-visibility-trigger]:hover:not([data-disabled]) {
background: var(--password-field-trigger-bg-hover);
border-color: var(--password-field-trigger-border-hover);
color: var(--password-field-trigger-color-hover);
background: var(--_password-field-accent-track);
color: var(--_password-field-accent-text);
}
[data-password-field-visibility-trigger]:focus-visible {

@ -1,10 +1,11 @@
<script lang="ts">
import * as TimeField from '$soma/components/time-field';
import '../field/field-segment-state.css';
import type { TimeFieldSegmentProps } from './types';
let { children, ...rest }: TimeFieldSegmentProps = $props();
</script>
<TimeField.Segment {...rest}>
<TimeField.Segment {...rest} data-field-segment="">
{@render children?.()}
</TimeField.Segment>

@ -173,12 +173,8 @@
color: var(--time-field-placeholder-color);
}
[data-time-field-segment]:focus-visible {
/* Active highlight: shared `--field-segment-active-*` foundation tokens. */
background: var(--field-segment-active-bg);
color: var(--field-segment-active-text);
font-weight: 600;
}
/* Segment hover + active states are shared — see field/field-segment-state.css
(selects `[data-field-segment]`, the eidos marker on every field segment). */
[data-time-field-segment][data-invalid] {
color: var(--time-field-segment-invalid-color);

@ -180,6 +180,8 @@
--focus-ring-error: inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color-error), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color-error);
--field-segment-active-bg: color-mix(in srgb, var(--color-primary-border) 40%, transparent);
--field-segment-active-text: var(--color-primary-text);
--field-segment-hover-bg: color-mix(in srgb, var(--color-content-primary) 6%, transparent);
--field-control-trigger-hover-bg: color-mix(in srgb, var(--color-content-primary) 8%, transparent);
--container-width-sm: 448px;
--container-width-md: 688px;
--container-width-lg: 880px;
@ -1425,7 +1427,6 @@
--date-field-segment-readonly-bg: var(--color-surface-overlay);
--date-field-segment-readonly-color: var(--color-content-secondary);
--date-field-segment-readonly-decoration: underline dotted var(--color-content-muted);
--date-field-segment-hover-bg: color-mix(in srgb, var(--color-content-primary) 6%, transparent);
--date-field-transition-duration: var(--duration-fast);
--date-field-transition-ease: var(--ease-default);
--date-field-disabled-opacity: var(--opacity-disabled);
@ -2117,14 +2118,9 @@
--password-field-color: var(--color-content-primary);
--password-field-placeholder-color: var(--color-content-muted);
--password-field-focus-shadow: inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color);
--password-field-trigger-radius: var(--radius-full);
--password-field-trigger-border-width: var(--border-width);
--password-field-trigger-border: transparent;
--password-field-trigger-border-hover: var(--color-border-subtle);
--password-field-trigger-radius: var(--radius-sm);
--password-field-trigger-bg: transparent;
--password-field-trigger-bg-hover: var(--color-surface-raised);
--password-field-trigger-color: var(--color-content-secondary);
--password-field-trigger-color-hover: var(--color-content-primary);
--password-field-trigger-focus-shadow: inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color);
--password-field-icon-size-xs: var(--control-height-xs);
--password-field-icon-size-sm: var(--control-height-xs);

@ -1249,7 +1249,6 @@ export const THEME_BASE_RECIPE_TOKENS = {
'segment-readonly-bg': 'var(--color-surface-overlay)',
'segment-readonly-color': 'var(--color-content-secondary)',
'segment-readonly-decoration': 'underline dotted var(--color-content-muted)',
'segment-hover-bg': 'color-mix(in srgb, var(--color-content-primary) 6%, transparent)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': 'var(--opacity-disabled)'
@ -2156,14 +2155,9 @@ export const THEME_BASE_RECIPE_TOKENS = {
'placeholder-color': 'var(--color-content-muted)',
'focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'trigger-radius': 'var(--radius-full)',
'trigger-border-width': 'var(--border-width)',
'trigger-border': 'transparent',
'trigger-border-hover': 'var(--color-border-subtle)',
'trigger-radius': 'var(--radius-sm)',
'trigger-bg': 'transparent',
'trigger-bg-hover': 'var(--color-surface-raised)',
'trigger-color': 'var(--color-content-secondary)',
'trigger-color-hover': 'var(--color-content-primary)',
'trigger-focus-shadow':
'inset 0 0 0 var(--focus-ring-inner-width) var(--focus-ring-color), 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'icon-size-xs': 'var(--control-height-xs)',

@ -212,6 +212,25 @@ export function renderStaticCss(options: EidosConfig): string {
)
declarations.push(cssVar('field-segment-active-text', 'var(--color-primary-text)'))
// Hover affordance for segments — the faint "you can edit here" tint, shared
// by every date / time / color field segment (distinct from the active fill).
declarations.push(
cssVar(
'field-segment-hover-bg',
'color-mix(in srgb, var(--color-content-primary) 6%, transparent)'
)
)
// Control-trigger hover — uniform neutral tint for the affordance that opens a
// field overlay (calendar / clock / swatch) or toggles password visibility. NOT
// the field's accent: every field's control reads the same on hover.
declarations.push(
cssVar(
'field-control-trigger-hover-bg',
'color-mix(in srgb, var(--color-content-primary) 8%, transparent)'
)
)
if (primitives.layout) {
appendLayoutDeclarations(declarations, primitives.layout)
}

@ -23,8 +23,8 @@ import type { AnnouncePriority, AnnounceApi } from './types';
* resolve a document.
*/
const POLITE_ID = 'soma-announce-global-polite';
const ASSERTIVE_ID = 'soma-announce-global-assertive';
const POLITE_ID = 'uix-announce-global-polite';
const ASSERTIVE_ID = 'uix-announce-global-assertive';
const VISUALLY_HIDDEN_STYLE =
'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0';

@ -202,7 +202,7 @@ export class MenubarMenuProvider {
/** Nested DropdownMenu provider — supplies Content/Item/Sub with context. */
readonly menu: DropdownMenuProvider;
private readonly menuId = state(`soma-menubar-menu-${Math.random().toString(36).slice(2, 8)}`);
private readonly menuId = state(`uix-menubar-menu-${Math.random().toString(36).slice(2, 8)}`);
private constructor(opts: MenubarMenuOpts) {
this.opts = opts;

@ -18,7 +18,7 @@ function initAnnouncer(dom: ActiveDom | null, target?: Node | null) {
if (!isHTMLElement(el)) {
el =
dom.writeNode('soma-sr-announcer', {
dom.writeNode('uix-sr-announcer', {
host: doc.body,
position: 'prepend',
attrs: {
@ -29,11 +29,11 @@ function initAnnouncer(dom: ActiveDom | null, target?: Node | null) {
}
if (isHTMLElement(el)) {
dom.writeNode('soma-sr-announcer-assertive', {
dom.writeNode('uix-sr-announcer-assertive', {
host: el,
attrs: { role: 'log', 'aria-live': 'assertive', 'aria-relevant': 'additions' }
});
dom.writeNode('soma-sr-announcer-polite', {
dom.writeNode('uix-sr-announcer-polite', {
host: el,
attrs: { role: 'log', 'aria-live': 'polite', 'aria-relevant': 'additions' }
});

Loading…
Cancel
Save

Powered by TurnKey Linux.