diff --git a/src/libs/days/queries.ts b/src/libs/days/queries.ts
index cee6e3b64..528a62609 100644
--- a/src/libs/days/queries.ts
+++ b/src/libs/days/queries.ts
@@ -72,3 +72,27 @@ export function isDiasZonedDateTime(value: unknown): value is ZonedDateTime {
}
export { isDiasZonedDateTime as isZonedDateTime };
+
+/**
+ * Returns the first year of the decade containing `year` (e.g. 2026 →
+ * 2020, 1999 → 1990). Pure number math, no DateValue allocation.
+ *
+ * Used by year-grid range pickers to anchor a 10-year window for
+ * decade-based pagination.
+ */
+export function startOfDecadeYear(year: number): number {
+ return year - (year % 10);
+}
+
+/**
+ * Encodes a `{ year, month }` pair into a strictly-ordered integer key
+ * (`year * 12 + (month - 1)`) so range comparisons can be done with
+ * plain `<` / `>` instead of cascading conditionals. Pure math, no
+ * DateValue allocation.
+ *
+ * Used by month-grid range pickers to classify months as start / end /
+ * in-range across year boundaries.
+ */
+export function monthOrdinal(year: number, month: number): number {
+ return year * 12 + (month - 1);
+}
diff --git a/src/uix/eidos/components/date-picker/date-picker-month-view.svelte b/src/uix/eidos/components/date-picker/date-picker-month-view.svelte
index 7a4afca1e..fd385d03f 100644
--- a/src/uix/eidos/components/date-picker/date-picker-month-view.svelte
+++ b/src/uix/eidos/components/date-picker/date-picker-month-view.svelte
@@ -1,76 +1,21 @@
-
- {#snippet children({ months }: MonthGridProviderSnippetProps)}
-
- <
-
- >
-
-
- {#each months as month (month)}
-
- {/each}
-
- {/snippet}
-
+
diff --git a/src/uix/eidos/components/date-picker/date-picker-year-view.svelte b/src/uix/eidos/components/date-picker/date-picker-year-view.svelte
index ace8a90ca..5ebfbc369 100644
--- a/src/uix/eidos/components/date-picker/date-picker-year-view.svelte
+++ b/src/uix/eidos/components/date-picker/date-picker-year-view.svelte
@@ -1,75 +1,21 @@
-
- {#snippet children({ years }: YearGridProviderSnippetProps)}
-
- <
-
- >
-
-
- {#each years as year (year)}
-
- {/each}
-
- {/snippet}
-
+
diff --git a/src/uix/eidos/components/date-range-picker/date-range-picker-month-view.svelte b/src/uix/eidos/components/date-range-picker/date-range-picker-month-view.svelte
index 42783afec..102565a0f 100644
--- a/src/uix/eidos/components/date-range-picker/date-range-picker-month-view.svelte
+++ b/src/uix/eidos/components/date-range-picker/date-range-picker-month-view.svelte
@@ -1,124 +1,16 @@
-
-
- stepYear(-1)}
- >
- ‹
-
- {year}
- stepYear(1)}
- >
- ›
-
-
-
- {#each months as month (month)}
- {@const c = classFor(month)}
- selectMonth(month)}
- >
- {monthLabel(month)}
-
- {/each}
-
-
+
diff --git a/src/uix/eidos/components/date-range-picker/date-range-picker-year-view.svelte b/src/uix/eidos/components/date-range-picker/date-range-picker-year-view.svelte
index 8126767db..706411efe 100644
--- a/src/uix/eidos/components/date-range-picker/date-range-picker-year-view.svelte
+++ b/src/uix/eidos/components/date-range-picker/date-range-picker-year-view.svelte
@@ -1,110 +1,16 @@
-
-
- stepDecade(-1)}
- >
- ‹
-
-
- {decadeStart} – {decadeStart + 11}
-
- stepDecade(1)}
- >
- ›
-
-
-
- {#each years as year (year)}
- {@const c = classFor(year)}
- selectYear(year)}
- >
- {year}
-
- {/each}
-
-
+
diff --git a/src/uix/morfo/components/date-picker.ts b/src/uix/morfo/components/date-picker.ts
index c8739a7a8..c3a156b8c 100644
--- a/src/uix/morfo/components/date-picker.ts
+++ b/src/uix/morfo/components/date-picker.ts
@@ -232,6 +232,30 @@ export const datePickerMorfo = {
{ key: 'Tab', action: 'focus-next' },
{ key: 'Shift+Tab', action: 'focus-prev' }
]
+ },
+ {
+ // Month-view bridge for `kind='month'`. Owns the bridge from
+ // CalendarDate ↔ MonthValue + handleDateSelect. Visual chrome
+ // (header/grid composition) is the wrapped MonthGrid's job.
+ name: 'MonthView',
+ kebab: 'month-view',
+ archetype: 'group',
+ kind: 'public',
+ defaultElement: 'div',
+ optional: true,
+ data: [],
+ aria: []
+ },
+ {
+ // Year-view bridge for `kind='year'`. Symmetric to MonthView.
+ name: 'YearView',
+ kebab: 'year-view',
+ archetype: 'group',
+ kind: 'public',
+ defaultElement: 'div',
+ optional: true,
+ data: [],
+ aria: []
}
// Footer / Clear / Cancel / Close — declared by the shared
// `picker-shell` (Eidos ``).
diff --git a/src/uix/morfo/components/date-range-picker.ts b/src/uix/morfo/components/date-range-picker.ts
index 5de58498d..92dd9aadf 100644
--- a/src/uix/morfo/components/date-range-picker.ts
+++ b/src/uix/morfo/components/date-range-picker.ts
@@ -237,6 +237,45 @@ export const dateRangePickerMorfo = {
{ key: 'Tab', action: 'focus-next' },
{ key: 'Shift+Tab', action: 'focus-prev' }
]
+ },
+ {
+ // Month-view range bridge for `kind='month'`. Owns the range
+ // selection state machine (empty / pending / complete) + year
+ // stepper. Visual chrome painted by eidos via the snippet.
+ name: 'MonthView',
+ kebab: 'month-view',
+ archetype: 'group',
+ kind: 'public',
+ defaultElement: 'div',
+ role: 'grid',
+ optional: true,
+ data: [],
+ aria: [
+ {
+ attr: 'aria-label',
+ value: v.literal('Select month range'),
+ severity: 'optional'
+ }
+ ]
+ },
+ {
+ // Year-view range bridge for `kind='year'`. Symmetric to the
+ // MonthView but with decade pagination.
+ name: 'YearView',
+ kebab: 'year-view',
+ archetype: 'group',
+ kind: 'public',
+ defaultElement: 'div',
+ role: 'grid',
+ optional: true,
+ data: [],
+ aria: [
+ {
+ attr: 'aria-label',
+ value: v.literal('Select year range'),
+ severity: 'optional'
+ }
+ ]
}
// Footer / Clear / Cancel / Close — chrome shared via picker-shell;
// not declared here. Eidos `
+ /**
+ * Soma `` — month-grid view for `kind='month'`.
+ *
+ * Bridges DatePicker's CalendarDate state to MonthGrid's `{year, month}`
+ * value: reads the picker's current value/placeholder/locale/disabled/
+ * readonly, exposes them via MonthGrid, and on month-select converts
+ * back to a CalendarDate (day=1) + fires `handleDateSelect()` so the
+ * picker's commit pipeline runs.
+ *
+ * Internal composition: renders the default `<` Heading `>` header +
+ * 3x4 grid. Consumers can override the body via the `children` snippet
+ * (receives the live `months` array from MonthGrid).
+ */
+ import type { Snippet } from 'svelte';
+ import { CalendarDate } from '$libs/days';
+ import { DatePickerProvider } from '../date-picker-provider.svelte';
+ import * as MonthGrid from '../../month-grid/exports';
+ import type {
+ MonthValue,
+ MonthPlaceholder,
+ MonthGridProviderSnippetProps,
+ MonthGridProps
+ } from '../../month-grid/types';
+
+ type Props = Omit<
+ MonthGridProps,
+ 'value' | 'placeholder' | 'onValueChange' | 'onPlaceholderChange' | 'children'
+ > & {
+ children?: Snippet<[MonthGridProviderSnippetProps]>;
+ };
+
+ let { children, ...rest }: Props = $props();
+
+ const provider = DatePickerProvider.require();
+
+ const anchor = $derived(
+ provider.opts.value.current ?? provider.opts.placeholder.current
+ );
+
+ const monthValue = $derived(
+ provider.opts.value.current
+ ? {
+ year: provider.opts.value.current.year,
+ month: provider.opts.value.current.month
+ }
+ : undefined
+ );
+ const monthPlaceholder = $derived({ year: anchor.year });
+
+ function onValueChange(next: MonthValue | undefined): void {
+ if (!next) return;
+ const date = new CalendarDate(next.year, next.month, 1);
+ provider.opts.value.current = date;
+ provider.opts.placeholder.current = date;
+ provider.handleDateSelect();
+ }
+
+ function onPlaceholderChange(next: MonthPlaceholder): void {
+ const placeholder = provider.opts.placeholder.current;
+ provider.opts.placeholder.current = new CalendarDate(
+ next.year,
+ placeholder.month,
+ placeholder.day
+ );
+ }
+
+
+
+ {#snippet children(snippetProps: MonthGridProviderSnippetProps)}
+ {#if children}
+ {@render children(snippetProps)}
+ {:else}
+
+ <
+
+ >
+
+
+ {#each snippetProps.months as month (month)}
+
+ {/each}
+
+ {/if}
+ {/snippet}
+
diff --git a/src/uix/soma/components/date-picker/components/date-picker-year-view.svelte b/src/uix/soma/components/date-picker/components/date-picker-year-view.svelte
new file mode 100644
index 000000000..bc5827d15
--- /dev/null
+++ b/src/uix/soma/components/date-picker/components/date-picker-year-view.svelte
@@ -0,0 +1,93 @@
+
+
+
+ {#snippet children(snippetProps: YearGridProviderSnippetProps)}
+ {#if children}
+ {@render children(snippetProps)}
+ {:else}
+
+ <
+
+ >
+
+
+ {#each snippetProps.years as year (year)}
+
+ {/each}
+
+ {/if}
+ {/snippet}
+
diff --git a/src/uix/soma/components/date-picker/exports.ts b/src/uix/soma/components/date-picker/exports.ts
index e12475284..efb78ccc7 100644
--- a/src/uix/soma/components/date-picker/exports.ts
+++ b/src/uix/soma/components/date-picker/exports.ts
@@ -2,6 +2,8 @@
export { default as Provider } from './components/date-picker.svelte';
export { default as Trigger } from './components/date-picker-trigger.svelte';
export { default as Calendar } from './components/date-picker-calendar.svelte';
+export { default as MonthView } from './components/date-picker-month-view.svelte';
+export { default as YearView } from './components/date-picker-year-view.svelte';
// Provider class (action helpers: commit / cancel / clear / handleDateSelect)
export { DatePickerProvider, datePickerAttrs } from './internals';
diff --git a/src/uix/soma/components/date-range-picker/components/date-range-picker-month-view.svelte b/src/uix/soma/components/date-range-picker/components/date-range-picker-month-view.svelte
new file mode 100644
index 000000000..9dae9c210
--- /dev/null
+++ b/src/uix/soma/components/date-range-picker/components/date-range-picker-month-view.svelte
@@ -0,0 +1,143 @@
+
+
+
+ {#if children}
+ {@render children({ year, months, classify, selectMonth, stepYear })}
+ {:else}
+
+ stepYear(-1)}>‹
+ {year}
+ stepYear(1)}>›
+
+
+ {#each months as month (month)}
+ {@const c = classify(month)}
+ selectMonth(month)}
+ >
+ {month}
+
+ {/each}
+
+ {/if}
+
diff --git a/src/uix/soma/components/date-range-picker/components/date-range-picker-year-view.svelte b/src/uix/soma/components/date-range-picker/components/date-range-picker-year-view.svelte
new file mode 100644
index 000000000..8e2081487
--- /dev/null
+++ b/src/uix/soma/components/date-range-picker/components/date-range-picker-year-view.svelte
@@ -0,0 +1,141 @@
+
+
+
+ {#if children}
+ {@render children({ decadeStart, years, classify, selectYear, stepDecade })}
+ {:else}
+
+ stepDecade(-1)}>‹
+
+ {decadeStart} – {decadeStart + 11}
+
+ stepDecade(1)}>›
+
+
+ {#each years as yearN (yearN)}
+ {@const c = classify(yearN)}
+ selectYear(yearN)}
+ >
+ {yearN}
+
+ {/each}
+
+ {/if}
+
diff --git a/src/uix/soma/components/date-range-picker/exports.ts b/src/uix/soma/components/date-range-picker/exports.ts
index b267cddd7..263eb3b54 100644
--- a/src/uix/soma/components/date-range-picker/exports.ts
+++ b/src/uix/soma/components/date-range-picker/exports.ts
@@ -2,6 +2,8 @@
export { default as Provider } from './components/date-range-picker.svelte';
export { default as Trigger } from './components/date-range-picker-trigger.svelte';
export { default as Calendar } from './components/date-range-picker-calendar.svelte';
+export { default as MonthView } from './components/date-range-picker-month-view.svelte';
+export { default as YearView } from './components/date-range-picker-year-view.svelte';
// Provider class — required by consumers that wire footer actions
// (Clear / Cancel / Save) inside the calendar tree via `.require()`.