Date Editors
<oge-calendar> is a standalone month/year/decade calendar; <oge-date-box> puts it behind a field with locale-aware text parsing. Everything runs on native Date + Intl — no date library, no adapter, and all day math is local (never Date.parse), so values can't drift across timezones.
Calendar
WAI-ARIA date grid with a roving-tabindex day: arrows move ±1/±7 days, PgUp/PgDn ±1 month (Shift ±1 year), Home/End week edges. The header drills out to year and decade views. min/max/disabledDates gate selection; week numbers follow a configurable rule.
Sat Aug 15 2026import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeCalendar } from '@oge-ui/inputs';
@Component({
selector: 'demo-root',
imports: [OgeCalendar],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-calendar [(value)]="date" />
<oge-calendar
[min]="min"
[disabledDates]="isWeekend"
[showTodayButton]="true"
[showWeekNumbers]="true"
[firstDayOfWeek]="1"
[(value)]="date"
/>
`,
})
export class Demo {
protected readonly date = signal<Date | null>(null);
protected readonly min = new Date(2026, 0, 1);
protected readonly isWeekend = (day: Date): boolean =>
day.getDay() === 0 || day.getDay() === 6;
}Date Box
The value is always a local Date | null — serialization is the app's concern. Typed text parses by the locale's own part order (dd/mm vs mm/dd) incl. month names; unparseable or out-of-range text shows the invalid state and reverts on blur — a wrong date is never committed. The popup follows the APG date-picker-dialog pattern: focus moves into the calendar, Esc returns it.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeDateBox } from '@oge-ui/inputs';
@Component({
selector: 'demo-root',
imports: [OgeDateBox],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-date-box label="Start date" [(value)]="start" />
<!-- typed text parses locale-aware through Intl — never Date.parse -->
<oge-date-box
label="Delivery"
[min]="today"
[showClearButton]="true"
[(value)]="delivery"
/>
`,
})
export class Demo {
protected readonly start = signal<Date | null>(null);
protected readonly delivery = signal<Date | null>(null);
protected readonly today = new Date();
}Range selection
selectionMode: 'range' turns the calendar into a start–end picker with a live hover preview; viewsCount: 2 lays two months side by side. <oge-date-range-box> puts the same picker behind a single field with two parsed inputs — a reversed pair reorders on commit, either end may stay open. type: 'datetime' adds start/end time lists: day and time picks collect in a draft and commit together on OK.
Mon Aug 10 2026 → Thu Aug 20 20268/14/2026, 10:00:00 PM → 8/15/2026, 6:30:00 AMimport { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeCalendar, OgeDateRangeBox } from '@oge-ui/inputs';
import type { OgeCalendarRange } from '@oge-ui/inputs';
@Component({
selector: 'demo-root',
imports: [OgeCalendar, OgeDateRangeBox],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- two-view range calendar with hover preview -->
<oge-calendar selectionMode="range" [viewsCount]="2" [(range)]="range" />
<!-- start–end on one field; typed or picked, reversed pairs reorder -->
<oge-date-range-box label="Period" [(value)]="period" />
<!-- datetime range: start/end time lists + OK, commits as a draft -->
<oge-date-range-box type="datetime" [interval]="30" [(value)]="window" />
`,
})
export class Demo {
protected readonly range = signal<OgeCalendarRange>([null, null]);
protected readonly period = signal<OgeCalendarRange>([null, null]);
protected readonly window = signal<OgeCalendarRange>([null, null]);
}Time & datetime
type switches the editor: time shows a time picker (clock rail icon), datetime pairs it with the calendar — picking a date keeps the popup open for the time. timeView selects the picker layout: one interval list, or iOS-style hour + minute columns. applyValueMode: 'useButtons' collects picks in a draft and commits on OK.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeDateBox } from '@oge-ui/inputs';
@Component({
selector: 'demo-root',
imports: [OgeDateBox],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- one interval list (default) … -->
<oge-date-box type="time" timeView="list" [interval]="30" [(value)]="t1" />
<!-- … or hour + minute columns -->
<oge-date-box type="time" timeView="columns" [interval]="5" [(value)]="t2" />
`,
})
export class Demo {
protected readonly t1 = signal<Date | null>(null);
protected readonly t2 = signal<Date | null>(null);
}Grid integration
Grid and tree-list dataType: 'date' cells now edit through <oge-date-box> (compact sm shape) and the filter row applies a timezone-safe local day-range — [startOfDay, nextDay) for equality, day boundaries for ordering operators. Rows keep their storage shape: Date stays Date, yyyy-MM-dd strings round-trip as strings.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeColumn, OgeGrid } from '@oge-ui/grid';
@Component({
selector: 'demo-root',
imports: [OgeColumn, OgeGrid],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- grid date editors and the filter row now run on <oge-date-box> -->
<oge-grid [data]="rows" keyField="id" [filterRow]="true" [editing]="{ mode: 'cell' }">
<oge-column field="shipped" dataType="date" />
</oge-grid>
<!-- filtering builds a timezone-safe local day-range: [startOfDay, nextDay) -->
`,
})
export class Demo {
protected readonly rows = [
{ id: 1, shipped: new Date(2026, 2, 11) },
{ id: 2, shipped: new Date(2026, 5, 2) },
];
}Keyboard & accessibility
- Calendar:
role="grid"with real DOM focus on the day buttons (roving tabindex — deliberately notaria-activedescendant, per the APG date-picker-dialog pattern);aria-current="date"marks today. - Date box: ↓ opens the picker and hands focus to the calendar; Esc closes it and restores focus to the input, pressed again it reverts uncommitted text; Enter commits the typed text.
- All texts (month/weekday names, aria labels, error messages) come from
Intl+ the localizedOgeInputsMessages.