uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< script lang = "ts" >
import {
DatePicker,
type DatePickerCalendarSnippetProps,
type DatePickerColor,
type DatePickerGridRowSnippetProps,
type DatePickerInputSnippetProps,
type DatePickerSize,
type DatePickerVariant
} from '$uix/eidos/components/date-picker';
import { compileMorfo } from '$uix/morfo';
import { datePickerMorfo } from '@/uix/morfo/components/date-picker';
import { calendarMorfo } from '@/uix/morfo/components/calendar';
import { dateFieldMorfo } from '@/uix/morfo/components/date-field';
import { popoverMorfo } from '@/uix/morfo/components/popover';
import { getActiveUix } from '$active-uix';
import { CalendarDate , type DateMatcher , type DateValue , type WeekStartsOn } from '$libs/days';
const uix = getActiveUix();
void uix;
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
type TraceEntry = { event : string ; family : string ; intent? : string ; at : number } ;
type BoundPreset = 'none' | 'min' | 'max' | 'window' | 'invalid';
type WeekStartControl = 'locale' | '0' | '1';
const delivery = new CalendarDate(2026, 5, 20);
const review = new CalendarDate(2026, 6, 3);
const early = new CalendarDate(2026, 5, 1);
const late = new CalendarDate(2026, 8, 1);
const defaultMin = new CalendarDate(2026, 5, 10);
const defaultMax = new CalendarDate(2026, 7, 10);
const invalidMax = new CalendarDate(2026, 5, 5);
const june = new CalendarDate(2026, 6, 1);
const july = new CalendarDate(2026, 7, 1);
const bookedDates = [new CalendarDate(2026, 5, 22), new CalendarDate(2026, 6, 12)];
const holidays = [new CalendarDate(2026, 5, 25), new CalendarDate(2026, 7, 4)];
let tab = $state< Tab > ('live');
let trace = $state< TraceEntry [ ] > ([]);
let stageRef = $state< HTMLElement | null > (null);
let value = $state< DateValue | undefined > (delivery);
let valueInput = $state(toDateInput(delivery));
let placeholder = $state< DateValue > (delivery);
let open = $state(false);
let minInput = $state(toDateInput(defaultMin));
let maxInput = $state(toDateInput(defaultMax));
let required = $state(true);
let disabled = $state(false);
let readonly = $state(false);
let closeOnDateSelect = $state(true);
date-picker: mode='modal' + footer (clear/cancel/closeButton)
Replicates the date-range-picker pattern on the single date-picker:
soma:
- DatePickerMode type ('inline' | 'modal') exported.
- Provider gains mode/clearButton/cancelButton/closeButton opts plus
commit() / cancel() / clear() action helpers; cancel() reverts the
value snapshot taken via watch() on the OPEN edge of opts.open.
- handleDateSelect now early-returns when mode === 'modal' so modal
pickers don't auto-close on selection.
- exports.ts surfaces DatePickerProvider + DatePickerMode for demo /
footer wiring (mirrors date-range-picker's barrel).
- Test fixture extended with the new opts (closeOnDateSelect stays
true by default; mode='inline', all buttons false).
morfo:
- scope=['soma','sema'], apg=dialog-modal.
- 6 events: open / close-commit / close-cancel / close-dismiss /
close-dismiss-outside / commit-clear. prewrite stamps data-last-action
with the causal exit reason so Sema can tint the exit animation.
- Calendar part declares the state machine (open/closed,
data-last-action, data-starting-style, data-ending-style) and the
modal-keyboard surface (Escape / Tab / Shift+Tab).
demo:
- New picker-actions.svelte mirrors the range demo: pulls provider via
context, renders Clear / Cancel / Close conditionally, forces Close in
modal mode. CSS is local to the file (uses --color-primary-* +
--color-surface-overlay tokens, no raw colors).
- +page.svelte adds mode radio chip group + 3 footer-button switches and
passes mode/clearButton/cancelButton/closeButton through to the Eidos
wrapper (which spreads to soma).
The Eidos wrapper needs no change — it already spreads everything via
...rest, so the new soma opts reach Soma without further wiring.
Verification: 0 type errors, 3/3 soma tests pass, morfo:check PASS,
component:audit 67/67 PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
let mode = $state< 'inline' | 'modal'>('inline');
let clearButton = $state(false);
let cancelButton = $state(false);
let closeButton = $state(false);
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
let disableDaysOutsideMonth = $state(true);
let fixedWeeks = $state(false);
let pagedNavigation = $state(false);
let preventDeselect = $state(false);
let showWeekNumbers = $state(false);
let blockWeekends = $state(false);
let markBookedUnavailable = $state(true);
let numberOfMonths = $state(1);
let weekStartsOn = $state< WeekStartControl > ('1');
let locale = $state('en-US');
let dir = $state< 'ltr' | 'rtl'>('ltr');
let size = $state< DatePickerSize > ('md');
let variant = $state< DatePickerVariant > ('surface');
let color = $state< DatePickerColor > ('primary');
let labelText = $state('Delivery date');
let inputName = $state('delivery-date');
const compiled = compileMorfo(datePickerMorfo);
const calendarCompiled = compileMorfo(calendarMorfo);
const dateFieldCompiled = compileMorfo(dateFieldMorfo);
const popoverCompiled = compileMorfo(popoverMorfo);
const partsList = $derived([...compiled.parts.byKebab.values()]);
const datePickerEvents = $derived([...compiled.actions.byName.values()]);
const dateFieldEvents = $derived([...dateFieldCompiled.actions.byName.values()]);
const calendarEvents = $derived([...calendarCompiled.actions.byName.values()]);
const popoverEvents = $derived([...popoverCompiled.actions.byName.values()]);
const eventRows = $derived([
...datePickerEvents.map((action) => ({ owner : 'DatePicker' , action } )),
...dateFieldEvents.map((action) => ({ owner : 'DateField' , action } )),
...calendarEvents.map((action) => ({ owner : 'Calendar' , action } )),
...popoverEvents.map((action) => ({ owner : 'Popover' , action } ))
]);
const events = $derived(eventRows.map((row) => row.action));
const minValue = $derived(parseDateInput(minInput));
const maxValue = $derived(parseDateInput(maxInput));
const resolvedWeekStartsOn = $derived(
weekStartsOn === 'locale' ? undefined : (Number(weekStartsOn) as WeekStartsOn)
);
const dateDisabledMatcher = $derived< DateMatcher | undefined > (
blockWeekends ? isWeekend : undefined
);
const dateUnavailableMatcher = $derived< DateMatcher | undefined > (
markBookedUnavailable ? isBooked : undefined
);
const boundsMessage = $derived(getBoundsMessage());
const valueMessage = $derived(getValueMessage());
const valueLabel = $derived(value?.toString() ?? 'empty');
const boundsLabel = $derived(`${ minValue ? . toString () ?? 'none' } / ${ maxValue ? . toString () ?? 'none' } `);
function toDateInput(date: DateValue): string {
return [
String(date.year).padStart(4, '0'),
String(date.month).padStart(2, '0'),
String(date.day).padStart(2, '0')
].join('-');
}
function parseDateInput(text: string): DateValue | undefined {
if (!text) return undefined;
const [year, month, day] = text.split('-').map(Number);
if (!year || !month || !day) return undefined;
return new CalendarDate(year, month, day);
}
function dateCtor(date: DateValue | undefined): string {
return date ? `new CalendarDate(${ date . year } , ${ date . month } , ${ date . day } )` : 'undefined';
}
function sameDate(a: DateValue, b: DateValue): boolean {
return a.compare(b) === 0;
}
function isWeekend(date: DateValue): boolean {
const day = date.toDate('UTC').getUTCDay();
return day === 0 || day === 6;
}
function isBooked(date: DateValue): boolean {
return bookedDates.some((item) => sameDate(item, date));
}
function isHoliday(date: DateValue): boolean {
return holidays.some((item) => sameDate(item, date));
}
function inputValue(event: Event): string {
return (event.currentTarget as HTMLInputElement).value;
}
function setValueFromInput(text: string): void {
valueInput = text;
value = parseDateInput(text);
if (value) placeholder = value;
}
function setValue(next: DateValue | undefined): void {
value = next;
valueInput = next ? toDateInput(next) : '';
if (next) placeholder = next;
}
function setVisibleMonth(next: DateValue): void {
placeholder = next;
}
function setBoundsPreset(preset: BoundPreset): void {
if (preset === 'none') {
minInput = '';
maxInput = '';
}
if (preset === 'min') {
minInput = toDateInput(defaultMin);
maxInput = '';
}
if (preset === 'max') {
minInput = '';
maxInput = toDateInput(defaultMax);
}
if (preset === 'window') {
minInput = toDateInput(defaultMin);
maxInput = toDateInput(defaultMax);
}
if (preset === 'invalid') {
minInput = toDateInput(defaultMin);
maxInput = toDateInput(invalidMax);
}
}
function getBoundsMessage(): string {
if (minInput & & !minValue) return 'minValue is not a valid date.';
if (maxInput & & !maxValue) return 'maxValue is not a valid date.';
if (minValue && maxValue && maxValue.compare(minValue) < 0 ) {
return 'minValue must be on or before maxValue.';
}
return '';
}
function getValueMessage(): string {
if (!value) return required ? 'Required value is empty.' : 'No selected date.';
if (minValue & & value.compare(minValue) < 0 ) return ' Selected date is before minValue . ' ;
if (maxValue & & value.compare(maxValue) > 0) return 'Selected date is after maxValue.';
if (dateDisabledMatcher?.(value)) return 'Selected date matches isDateDisabled.';
if (dateUnavailableMatcher?.(value)) return 'Selected date matches isDateUnavailable.';
return 'Selected date is valid.';
}
function monthHeadingId(date: DateValue): string {
return `date-picker-month-${ date . toString ()} `;
}
function fmtTime(at: number): string {
const d = new Date(at);
return `${ String ( d . getSeconds ()). padStart ( 2 , '0' )} .${ String ( d . getMilliseconds ()). padStart ( 3 , '0' )} `;
}
$effect(() => {
valueInput = value ? toDateInput(value) : '';
});
$effect(() => {
const el = stageRef;
if (!el) return;
const obs = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.attributeName !== 'data-event') continue;
const target = m.target as Element;
const ev = target.getAttribute('data-event');
if (!ev) continue;
trace = [
{
event: ev,
family: target.getAttribute('data-event-family') ?? '-',
intent: target.getAttribute('data-event-intent') ?? undefined,
at: Date.now()
},
...trace
].slice(0, 6);
}
});
obs.observe(el, { attributes : true , subtree : true , attributeFilter : [ 'data-event' ] } );
return () => obs.disconnect();
});
const somaSnippet = $derived(
[
"< script lang = 'ts' > " ,
" import * as DatePicker from '$soma/components/date-picker';",
" import { CalendarDate , type DateValue } from '$libs/days';",
'',
` const minValue = ${ dateCtor ( minValue )} ;`,
` const maxValue = ${ dateCtor ( maxValue )} ;`,
` let value = $state< DateValue | undefined > (${ dateCtor ( value )} );`,
` let placeholder = $state< DateValue > (${ dateCtor ( placeholder )} );`,
' let open = $state(false);',
' const monthHeadingId = (date: DateValue) => `date-picker-month-${ date . toString ()} `;',
'< /' + 'script>',
'',
'< DatePicker.Provider ' ,
' bind:value',
' bind:placeholder',
' bind:open',
required & & ' required',
disabled & & ' disabled',
readonly & & ' readonly',
minValue && ' { minValue } ',
maxValue && ' { maxValue } ',
!closeOnDateSelect && ' closeOnDateSelect={ false } ',
fixedWeeks & & ' fixedWeeks',
pagedNavigation & & ' pagedNavigation',
preventDeselect & & ' preventDeselect',
numberOfMonths !== 1 && ` numberOfMonths={ $ { numberOfMonths }} `,
disableDaysOutsideMonth & & ' disableDaysOutsideMonth',
showWeekNumbers & & ' showWeekNumbers',
weekStartsOn !== 'locale' && ` weekStartsOn={ $ { weekStartsOn }} `,
locale !== 'en-US' && ` locale="${ locale } "`,
dir !== 'ltr' && ` dir="${ dir } "`,
blockWeekends && ' isDateDisabled={( date ) => date . toDate ( "UTC" ). getUTCDay () % 6 === 0 } ',
markBookedUnavailable && ' isDateUnavailable={( date ) => date . day === 22 } ',
' weekdayFormat="short"',
'>',
` < DatePicker.Label > ${ labelText } </ DatePicker.Label > `,
` < DatePicker.Input name = "$ { inputName } " aria-label = "$ { labelText } " > `,
' { # snippet children ({ segments })} ',
' { #each segments as { part , value }, index ( `$ { part } - $ { index } `) } ',
' < DatePicker.Segment { part } > { value } </ DatePicker.Segment > ',
' { /each } ',
' < DatePicker.Trigger / > ',
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
' { /snippet } ',
' < / DatePicker.Input > ',
' < DatePicker.Portal > ',
' < DatePicker.Content align = "end" sideOffset = { 10 } collisionPadding= { 12 } > ',
' < DatePicker.Calendar > ',
' { # snippet children ({ months , weekdays })} ',
' < DatePicker.Header > ...< / DatePicker.Header > ',
' { #each months as month } ',
' { #if months . length > 1 } ',
' < DatePicker.Heading id = { monthHeadingId ( month . value )} value= { month . value } /> ',
' { /if } ',
' < DatePicker.Grid > ...< / DatePicker.Grid > ',
' { /each } ',
' { /snippet } ',
' < / DatePicker.Calendar > ',
' < / DatePicker.Content > ',
' < / DatePicker.Portal > ',
'< / DatePicker.Provider > '
]
.filter(Boolean)
.join('\n')
);
const eidosSnippet = $derived(
[
"< script lang = 'ts' > " ,
" import { DatePicker } from '$uix/eidos/components/date-picker';",
" import { CalendarDate , type DateValue } from '$libs/days';",
'',
` const minValue = ${ dateCtor ( minValue )} ;`,
` const maxValue = ${ dateCtor ( maxValue )} ;`,
` let value = $state< DateValue | undefined > (${ dateCtor ( value )} );`,
` let placeholder = $state< DateValue > (${ dateCtor ( placeholder )} );`,
' let open = $state(false);',
' const monthHeadingId = (date: DateValue) => `date-picker-month-${ date . toString ()} `;',
'< /' + 'script>',
'',
'< DatePicker ' ,
' bind:value',
' bind:placeholder',
' bind:open',
size !== 'md' && ` size="${ size } "`,
variant !== 'surface' && ` variant="${ variant } "`,
color !== 'primary' && ` color="${ color } "`,
required & & ' required',
disabled & & ' disabled',
readonly & & ' readonly',
minValue && ' { minValue } ',
maxValue && ' { maxValue } ',
!closeOnDateSelect && ' closeOnDateSelect={ false } ',
fixedWeeks & & ' fixedWeeks',
pagedNavigation & & ' pagedNavigation',
preventDeselect & & ' preventDeselect',
numberOfMonths !== 1 && ` numberOfMonths={ $ { numberOfMonths }} `,
disableDaysOutsideMonth & & ' disableDaysOutsideMonth',
showWeekNumbers & & ' showWeekNumbers',
weekStartsOn !== 'locale' && ` weekStartsOn={ $ { weekStartsOn }} `,
locale !== 'en-US' && ` locale="${ locale } "`,
dir !== 'ltr' && ` dir="${ dir } "`,
blockWeekends && ' isDateDisabled={( date ) => date . toDate ( "UTC" ). getUTCDay () % 6 === 0 } ',
markBookedUnavailable && ' isDateUnavailable={( date ) => date . day === 22 } ',
' weekdayFormat="short"',
'>',
` < DatePicker.Label > ${ labelText } </ DatePicker.Label > `,
` < DatePicker.Input name = "$ { inputName } " aria-label = "$ { labelText } " > `,
' { # snippet children ({ segments })} ',
' { #each segments as { part , value }, index ( `$ { part } - $ { index } `) } ',
' < DatePicker.Segment { part } > { value } </ DatePicker.Segment > ',
' { /each } ',
' < DatePicker.Trigger / > ',
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
' { /snippet } ',
' < / DatePicker.Input > ',
' < DatePicker.Portal > ',
' < DatePicker.Content align = "end" sideOffset = { 10 } collisionPadding= { 12 } > ',
' < DatePicker.Calendar > ',
' { # snippet children ({ months , weekdays })} ',
' < DatePicker.Header > ...< / DatePicker.Header > ',
' { #each months as month } ',
' { #if months . length > 1 } ',
' < DatePicker.Heading id = { monthHeadingId ( month . value )} value= { month . value } /> ',
' { /if } ',
' < DatePicker.Grid > ...< / DatePicker.Grid > ',
' { /each } ',
' { /snippet } ',
' < / DatePicker.Calendar > ',
' < / DatePicker.Content > ',
' < / DatePicker.Portal > ',
'< / DatePicker > '
]
.filter(Boolean)
.join('\n')
);
< / script >
< div data-uix-canvas-inner >
< header >
< div data-uix-eyebrow > Forms · DatePicker< / div >
< h1 data-uix-page-title > DatePicker< / h1 >
< p data-uix-page-lede >
Date input with an attached calendar popover. DatePicker owns the shared value, visible
month and open state; Soma composes DateField, Popover and Calendar, while Eidos only adds
the visual size, variant and color recipe.
< / p >
< div data-uix-page-meta >
< span data-uix-meta-pill >
< span data-uix-meta-key > parts</ span > { compiled . parts . order . length }
< / span >
< span data-uix-meta-pill >
< span data-uix-meta-key > events</ span > { events . length }
< / span >
< span data-uix-meta-pill >
< span data-uix-meta-key > value</ span > { valueLabel }
< / span >
< span data-uix-meta-pill >
< span data-uix-meta-key > min/max</ span > { boundsLabel }
< / span >
< span data-uix-meta-pill >
< span data-uix-meta-key > months</ span > { numberOfMonths }
< / span >
< / div >
< / header >
< div data-uix-stage >
< div data-uix-stage-area bind:this = { stageRef } >
< div class = "date-picker-stage" >
< DatePicker
bind:value
bind:placeholder
bind:open
{ required }
{ disabled }
{ readonly }
{ closeOnDateSelect }
{ disableDaysOutsideMonth }
{ fixedWeeks }
{ pagedNavigation }
{ preventDeselect }
{ showWeekNumbers }
{ numberOfMonths }
{ locale }
{ dir }
{ size }
{ variant }
{ color }
date-picker: mode='modal' + footer (clear/cancel/closeButton)
Replicates the date-range-picker pattern on the single date-picker:
soma:
- DatePickerMode type ('inline' | 'modal') exported.
- Provider gains mode/clearButton/cancelButton/closeButton opts plus
commit() / cancel() / clear() action helpers; cancel() reverts the
value snapshot taken via watch() on the OPEN edge of opts.open.
- handleDateSelect now early-returns when mode === 'modal' so modal
pickers don't auto-close on selection.
- exports.ts surfaces DatePickerProvider + DatePickerMode for demo /
footer wiring (mirrors date-range-picker's barrel).
- Test fixture extended with the new opts (closeOnDateSelect stays
true by default; mode='inline', all buttons false).
morfo:
- scope=['soma','sema'], apg=dialog-modal.
- 6 events: open / close-commit / close-cancel / close-dismiss /
close-dismiss-outside / commit-clear. prewrite stamps data-last-action
with the causal exit reason so Sema can tint the exit animation.
- Calendar part declares the state machine (open/closed,
data-last-action, data-starting-style, data-ending-style) and the
modal-keyboard surface (Escape / Tab / Shift+Tab).
demo:
- New picker-actions.svelte mirrors the range demo: pulls provider via
context, renders Clear / Cancel / Close conditionally, forces Close in
modal mode. CSS is local to the file (uses --color-primary-* +
--color-surface-overlay tokens, no raw colors).
- +page.svelte adds mode radio chip group + 3 footer-button switches and
passes mode/clearButton/cancelButton/closeButton through to the Eidos
wrapper (which spreads to soma).
The Eidos wrapper needs no change — it already spreads everything via
...rest, so the new soma opts reach Soma without further wiring.
Verification: 0 type errors, 3/3 soma tests pass, morfo:check PASS,
component:audit 67/67 PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
{ mode }
{ clearButton }
{ cancelButton }
{ closeButton }
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
minValue={ minValue }
maxValue={ maxValue }
weekStartsOn={ resolvedWeekStartsOn }
isDateDisabled={ dateDisabledMatcher }
isDateUnavailable={ dateUnavailableMatcher }
isDateHoliday={ isHoliday }
weekdayFormat="short"
>
< DatePicker.Label > { labelText } </ DatePicker.Label >
< DatePicker.Input name = { inputName } aria-label= { labelText } >
{ # snippet children ({ segments } : DatePickerInputSnippetProps )}
{ #each segments as { part , value }, index ( `$ { part } - $ { index } `) }
< DatePicker.Segment { part } > { value } </ DatePicker.Segment >
{ /each }
< DatePicker.Trigger / >
{ /snippet }
< / DatePicker.Input >
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< div class = "date-picker-status" data-invalid = { boundsMessage || valueMessage !== 'Selected date is valid.' || undefined } >
< span > { boundsMessage || valueMessage } </ span >
< / div >
< DatePicker.Portal >
< DatePicker.Content align = "end" sideOffset = { 10 } collisionPadding= { 12 } >
< DatePicker.Calendar >
{ # snippet children ( args : DatePickerCalendarSnippetProps )}
{ @render calendarBody ( args )}
{ /snippet }
< / DatePicker.Calendar >
< / DatePicker.Content >
< / DatePicker.Portal >
< / DatePicker >
< / div >
< / div >
< div data-uix-stage-trace >
< span data-uix-stage-trace-key > trace< / span >
{ #if trace . length === 0 }
< span > open the picker and select a date< / span >
{ : else }
{ #each trace . slice ( 0 , 3 ) as entry }
< span >< span data-uix-stage-trace-event > { entry . event } </ span > · { entry . family }{ entry . intent
? ' · ' + entry.intent
: ''}< / span >
< span style = "color: var(--uix-text-faint)" > { fmtTime ( entry . at )} </ span >
{ /each }
{ /if }
< span style = "margin-inline-start: auto;" >
< span data-uix-stage-trace-key > open</ span > { String ( open )}
< span data-uix-stage-trace-key style = "margin-inline-start: var(--uix-space-2);" > value< / span >
{ valueLabel }
< / span >
< / div >
< / div >
< div data-uix-tabs role = "tablist" >
< button data-uix-tab data-active = { tab === 'live' } onclick= {() => ( tab = 'live' )} > Live</ button >
< button data-uix-tab data-active = { tab === 'api' } onclick= {() => ( tab = 'api' )} >
API < span data-uix-tab-count > 17< / span >
< / button >
< button data-uix-tab data-active = { tab === 'morfo' } onclick= {() => ( tab = 'morfo' )} >
< span data-uix-layer-badge = "morfo" > morfo< / span >
< span data-uix-tab-count > { partsList . length } p · { events . length } e</ span >
< / button >
< button data-uix-tab data-active = { tab === 'sema' } onclick= {() => ( tab = 'sema' )} >
< span data-uix-layer-badge = "sema" > sema< / span >
< span data-uix-tab-count > { events . length } </ span >
< / button >
< button data-uix-tab data-active = { tab === 'recipe' } onclick= {() => ( tab = 'recipe' )} > Recipe</ button >
< button data-uix-tab data-active = { tab === 'a11y' } onclick= {() => ( tab = 'a11y' )} > A11y</ button >
< / div >
{ #if tab === 'live' }
< section data-uix-section >
< h2 data-uix-section-title > Controls< / h2 >
< p data-uix-section-desc >
Props grouped by the layer that owns them:
< span data-uix-layer-badge = "soma" > soma< / span > keeps value, min/max, popover
state and calendar behavior; < span data-uix-layer-badge = "eidos" > eidos< / span >
adds visual treatment. Sema events live in the dedicated
< a href = "#sema" onclick = {( event ) => { event . preventDefault (); tab = 'sema' ; }} > Sema</a >
tab.
< / p >
< div data-uix-subsection-head >
< span data-uix-layer-badge = "soma" > soma< / span > props · value, min/max and calendar
< / div >
< div data-uix-controls >
< label data-uix-control >
< span data-uix-control-label > open < span data-uix-control-hint > bindable< / span > < / span >
< span data-uix-switch >
< input type = "checkbox" bind:checked = { open } / >
< span data-uix-switch-label > { open ? 'open' : 'closed' } </ span >
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > value < span data-uix-control-hint > selected date< / span > < / span >
< input type = "date" value = { valueInput } oninput= {( event ) => setValueFromInput ( inputValue ( event ))} />
< span data-uix-chips role = "group" >
< button data-uix-chip data-active = { sameDate ( value ?? delivery , delivery )} onclick= {() => setValue ( delivery )} > May 20</ button >
< button data-uix-chip data-active = { sameDate ( value ?? delivery , review )} onclick= {() => setValue ( review )} > Jun 3</ button >
< button data-uix-chip data-active = { value === undefined } onclick= {() => setValue ( undefined )} > empty</ button >
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > minValue < span data-uix-control-hint > { minValue ? . toString () ?? 'none' } </ span ></ span >
< input type = "date" bind:value = { minInput } / >
< / label >
< label data-uix-control >
< span data-uix-control-label > maxValue < span data-uix-control-hint > { maxValue ? . toString () ?? 'none' } </ span ></ span >
< input type = "date" bind:value = { maxInput } / >
< / label >
< label data-uix-control >
< span data-uix-control-label > min/max preset< / span >
< span data-uix-chips role = "radiogroup" >
< button data-uix-chip data-active = { ! minValue && ! maxValue } onclick= {() => setBoundsPreset ( 'none' )} > none</ button >
< button data-uix-chip data-active = { !! minValue && ! maxValue } onclick= {() => setBoundsPreset ( 'min' )} > min only</ button >
< button data-uix-chip data-active = { ! minValue && !! maxValue } onclick= {() => setBoundsPreset ( 'max' )} > max only</ button >
< button data-uix-chip data-active = { minValue ? . compare ( defaultMin ) === 0 && maxValue ? . compare ( defaultMax ) === 0 } onclick= {() => setBoundsPreset ( 'window' )} > May-Jul</ button >
< button data-uix-chip data-active = { !! boundsMessage } onclick= {() => setBoundsPreset ( 'invalid' )} > invalid</ button >
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > visible month< / span >
< span data-uix-chips role = "radiogroup" >
< button data-uix-chip data-active = { placeholder . month === 5 } onclick= {() => setVisibleMonth ( delivery )} > May</ button >
< button data-uix-chip data-active = { placeholder . month === 6 } onclick= {() => setVisibleMonth ( june )} > Jun</ button >
< button data-uix-chip data-active = { placeholder . month === 7 } onclick= {() => setVisibleMonth ( july )} > Jul</ button >
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > numberOfMonths< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 1 , 2 ] as value }
< button data-uix-chip data-active = { numberOfMonths === value } onclick= {() => ( numberOfMonths = value )} > { value } </ button >
{ /each }
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > weekStartsOn< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 'locale' , '0' , '1' ] as value }
< button data-uix-chip data-active = { weekStartsOn === value } onclick= {() => ( weekStartsOn = value as WeekStartControl )} > { value } </ button >
{ /each }
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > locale< / span >
< select bind:value = { locale } >
< option value = "en-US" > en-US< / option >
< option value = "es-ES" > es-ES< / option >
< option value = "fr-FR" > fr-FR< / option >
< / select >
< / label >
< label data-uix-control >
< span data-uix-control-label > direction< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 'ltr' , 'rtl' ] as value }
< button data-uix-chip data-active = { dir === value } onclick= {() => ( dir = value as 'ltr' | 'rtl' )} > { value } </ button >
{ /each }
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > state flags< / span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { required } / >< span data-uix-switch-label > required</ span ></ span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { disabled } / >< span data-uix-switch-label > disabled</ span ></ span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { readonly } / >< span data-uix-switch-label > readonly</ span ></ span >
< / label >
date-picker: mode='modal' + footer (clear/cancel/closeButton)
Replicates the date-range-picker pattern on the single date-picker:
soma:
- DatePickerMode type ('inline' | 'modal') exported.
- Provider gains mode/clearButton/cancelButton/closeButton opts plus
commit() / cancel() / clear() action helpers; cancel() reverts the
value snapshot taken via watch() on the OPEN edge of opts.open.
- handleDateSelect now early-returns when mode === 'modal' so modal
pickers don't auto-close on selection.
- exports.ts surfaces DatePickerProvider + DatePickerMode for demo /
footer wiring (mirrors date-range-picker's barrel).
- Test fixture extended with the new opts (closeOnDateSelect stays
true by default; mode='inline', all buttons false).
morfo:
- scope=['soma','sema'], apg=dialog-modal.
- 6 events: open / close-commit / close-cancel / close-dismiss /
close-dismiss-outside / commit-clear. prewrite stamps data-last-action
with the causal exit reason so Sema can tint the exit animation.
- Calendar part declares the state machine (open/closed,
data-last-action, data-starting-style, data-ending-style) and the
modal-keyboard surface (Escape / Tab / Shift+Tab).
demo:
- New picker-actions.svelte mirrors the range demo: pulls provider via
context, renders Clear / Cancel / Close conditionally, forces Close in
modal mode. CSS is local to the file (uses --color-primary-* +
--color-surface-overlay tokens, no raw colors).
- +page.svelte adds mode radio chip group + 3 footer-button switches and
passes mode/clearButton/cancelButton/closeButton through to the Eidos
wrapper (which spreads to soma).
The Eidos wrapper needs no change — it already spreads everything via
...rest, so the new soma opts reach Soma without further wiring.
Verification: 0 type errors, 3/3 soma tests pass, morfo:check PASS,
component:audit 67/67 PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< label data-uix-control >
< span data-uix-control-label > mode< / span >
< span data-uix-chips role = "radiogroup" >
< label data-uix-chip data-active = { mode === 'inline' } >
< input type = "radio" bind:group = { mode } value="inline" />
inline
< / label >
< label data-uix-chip data-active = { mode === 'modal' } >
< input type = "radio" bind:group = { mode } value="modal" />
modal
< / label >
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > footer buttons< / span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { clearButton } / >< span data-uix-switch-label > clear</ span ></ span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { cancelButton } / >< span data-uix-switch-label > cancel</ span ></ span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { closeButton } / >< span data-uix-switch-label > close</ span ></ span >
< / label >
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< label data-uix-control >
< span data-uix-control-label > calendar behavior< / span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { closeOnDateSelect } / >< span data-uix-switch-label > close on select</ span ></ span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { disableDaysOutsideMonth } / >< span data-uix-switch-label > disable outside</ span ></ span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { fixedWeeks } / >< span data-uix-switch-label > fixed weeks</ span ></ span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { pagedNavigation } / >< span data-uix-switch-label > paged nav</ span ></ span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { preventDeselect } / >< span data-uix-switch-label > prevent deselect</ span ></ span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { showWeekNumbers } / >< span data-uix-switch-label > week numbers</ span ></ span >
< / label >
< label data-uix-control >
< span data-uix-control-label > date matchers< / span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { blockWeekends } / >< span data-uix-switch-label > disable weekends</ span ></ span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { markBookedUnavailable } / >< span data-uix-switch-label > booked unavailable</ span ></ span >
< / label >
< / div >
< div data-uix-subsection-head >
< span data-uix-layer-badge = "eidos" > eidos< / span > props · visual treatment
< / div >
< div data-uix-controls >
< label data-uix-control >
< span data-uix-control-label > size< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 'sm' , 'md' , 'lg' ] as value }
< button data-uix-chip data-active = { size === value } onclick= {() => ( size = value as DatePickerSize )} > { value } </ button >
{ /each }
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > variant< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 'surface' , 'outline' , 'ghost' ] as value }
< button data-uix-chip data-active = { variant === value } onclick= {() => ( variant = value as DatePickerVariant )} > { value } </ button >
{ /each }
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > color< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 'primary' , 'secondary' , 'neutral' , 'affirm' , 'risk' ] as value }
< button data-uix-chip data-active = { color === value } onclick= {() => ( color = value as DatePickerColor )} > { value } </ button >
{ /each }
< / span >
< / label >
< / div >
< div data-uix-subsection-head > Demo content< / div >
< div data-uix-controls >
< label data-uix-control >
< span data-uix-control-label > label< / span >
< input type = "text" bind:value = { labelText } / >
< / label >
< label data-uix-control >
< span data-uix-control-label > input name< / span >
< input type = "text" bind:value = { inputName } / >
< / label >
< / div >
< div data-uix-code >
< div data-uix-code-head >
< span data-uix-layer-badge = "soma" > soma< / span >
< span > headless · shared value, min/max, popover and calendar< / span >
< span data-uix-code-lang > svelte< / span >
< / div >
< pre >< code > { somaSnippet } </ code ></ pre >
< / div >
< div data-uix-code style = "margin-top: var(--uix-space-3);" >
< div data-uix-code-head >
< span data-uix-layer-badge = "eidos" > eidos< / span >
< span > visual · root plus attached DateField, Popover and Calendar parts< / span >
< span data-uix-code-lang > svelte< / span >
< / div >
< pre >< code > { eidosSnippet } </ code ></ pre >
< / div >
< / section >
{ /if }
{ #if tab === 'api' }
< section data-uix-section >
< h2 data-uix-section-title > API reference< / h2 >
< p data-uix-section-desc >
Public DatePicker surface used by this demo. The component is date-first: value,
min/max, popup state and calendar behavior are the relevant controls here.
< / p >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Prop< / th > < th > Owner< / th > < th > Default< / th > < th > Description< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > value < span data-uix-tag data-kind = "bindable" > bindable< / span > < / td > < td > soma< / td > < td class = "default" > undefined< / td > < td > Selected `DateValue`. Calendar and input segments share this state.< / td > < / tr >
< tr > < td class = "name" > placeholder < span data-uix-tag data-kind = "bindable" > bindable< / span > < / td > < td > soma< / td > < td class = "default" > today< / td > < td > Visible month anchor when the picker opens.< / td > < / tr >
< tr > < td class = "name" > open < span data-uix-tag data-kind = "bindable" > bindable< / span > < / td > < td > soma< / td > < td class = "default" > false< / td > < td > Popover open state.< / td > < / tr >
< tr > < td class = "name" > minValue / maxValue< / td > < td > soma< / td > < td class = "default empty" > -< / td > < td > Selectable date limits. Invalid min/max marks Calendar invalid and exposes the message.< / td > < / tr >
< tr > < td class = "name" > isDateDisabled< / td > < td > soma< / td > < td class = "default empty" > -< / td > < td > Matcher for hard-disabled days.< / td > < / tr >
< tr > < td class = "name" > isDateUnavailable< / td > < td > soma< / td > < td class = "default empty" > -< / td > < td > Matcher for unavailable days that remain visible in the grid.< / td > < / tr >
< tr > < td class = "name" > required / disabled / readonly< / td > < td > soma< / td > < td class = "default" > false< / td > < td > Root, input, trigger and calendar state.< / td > < / tr >
< tr > < td class = "name" > closeOnDateSelect< / td > < td > soma< / td > < td class = "default" > true< / td > < td > Close the popover after a calendar selection.< / td > < / tr >
< tr > < td class = "name" > numberOfMonths< / td > < td > soma< / td > < td class = "default" > 1< / td > < td > Render one or two visible months in the popup.< / td > < / tr >
< tr > < td class = "name" > fixedWeeks / pagedNavigation< / td > < td > soma< / td > < td class = "default" > false< / td > < td > Calendar grid height and navigation step.< / td > < / tr >
< tr > < td class = "name" > disableDaysOutsideMonth< / td > < td > soma< / td > < td class = "default" > true< / td > < td > Days from adjacent months are shown but not selectable.< / td > < / tr >
< tr > < td class = "name" > weekStartsOn / locale / dir< / td > < td > soma< / td > < td class = "default" > services< / td > < td > Calendar ordering, labels and directional keys.< / td > < / tr >
< tr > < td class = "name" > size < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td > eidos< / td > < td class = "default" > md< / td > < td > Shared visual scale for field, trigger and calendar.< / td > < / tr >
< tr > < td class = "name" > variant < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td > eidos< / td > < td class = "default" > surface< / td > < td > Visual treatment of the input and popup calendar.< / td > < / tr >
< tr > < td class = "name" > color < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td > eidos< / td > < td class = "default" > primary< / td > < td > Intent palette for focus and selected-day affordances.< / td > < / tr >
< / tbody >
< / table >
< / div >
< div data-uix-subsection-head > Reference comparison< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Feature< / th > < th > Air< / th > < th > React Aria< / th > < th > Ark< / th > < th > Bits< / th > < th > UIX< / th > < th > Decision< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > Segmented input + calendar popover< / td > < td > no local Air component< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > Compose DateField, Popover and Calendar.< / td > < / tr >
< tr > < td class = "name" > Explicit minValue / maxValue< / td > < td > -< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > Expose directly in Soma and demo controls.< / td > < / tr >
< tr > < td class = "name" > Unavailable / disabled matchers< / td > < td > -< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > Forward to Calendar as first-class picker props.< / td > < / tr >
< tr > < td class = "name" > Multiple months< / td > < td > -< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > Calendar remains the source of month layout.< / td > < / tr >
< tr > < td class = "name" > Composite semantic events< / td > < td > -< / td > < td > callbacks< / td > < td > callbacks< / td > < td > callbacks< / td > < td > yes< / td > < td > Field, calendar and popover events are surfaced together in the picker demo.< / td > < / tr >
< / tbody >
< / table >
< / div >
< / section >
{ /if }
{ #if tab === 'morfo' }
< section data-uix-section >
< h2 data-uix-section-title >
< span data-uix-layer-badge = "morfo" > morfo< / span >
· declarative contract
< / h2 >
< p data-uix-section-desc >
Source: < code > src/uix/morfo/components/date-picker.ts< / code > . DatePicker declares only
the composition-specific root, trigger and calendar bridge. DateField, Popover and
Calendar keep their own contracts.
< / p >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Field< / th > < th > Value< / th > < / tr > < / thead >
< tbody >
< tr >< td class = "name" > name</ td >< td class = "type" > "{ datePickerMorfo . name } "</ td ></ tr >
< tr >< td class = "name" > kebab</ td >< td class = "type" > "{ datePickerMorfo . kebab } "</ td ></ tr >
< tr >< td class = "name" > scope</ td >< td class = "type" > [{ datePickerMorfo . scope . map (( s ) => `"$ { s } " `).join(', ') } ]</ td ></ tr >
< tr > < td class = "name" > apg< / td > < td class = "default" > composed: DateField + Popover + Calendar< / td > < / tr >
< tr >< td class = "name" > parts.length</ td >< td class = "default" > { datePickerMorfo . parts . length } </ td ></ tr >
< tr >< td class = "name" > own events</ td >< td class = "default" > { datePickerEvents . length } </ td ></ tr >
< tr >< td class = "name" > composed events</ td >< td class = "default" > { events . length } </ td ></ tr >
< / tbody >
< / table >
< / div >
< div data-uix-subsection-head > Parts< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Part< / th > < th > Marker< / th > < th > Element< / th > < th > Role< / th > < th > Archetype< / th > < th > States< / th > < th > Optional< / th > < / tr > < / thead >
< tbody >
{ #each partsList as part }
< tr >
< td class = "name" > { part . kebab } </ td >
< td >< code data-uix-part-marker > [{ part . marker } ]</ code ></ td >
< td class = "type" > < { part . defaultElement } > </ td >
< td class = "type" > { part . role ?? '-' } </ td >
< td class = "default" > { part . archetype ?? '-' } </ td >
< td class = "default" > { part . states . length ? part . states . join ( ' | ' ) : '-' } </ td >
< td class = "default" > { part . optional ? 'yes' : 'no' } </ td >
< / tr >
{ /each }
< / tbody >
< / table >
< / div >
{ #each datePickerMorfo . parts as rawPart }
{ @const partAny = rawPart as unknown as { kebab : string ; aria? : ReadonlyArray < { attr : string ; value : { kind : string }; condition ?: { when : string ; prop? : string ; part? : string }; severity? : string } > ; keyboard? : ReadonlyArray < { key : string ; action : string } > }}
{ @const dataAttrs = compiled . contracts . dataAttrsByPart . get ( partAny . kebab ) ?? []}
{ @const ariaAttrs = partAny . aria ?? []}
{ @const keyboard = partAny . keyboard ?? []}
{ #if dataAttrs . length || ariaAttrs . length || keyboard . length }
< div data-uix-subsection-head > { partAny . kebab } </ div >
{ #if dataAttrs . length }
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > data-attr< / th > < th > Values< / th > < th > Source kind< / th > < / tr > < / thead >
< tbody >
{ #each dataAttrs as attr }
< tr >
< td class = "name" > { attr . attr } </ td >
< td class = "type" > { attr . values ? attr . values . join ( ' | ' ) : '-' } </ td >
< td class = "default" > { 'value' in attr && attr . value ? ( attr as { value : { kind : string } }). value . kind : '-' } </ td >
< / tr >
{ /each }
< / tbody >
< / table >
< / div >
{ /if }
{ #if ariaAttrs . length }
< div data-uix-table-wrap style = "margin-top: var(--uix-space-2);" >
< table data-uix-table >
< thead > < tr > < th > aria-attr< / th > < th > Source kind< / th > < th > Condition< / th > < th > Severity< / th > < / tr > < / thead >
< tbody >
{ #each ariaAttrs as a }
< tr >
< td class = "name" > { a . attr } </ td >
< td class = "type" > { a . value . kind } </ td >
< td class = "default" > { a . condition ? `when $ { a . condition . when } $ { a . condition . prop ? ` ($ { a . condition . prop }) ` : '' } $ { a . condition . part ? ` ($ { a . condition . part }) ` : '' } ` : 'always' } </ td >
< td class = "default" > { a . severity ?? 'required' } </ td >
< / tr >
{ /each }
< / tbody >
< / table >
< / div >
{ /if }
{ #if keyboard . length }
< div data-uix-table-wrap style = "margin-top: var(--uix-space-2);" >
< table data-uix-table >
< thead > < tr > < th > key< / th > < th > action< / th > < / tr > < / thead >
< tbody >
{ #each keyboard as kb }
< tr >
< td >< span data-uix-kbd > { kb . key === ' ' ? 'Space' : kb . key } </ span ></ td >
< td class = "default" > { kb . action } </ td >
< / tr >
{ /each }
< / tbody >
< / table >
< / div >
{ /if }
{ /if }
{ /each }
< div data-uix-subsection-head > Events declaration< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Name< / th > < th > Family< / th > < th > Verb< / th > < th > Sequence< / th > < th > Intent< / th > < th > Target< / th > < / tr > < / thead >
< tbody >
{ #each eventRows as { owner , action }}
{ @const intentDecl = 'intent' in action . semantic ? action.semantic.intent : undefined }
< tr >
< td class = "name" > { owner } .{ action . name } </ td >
< td class = "type" > { action . semantic . family } </ td >
< td > { action . semantic . verb ?? '-' } </ td >
< td > { action . semantic . sequence ?? 'pre' } </ td >
< td class = "default" > { typeof intentDecl === 'string' ? intentDecl : intentDecl ? 'fromProp' : '-' } </ td >
< td class = "type" > { action . target } </ td >
< / tr >
{ /each }
< / tbody >
< / table >
< / div >
< / section >
{ /if }
{ #if tab === 'sema' }
< section data-uix-section id = "sema" >
< h2 data-uix-section-title >
< span data-uix-layer-badge = "sema" > sema< / span >
· events + perceptual signature
< / h2 >
< p data-uix-section-desc >
The picker event surface is composed: DateField commits typed segment changes,
Calendar commits date selection and navigation, and Popover owns disclosure events.
< / p >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Owner< / th > < th > Events< / th > < th > Why< / th > < / tr > < / thead >
< tbody >
< tr >< td class = "name" > DatePicker</ td >< td class = "default" > { datePickerEvents . length } </ td >< td > Root state and composition.</ td ></ tr >
< tr >< td class = "name" > DateField</ td >< td class = "default" > { dateFieldEvents . length } </ td >< td > Typed date commits and clears.</ td ></ tr >
< tr >< td class = "name" > Calendar</ td >< td class = "default" > { calendarEvents . length } </ td >< td > Date select, unselect and month navigation.</ td ></ tr >
< tr >< td class = "name" > Popover</ td >< td class = "default" > { popoverEvents . length } </ td >< td > Open and close causes.</ td ></ tr >
< / tbody >
< / table >
< / div >
< div data-uix-subsection-head > Composed event declarations< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Owner< / th > < th > Name< / th > < th > Family< / th > < th > Verb< / th > < th > Sequence< / th > < th > Intent< / th > < / tr > < / thead >
< tbody >
{ #each eventRows as { owner , action }}
{ @const intentDecl = 'intent' in action . semantic ? action.semantic.intent : undefined }
< tr >
< td class = "name" > { owner } </ td >
< td class = "name" > { action . name } </ td >
< td class = "type" > { action . semantic . family } </ td >
< td > { action . semantic . verb ?? '-' } </ td >
< td > { action . semantic . sequence ?? 'pre' } </ td >
< td class = "default" > { typeof intentDecl === 'string' ? intentDecl : intentDecl ? 'fromProp' : '-' } </ td >
< / tr >
{ /each }
< / tbody >
< / table >
< / div >
< / section >
{ /if }
{ #if tab === 'recipe' }
< section data-uix-section >
< h2 data-uix-section-title > Eidos recipe< / h2 >
< p data-uix-section-desc >
Selectors live in < code > src/uix/eidos/components/date-picker/date-picker.css< / code > .
The root supplies DateField variables and the popup calendar reuses the Calendar recipe
without drawing a second card.
< / p >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Selector< / th > < th > Source< / th > < th > Purpose< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > < code > [data-date-picker]< / code > < / td > < td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td > < td > Root provider, open and validation states.< / td > < / tr >
< tr > < td class = "name" > < code > [data-date-picker][data-size][data-variant][data-color]< / code > < / td > < td > < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td > Visual scale, treatment and intent palette.< / td > < / tr >
< tr > < td class = "name" > < code > [data-date-picker-trigger]< / code > < / td > < td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td > < td > Calendar opener button.< / td > < / tr >
< tr > < td class = "name" > < code > [data-popover-content]:has([data-date-picker-calendar])< / code > < / td > < td > < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td > < td > Popup width follows one or two calendar months.< / td > < / tr >
< tr > < td class = "name" > < code > [data-date-picker-calendar][data-calendar]< / code > < / td > < td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td > < td > Calendar bridge with nested card chrome removed.< / td > < / tr >
< tr > < td class = "name" > < code > [data-calendar-day][data-selected]< / code > < / td > < td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td > < td > Selected date state inherited from Calendar.< / td > < / tr >
< / tbody >
< / table >
< / div >
< / section >
{ /if }
{ #if tab === 'a11y' }
< section data-uix-section >
< h2 data-uix-section-title > Accessibility< / h2 >
< p data-uix-section-desc >
The picker combines the ARIA contracts of DateField, Popover and Calendar. The trigger
opens a dialog-style popup; the grid keeps Calendar keyboard behavior.
< / p >
< div data-uix-subsection-head > Keyboard< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Key< / th > < th > Action< / th > < / tr > < / thead >
< tbody >
< tr > < td > < span data-uix-kbd > Enter< / span > / < span data-uix-kbd > Space< / span > < / td > < td > Open the popover from Trigger; select focused day inside Calendar.< / td > < / tr >
< tr > < td > < span data-uix-kbd > Escape< / span > < / td > < td > Close the popover.< / td > < / tr >
< tr > < td > < span data-uix-kbd > Arrow keys< / span > < / td > < td > Edit date segments in the input or move focus by day/week in the calendar grid.< / td > < / tr >
< tr > < td > < span data-uix-kbd > PageUp< / span > / < span data-uix-kbd > PageDown< / span > < / td > < td > Calendar month navigation.< / td > < / tr >
< tr > < td > < span data-uix-kbd > Home< / span > / < span data-uix-kbd > End< / span > < / td > < td > Move to first or last day of the current week.< / td > < / tr >
< / tbody >
< / table >
< / div >
< div data-uix-subsection-head > ARIA contract< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Part< / th > < th > Attribute< / th > < th > Value< / th > < / tr > < / thead >
< tbody >
< tr >< td class = "name" > root</ td >< td > aria-disabled / aria-readonly / aria-required</ td >< td class = "type" > { `{state flags}` } </ td ></ tr >
< tr > < td class = "name" > input< / td > < td > role< / td > < td class = "type" > "group"< / td > < / tr >
< tr > < td class = "name" > segment< / td > < td > role< / td > < td class = "type" > "spinbutton"< / td > < / tr >
< tr > < td class = "name" > trigger< / td > < td > aria-haspopup< / td > < td class = "type" > "dialog"< / td > < / tr >
< tr >< td class = "name" > trigger</ td >< td > aria-expanded</ td >< td class = "type" > { `{open}` } </ td ></ tr >
< tr > < td class = "name" > content< / td > < td > role< / td > < td class = "type" > "dialog"< / td > < / tr >
< tr > < td class = "name" > calendar grid< / td > < td > role< / td > < td class = "type" > "grid"< / td > < / tr >
< tr >< td class = "name" > calendar day</ td >< td > aria-selected / aria-disabled</ td >< td class = "type" > { `{date state}` } </ td ></ tr >
< / tbody >
< / table >
< / div >
< / section >
{ /if }
< / div >
{ # snippet calendarBody ({ months , weekdays , validationMessage } : DatePickerCalendarSnippetProps )}
< DatePicker.Header >
< DatePicker.PrevButton aria-label = "Previous month" > < < / DatePicker.PrevButton >
< div data-calendar-header-controls >
< DatePicker.MonthSelect / >
< DatePicker.YearSelect / >
< / div >
< DatePicker.NextButton aria-label = "Next month" > > < / DatePicker.NextButton >
< / DatePicker.Header >
{ #if validationMessage }
< p class = "calendar-error" role = "alert" > { validationMessage } </ p >
{ /if }
< div class = "month-stack" data-month-count = { months . length } >
{ #each months as month ( month . value . toString ())}
< div data-calendar-month-panel >
{ #if months . length > 1 }
< DatePicker.Heading id = { monthHeadingId ( month . value )} value= { month . value } />
{ /if }
< DatePicker.Grid labelledBy = { months . length > 1 ? monthHeadingId ( month . value ) : undefined } >
< DatePicker.GridHead >
< DatePicker.GridRow >
{ #if showWeekNumbers }
< th data-calendar-head-cell data-calendar-week-number scope = "col" > wk< / th >
{ /if }
{ #each weekdays as day }
< DatePicker.HeadCell > { day } </ DatePicker.HeadCell >
{ /each }
< / DatePicker.GridRow >
< / DatePicker.GridHead >
< DatePicker.GridBody >
{ #each month . weeks as week }
< DatePicker.GridRow { week } >
{ # snippet children ({ weekNumber } : DatePickerGridRowSnippetProps )}
{ #if showWeekNumbers }
< th data-calendar-head-cell data-calendar-week-number scope = "row" >
{ weekNumber ?? '' }
< / th >
{ /if }
{ #each week as date }
< DatePicker.Cell { date } month = { month . value } >
< DatePicker.Day / >
< / DatePicker.Cell >
{ /each }
{ /snippet }
< / DatePicker.GridRow >
{ /each }
< / DatePicker.GridBody >
< / DatePicker.Grid >
< / div >
{ /each }
< / div >
< DatePicker.Footer >
< DatePicker.Clear / >
< DatePicker.Cancel / >
< DatePicker.Close / >
< / DatePicker.Footer >
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
{ /snippet }
< style >
.date-picker-stage {
inline-size: min(32rem, 100%);
}
.date-picker-status {
display: flex;
align-items: center;
min-block-size: 1.5rem;
color: var(--uix-text-muted);
font-size: var(--uix-text-sm);
}
.date-picker-status[data-invalid='true'] {
color: var(--color-threat-text);
}
.month-stack {
display: grid;
grid-auto-flow: column;
grid-auto-columns: max-content;
justify-content: center;
gap: var(--calendar-month-gap, 32px);
max-inline-size: 100%;
overflow-x: auto;
}
.month-stack[data-month-count='1'] {
grid-auto-flow: row;
}
.calendar-error {
margin: 0;
max-inline-size: calc((var(--_calendar-day-size) * 7) + (var(--calendar-grid-gap) * 8));
color: var(--color-threat-text);
font-size: var(--uix-text-sm);
text-align: center;
overflow-wrap: anywhere;
}
[data-uix-control] input[type='date'] {
min-inline-size: 0;
padding: 6px var(--uix-space-3);
border: 1px solid var(--uix-line);
border-radius: var(--uix-radius-sm);
background: var(--uix-surface);
color: var(--uix-text);
font: inherit;
font-size: var(--uix-text-sm);
}
[data-uix-control] input[type='date']:focus {
outline: none;
border-color: var(--uix-accent-line);
box-shadow: var(--uix-ring);
}
< / style >