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 {
DateRangePicker,
type DateRangePickerCalendarSnippetProps,
type DateRangePickerColor,
type DateRangePickerGridRowSnippetProps,
type DateRangePickerInputSnippetProps,
type DateRangePickerSize,
type DateRangePickerVariant
} from '$uix/eidos/components/date-range-picker';
import { compileMorfo } from '$uix/morfo';
import { dateRangePickerMorfo } from '@/uix/morfo/components/date-range-picker';
import { dateRangeFieldMorfo } from '@/uix/morfo/components/date-range-field';
import { rangeCalendarMorfo } from '@/uix/morfo/components/range-calendar';
import { popoverMorfo } from '@/uix/morfo/components/popover';
import { getActiveUix } from '$active-uix';
import {
CalendarDate,
getLocalTimeZone,
type DateMatcher,
type DateRange,
type DateValue,
type EditableSegmentPart,
type WeekStartsOn
} from '$libs/days';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
type TraceEntry = { event : string ; family : string ; intent? : string ; at : number } ;
type WeekStartControl = 'locale' | '0' | '1';
type SegmentLock = 'none' | 'year' | 'monthYear' | 'date';
type Preset = 'sprint' | 'single' | 'late' | 'empty';
const uix = getActiveUix();
const sprintStart = new CalendarDate(2026, 5, 20);
const sprintEnd = new CalendarDate(2026, 6, 3);
const lateStart = new CalendarDate(2026, 6, 8);
const lateEnd = new CalendarDate(2026, 6, 18);
const defaultMin = new CalendarDate(2026, 5, 1);
const defaultMax = new CalendarDate(2026, 7, 31);
const invalidMax = new CalendarDate(2026, 4, 30);
const blockedDates = [new CalendarDate(2026, 6, 12), new CalendarDate(2026, 7, 4)];
const holidays = [new CalendarDate(2026, 5, 25), new CalendarDate(2026, 6, 24)];
let tab = $state< Tab > ('live');
let trace = $state< TraceEntry [ ] > ([]);
let stageRef = $state< HTMLElement | null > (null);
let value = $state< DateRange | undefined > ({ start : sprintStart , end : sprintEnd } );
let placeholder = $state< DateValue > (sprintStart);
date-range-picker: mode prop + footer button props + revert group chrome
User feedback batch from incidencia 2026-05-20:
1. Modal vs inline mode
- New `mode: 'inline' | 'modal'` prop on the soma `DateRangePicker.Provider`.
- Modal wires the popover `modal: true` → outside-click and Escape are
ignored; user must commit via the footer Close button (or revert via
Cancel).
- Provider exposes helpers `clear()`, `cancel()`, `commit()` for the
footer. `cancel()` restores the value snapshot taken on the OPEN edge
(captured via a `watch` on `opts.open` true-edge transition).
2. Footer buttons as boolean props
- `clearButton`, `cancelButton`, `closeButton` props on the picker.
The footer renders only if at least one is true. In `mode='modal'`
the closeButton is forced on (the user always needs an exit).
- `picker-actions.svelte` in the demo reads the picker context via
`DateRangePickerProvider.require()` and renders the enabled buttons
against `provider.clear/cancel/commit`.
3. Range field shape (revert to Chakra-style two boxes)
- Removed the `data-date-range-field-group` wrapper from the demo so
the start and end inputs are rendered as two separate boxed fields
with the icon embedded in the end box, matching Chakra's layout.
- The recipe CSS rules for `data-date-range-field-group` stay
available as an opt-in for consumers who prefer the unified pill.
4. Demo defaults
- `open` starts at `false` so the picker exercises the real
open/close flow when the user clicks the trigger — the segments are
for direct keyboard entry, the popover is for visual exploration.
- Mode toggle (inline / modal) + footer button switches surfaced as
controls in the demo.
5. Plumbing
- DateRangePickerOpts gains `mode`, `clearButton`, `cancelButton`,
`closeButton` (StateProps for mode, ActiveProps for the booleans).
- `DateRangePickerProvider` and `DateRangePickerMode` are now re-
exported from the soma barrel for consumers that wire footer
actions in the calendar tree.
- Test factory updated to seed the new opts.
Verified in browser: trigger opens; click-outside in modal mode is
ignored; Close commits & closes; Cancel reverts to snapshot & closes;
Clear empties the range & keeps open. Heading "May – June 2026" (year
collapse) and centered per-calendar titles still working from the
previous commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// Popover starts closed: the field's segments are for direct keyboard
// entry; the popover is only for visual exploration via the trigger icon.
let open = $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 startInput = $state(toDateInput(sprintStart));
let endInput = $state(toDateInput(sprintEnd));
let minInput = $state(toDateInput(defaultMin));
let maxInput = $state(toDateInput(defaultMax));
let minDaysInput = $state('2');
let maxDaysInput = $state('21');
let allowSingleDay = $state(false);
let closeOnRangeSelect = $state(false);
date-range-picker: mode prop + footer button props + revert group chrome
User feedback batch from incidencia 2026-05-20:
1. Modal vs inline mode
- New `mode: 'inline' | 'modal'` prop on the soma `DateRangePicker.Provider`.
- Modal wires the popover `modal: true` → outside-click and Escape are
ignored; user must commit via the footer Close button (or revert via
Cancel).
- Provider exposes helpers `clear()`, `cancel()`, `commit()` for the
footer. `cancel()` restores the value snapshot taken on the OPEN edge
(captured via a `watch` on `opts.open` true-edge transition).
2. Footer buttons as boolean props
- `clearButton`, `cancelButton`, `closeButton` props on the picker.
The footer renders only if at least one is true. In `mode='modal'`
the closeButton is forced on (the user always needs an exit).
- `picker-actions.svelte` in the demo reads the picker context via
`DateRangePickerProvider.require()` and renders the enabled buttons
against `provider.clear/cancel/commit`.
3. Range field shape (revert to Chakra-style two boxes)
- Removed the `data-date-range-field-group` wrapper from the demo so
the start and end inputs are rendered as two separate boxed fields
with the icon embedded in the end box, matching Chakra's layout.
- The recipe CSS rules for `data-date-range-field-group` stay
available as an opt-in for consumers who prefer the unified pill.
4. Demo defaults
- `open` starts at `false` so the picker exercises the real
open/close flow when the user clicks the trigger — the segments are
for direct keyboard entry, the popover is for visual exploration.
- Mode toggle (inline / modal) + footer button switches surfaced as
controls in the demo.
5. Plumbing
- DateRangePickerOpts gains `mode`, `clearButton`, `cancelButton`,
`closeButton` (StateProps for mode, ActiveProps for the booleans).
- `DateRangePickerProvider` and `DateRangePickerMode` are now re-
exported from the soma barrel for consumers that wire footer
actions in the calendar tree.
- Test factory updated to seed the new opts.
Verified in browser: trigger opens; click-outside in modal mode is
ignored; Close commits & closes; Cancel reverts to snapshot & closes;
Clear empties the range & keeps open. Heading "May – June 2026" (year
collapse) and centered per-calendar titles still working from the
previous commit.
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(true);
let pagedNavigation = $state(false);
let showWeekNumbers = $state(false);
let blockWeekends = $state(false);
let markBlockedUnavailable = $state(true);
let numberOfMonths = $state(2);
let weekStartsOn = $state< WeekStartControl > ('1');
let locale = $state('en-US');
let dir = $state< 'ltr' | 'rtl'>('ltr');
let required = $state(true);
let disabled = $state(false);
let readonly = $state(false);
let startLock = $state< SegmentLock > ('none');
let endLock = $state< SegmentLock > ('none');
let size = $state< DateRangePickerSize > ('md');
let variant = $state< DateRangePickerVariant > ('surface');
let color = $state< DateRangePickerColor > ('primary');
let labelText = $state('Booking range');
let startName = $state('booking-start');
let endName = $state('booking-end');
let lastLoggedConfigError = $state('');
const compiled = compileMorfo(dateRangePickerMorfo);
const rangeCalendarCompiled = compileMorfo(rangeCalendarMorfo);
const dateRangeFieldCompiled = compileMorfo(dateRangeFieldMorfo);
const popoverCompiled = compileMorfo(popoverMorfo);
const partsList = $derived([...compiled.parts.byKebab.values()]);
const dateRangePickerEvents = $derived([...compiled.actions.byName.values()]);
const dateRangeFieldEvents = $derived([...dateRangeFieldCompiled.actions.byName.values()]);
const rangeCalendarEvents = $derived([...rangeCalendarCompiled.actions.byName.values()]);
const popoverEvents = $derived([...popoverCompiled.actions.byName.values()]);
const eventRows = $derived([
...dateRangePickerEvents.map((action) => ({ owner : 'DateRangePicker' , action } )),
...dateRangeFieldEvents.map((action) => ({ owner : 'DateRangeField' , action } )),
...rangeCalendarEvents.map((action) => ({ owner : 'RangeCalendar' , 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 minDays = $derived(parseOptionalInt(minDaysInput));
const maxDays = $derived(parseOptionalInt(maxDaysInput));
const resolvedWeekStartsOn = $derived(
weekStartsOn === 'locale' ? undefined : (Number(weekStartsOn) as WeekStartsOn)
);
const startReadonlySegments = $derived(lockToSegments(startLock));
const endReadonlySegments = $derived(lockToSegments(endLock));
const dateDisabledMatcher = $derived< DateMatcher | undefined > (
blockWeekends ? isWeekend : undefined
);
const dateUnavailableMatcher = $derived< DateMatcher | undefined > (
markBlockedUnavailable ? isBlocked : undefined
);
const boundsMessage = $derived(getBoundsMessage());
const spanMessage = $derived(getSpanConfigMessage());
const rangeMessage = $derived(getRangeMessage());
const statusMessage = $derived(boundsMessage || spanMessage || rangeMessage);
const valueLabel = $derived(formatRange(value));
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 parseOptionalInt(text: string): number | undefined {
if (!text) return undefined;
const value = Number.parseInt(text, 10);
return Number.isFinite(value) & & value > 0 ? value : undefined;
}
function dateCtor(date: DateValue | undefined): string {
return date ? `new CalendarDate(${ date . year } , ${ date . month } , ${ date . day } )` : 'undefined';
}
function sameDate(a: DateValue | undefined, b: DateValue): boolean {
return a?.compare(b) === 0;
}
function daysBetween(start: DateValue, end: DateValue): number {
const s = start.toDate(getLocalTimeZone()).getTime();
const e = end.toDate(getLocalTimeZone()).getTime();
return Math.abs(Math.round((e - s) / 86_400_000)) + 1;
}
function isWeekend(date: DateValue): boolean {
const day = date.toDate('UTC').getUTCDay();
return day === 0 || day === 6;
}
function isBlocked(date: DateValue): boolean {
return blockedDates.some((item) => sameDate(item, date));
}
function isHoliday(date: DateValue): boolean {
return holidays.some((item) => sameDate(item, date));
}
function lockToSegments(lock: SegmentLock): EditableSegmentPart[] {
if (lock === 'year') return ['year'];
if (lock === 'monthYear') return ['month', 'year'];
if (lock === 'date') return ['day', 'month', 'year'];
return [];
}
function inputValue(event: Event): string {
return (event.currentTarget as HTMLInputElement).value;
}
function setEndpoint(endpoint: 'start' | 'end', text: string): void {
const next = parseDateInput(text);
const current = value ?? { start : undefined , end : undefined } ;
value = endpoint === 'start' ? { ... current , start : next } : { ... current , end : next } ;
if (next) placeholder = next;
}
function setPreset(preset: Preset): void {
if (preset === 'sprint') value = { start : sprintStart , end : sprintEnd } ;
if (preset === 'single') value = { start : sprintStart , end : sprintStart } ;
if (preset === 'late') value = { start : lateStart , end : lateEnd } ;
if (preset === 'empty') value = { start : undefined , end : undefined } ;
const anchor = value?.start ?? value?.end;
if (anchor) placeholder = anchor;
}
function setBoundsPreset(preset: 'none' | 'min' | 'max' | 'window' | 'invalid'): 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 && minValue.compare(maxValue) > 0) {
return 'minValue must be on or before maxValue.';
}
return '';
}
function getSpanConfigMessage(): string {
if (minDaysInput & & minDays === undefined) return 'minDays must be a positive integer.';
if (maxDaysInput & & maxDays === undefined) return 'maxDays must be a positive integer.';
if (minDays !== undefined && maxDays !== undefined && minDays > maxDays) {
return 'minDays must be lower than or equal to maxDays.';
}
return '';
}
function validateRange(range: DateRange): string | undefined {
if (!range.start || !range.end) return required ? 'Both endpoints are required.' : undefined;
const length = daysBetween(range.start, range.end);
if (!allowSingleDay & & length === 1) return 'Single-day ranges are disabled.';
if (minDays !== undefined && length < minDays ) return ` Range must be at least $ { minDays } days .`;
if (maxDays !== undefined && length > maxDays) return `Range must be at most ${ maxDays } days.`;
return undefined;
}
function getRangeMessage(): string {
const range = value;
if (!range?.start || !range.end) return required ? 'Both endpoints are required.' : 'No range selected.';
const custom = validateRange(range);
if (custom) return custom;
if (minValue && (range.start.compare(minValue) < 0 || range . end . compare ( minValue ) < 0 )) {
return 'Selected range starts before minValue.';
}
if (maxValue && (range.start.compare(maxValue) > 0 || range.end.compare(maxValue) > 0)) {
return 'Selected range ends after maxValue.';
}
if (dateDisabledMatcher?.(range.start) || dateDisabledMatcher?.(range.end)) {
return 'Selected range endpoint matches isDateDisabled.';
}
if (dateUnavailableMatcher?.(range.start) || dateUnavailableMatcher?.(range.end)) {
return 'Selected range endpoint matches isDateUnavailable.';
}
return 'Range is valid.';
}
function formatRange(range: DateRange | undefined): string {
if (!range?.start & & !range?.end) return 'empty';
return `${ range . start ? . toString () ?? '...' } -> ${ range . end ? . toString () ?? '...' } `;
}
function formatMonthYear(date: DateValue): string {
return new Intl.DateTimeFormat(locale, { month : 'long' , year : 'numeric' } ).format(
date.toDate(getLocalTimeZone())
);
}
function monthHeadingId(date: DateValue): string {
return `date-range-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(() => {
startInput = value?.start ? toDateInput(value.start) : '';
endInput = value?.end ? toDateInput(value.end) : '';
});
$effect(() => {
const message = boundsMessage || spanMessage;
if (!message) {
lastLoggedConfigError = '';
return;
}
if (message === lastLoggedConfigError) return;
lastLoggedConfigError = message;
uix.logger.error('soma.date-range-picker', message, {
context: {
minValue: minValue?.toString() ?? null,
maxValue: maxValue?.toString() ?? null,
minDays,
maxDays
}
});
});
$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 DateRangePicker from '$soma/components/date-range-picker';",
" import { CalendarDate , type DateRange , type DateValue } from '$libs/days';",
'',
` const minValue = ${ dateCtor ( minValue )} ;`,
` const maxValue = ${ dateCtor ( maxValue )} ;`,
` let value = $state< DateRange | undefined > ({ start : $ { dateCtor ( value ? . start )}, end : $ { dateCtor ( value ? . end )} } );`,
` let placeholder = $state< DateValue > (${ dateCtor ( placeholder )} );`,
` let open = $state(${ open } );`,
'< /' + 'script>',
'',
'< DateRangePicker.Provider ' ,
' bind:value',
' bind:placeholder',
' bind:open',
required & & ' required',
disabled & & ' disabled',
readonly & & ' readonly',
minValue && ' { minValue } ',
maxValue && ' { maxValue } ',
minDays !== undefined && ` minDays={ $ { minDays }} `,
maxDays !== undefined && ` maxDays={ $ { maxDays }} `,
allowSingleDay & & ' allowSingleDay',
!closeOnRangeSelect && ' closeOnRangeSelect={ false } ',
numberOfMonths !== 2 && ` numberOfMonths={ $ { numberOfMonths }} `,
fixedWeeks & & ' fixedWeeks',
pagedNavigation & & ' pagedNavigation',
disableDaysOutsideMonth & & ' disableDaysOutsideMonth',
showWeekNumbers & & ' showWeekNumbers',
weekStartsOn !== 'locale' && ` weekStartsOn={ $ { weekStartsOn }} `,
startReadonlySegments.length & &
` startReadonlySegments={ $ { JSON . stringify ( startReadonlySegments )}} `,
endReadonlySegments.length && ` endReadonlySegments={ $ { JSON . stringify ( endReadonlySegments )}} `,
blockWeekends && ' isDateDisabled={( date ) => date . toDate ( "UTC" ). getUTCDay () % 6 === 0 } ',
markBlockedUnavailable && ' isDateUnavailable={( date ) => date . day === 22 } ',
' weekdayFormat="short"',
'>',
` < DateRangePicker.Label > ${ labelText } </ DateRangePicker.Label > `,
` < DateRangePicker.Input type = "start" name = "$ { startName } " > ...</ DateRangePicker.Input > `,
` < DateRangePicker.Input type = "end" name = "$ { endName } " > `,
' <!-- end segments --> ',
' < DateRangePicker.Trigger / > ',
' < / DateRangePicker.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
' < DateRangePicker.Portal > ',
' < DateRangePicker.Content align = "end" sideOffset = { 10 } collisionPadding= { 12 } > ',
' < DateRangePicker.Calendar > ...< / DateRangePicker.Calendar > ',
' < / DateRangePicker.Content > ',
' < / DateRangePicker.Portal > ',
'< / DateRangePicker.Provider > '
]
.filter(Boolean)
.join('\n')
);
const eidosSnippet = $derived(
[
"< script lang = 'ts' > " ,
" import { DateRangePicker } from '$uix/eidos/components/date-range-picker';",
" import { CalendarDate , type DateRange , type DateValue } from '$libs/days';",
'',
` const minValue = ${ dateCtor ( minValue )} ;`,
` const maxValue = ${ dateCtor ( maxValue )} ;`,
` let value = $state< DateRange | undefined > ({ start : $ { dateCtor ( value ? . start )}, end : $ { dateCtor ( value ? . end )} } );`,
` let placeholder = $state< DateValue > (${ dateCtor ( placeholder )} );`,
` let open = $state(${ open } );`,
'< /' + 'script>',
'',
'< DateRangePicker ' ,
' 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 } ',
minDays !== undefined && ` minDays={ $ { minDays }} `,
maxDays !== undefined && ` maxDays={ $ { maxDays }} `,
allowSingleDay & & ' allowSingleDay',
!closeOnRangeSelect && ' closeOnRangeSelect={ false } ',
numberOfMonths !== 2 && ` numberOfMonths={ $ { numberOfMonths }} `,
fixedWeeks & & ' fixedWeeks',
pagedNavigation & & ' pagedNavigation',
disableDaysOutsideMonth & & ' disableDaysOutsideMonth',
showWeekNumbers & & ' showWeekNumbers',
weekStartsOn !== 'locale' && ` weekStartsOn={ $ { weekStartsOn }} `,
startReadonlySegments.length & &
` startReadonlySegments={ $ { JSON . stringify ( startReadonlySegments )}} `,
endReadonlySegments.length && ` endReadonlySegments={ $ { JSON . stringify ( endReadonlySegments )}} `,
blockWeekends && ' isDateDisabled={( date ) => date . toDate ( "UTC" ). getUTCDay () % 6 === 0 } ',
markBlockedUnavailable && ' isDateUnavailable={( date ) => date . day === 22 } ',
' weekdayFormat="short"',
'>',
` < DateRangePicker.Label > ${ labelText } </ DateRangePicker.Label > `,
' < div class = "range-field-row" > ',
` < DateRangePicker.Input type = "start" name = "$ { startName } " > ...</ DateRangePicker.Input > `,
' < span aria-hidden = "true" > to< / span > ',
` < DateRangePicker.Input type = "end" name = "$ { endName } " > `,
' <!-- end segments --> ',
' < DateRangePicker.Trigger / > ',
' < / DateRangePicker.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 > ',
' < DateRangePicker.Portal > ',
' < DateRangePicker.Content align = "end" sideOffset = { 10 } collisionPadding= { 12 } > ',
' < DateRangePicker.Calendar > ...< / DateRangePicker.Calendar > ',
' < / DateRangePicker.Content > ',
' < / DateRangePicker.Portal > ',
'< / DateRangePicker > '
]
.filter(Boolean)
.join('\n')
);
< / script >
< div data-uix-canvas-inner >
< header >
< div data-uix-eyebrow > Forms - DateRangePicker< / div >
< h1 data-uix-page-title > DateRangePicker< / h1 >
< p data-uix-page-lede >
Segmented range input with an attached range calendar popover. Soma owns the two
endpoints, min/max validation, popover state and range selection; 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-range-picker-stage" >
< DateRangePicker
bind:value
bind:placeholder
bind:open
{ required }
{ disabled }
{ readonly }
{ allowSingleDay }
{ closeOnRangeSelect }
date-range-picker: mode prop + footer button props + revert group chrome
User feedback batch from incidencia 2026-05-20:
1. Modal vs inline mode
- New `mode: 'inline' | 'modal'` prop on the soma `DateRangePicker.Provider`.
- Modal wires the popover `modal: true` → outside-click and Escape are
ignored; user must commit via the footer Close button (or revert via
Cancel).
- Provider exposes helpers `clear()`, `cancel()`, `commit()` for the
footer. `cancel()` restores the value snapshot taken on the OPEN edge
(captured via a `watch` on `opts.open` true-edge transition).
2. Footer buttons as boolean props
- `clearButton`, `cancelButton`, `closeButton` props on the picker.
The footer renders only if at least one is true. In `mode='modal'`
the closeButton is forced on (the user always needs an exit).
- `picker-actions.svelte` in the demo reads the picker context via
`DateRangePickerProvider.require()` and renders the enabled buttons
against `provider.clear/cancel/commit`.
3. Range field shape (revert to Chakra-style two boxes)
- Removed the `data-date-range-field-group` wrapper from the demo so
the start and end inputs are rendered as two separate boxed fields
with the icon embedded in the end box, matching Chakra's layout.
- The recipe CSS rules for `data-date-range-field-group` stay
available as an opt-in for consumers who prefer the unified pill.
4. Demo defaults
- `open` starts at `false` so the picker exercises the real
open/close flow when the user clicks the trigger — the segments are
for direct keyboard entry, the popover is for visual exploration.
- Mode toggle (inline / modal) + footer button switches surfaced as
controls in the demo.
5. Plumbing
- DateRangePickerOpts gains `mode`, `clearButton`, `cancelButton`,
`closeButton` (StateProps for mode, ActiveProps for the booleans).
- `DateRangePickerProvider` and `DateRangePickerMode` are now re-
exported from the soma barrel for consumers that wire footer
actions in the calendar tree.
- Test factory updated to seed the new opts.
Verified in browser: trigger opens; click-outside in modal mode is
ignored; Close commits & closes; Cancel reverts to snapshot & closes;
Clear empties the range & keeps open. Heading "May – June 2026" (year
collapse) and centered per-calendar titles still working from the
previous commit.
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
{ disableDaysOutsideMonth }
{ fixedWeeks }
{ pagedNavigation }
{ showWeekNumbers }
{ numberOfMonths }
{ locale }
{ dir }
{ size }
{ variant }
{ color }
{ minValue }
{ maxValue }
{ minDays }
{ maxDays }
{ startReadonlySegments }
{ endReadonlySegments }
weekStartsOn={ resolvedWeekStartsOn }
isDateDisabled={ dateDisabledMatcher }
isDateUnavailable={ dateUnavailableMatcher }
isDateHoliday={ isHoliday }
validate={ validateRange }
weekdayFormat="short"
calendarLabel="Booking date range"
>
< DateRangePicker.Label > { labelText } </ DateRangePicker.Label >
date-range-picker: mode prop + footer button props + revert group chrome
User feedback batch from incidencia 2026-05-20:
1. Modal vs inline mode
- New `mode: 'inline' | 'modal'` prop on the soma `DateRangePicker.Provider`.
- Modal wires the popover `modal: true` → outside-click and Escape are
ignored; user must commit via the footer Close button (or revert via
Cancel).
- Provider exposes helpers `clear()`, `cancel()`, `commit()` for the
footer. `cancel()` restores the value snapshot taken on the OPEN edge
(captured via a `watch` on `opts.open` true-edge transition).
2. Footer buttons as boolean props
- `clearButton`, `cancelButton`, `closeButton` props on the picker.
The footer renders only if at least one is true. In `mode='modal'`
the closeButton is forced on (the user always needs an exit).
- `picker-actions.svelte` in the demo reads the picker context via
`DateRangePickerProvider.require()` and renders the enabled buttons
against `provider.clear/cancel/commit`.
3. Range field shape (revert to Chakra-style two boxes)
- Removed the `data-date-range-field-group` wrapper from the demo so
the start and end inputs are rendered as two separate boxed fields
with the icon embedded in the end box, matching Chakra's layout.
- The recipe CSS rules for `data-date-range-field-group` stay
available as an opt-in for consumers who prefer the unified pill.
4. Demo defaults
- `open` starts at `false` so the picker exercises the real
open/close flow when the user clicks the trigger — the segments are
for direct keyboard entry, the popover is for visual exploration.
- Mode toggle (inline / modal) + footer button switches surfaced as
controls in the demo.
5. Plumbing
- DateRangePickerOpts gains `mode`, `clearButton`, `cancelButton`,
`closeButton` (StateProps for mode, ActiveProps for the booleans).
- `DateRangePickerProvider` and `DateRangePickerMode` are now re-
exported from the soma barrel for consumers that wire footer
actions in the calendar tree.
- Test factory updated to seed the new opts.
Verified in browser: trigger opens; click-outside in modal mode is
ignored; Close commits & closes; Cancel reverts to snapshot & closes;
Clear empties the range & keeps open. Heading "May – June 2026" (year
collapse) and centered per-calendar titles still working from the
previous commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< div class = "range-field-row" >
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
< DateRangePicker.Input type = "start" name = { startName } aria-label="Start date " >
{ # snippet children ({ segments } : DateRangePickerInputSnippetProps )}
{ #each segments as { part , value }, index ( `start-$ { part } - $ { index } `) }
< DateRangePicker.Segment { part } > { value } </ DateRangePicker.Segment >
{ /each }
{ /snippet }
< / DateRangePicker.Input >
< span class = "range-separator" aria-hidden = "true" > to< / span >
< DateRangePicker.Input type = "end" name = { endName } aria-label="End date " >
{ # snippet children ({ segments } : DateRangePickerInputSnippetProps )}
{ #each segments as { part , value }, index ( `end-$ { part } - $ { index } `) }
< DateRangePicker.Segment { part } > { value } </ DateRangePicker.Segment >
{ /each }
< DateRangePicker.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 }
< / DateRangePicker.Input >
< / div >
< div class = "range-picker-status" data-invalid = { statusMessage !== 'Range is valid.' || undefined } >
< span > { statusMessage } </ span >
< / div >
< DateRangePicker.Portal >
< DateRangePicker.Content align = "end" sideOffset = { 10 } collisionPadding= { 12 } >
< DateRangePicker.Calendar >
{ # snippet children ( args : DateRangePickerCalendarSnippetProps )}
{ @render calendarBody ( args )}
{ /snippet }
< / DateRangePicker.Calendar >
< / DateRangePicker.Content >
< / DateRangePicker.Portal >
< / DateRangePicker >
< / 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 range< / 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);" > range< / 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 > 23</ 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 >
< span data-uix-layer-badge = "soma" > soma< / span > owns value, min/max dates, range
length, disabled/unavailable dates and popover behavior.
< span data-uix-layer-badge = "eidos" > eidos< / span > owns visual treatment.
< / p >
< div data-uix-subsection-head >
< span data-uix-layer-badge = "soma" > soma< / span > props - range value and constraints
< / 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 > start< / span >
< input type = "date" value = { startInput } oninput= {( event ) => setEndpoint ( 'start' , inputValue ( event ))} />
< / label >
< label data-uix-control >
< span data-uix-control-label > end< / span >
< input type = "date" value = { endInput } oninput= {( event ) => setEndpoint ( 'end' , inputValue ( event ))} />
< / label >
< label data-uix-control >
< span data-uix-control-label > range presets< / span >
< span data-uix-chips role = "group" >
< button data-uix-chip data-active = { sameDate ( value ? . start , sprintStart ) && sameDate ( value ? . end , sprintEnd )} onclick= {() => setPreset ( 'sprint' )} > May-Jun</ button >
< button data-uix-chip data-active = { sameDate ( value ? . start , sprintStart ) && sameDate ( value ? . end , sprintStart )} onclick= {() => setPreset ( 'single' )} > single day</ button >
< button data-uix-chip data-active = { sameDate ( value ? . start , lateStart )} onclick= {() => setPreset ( 'late' )} > June range</ button >
< button data-uix-chip data-active = { ! value ? . start && ! value ? . end } onclick= {() => setPreset ( 'empty' )} > 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 presets< / span >
< span data-uix-chips role = "radiogroup" >
< button data-uix-chip data-active = { ! minInput && ! maxInput } onclick= {() => setBoundsPreset ( 'none' )} > none</ button >
< button data-uix-chip data-active = { !! minInput && ! maxInput } onclick= {() => setBoundsPreset ( 'min' )} > min only</ button >
< button data-uix-chip data-active = { ! minInput && !! maxInput } onclick= {() => setBoundsPreset ( 'max' )} > max only</ button >
< button data-uix-chip data-active = { minInput === toDateInput ( defaultMin ) && maxInput === toDateInput ( defaultMax )} onclick= {() => setBoundsPreset ( 'window' )} > May-Jul</ button >
< button data-uix-chip data-active = { maxInput === toDateInput ( invalidMax )} onclick= {() => setBoundsPreset ( 'invalid' )} > invalid</ button >
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > minDays< / span >
< input type = "number" min = "1" bind:value = { minDaysInput } / >
< / label >
< label data-uix-control >
< span data-uix-control-label > maxDays< / span >
< input type = "number" min = "1" bind:value = { maxDaysInput } / >
< / label >
< label data-uix-control >
< span data-uix-control-label > visible months< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 1 , 2 ] as count }
< button data-uix-chip data-active = { numberOfMonths === count } onclick= {() => ( numberOfMonths = count )} > { count } </ button >
{ /each }
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > week starts< / 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 > start segments< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 'none' , 'year' , 'monthYear' , 'date' ] as value }
< button data-uix-chip data-active = { startLock === value } onclick= {() => ( startLock = value as SegmentLock )} > { value } </ button >
{ /each }
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > end segments< / span >
< span data-uix-chips role = "radiogroup" >
{ #each [ 'none' , 'year' , 'monthYear' , 'date' ] as value }
< button data-uix-chip data-active = { endLock === value } onclick= {() => ( endLock = value as SegmentLock )} > { value } </ button >
{ /each }
< / span >
< / label >
date-range-picker: mode prop + footer button props + revert group chrome
User feedback batch from incidencia 2026-05-20:
1. Modal vs inline mode
- New `mode: 'inline' | 'modal'` prop on the soma `DateRangePicker.Provider`.
- Modal wires the popover `modal: true` → outside-click and Escape are
ignored; user must commit via the footer Close button (or revert via
Cancel).
- Provider exposes helpers `clear()`, `cancel()`, `commit()` for the
footer. `cancel()` restores the value snapshot taken on the OPEN edge
(captured via a `watch` on `opts.open` true-edge transition).
2. Footer buttons as boolean props
- `clearButton`, `cancelButton`, `closeButton` props on the picker.
The footer renders only if at least one is true. In `mode='modal'`
the closeButton is forced on (the user always needs an exit).
- `picker-actions.svelte` in the demo reads the picker context via
`DateRangePickerProvider.require()` and renders the enabled buttons
against `provider.clear/cancel/commit`.
3. Range field shape (revert to Chakra-style two boxes)
- Removed the `data-date-range-field-group` wrapper from the demo so
the start and end inputs are rendered as two separate boxed fields
with the icon embedded in the end box, matching Chakra's layout.
- The recipe CSS rules for `data-date-range-field-group` stay
available as an opt-in for consumers who prefer the unified pill.
4. Demo defaults
- `open` starts at `false` so the picker exercises the real
open/close flow when the user clicks the trigger — the segments are
for direct keyboard entry, the popover is for visual exploration.
- Mode toggle (inline / modal) + footer button switches surfaced as
controls in the demo.
5. Plumbing
- DateRangePickerOpts gains `mode`, `clearButton`, `cancelButton`,
`closeButton` (StateProps for mode, ActiveProps for the booleans).
- `DateRangePickerProvider` and `DateRangePickerMode` are now re-
exported from the soma barrel for consumers that wire footer
actions in the calendar tree.
- Test factory updated to seed the new opts.
Verified in browser: trigger opens; click-outside in modal mode is
ignored; Close commits & closes; Cancel reverts to snapshot & closes;
Clear empties the range & keeps open. Heading "May – June 2026" (year
collapse) and centered per-calendar titles still working from the
previous commit.
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-radio >
< input type = "radio" bind:group = { mode } value="inline" />
< span data-uix-radio-label > inline< / span >
< / span >
< span data-uix-radio >
< input type = "radio" bind:group = { mode } value="modal" />
< span data-uix-radio-label > modal< / span >
< / 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 > states< / 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 >
< span data-uix-switch >< input type = "checkbox" bind:checked = { allowSingleDay } / >< span data-uix-switch-label > single day</ span ></ span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { closeOnRangeSelect } / >< 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 = { showWeekNumbers } / >< span data-uix-switch-label > week numbers</ span ></ span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { blockWeekends } / >< span data-uix-switch-label > block weekends</ span ></ span >
< span data-uix-switch >< input type = "checkbox" bind:checked = { markBlockedUnavailable } / >< span data-uix-switch-label > unavailable dates</ 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 DateRangePickerSize )} > { 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 DateRangePickerVariant )} > { 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' , 'fulfill' , 'risk' , 'threat' , 'loss' ] as value }
< button data-uix-chip data-active = { color === value } onclick= {() => ( color = value as DateRangePickerColor )} > { 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 > start name< / span >
< input type = "text" bind:value = { startName } / >
< / label >
< label data-uix-control >
< span data-uix-control-label > end name< / span >
< input type = "text" bind:value = { endName } / >
< / label >
< / div >
< div data-uix-code >
< div data-uix-code-head >
< span data-uix-layer-badge = "soma" > soma< / span >
< span > headless - shared range, field, 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 field, popover and range-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 >
DateRangePicker is a composition over DateRangeField, Popover and RangeCalendar. Air
has no local DateRangePicker; Terra in morfo-runtime is the historical baseline.
< / 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 > Range < code > { '{ start, end }' } </ code > shared by both inputs and calendar.</ 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.< / 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 state.< / td > < / tr >
< tr > < td class = "name" > minValue / maxValue< / td > < td > soma< / td > < td class = "default empty" > -< / td > < td > Selectable date bounds. Invalid pairs mark the range calendar invalid and log through UIX.< / td > < / tr >
< tr > < td class = "name" > minDays / maxDays / allowSingleDay< / td > < td > soma< / td > < td class = "default" > - / - / true< / td > < td > Inclusive range length constraints.< / td > < / tr >
< tr > < td class = "name" > isDateDisabled / isDateUnavailable / isDateHoliday< / td > < td > soma< / td > < td class = "default empty" > -< / td > < td > Per-date matchers for hard disabled, unavailable and decorative holiday dates.< / td > < / tr >
< tr > < td class = "name" > startReadonlySegments / endReadonlySegments< / td > < td > soma< / td > < td class = "default" > []< / td > < td > Independent segment locks for each endpoint.< / td > < / tr >
< tr > < td class = "name" > numberOfMonths / fixedWeeks / pagedNavigation< / td > < td > soma< / td > < td class = "default" > 2 / false / false< / td > < td > Range calendar layout and navigation behavior.< / td > < / tr >
< tr > < td class = "name" > closeOnRangeSelect< / td > < td > soma< / td > < td class = "default" > true< / td > < td > Close the popover when both endpoints are selected.< / td > < / tr >
< tr > < td class = "name" > size / variant / color< / td > < td > eidos< / td > < td class = "default" > md / surface / primary< / td > < td > Visual-only data attrs for field, trigger and calendar.< / 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 > Terra morfo-runtime< / th > < th > React Aria< / th > < th > Ark< / th > < th > Bits< / th > < th > UIX decision< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > DateRangePicker primitive< / td > < td > no< / td > < td > yes< / td > < td > yes< / td > < td > range mode< / td > < td > yes< / td > < td > Add Eidos wrapper over current Soma composition.< / td > < / tr >
< tr > < td class = "name" > Segmented start/end inputs< / td > < td > -< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > Use DateRangeField parts inside the picker namespace.< / td > < / tr >
< tr > < td class = "name" > Min/max date controls< / td > < td > -< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > Expose as direct props and visible demo controls.< / td > < / tr >
< tr > < td class = "name" > Range length constraints< / td > < td > -< / td > < td > min/max days< / td > < td > custom validation< / td > < td > range constraints< / td > < td > custom< / td > < td > First-class `minDays`, `maxDays`, `allowSingleDay`.< / td > < / tr >
< tr > < td class = "name" > Multiple visible months< / td > < td > calendar only< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > yes< / td > < td > Top heading shows full range; each visible month gets its own title only when there are 2 months.< / 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 > contract< / h2 >
< p data-uix-section-desc >
Source: < code > src/uix/morfo/components/date-range-picker.ts< / code > . The picker
declares only the composition root, trigger and calendar bridge; DateRangeField,
RangeCalendar and Popover 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" > "{ dateRangePickerMorfo . name } "</ td ></ tr >
< tr >< td class = "name" > kebab</ td >< td class = "type" > "{ dateRangePickerMorfo . kebab } "</ td ></ tr >
< tr >< td class = "name" > scope</ td >< td class = "type" > [{ dateRangePickerMorfo . scope . map (( s ) => `"$ { s } " `).join(', ') } ]</ td ></ tr >
< tr > < td class = "name" > apg< / td > < td class = "default" > composed: DateRangeField + Popover + RangeCalendar< / td > < / tr >
< tr >< td class = "name" > parts.length</ td >< td class = "default" > { dateRangePickerMorfo . parts . length } </ td ></ tr >
< tr >< td class = "name" > own events</ td >< td class = "default" > { dateRangePickerEvents . 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 >
< div data-uix-subsection-head > Composed contracts< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Contract< / th > < th > Parts< / th > < th > Events< / th > < th > Role in picker< / th > < / tr > < / thead >
< tbody >
< tr >< td class = "name" > DateRangeField</ td >< td > { dateRangeFieldCompiled . parts . order . length } </ td >< td > { dateRangeFieldCompiled . actions . byName . size } </ td >< td > Label plus start/end segmented inputs.</ td ></ tr >
< tr >< td class = "name" > RangeCalendar</ td >< td > { rangeCalendarCompiled . parts . order . length } </ td >< td > { rangeCalendarCompiled . actions . byName . size } </ td >< td > Range grid, min/max, preview and navigation.</ td ></ tr >
< tr >< td class = "name" > Popover</ td >< td > { popoverCompiled . parts . order . length } </ td >< td > { popoverCompiled . actions . byName . size } </ td >< td > Disclosure, portal and floating content.</ td ></ tr >
< / tbody >
< / table >
< / div >
< 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< / h2 >
< p data-uix-section-desc >
The public picker is a composition. Its event surface is the union of range calendar
selection/navigation events, popover disclosure events and any field commit events.
< / p >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Owner< / th > < th > Events< / th > < th > Responsibility< / th > < / tr > < / thead >
< tbody >
< tr >< td class = "name" > DateRangePicker</ td >< td class = "default" > { dateRangePickerEvents . length } </ td >< td > Root state and composition.</ td ></ tr >
< tr >< td class = "name" > DateRangeField</ td >< td class = "default" > { dateRangeFieldEvents . length } </ td >< td > Endpoint field commits.</ td ></ tr >
< tr >< td class = "name" > RangeCalendar</ td >< td class = "default" > { rangeCalendarEvents . length } </ td >< td > Start selection, range commit, clear and calendar navigation.</ td ></ tr >
< tr >< td class = "name" > Popover</ td >< td class = "default" > { popoverCompiled . actions . byName . size } </ td >< td > Disclosure events.</ 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-range-picker/date-range-picker.css< / code > .
The field and trigger use DateField tokens; the popup range grid mirrors Calendar
without drawing a nested 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-range-picker]< / code > < / td > < td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td > < td > Root provider, open and invalid states.< / td > < / tr >
< tr > < td class = "name" > < code > [data-date-range-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-range-field-input][data-endpoint]< / code > < / td > < td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td > < td > Start and end segmented fields.< / td > < / tr >
< tr > < td class = "name" > < code > [data-date-range-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-date-range-picker-calendar][data-range-calendar]< / code > < / td > < td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td > < td > Range calendar bridge with nested chrome removed.< / td > < / tr >
< tr > < td class = "name" > < code > [data-range-calendar-day][data-in-range]< / code > < / td > < td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td > < td > Range track styling.< / td > < / tr >
< tr > < td class = "name" > < code > [data-range-calendar-day][data-range-start]< / code > < / td > < td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td > < td > Committed endpoint styling.< / 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 DateRangeField's grouped segmented inputs, Popover disclosure and
RangeCalendar's application-labelled grid.
< / p >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Concern< / th > < th > Contract< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > Trigger< / td > < td > < code > aria-haspopup="dialog"< / code > , < code > aria-expanded< / code > and calendar label from Morfo translations.< / td > < / tr >
< tr > < td class = "name" > Inputs< / td > < td > Each endpoint is a DateField group with editable spinbutton segments.< / td > < / tr >
< tr > < td class = "name" > Calendar< / td > < td > Range grid exposes < code > role="application"< / code > , grid cells and day buttons.< / td > < / tr >
< tr > < td class = "name" > Keyboard< / td > < td > Arrow keys move by day/week, PageUp/PageDown navigates months, Enter/Space selects, Escape cancels range selection.< / td > < / tr >
< tr > < td class = "name" > Validation< / td > < td > Invalid min/max pairs are surfaced in the calendar snippet and logged through the UIX logger.< / td > < / tr >
< / tbody >
< / table >
< / div >
< / section >
{ /if }
< / div >
{ # snippet calendarBody ({ months , weekdays , validationMessage } : DateRangePickerCalendarSnippetProps )}
< DateRangePicker.Header >
< DateRangePicker.PrevButton aria-label = "Previous month" > < < / DateRangePicker.PrevButton >
< div class = "range-header-titles" data-month-count = { months . length } >
{ #each months as month ( `hdr-$ { month . value . toString ()} `) }
< div class = "range-month-title" id = { monthHeadingId ( month . value )} >
{ formatMonthYear ( month . value )}
< / div >
{ /each }
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 >
< DateRangePicker.NextButton aria-label = "Next month" > > < / DateRangePicker.NextButton >
< / DateRangePicker.Header >
{ #if validationMessage || boundsMessage || spanMessage }
< p class = "calendar-error" role = "alert" > { validationMessage || boundsMessage || spanMessage } </ p >
{ /if }
< div class = "range-months" data-month-count = { months . length } >
{ #each months as month ( month . value . toString ())}
date-range-picker: mode prop + footer button props + revert group chrome
User feedback batch from incidencia 2026-05-20:
1. Modal vs inline mode
- New `mode: 'inline' | 'modal'` prop on the soma `DateRangePicker.Provider`.
- Modal wires the popover `modal: true` → outside-click and Escape are
ignored; user must commit via the footer Close button (or revert via
Cancel).
- Provider exposes helpers `clear()`, `cancel()`, `commit()` for the
footer. `cancel()` restores the value snapshot taken on the OPEN edge
(captured via a `watch` on `opts.open` true-edge transition).
2. Footer buttons as boolean props
- `clearButton`, `cancelButton`, `closeButton` props on the picker.
The footer renders only if at least one is true. In `mode='modal'`
the closeButton is forced on (the user always needs an exit).
- `picker-actions.svelte` in the demo reads the picker context via
`DateRangePickerProvider.require()` and renders the enabled buttons
against `provider.clear/cancel/commit`.
3. Range field shape (revert to Chakra-style two boxes)
- Removed the `data-date-range-field-group` wrapper from the demo so
the start and end inputs are rendered as two separate boxed fields
with the icon embedded in the end box, matching Chakra's layout.
- The recipe CSS rules for `data-date-range-field-group` stay
available as an opt-in for consumers who prefer the unified pill.
4. Demo defaults
- `open` starts at `false` so the picker exercises the real
open/close flow when the user clicks the trigger — the segments are
for direct keyboard entry, the popover is for visual exploration.
- Mode toggle (inline / modal) + footer button switches surfaced as
controls in the demo.
5. Plumbing
- DateRangePickerOpts gains `mode`, `clearButton`, `cancelButton`,
`closeButton` (StateProps for mode, ActiveProps for the booleans).
- `DateRangePickerProvider` and `DateRangePickerMode` are now re-
exported from the soma barrel for consumers that wire footer
actions in the calendar tree.
- Test factory updated to seed the new opts.
Verified in browser: trigger opens; click-outside in modal mode is
ignored; Close commits & closes; Cancel reverts to snapshot & closes;
Clear empties the range & keeps open. Heading "May – June 2026" (year
collapse) and centered per-calendar titles still working from the
previous commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< div class = "range-month-panel" > <!-- per - month title now lives in the header above -->
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
< DateRangePicker.Grid >
< DateRangePicker.GridHead >
< DateRangePicker.GridRow >
{ #if showWeekNumbers }
< th data-range-calendar-head-cell data-range-calendar-week-number scope = "col" > wk< / th >
{ /if }
{ #each weekdays as day }
< DateRangePicker.HeadCell > { day } </ DateRangePicker.HeadCell >
{ /each }
< / DateRangePicker.GridRow >
< / DateRangePicker.GridHead >
< DateRangePicker.GridBody >
{ #each month . weeks as week }
< DateRangePicker.GridRow { week } >
{ # snippet children ({ weekNumber } : DateRangePickerGridRowSnippetProps )}
{ #if showWeekNumbers }
< th data-range-calendar-head-cell data-range-calendar-week-number scope = "row" >
{ weekNumber ?? '' }
< / th >
{ /if }
{ #each week as date }
< DateRangePicker.Cell { date } month = { month . value } >
< DateRangePicker.Day / >
< / DateRangePicker.Cell >
{ /each }
{ /snippet }
< / DateRangePicker.GridRow >
{ /each }
< / DateRangePicker.GridBody >
< / DateRangePicker.Grid >
< / div >
{ /each }
< / div >
< DateRangePicker.Footer >
< DateRangePicker.Clear / >
< DateRangePicker.Cancel / >
< DateRangePicker.Close / >
< / DateRangePicker.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-range-picker-stage {
inline-size: min(44rem, 100%);
}
.range-field-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto;
align-items: center;
gap: var(--uix-space-2);
inline-size: 100%;
}
.range-separator {
color: var(--uix-text-muted);
font-size: var(--uix-text-sm);
white-space: nowrap;
}
.range-picker-status {
display: flex;
align-items: center;
flex: 1 0 100%;
min-block-size: 1.5rem;
color: var(--uix-text-muted);
font-size: var(--uix-text-sm);
}
.range-picker-status[data-invalid='true'] {
color: var(--color-threat-text);
}
.range-months {
display: grid;
grid-auto-flow: column;
grid-auto-columns: max-content;
justify-content: center;
gap: var(--calendar-month-gap, 36px);
max-inline-size: 100%;
overflow-x: auto;
}
.range-months[data-month-count='1'] {
grid-auto-flow: row;
}
.range-month-panel {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--calendar-grid-gap);
min-inline-size: max-content;
}
.range-header-titles {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 1fr;
gap: var(--calendar-month-gap, 36px);
flex: 1 1 auto;
min-inline-size: 0;
}
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
.range-month-title {
color: var(--calendar-heading-color);
font-size: var(--calendar-heading-font-size);
font-weight: var(--calendar-heading-font-weight);
text-align: center;
}
.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'],
[data-uix-control] input[type='number'] {
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,
[data-uix-control] input[type='number']:focus {
outline: none;
border-color: var(--uix-accent-line);
box-shadow: var(--uix-ring);
}
@media (max-width: 44rem) {
.range-field-row {
grid-template-columns: minmax(0, 1fr) auto;
}
:global(.range-field-row [data-date-range-field-input]) {
grid-column: 1 / -1;
}
.range-separator {
display: none;
}
:global(.range-field-row [data-date-range-picker-trigger]) {
grid-column: 2;
justify-self: end;
}
}
< / style >