refactor(date-picker): rename kind 'date' to 'day' for trio consistency

Audit caught `data-kind="date"` violation — the date-picker / date-range-picker
morfos declare `data-kind ∈ [day, month, year]` but the public `kind` prop
accepted `'date' | 'month' | 'year'` and defaulted to `'date'`. Rename
prop value `'date' → 'day'` so the trio is consistent (all named after
the grid cell granularity).

**Rationale (book-pure)**:
- The trio `day` / `month` / `year` is internally consistent — all three
  name the granularity of the grid cell the calendar view shows.
- `'date'` was awkward in the trio: month and year pickers also pick
  "dates", just at coarser granularities.
- The morfo enum was already canonical (`['day', 'month', 'year']`);
  this aligns the prop with the contract.

**Surface touched** (all `'date' → 'day'` for the kind value):

soma components:
- date-picker / date-range-picker / date-field / date-range-field:
  provider type (DatePickerKind / DateRangePickerKind), default value,
  test fixtures, types.ts JSDoc.

demos (web/routes/uix/components/):
- date-picker / date-range-picker / date-range-field: kind state,
  selector chips, doc table.

`'date'` literals that DON'T refer to kind (kept untouched):
- ValueProfile = 'date' | 'datetime' | 'zoned' (different concept).
- SegmentLock = 'none' | 'year' | 'monthYear' | 'date'.
- CSS `input[type='date']` (HTML attribute).

**Result**:
- `npm run morfo:check`: 1 issue / 1 component → **0 issues**. All 107
  routed morfos validate against their demo DOM ✓
- `npm run check`: 0 errors ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent 59c3bc96c4
commit 312c8740ca

@ -33,7 +33,7 @@
disabled = false,
readonly = false,
readonlySegments = [],
kind = 'date',
kind = 'day',
granularity,
hideTimeZone = false,
required = false,

