fix(eidos): DatePicker/Calendar — flush field triggers + drop listbox `item` chrome from grid cells

Several distinct regressions/latent bugs surfaced via the DatePicker, all the same
root: parts wrongly typed as the interactive `trigger`/`item` archetype pulling
button/listbox chrome they should not have.

- Field overlay triggers (date/time/color picker, date-range) are NOT buttons:
  new `field-trigger` archetype; the picker trigger soma overrides the popover's
  generic `trigger` archetype with it. popover.css no longer applies the generic
  popover-button envelope to `[data-field-control-trigger]` (composing IconButton
  flipped CSS load order, so the button border/bg bled into the flush field icon).
- The field input drops its padding on the trigger side (logical → LTR + RTL):
  `[data-archetype='input']:has([data-field-control-trigger]) { padding-inline-end: 0 }`
  — the trigger sits flush instead of behind the full text padding.
- Calendar/range/month/year-grid Cell + GridRow + Day declared `archetype: 'item'`,
  which shipped the listbox-row chrome: 6px cell padding (tall rows), a full-week
  hover band, and `[data-archetype='item'][data-selected]` (higher specificity)
  OVERRODE the calendar's accent fill so the selected day showed the menu tint
  instead of solid accent. These are display gridcells, not menu items — archetype
  removed; the Day keeps its bespoke `[data-calendar-day]` styling.

Verified in browser: DatePicker now matches the standalone Calendar (32px rows,
solid-accent selected day, no week-band hover, flush calendar-icon trigger).
morfo 62/62. Excludes words/palabras/chronos.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
active-uix
dev 4 months ago
parent a4d86edb24
commit f822d6e4dd

@ -14,6 +14,17 @@
* (which column / flex slot) stays with the host field. * (which column / flex slot) stays with the host field.
*/ */
/*
* When a field input hosts a control trigger, the input drops its padding on
* the trigger side (logical → works in both LTR and RTL): the trigger sits flush
* at the edge and provides its own inset (the centred glyph in its 1.75em box),
* instead of the input's full text padding adding a second, larger gap. One rule
* for every field — keys off the input archetype + the shared trigger marker.
*/
[data-archetype='input']:has([data-field-control-trigger]) {
padding-inline-end: 0;
}
[data-field-control-trigger] { [data-field-control-trigger] {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;

@ -2,8 +2,15 @@
* Air relied on the Button recipe via .air-button class. Eidos applies a * Air relied on the Button recipe via .air-button class. Eidos applies a
* baseline button envelope here. Consumes Capa 2 directly (same as Dialog * baseline button envelope here. Consumes Capa 2 directly (same as Dialog
* trigger). Consumers can override per-instance via inline tokens. * trigger). Consumers can override per-instance via inline tokens.
*
* `:not([data-field-control-trigger])` — a field's icon affordance (date/time/
* color picker calendar/clock/swatch button) opens its overlay via a popover, so
* it ALSO carries `data-popover-trigger`, but it owns a flush, border-less
* treatment in `field-control-trigger.css`. This generic button envelope must
* NOT win over it (the two are equal-specificity, so it would otherwise depend
* on CSS load order).
*/ */
[data-popover-trigger] { [data-popover-trigger]:not([data-field-control-trigger]) {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -21,17 +28,17 @@
border-color var(--duration-fast) var(--ease-default); border-color var(--duration-fast) var(--ease-default);
} }
[data-popover-trigger]:hover:not([disabled]):not([data-disabled]) { [data-popover-trigger]:not([data-field-control-trigger]):hover:not([disabled]):not([data-disabled]) {
background: var(--color-surface-overlay); background: var(--color-surface-overlay);
border-color: var(--color-border-strong); border-color: var(--color-border-strong);
} }
[data-popover-trigger][data-state='open'] { [data-popover-trigger]:not([data-field-control-trigger])[data-state='open'] {
background: var(--color-surface-overlay); background: var(--color-surface-overlay);
border-color: var(--color-border-strong); border-color: var(--color-border-strong);
} }
[data-popover-trigger]:focus-visible { [data-popover-trigger]:not([data-field-control-trigger]):focus-visible {
outline: var(--focus-ring-width) solid var(--focus-ring-color); outline: var(--focus-ring-width) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset); outline-offset: var(--focus-ring-offset);
} }

