From f822d6e4dd104b74a4f140a3d2aa473dc31a5467 Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 24 Jun 2026 19:50:42 +0200 Subject: [PATCH] =?UTF-8?q?fix(eidos):=20DatePicker/Calendar=20=E2=80=94?= =?UTF-8?q?=20flush=20field=20triggers=20+=20drop=20listbox=20`item`=20chr?= =?UTF-8?q?ome=20from=20grid=20cells?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../components/field/field-control-trigger.css | 11 +++++++++++ src/uix/eidos/components/popover/popover.css | 15 +++++++++++---- src/uix/morfo/components/calendar.ts | 15 ++++++++++++--- src/uix/morfo/components/date-picker.ts | 2 +- src/uix/morfo/components/month-grid.ts | 3 ++- src/uix/morfo/components/range-calendar.ts | 10 +++++++--- src/uix/morfo/components/year-grid.ts | 3 ++- src/uix/morfo/types.ts | 5 ++++- .../components/color-picker-trigger.svelte | 4 +++- .../components/date-picker-trigger.svelte | 4 ++++ .../components/date-range-picker-trigger.svelte | 2 ++ .../components/time-picker-trigger.svelte | 2 ++ 12 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/uix/eidos/components/field/field-control-trigger.css b/src/uix/eidos/components/field/field-control-trigger.css index 34112cb52..71933d032 100644 --- a/src/uix/eidos/components/field/field-control-trigger.css +++ b/src/uix/eidos/components/field/field-control-trigger.css @@ -14,6 +14,17 @@ * (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] { display: inline-flex; align-items: center; diff --git a/src/uix/eidos/components/popover/popover.css b/src/uix/eidos/components/popover/popover.css index aad143089..2e0815389 100644 --- a/src/uix/eidos/components/popover/popover.css +++ b/src/uix/eidos/components/popover/popover.css @@ -2,8 +2,15 @@ * Air relied on the Button recipe via .air-button class. Eidos applies a * baseline button envelope here. Consumes Capa 2 directly (same as Dialog * 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; align-items: center; justify-content: center; @@ -21,17 +28,17 @@ 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); 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); 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-offset: var(--focus-ring-offset); } diff --git a/src/uix/morfo/components/calendar.ts b/src/uix/morfo/components/calendar.ts index 6774b14d4..a416475d3 100644 --- a/src/uix/morfo/components/calendar.ts +++ b/src/uix/morfo/components/calendar.ts @@ -244,7 +244,9 @@ export const calendarMorfo = { { name: 'GridRow', kebab: 'grid-row', - archetype: 'item', + // No archetype: a `` 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', defaultElement: 'tr', role: 'row', @@ -265,7 +267,10 @@ export const calendarMorfo = { { name: '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', defaultElement: 'td', role: 'gridcell', @@ -300,7 +305,11 @@ export const calendarMorfo = { { name: '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', defaultElement: 'div', role: 'button', diff --git a/src/uix/morfo/components/date-picker.ts b/src/uix/morfo/components/date-picker.ts index 188f03b89..c25475205 100644 --- a/src/uix/morfo/components/date-picker.ts +++ b/src/uix/morfo/components/date-picker.ts @@ -124,7 +124,7 @@ export const datePickerMorfo = { { name: 'Trigger', kebab: 'trigger', - archetype: 'trigger', + archetype: 'field-trigger', kind: 'public', defaultElement: 'button', role: 'button', diff --git a/src/uix/morfo/components/month-grid.ts b/src/uix/morfo/components/month-grid.ts index fccb4e833..7fdfb1e0d 100644 --- a/src/uix/morfo/components/month-grid.ts +++ b/src/uix/morfo/components/month-grid.ts @@ -180,7 +180,8 @@ export const monthGridMorfo = { { name: 'Cell', kebab: 'cell', - archetype: 'item', + // No archetype: display gridcell container, not a menu `item` (the + // inner month button owns the interaction styling). kind: 'public', defaultElement: 'button', role: 'gridcell', diff --git a/src/uix/morfo/components/range-calendar.ts b/src/uix/morfo/components/range-calendar.ts index c47f97244..6aa62d189 100644 --- a/src/uix/morfo/components/range-calendar.ts +++ b/src/uix/morfo/components/range-calendar.ts @@ -219,7 +219,8 @@ export const rangeCalendarMorfo = { { name: 'GridRow', kebab: 'grid-row', - archetype: 'item', + // No archetype: structural `` row, not a menu `item` (would tint the + // whole week on hover). kind: 'public', defaultElement: 'tr', role: 'row', @@ -240,7 +241,8 @@ export const rangeCalendarMorfo = { { name: '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', defaultElement: 'td', role: 'gridcell', @@ -279,7 +281,9 @@ export const rangeCalendarMorfo = { { name: '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', defaultElement: 'div', role: 'button', diff --git a/src/uix/morfo/components/year-grid.ts b/src/uix/morfo/components/year-grid.ts index a08a65197..ceaa5b7a0 100644 --- a/src/uix/morfo/components/year-grid.ts +++ b/src/uix/morfo/components/year-grid.ts @@ -179,7 +179,8 @@ export const yearGridMorfo = { { name: 'Cell', kebab: 'cell', - archetype: 'item', + // No archetype: display gridcell container, not a menu `item` (the + // inner year button owns the interaction styling). kind: 'public', defaultElement: 'button', role: 'gridcell', diff --git a/src/uix/morfo/types.ts b/src/uix/morfo/types.ts index c52cec7e7..47c2e0d56 100644 --- a/src/uix/morfo/types.ts +++ b/src/uix/morfo/types.ts @@ -628,7 +628,9 @@ export interface MorfoFocus { */ export type MorfoArchetype = | '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 | 'overlay' // modal/dim backdrop behind content | 'viewport' // scrollable / focusable container @@ -663,6 +665,7 @@ export type MorfoArchetype = export const ARCHETYPE_VOCABULARY = [ 'provider', 'trigger', + 'field-trigger', 'content', 'overlay', 'viewport', diff --git a/src/uix/soma/components/color-picker/components/color-picker-trigger.svelte b/src/uix/soma/components/color-picker/components/color-picker-trigger.svelte index 5af1631d5..4553eb5be 100644 --- a/src/uix/soma/components/color-picker/components/color-picker-trigger.svelte +++ b/src/uix/soma/components/color-picker/components/color-picker-trigger.svelte @@ -55,7 +55,9 @@ triggerState.props, (popoverTriggerProps ?? {}) as Record, { - [colorPickerAttrs.trigger]: '' + [colorPickerAttrs.trigger]: '', + // Field overlay trigger composed on a popover trigger — not a button. + 'data-archetype': 'field-trigger' } as Record ) ); diff --git a/src/uix/soma/components/date-picker/components/date-picker-trigger.svelte b/src/uix/soma/components/date-picker/components/date-picker-trigger.svelte index 331982e73..425758867 100644 --- a/src/uix/soma/components/date-picker/components/date-picker-trigger.svelte +++ b/src/uix/soma/components/date-picker/components/date-picker-trigger.svelte @@ -45,6 +45,10 @@ state.props, { [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, disabled: provider.opts.disabled.current || undefined } as Record diff --git a/src/uix/soma/components/date-range-picker/components/date-range-picker-trigger.svelte b/src/uix/soma/components/date-range-picker/components/date-range-picker-trigger.svelte index 29c5726d6..89a63e9ed 100644 --- a/src/uix/soma/components/date-range-picker/components/date-range-picker-trigger.svelte +++ b/src/uix/soma/components/date-range-picker/components/date-range-picker-trigger.svelte @@ -45,6 +45,8 @@ const mergedProps = $derived( mergeProps(restProps, state.props, { [dateRangePickerAttrs.trigger]: '', + // Field overlay trigger composed on a popover trigger — not a button. + 'data-archetype': 'field-trigger', 'aria-label': resolvedLabel, disabled: provider.opts.disabled.current || undefined } as Record) diff --git a/src/uix/soma/components/time-picker/components/time-picker-trigger.svelte b/src/uix/soma/components/time-picker/components/time-picker-trigger.svelte index 2a94eba63..f00634984 100644 --- a/src/uix/soma/components/time-picker/components/time-picker-trigger.svelte +++ b/src/uix/soma/components/time-picker/components/time-picker-trigger.svelte @@ -42,6 +42,8 @@ const mergedProps = $derived( mergeProps(restProps, state.props, { [timePickerAttrs.trigger]: '', + // Field overlay trigger composed on a popover trigger — not a button. + 'data-archetype': 'field-trigger', 'aria-label': resolvedLabel, disabled: provider.opts.disabled.current || undefined } as Record)