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
parent
a9111572cd
commit
e0bb7ed5f5
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,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>
|
||||
|
||||
Loading…
Reference in new issue