API reference
Headless Date Range Picker is a controller plus a small set of binding directives. You still own the field, trigger, overlay, and calendar layout, while the primitive owns parsing, roving focus, panel transitions, and the public accessibility attributes.
createDateRangePickerController(...)
Controller + Angular binding
import { bindTngDateRangePicker, createDateRangePickerController } from '@tailng-ui/primitives';
readonly controller = createDateRangePickerController<Date>({
ownerDocument: document,
value: { start: '2024-04-22', end: '2024-04-26' },
today: '2024-04-18',
minDate: '2024-04-01',
maxDate: '2026-03-31',
closeOnSelect: true,
calendarLayout: 'dual',
trapFocus: true,
showOutsideDays: true,
});
readonly dateRangePicker = bindTngDateRangePicker(this.controller);
| Option | Purpose |
|---|---|
value / defaultValue | Initial or controlled range selection state. |
today | Overrides the date marked as today in the calendar grid. |
minDate / maxDate | Disables out-of-range days, months, and years. |
adapter | Controls input parsing, formatting, and visible period labels. |
closeOnSelect | Closes the popup after a committed range selection. |
trapFocus | Keeps focus inside the popup while it is open. |
showOutsideDays | Shows adjacent-month days in the visible month grid. |
calendarLayout | Uses Material-style sequential selection in single. In dual, calendar index 0 edits start and index 1 edits end, rejecting an end before start. Headless consumers resolve their own responsive breakpoint. |
overlayMode / position | Controls overlay layout and alignment from the field. |
Primitive directives
These directives are the current headless binding layer. They keep the docs and applications out of the manual key, click, aria, and active-descendant plumbing that the older examples had to repeat.
Field + overlay wiring
<section [tngDateRangePickerHost]="controller">
<div data-slot="date-range-picker-field">
<div #anchorShell>
<div
data-slot="date-range-picker-input-shell"
[attr.data-invalid]="dateRangePicker.outputs().validationError !== null ? 'true' : null"
[attr.data-open]="dateRangePicker.outputs().getTriggerAttributes()['data-open']"
>
<input [tngDateRangePickerInput]="controller" type="text" placeholder="MM-DD-YYYY - MM-DD-YYYY" />
<button [tngDateRangePickerTrigger]="controller" type="button">Open</button>
</div>
<section [tngDateRangePickerOverlay]="controller" [tngDateRangePickerOverlayAnchor]="anchorShell">
<button [tngDateRangePickerPrevButton]="controller" type="button">‹</button>
<button [tngDateRangePickerPeriodButton]="controller" type="button">
{{ dateRangePicker.periodLabel() }}
</button>
<button [tngDateRangePickerNextButton]="controller" type="button">›</button>
</section>
</div>
</div>
</section>
| Directive | Purpose |
|---|---|
[tngDateRangePickerHost] | Applies the public root attributes like data-open, data-view, and root ARIA labels. |
[tngDateRangePickerInput] | Wires input text, manual commits, open-on-click, and basic keyboard entry into the controller. |
[tngDateRangePickerTrigger] | Registers the trigger element and forwards the wrapper-grade trigger keyboard behavior. |
[tngDateRangePickerOverlay] | Ports the popup to document.body, syncs overlay attrs, and keeps positioning/focus semantics aligned. Set [tngDateRangePickerOverlayScrollStrategy] to reposition (default), close, or block. |
[tngDateRangePickerPrevButton] / [tngDateRangePickerNextButton] | Pages the current view without repeating day/month/year branching in your component code. |
[tngDateRangePickerPeriodButton] | Handles the standard period drill-down flow and keeps focus synced after the view changes. |
[tngDateRangePickerDayGrid], [tngDateRangePickerMonthGrid], [tngDateRangePickerYearGrid] | Forward the correct keyboard behavior for each panel. Bind [tngDateRangePickerCalendarIndex] when rendering a dual day grid. |
[tngDateRangePickerDayCell], [tngDateRangePickerMonthOption], [tngDateRangePickerYearOption] | Apply the public cell attrs and click behavior so buttons stay headless but not repetitive. |
Grid rendering
The controller still exposes the view data. The difference is that the cell and grid directives now own the common interactions, so the template only needs to render the panels it wants.
This keeps the layout genuinely headless: you decide which panel to show and how to arrange it, while the primitive keeps the keyboard and selection model consistent.
Day / month / year panels
@if (dateRangePicker.outputs().view === 'day') {
@for (calendar of dateRangePicker.outputs().calendars; track calendar.index) {
<div
[tngDateRangePickerDayGrid]="controller"
[tngDateRangePickerCalendarIndex]="calendar.index"
>
@for (cell of calendar.cells; track cell.id) {
<button [tngDateRangePickerDayCell]="cell" type="button">{{ cell.label }}</button>
}
</div>
}
}
@if (dateRangePicker.outputs().view === 'month') {
<div [tngDateRangePickerMonthGrid]="controller">
@for (option of dateRangePicker.outputs().monthOptions; track option.id) {
<button [tngDateRangePickerMonthOption]="option" type="button">{{ option.label }}</button>
}
</div>
}
@if (dateRangePicker.outputs().view === 'year') {
<div [tngDateRangePickerYearGrid]="controller">
@for (option of dateRangePicker.outputs().yearOptions; track option.id) {
<button [tngDateRangePickerYearOption]="option" type="button">{{ option.label }}</button>
}
</div>
}
Controller outputs
dateRangePicker.outputs() is the primary read model. The get*Attributes() helpers are still available for advanced custom composition, but they are now the lower-level escape hatch rather than the default path.
| Output | Details |
|---|---|
calendarLayout / calendars | The resolved single or dual layout and its month panel models. Each calendar exposes its month, label, range boundary, cells, and grid attributes. |
cells | Backward-compatible alias for the leading calendar's cells. |
monthOptions / yearOptions | Picker options for month and year drill-down panels. |
inputText | Current editable input text, including in-progress manual entry. |
labelMonthYear | Ready-to-render month/year label for the day view header. |
getHostAttributes() / getOverlayAttributes() / getGridAttributes() | Low-level attribute maps for advanced compositions or environments where you are not using the helper directives. |
getCellAttributes(...) / getMonthAttributes(...) / getYearAttributes(...) | Low-level item attrs for fully custom cell markup beyond the provided option directives. |
Controller methods
| Method | Purpose |
|---|---|
open() / close() / toggleOpen() | Owns popup visibility. |
setInputText(...) / commitInputText() | Supports manual editing with adapter validation and bounds checks. |
subscribe(...) | Low-level subscription hook. Angular apps can usually prefer bindTngDateRangePicker(...) instead. |
showYearsPanel() / showMonthsPanel() / showDaysPanel() | Lets the implementation drive the visible panel explicitly when it wants a custom view flow. |
prevMonth() / nextMonth() / prevYear() / nextYear() | Pages the visible range for the current view. |
selectMonth(...) / selectYear(...) / handleCellClick(...) | Low-level selection hooks for layouts that want to bypass the stock option directives. |
handleTriggerKeyDown(...) / handleOverlayKeyDown(...) / handleGridKeyDown(...) | Still available as the lower-level escape hatch if you are building a custom directive layer of your own. |