@ -94,7 +94,7 @@ function createDateFieldOpts() {
value: state<DateValue | undefined>(undefined),
placeholder: state<DateValue>(new CalendarDate(2026, 1, 1)),
readonlySegments: state<EditableSegmentPart[]>([]),
kind: state<'date' | 'month' | 'year'>('date'),
kind: state<'day' | 'month' | 'year'>('day'),
validate: state<DateValidator | undefined>(undefined),
onInvalid: state<DateOnInvalid | undefined>(undefined),
minValue: state<DateValue | undefined>(undefined),

@ -158,17 +158,18 @@ export interface DateFieldOpts
/**
* Chakra-style picker kind. Drives which date segments are
* emitted in `segmentContents` (and therefore which segment
* elements the consumer's snippet renders):
* elements the consumer's snippet renders). Named after the
* grid cell granularity for trio consistency:
*
* - `'date'` (default): day + month + year segments
* - `'month'`: month + year segments
* - `'year'`: year segment only
* - `'day'` (default): day + month + year segments
* - `'month'`: month + year segments
* - `'year'`: year segment only
*
* `granularity` (time precision: day/hour/minute/second) is
* orthogonal — `kind='year'` + `granularity='hour'` is invalid
* by construction (year-only pickers don't have hours).
*/
kind: 'date' | 'month' | 'year';
kind: 'day' | 'month' | 'year';
validate: DateValidator | undefined;
onInvalid: DateOnInvalid | undefined;
minValue: DateValue | undefined;
@ -597,7 +598,7 @@ export class DateFieldProvider {
/**
* Visible segment parts derived from `kind` (Chakra-style picker).
* `'date'` keeps the full day+month+year triplet; `'month'` drops
* `'day'` keeps the full day+month+year triplet; `'month'` drops
* day; `'year'` drops day AND month. Time segments (hour/minute/
* second/dayPeriod) are governed by `granularity`, not `kind`, so
* we leave them untouched.

@ -80,11 +80,12 @@ export type DateFieldProviderProps = WithChild<{
* - `'year'`: year segment only
*
* Orthogonal to `granularity` (which governs time precision —
* hour/minute/second).
* hour/minute/second). Named after the grid cell granularity for
* trio consistency.
*
* @default 'date'
* @default 'day'
*/
kind?: 'date' | 'month' | 'year';
kind?: 'day' | 'month' | 'year';
/**
* Visual granularity override. By default CalendarDate → 'day',
* CalendarDateTime / ZonedDateTime → 'minute'. Granularity only affects

@ -24,7 +24,7 @@
onOpenChangeComplete = () => {},
closeOnDateSelect = true,
mode = 'inline',
kind = 'date',
kind = 'day',
validate,
onInvalid,
minValue,

@ -82,7 +82,7 @@ function createDatePickerOpts() {
required: state(false),
closeOnDateSelect: state(true),
mode: state<DatePickerMode>('inline'),
kind: state<DatePickerKind>('date'),
kind: state<DatePickerKind>('day'),
granularity: state<Granularity | undefined>(undefined),
hideTimeZone: state(false),
hourCycle: state<HourCycle | undefined>(undefined),

@ -34,16 +34,21 @@ export type DatePickerMode = 'inline' | 'modal';
* Picker granularity / type of date (Chakra-style). Drives both the
* visible segments in the input AND the calendar view in the popover:
*
* - `'date'` (default): MM/DD/YYYY input + day grid in popover
* - `'month'`: MM/YYYY input + month grid in popover
* - `'year'`: YYYY input + year grid (decade) in popover
* - `'day'` (default): MM/DD/YYYY input + day grid in popover
* - `'month'`: MM/YYYY input + month grid in popover
* - `'year'`: YYYY input + year grid (decade) in popover
*
* The contract is consumer-driven for now — the picker exposes `kind`,
* the consumer's segment snippet filters what to render, and the
* calendar honours `data-kind` for view switching (forthcoming
* commits B + C wire the grid renderers).
*
* Named after the grid cell granularity (`day` / `month` / `year`) for
* trio consistency. The legacy `'date'` value used until 2026-05-27
* was awkward — `month` and `year` pickers also pick "dates", and the
* morfo `data-kind` enum already canonized `'day'`.
*/
export type DatePickerKind = 'date' | 'month' | 'year';
export type DatePickerKind = 'day' | 'month' | 'year';
// ── Contract ─────────────────────────────────────────────────────────────────

@ -78,13 +78,14 @@ export type DatePickerProviderProps = WithChild<Omit<PrimitiveDivAttributes, 'va
mode?: import('./date-picker-provider.svelte').DatePickerMode;
/**
* Picker granularity / Chakra-style date kind. Drives the visible
* segments in the input AND the calendar view in the popover.
* segments in the input AND the calendar view in the popover. Named
* after the grid cell granularity for trio consistency.
*
* - `'date'` (default): MM/DD/YYYY input + day grid
* - `'day'` (default): MM/DD/YYYY input + day grid
* - `'month'`: MM/YYYY input + month grid
* - `'year'`: YYYY input + year grid (decade)
*
* @default 'date'
* @default 'day'
*/
kind?: import('./date-picker-provider.svelte').DatePickerKind;
// ── Validation ──

@ -39,7 +39,7 @@
startReadonlySegments = [],
endReadonlySegments = [],
required = false,
kind = 'date',
kind = 'day',
granularity,
hideTimeZone = false,
hourCycle,

@ -73,7 +73,7 @@ function createDateRangeFieldOpts(root = document.createElement('div')) {
startReadonlySegments: state<EditableSegmentPart[]>([]),
endReadonlySegments: state<EditableSegmentPart[]>([]),
required: state(false),
kind: state<'date' | 'month' | 'year'>('date'),
kind: state<'day' | 'month' | 'year'>('day'),
granularity: state<Granularity | undefined>(undefined),
hideTimeZone: state(false),
hourCycle: state<HourCycle | undefined>(undefined),

@ -52,11 +52,12 @@ interface DateRangeFieldOpts
* providers so the start and end inputs filter their segments
* uniformly (range pickers always show the same kind on both
* sides — there's no use case for kind='year' on start and
* kind='month' on end).
* kind='month' on end). Named after the grid cell granularity
* for trio consistency.
*
* @default 'date'
* @default 'day'
*/
kind: 'date' | 'month' | 'year';
kind: 'day' | 'month' | 'year';
granularity: Granularity | undefined;
hideTimeZone: boolean;
hourCycle: HourCycle | undefined;

@ -68,9 +68,9 @@ export type DateRangeFieldProviderProps = WithChild<{
* - `'month'`: month + year segments
* - `'year'`: year segment only
*
* @default 'date'
* @default 'day'
*/
kind?: 'date' | 'month' | 'year';
kind?: 'day' | 'month' | 'year';
/** Visual granularity (auto from placeholder when omitted). */
granularity?: Granularity;
/** Hide the timezone segment on ZonedDateTime. @default false */

@ -32,7 +32,7 @@
onOpenChangeComplete = () => {},
closeOnRangeSelect = true,
mode = 'inline',
kind = 'date',
kind = 'day',
validate,
onInvalid,
minValue,

@ -86,7 +86,7 @@ function createDateRangePickerOpts() {
required: state(false),
closeOnRangeSelect: state(true),
mode: state<'inline' | 'modal'>('inline'),
kind: state<'date' | 'month' | 'year'>('date'),
kind: state<'day' | 'month' | 'year'>('day'),
granularity: state<Granularity | undefined>(undefined),
hideTimeZone: state(false),
hourCycle: state<HourCycle | undefined>(undefined),

@ -25,11 +25,13 @@ export type DateRangePickerMode = 'inline' | 'modal';
* segments in the start/end inputs AND the calendar view in the
* popover:
*
* - `'date'` (default): MM/DD/YYYY inputs + day calendar
* - `'day'` (default): MM/DD/YYYY inputs + day calendar
* - `'month'`: MM/YYYY inputs + month grid
* - `'year'`: YYYY inputs + year grid (decade)
*
* Named after the grid cell granularity for trio consistency.
*/
export type DateRangePickerKind = 'date' | 'month' | 'year';
export type DateRangePickerKind = 'day' | 'month' | 'year';
import type {
DateValue,
DateRange,

@ -56,7 +56,7 @@
let readonly = $state(false);
let closeOnDateSelect = $state(false);
let mode = $state<'inline' | 'modal'>('inline');
let kind = $state<'date' | 'month' | 'year'>('date');
let kind = $state<'day' | 'month' | 'year'>('day');
// Footer composition demo toggles. Visibility is purely structural:
// the switches decide whether each part is INCLUDED in the markup,
@ -281,7 +281,7 @@
maxValue && ' {maxValue}',
!closeOnDateSelect && ' closeOnDateSelect={false}',
mode === 'modal' && ` mode="${mode}"`,
kind !== 'date' && ` kind="${kind}"`,
kind !== 'day' && ` kind="${kind}"`,
fixedWeeks && ' fixedWeeks',
pagedNavigation && ' pagedNavigation',
preventDeselect && ' preventDeselect',
@ -362,7 +362,7 @@
maxValue && ' {maxValue}',
!closeOnDateSelect && ' closeOnDateSelect={false}',
mode === 'modal' && ` mode="${mode}"`,
kind !== 'date' && ` kind="${kind}"`,
kind !== 'day' && ` kind="${kind}"`,
fixedWeeks && ' fixedWeeks',
pagedNavigation && ' pagedNavigation',
preventDeselect && ' preventDeselect',
@ -662,11 +662,11 @@
>kind <span data-uix-control-hint>{kind === 'year' ? 'YYYY' : kind === 'month' ? 'MM/YYYY' : 'MM/DD/YYYY'}</span></span
>
<span data-uix-chips role="radiogroup">
{#each ['date', 'month', 'year'] as value (value)}
{#each ['day', 'month', 'year'] as value (value)}
<button
data-uix-chip
data-active={kind === value}
onclick={() => (kind = value as 'date' | 'month' | 'year')}
onclick={() => (kind = value as 'day' | 'month' | 'year')}
>
{value}
</button>

@ -25,7 +25,7 @@
type TraceEntry = { event: string; family: string; intent?: string; at: number };
type Direction = 'ltr' | 'rtl';
type ValueProfile = 'date' | 'datetime' | 'zoned';
type Kind = 'date' | 'month' | 'year';
type Kind = 'day' | 'month' | 'year';
// ── Sample anchors (one per value profile) ───────────────────────────
const startDate = new CalendarDate(2026, 5, 19);
@ -44,7 +44,7 @@
// ── Live state — every public prop wired ─────────────────────────────
let valueProfile = $state<ValueProfile>('date');
let kind = $state<Kind>('date');
let kind = $state<Kind>('day');
let granularity = $state<Granularity>('day');
let value = $state<DateRange>({ start: startDate, end: endDate });
let placeholder = $state<DateValue>(startDate);
@ -71,7 +71,7 @@
const sizes: DateRangeFieldSize[] = ['xs', 'sm', 'md', 'lg', 'xl'];
const variants: DateRangeFieldVariant[] = ['surface', 'outline', 'ghost'];
const colors: DateRangeFieldColor[] = ['primary', 'secondary', 'neutral', 'affirm', 'risk', 'threat'];
const kinds: Kind[] = ['date', 'month', 'year'];
const kinds: Kind[] = ['day', 'month', 'year'];
const granularities: Granularity[] = ['day', 'hour', 'minute', 'second'];
const hourCycles: HourCycle[] = [12, 24];
const directions: Direction[] = ['ltr', 'rtl'];
@ -229,7 +229,7 @@
'',
'<DateRangeField.Provider',
' bind:value',
kind !== 'date' && ` kind="${kind}"`,
kind !== 'day' && ` kind="${kind}"`,
locale !== 'en-US' && ` locale="${locale}"`,
dir !== 'ltr' && ` dir="${dir}"`,
hourCycle !== 24 && ` hourCycle={${hourCycle}}`,
@ -281,7 +281,7 @@
variant !== 'surface' && ` variant="${variant}"`,
color !== 'primary' && ` color="${color}"`,
size !== 'md' && ` size="${size}"`,
kind !== 'date' && ` kind="${kind}"`,
kind !== 'day' && ` kind="${kind}"`,
locale !== 'en-US' && ` locale="${locale}"`,
dir !== 'ltr' && ` dir="${dir}"`,
hourCycle !== 24 && ` hourCycle={${hourCycle}}`,
@ -590,7 +590,7 @@
<label data-uix-control>
<span data-uix-control-label>start · day</span>
<span data-uix-switch>
<input type="checkbox" bind:checked={readonlyStartDay} disabled={kind !== 'date'} />
<input type="checkbox" bind:checked={readonlyStartDay} disabled={kind !== 'day'} />
<span data-uix-switch-label>{readonlyStartDay ? 'locked' : 'editable'}</span>
</span>
</label>
@ -611,7 +611,7 @@
<label data-uix-control>
<span data-uix-control-label>end · day</span>
<span data-uix-switch>
<input type="checkbox" bind:checked={readonlyEndDay} disabled={kind !== 'date'} />
<input type="checkbox" bind:checked={readonlyEndDay} disabled={kind !== 'day'} />
<span data-uix-switch-label>{readonlyEndDay ? 'locked' : 'editable'}</span>
</span>
</label>
@ -691,7 +691,7 @@
<tr><td class="name">onInvalid <span data-uix-tag data-kind="soma">soma</span></td><td class="type">DateOnInvalid</td><td class="default empty">—</td><td>Called when validation fails.</td></tr>
<tr><td class="name">minValue <span data-uix-tag data-kind="soma">soma</span></td><td class="type">DateValue</td><td class="default empty">—</td><td>Earliest acceptable date for both endpoints.</td></tr>
<tr><td class="name">maxValue <span data-uix-tag data-kind="soma">soma</span></td><td class="type">DateValue</td><td class="default empty">—</td><td>Latest acceptable date for both endpoints.</td></tr>
<tr><td class="name">kind <span data-uix-tag data-kind="soma">soma</span></td><td class="type">'date' | 'month' | 'year'</td><td class="default">'date'</td><td>Segment filter (Chakra-style). Both endpoints share the same kind.</td></tr>
<tr><td class="name">kind <span data-uix-tag data-kind="soma">soma</span></td><td class="type">'day' | 'month' | 'year'</td><td class="default">'day'</td><td>Segment filter (Chakra-style). Both endpoints share the same kind.</td></tr>
<tr><td class="name">granularity <span data-uix-tag data-kind="soma">soma</span></td><td class="type">'day' | 'hour' | 'minute' | 'second'</td><td class="default">auto (from placeholder)</td><td>Visual granularity. Adds time segments for <code>hour+</code>.</td></tr>
<tr><td class="name">hourCycle <span data-uix-tag data-kind="soma">soma</span></td><td class="type">12 | 24</td><td class="default">locale-derived</td><td>12-hour or 24-hour clock.</td></tr>
<tr><td class="name">hideTimeZone <span data-uix-tag data-kind="soma">soma</span></td><td class="type">boolean</td><td class="default">false</td><td>Hide the timezone segment on <code>ZonedDateTime</code>.</td></tr>

@ -59,7 +59,7 @@
let allowSingleDay = $state(false);
let closeOnRangeSelect = $state(false);
let mode = $state<'inline' | 'modal'>('inline');
let kind = $state<'date' | 'month' | 'year'>('date');
let kind = $state<'day' | 'month' | 'year'>('day');
// Footer composition demo toggles. Visibility = whether the part is
// INCLUDED in the markup (the parts themselves render unconditionally).
@ -734,11 +734,11 @@
>kind <span data-uix-control-hint>{kind === 'year' ? 'YYYY' : kind === 'month' ? 'MM/YYYY' : 'MM/DD/YYYY'}</span></span
>
<span data-uix-chips role="radiogroup">
{#each ['date', 'month', 'year'] as value (value)}
{#each ['day', 'month', 'year'] as value (value)}
<button
data-uix-chip
data-active={kind === value}
onclick={() => (kind = value as 'date' | 'month' | 'year')}
onclick={() => (kind = value as 'day' | 'month' | 'year')}
>
{value}
</button>

Loading…
Cancel
Save

Powered by TurnKey Linux.