@ -244,7 +244,9 @@ export const calendarMorfo = {
{ {
name: 'GridRow', name: 'GridRow',
kebab: 'grid-row', kebab: 'grid-row',
archetype: 'item', // No archetype: a `<tr>` week row is a structural container, NOT a menu
// `item`. `data-archetype='item'` would tint the WHOLE row on hover (the
// "hover over the entire week" bug). Each Day owns its own hover.
kind: 'public', kind: 'public',
defaultElement: 'tr', defaultElement: 'tr',
role: 'row', role: 'row',
@ -265,7 +267,10 @@ export const calendarMorfo = {
{ {
name: 'Cell', name: 'Cell',
kebab: 'cell', kebab: 'cell',
archetype: 'item', // No archetype: the cell is a display `gridcell` container, NOT an
// interactive menu `item`. `data-archetype='item'` would ship the
// listbox-row chrome (padding-block, cursor, hover band) — the
// clickable Day inside owns its own interaction styling.
kind: 'public', kind: 'public',
defaultElement: 'td', defaultElement: 'td',
role: 'gridcell', role: 'gridcell',
@ -300,7 +305,11 @@ export const calendarMorfo = {
{ {
name: 'Day', name: 'Day',
kebab: 'day', kebab: 'day',
archetype: 'item', // No archetype: the Day owns bespoke `[data-calendar-day]` styling for
// hover / selected / focused. `data-archetype='item'` would let the
// generic listbox-item rules (higher specificity) OVERRIDE the selected
// fill — the selected day showed the menu-item tint instead of the
// calendar accent. The day is a calendar cell, not a menu item.
kind: 'public', kind: 'public',
defaultElement: 'div', defaultElement: 'div',
role: 'button', role: 'button',

@ -124,7 +124,7 @@ export const datePickerMorfo = {
{ {
name: 'Trigger', name: 'Trigger',
kebab: 'trigger', kebab: 'trigger',
archetype: 'trigger', archetype: 'field-trigger',
kind: 'public', kind: 'public',
defaultElement: 'button', defaultElement: 'button',
role: 'button', role: 'button',

@ -180,7 +180,8 @@ export const monthGridMorfo = {
{ {
name: 'Cell', name: 'Cell',
kebab: 'cell', kebab: 'cell',
archetype: 'item', // No archetype: display gridcell container, not a menu `item` (the
// inner month button owns the interaction styling).
kind: 'public', kind: 'public',
defaultElement: 'button', defaultElement: 'button',
role: 'gridcell', role: 'gridcell',

@ -219,7 +219,8 @@ export const rangeCalendarMorfo = {
{ {
name: 'GridRow', name: 'GridRow',
kebab: 'grid-row', kebab: 'grid-row',
archetype: 'item', // No archetype: structural `<tr>` row, not a menu `item` (would tint the
// whole week on hover).
kind: 'public', kind: 'public',
defaultElement: 'tr', defaultElement: 'tr',
role: 'row', role: 'row',
@ -240,7 +241,8 @@ export const rangeCalendarMorfo = {
{ {
name: 'Cell', name: 'Cell',
kebab: 'cell', kebab: 'cell',
archetype: 'item', // No archetype: display `gridcell` container, not a menu `item` (would
// add the listbox-row padding + hover band). The Day inside is the item.
kind: 'public', kind: 'public',
defaultElement: 'td', defaultElement: 'td',
role: 'gridcell', role: 'gridcell',
@ -279,7 +281,9 @@ export const rangeCalendarMorfo = {
{ {
name: 'Day', name: 'Day',
kebab: 'day', kebab: 'day',
archetype: 'item', // No archetype: bespoke `[data-range-calendar-day]` styling owns hover /
// selected / range. `data-archetype='item'` would let generic listbox-item
// rules override the selected fill (showed the menu tint, not the accent).
kind: 'public', kind: 'public',
defaultElement: 'div', defaultElement: 'div',
role: 'button', role: 'button',

@ -179,7 +179,8 @@ export const yearGridMorfo = {
{ {
name: 'Cell', name: 'Cell',
kebab: 'cell', kebab: 'cell',
archetype: 'item', // No archetype: display gridcell container, not a menu `item` (the
// inner year button owns the interaction styling).
kind: 'public', kind: 'public',
defaultElement: 'button', defaultElement: 'button',
role: 'gridcell', role: 'gridcell',

@ -628,7 +628,9 @@ export interface MorfoFocus {
*/ */
export type MorfoArchetype = export type MorfoArchetype =
| 'provider' // root context provider | 'provider' // root context provider
| 'trigger' // activates an action or opens an overlay | 'trigger' // activates an action or opens an overlay (button-like)
| 'field-trigger' // a field/input/select/combo trigger — NOT a button; owns
// the flush field-control treatment, never the generic button/popover chrome
| 'content' // primary content panel of an overlay or section | 'content' // primary content panel of an overlay or section
| 'overlay' // modal/dim backdrop behind content | 'overlay' // modal/dim backdrop behind content
| 'viewport' // scrollable / focusable container | 'viewport' // scrollable / focusable container
@ -663,6 +665,7 @@ export type MorfoArchetype =
export const ARCHETYPE_VOCABULARY = [ export const ARCHETYPE_VOCABULARY = [
'provider', 'provider',
'trigger', 'trigger',
'field-trigger',
'content', 'content',
'overlay', 'overlay',
'viewport', 'viewport',

@ -55,7 +55,9 @@
triggerState.props, triggerState.props,
(popoverTriggerProps ?? {}) as Record<string, unknown>, (popoverTriggerProps ?? {}) as Record<string, unknown>,
{ {
[colorPickerAttrs.trigger]: '' [colorPickerAttrs.trigger]: '',
// Field overlay trigger composed on a popover trigger — not a button.
'data-archetype': 'field-trigger'
} as Record<string, unknown> } as Record<string, unknown>
) )
); );

@ -45,6 +45,10 @@
state.props, state.props,
{ {
[datePickerAttrs.trigger]: '', [datePickerAttrs.trigger]: '',
// A field's overlay trigger is composed ON a popover trigger (which
// stamps the generic `trigger` archetype), but it is NOT a button —
// override to the `field-trigger` archetype so eidos treats it flush.
'data-archetype': 'field-trigger',
'aria-label': resolvedLabel, 'aria-label': resolvedLabel,
disabled: provider.opts.disabled.current || undefined disabled: provider.opts.disabled.current || undefined
} as Record<string, unknown> } as Record<string, unknown>

@ -45,6 +45,8 @@
const mergedProps = $derived( const mergedProps = $derived(
mergeProps(restProps, state.props, { mergeProps(restProps, state.props, {
[dateRangePickerAttrs.trigger]: '', [dateRangePickerAttrs.trigger]: '',
// Field overlay trigger composed on a popover trigger — not a button.
'data-archetype': 'field-trigger',
'aria-label': resolvedLabel, 'aria-label': resolvedLabel,
disabled: provider.opts.disabled.current || undefined disabled: provider.opts.disabled.current || undefined
} as Record<string, unknown>) } as Record<string, unknown>)

@ -42,6 +42,8 @@
const mergedProps = $derived( const mergedProps = $derived(
mergeProps(restProps, state.props, { mergeProps(restProps, state.props, {
[timePickerAttrs.trigger]: '', [timePickerAttrs.trigger]: '',
// Field overlay trigger composed on a popover trigger — not a button.
'data-archetype': 'field-trigger',
'aria-label': resolvedLabel, 'aria-label': resolvedLabel,
disabled: provider.opts.disabled.current || undefined disabled: provider.opts.disabled.current || undefined
} as Record<string, unknown>) } as Record<string, unknown>)

Loading…
Cancel
Save

Powered by TurnKey Linux.